详解webpack分包及异步加载套路


Posted in Javascript onJune 29, 2017

最近一个小项目是用webpack来进行构建的。其中用到了webpack分包异步加载的功能。今天抽时间看了下webpack打包后的文件,大致弄明白了webpack分包及异步加载的套路。

由于这个小项目是用自己写的一个路由,路由定义好了不同路径对应下的模板及逻辑代码:

webpack配置文件:

var path = require('path'),
 DashboardPlugin = require('webpack-dashboard/plugin'),
 HtmlWebpackPlugin = require('html-webpack-plugin'),
 webpack = require('webpack'),
 ExtractTextPlugin = require('extract-text-webpack-plugin');

var PATHS = {
 app: path.join(__dirname, 'src'),
 dist: path.join(__dirname, 'dist')
}

var PKG = require('./package.json');
var TARGET = process.env.npm_lifecycle_event; //获取当前正在运行的脚本名称

var isProduction = function() {
 return process.env.NODE_ENV === 'production';
}



module.exports ={
 entry: {
 'index': path.join(__dirname, 'src/index.js'),
 'lib': ['./src/lib/js/index.js'],
 },
 //filename是主入口文件的名称,即对应的entry
 //chunkFilename对应的是非主入口文件的名称,chunk
 output: {
 path: PATHS.dist,
 publicPath: '/static/taxi-driver/', //publicPath 的话是打包的时候生成的文件链接,如果是在生产环境当然是用服务器地址,如果是开发环境就是用本地静态服务器的地址
 filename: 'js/register/[name].js',
 chunkFilename: 'js/register/[name].js',
 //TODO: build文件中加入hash值
 },
 //生成source-map文件
 devtool: isProduction ? null : 'source-map',
 devServer: {
 proxy: {
  '/api/*': {
  target: 'http://localhost:3000',
  secure: false
  }
 }
 },
 module: {
 loaders: [
  {
  test: /\.js$/,
  exclude: /node_modules|picker.min.js/,
  loader: 'babel'
  },
  {
  test: /\.less$/,
  loader: ExtractTextPlugin.extract('style', 'css!less')
  },
  {
  test: /\.html$/,
  loader: 'raw'
  },
  {
  test: /\.css$/,
  loader: ExtractTextPlugin.extract('style', 'css')
  },
  {
  test: /\.json$/,
  loader: 'json'
  }
 ]
 },
 resolve: {
 alias: {
  src: path.join(__dirname, 'src'),
  modules: path.join(__dirname, 'src/modules'),
  lessLib: path.join(__dirname, 'src/lib/less'), 
  jsLib: path.join(__dirname, 'src/lib/js'),
  components: path.join(__dirname, 'src/components')
 },
 extensions: ['', '.js', '.less', '.html', '.json'],
 },
 plugins: [
 new HtmlWebpackPlugin({
  title: '认证资料',
  template: './dist/assets/info.html',
  inject: 'body',
  filename: 'pages/register/index.html' //输出html文件的位置
 }),
 new DashboardPlugin(),
 new ExtractTextPlugin('css/register/style.css'), //将引入的样式文件单独抽成style.css文件并插入到head标签当中,带有路径时,最后打包
 new webpack.optimize.CommonsChunkPlugin({
  name: 'common',
  filename: 'js/register/common.js',
  minChunks: 3
 })
 ]
}

接下来是定义好的路由文件:

const Router = new Route();
 
 Route
 .addRoute({
  path: 'path1',
  viewBox: '.public-container',
  template: require('modules/path1/index.html'),
  pageInit() {
  //webpack提供的分包的API. require.ensure
  require.ensure([], () => {
   let controller = require('modules/path1/controller');
   Router.registerCtrl('path1', new controller('.public-container'));
  }, 'path1');
  }
 })
 .addRoute({
  path: 'path2',
  viewBox: '.public-container',
  template: require('modules/path2/index.html'),
  pageInit() {
  require.ensure([], () => {
   let controller = require('modules/path2/controller');
   Router.registerCtrl('path2', new controller('.public-container'));
  }, 'path2');
  }
 });

最后webpack会将这2个需要异步加载的模块,分别打包成path1.js和path2.js.

当页面的路径为:

http://localhost:8080/pages/register/#/path1时,会加载path1.js文件
http://localhost:8080/pages/register/#/path2时,会加载path2.js文件.

再来看看webpack打包后的文件:

其中在common.js中, webpack定义了一个全局函数webpackJsonp.这个全局函数在项目一启动后就定义好。
局部函数__webpack_require__用以在某一个模块中初始化或者调用其他的模块方法。同时这个函数还有一个静态方法__webpack_require__.e这个方法就是用来异步加载js文件的。

接下来一步一步的看:

//common.js
 (function(modules) {
 //modules用来保存所有的分包,它是一个数组,数组每个元素对应的都是callback,每个分包都是通过数字来进行标识的
 
 //定义好的全局函数webpackJsonp
 //大家可以看看其他打包好的文件,例如index.js, path1.js和path2.js文件.都是webpackJsonp()这种的形式,大家用过JSONP应该会很好理解。首先在前端定义好函数,然后后端下发组装好的函数js文件,前端获取到这个文件后就可以立即进行执行了
 var parentJsonpFunction = window["webpackJsonp"];
 window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules) {
  var moduleId, chunkId, i = 0, callbacks = [];
 for(;i < chunkIds.length; i++) {
  chunkId = chunkIds[i];
  if(installedChunks[chunkId])
  callbacks.push.apply(callbacks, installedChunks[chunkId]);
  installedChunks[chunkId] = 0;
 }
  //这个全局函数会将各个分包缓存到modules
 for(moduleId in moreModules) {
  modules[moduleId] = moreModules[moduleId];
 }
 if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules);
 while(callbacks.length)
  callbacks.shift().call(null, __webpack_require__);
  //用以启动整个应用
 if(moreModules[0]) {
  installedModules[0] = 0;
  return __webpack_require__(0);
 }
 };
 })([]);
// The require function
  //通过数字标识的moduleId
 function __webpack_require__(moduleId) {

 // Check if module is in cache
 if(installedModules[moduleId])
  return installedModules[moduleId].exports;

 // Create a new module (and put it into the cache)
 var module = installedModules[moduleId] = {
  exports: {},
  id: moduleId,
  loaded: false
 };

 // Execute the module function
 modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

 // Flag the module as loaded
 module.loaded = true;

 // Return the exports of the module
 return module.exports;
 }

 // This file contains only the entry chunk.
 // The chunk loading function for additional chunks
  //异步加载函数
 __webpack_require__.e = function requireEnsure(chunkId, callback) {
 // "0" is the signal for "already loaded"
 if(installedChunks[chunkId] === 0)
  return callback.call(null, __webpack_require__);

 // an array means "currently loading".
 if(installedChunks[chunkId] !== undefined) {
  installedChunks[chunkId].push(callback);
 } else {
   //创建script表情,请求js文件
  // start chunk loading
  installedChunks[chunkId] = [callback];
  var head = document.getElementsByTagName('head')[0];
  var script = document.createElement('script');
  script.type = 'text/javascript';
  script.charset = 'utf-8';
  script.async = true;

  script.src = __webpack_require__.p + "js/register/" + ({"0":"index","1":"path1","2":"path2"}[chunkId]||chunkId) + ".js";
  head.appendChild(script);
 }
 };

 // expose the modules object (__webpack_modules__)
 __webpack_require__.m = modules;

 // expose the module cache
 __webpack_require__.c = installedModules;

 // __webpack_public_path__
  //配置文件中定义的publicPath,build完后加载文件的路径
 __webpack_require__.p = "/static/taxi-driver/";
})

在最后输出的index.html文件中首先加载的是这个common.js文件,然后是入口文件index.js。因为这个实例代码里面没有很多共用文件,因此webpack自己提供的commonChunkPlugin这个插件并没有起到作用,本来作为共用文件的xRoute.js因此也被打包进入了index.js.

webpackJsonp([0, 3], [
 /* 0 */
/***/ function(module, exports, __webpack_require__) {

 'use strict';

 __webpack_require__(1);

 __webpack_require__(8);

/***/ },
/* 1 */
/* 2 */
/* 3 */
//....
/* 8 */
 ])

index.js文件在common.js后加载,加载完后即开始执行.大家还记得webpackJsonp这个全局函数里面的倒数3行代码吧。就是用以调用这里:

/* 0 */
 function(module, exports, __webpack_require__) {

 'use strict';

 __webpack_require__(1);

 __webpack_require__(8);

}

其中模块Id为1和8的内容请查看相应文件, 其中模块1为我定义的路由文件,在执行模块1的代码前,会加载模块2的内容,模块2的内容为我定义的路由库。

接下来就看下模块1中路由定义的具体内容:

/* 1 */
/***/ function(module, exports, __webpack_require__) {

 'use strict';

 Object.defineProperty(exports, "__esModule", {
 value: true
 });
 
 //加载路由库
 var _index = __webpack_require__(2);
 //实例化一个路由
 var Router = new _index.Route();
 //定义好的路由规则
 Router.home('path1').addRoute({
 path: 'path1',
 viewBox: '.public-container',
 //模板文件,为模块4
 template: __webpack_require__(4),
 pageInit: function pageInit() {
 //这个方法是在common.js中__webpack_require__的静态方法,用来异步加载js。
 //异步加载js的文件(即chunk)用来数字来标识,chunk的顺序从0开始.
 //这里path1.js的chunk num为1,大家可以回过头到common.js的__webpack_require__.e方法里面看看,里面已经做好了chunk num和模块文件的映射, chunk 1对应的模块文件为path1.js,chunk 2对用的模块文件为path2.js
 //__webpack_require__.e()接收的第二个参数为异步加载模块后的回调. 当path1.js被加载完后,在modules里面进行了缓存.这时就可以通过模块id去获取这个模块。然后进行初始化等后续的操作
  __webpack_require__.e/* nsure */(1, function () {
  var controller = __webpack_require__(6);
  Router.registerCtrl('path1', new controller('.public-container'));
  });
 }
 }).addRoute({
 path: 'path2',
 viewBox: '.public-container',
 //模板文件,为模块5
 template: __webpack_require__(5),
 pageInit: function pageInit() {
  __webpack_require__.e/* nsure */(2, function () {
  var controller = __webpack_require__(7);
  Router.registerCtrl('path2', new controller('.public-container'));
  });
 }
 });

 Router.bootstrap();

 exports.default = Router;

/***/ },

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
jquery复选框CHECKBOX全选、反选
Aug 30 Javascript
javascript 二维数组的实现与应用
Mar 16 Javascript
关于图片按比例自适应缩放的js代码
Oct 30 Javascript
基于jquery实现状态限定编辑的代码
Feb 11 Javascript
js switch case default 的用法示例介绍
Oct 23 Javascript
解析JavaScript中点号“.”的多义性
Dec 02 Javascript
javascript中数组的冒泡排序使用示例
Dec 18 Javascript
jQuery对Select的操作大集合(收藏)
Dec 28 Javascript
javascript中Number对象的toString()方法分析
Dec 20 Javascript
总结Javascript中数组各种去重的方法
Oct 04 Javascript
解决element-ui中下拉菜单子选项click事件不触发的问题
Aug 22 Javascript
js防抖和节流的深入讲解
Dec 06 Javascript
JavaScript之map reduce_动力节点Java学院整理
Jun 29 #Javascript
Angular 2 ngForm中的ngModel、[ngModel]和[(ngModel)]的写法
Jun 29 #Javascript
JavaScript之iterable_动力节点Java学院整理
Jun 29 #Javascript
JavaScript之Map和Set_动力节点Java学院整理
Jun 29 #Javascript
JavaScript之面向对象_动力节点Java学院整理
Jun 29 #Javascript
JavaScript之json_动力节点Java学院整理
Jun 29 #Javascript
JavaScript之RegExp_动力节点Java学院整理
Jun 29 #Javascript
You might like
asp和php下textarea提交大量数据发生丢失的解决方法
2008/01/20 PHP
php中curl、fsocket、file_get_content三个函数的使用比较
2014/05/09 PHP
CI框架在CLI下执行占用内存过大问题的解决方法
2014/06/17 PHP
php获取apk包信息的方法
2014/08/15 PHP
php版微信公众平台入门教程之开发者认证的方法
2016/09/26 PHP
PHP缩略图生成和图片水印制作
2017/01/07 PHP
[企业公众号]升级到[企业微信]之后发送消息失败的解决方法
2017/06/30 PHP
php下的原生ajax请求用法实例分析
2020/02/28 PHP
jQuery实战之品牌展示列表效果
2011/04/10 Javascript
js跳转页面方法总结
2014/01/29 Javascript
js变量、作用域及内存详解
2014/09/23 Javascript
JavaScript调用客户端Java程序的方法
2015/07/27 Javascript
AngularJS使用ngMessages进行表单验证
2015/12/27 Javascript
JS组件系列之Bootstrap table表格组件神器【终结篇】
2016/05/10 Javascript
jQuery事件对象总结
2016/10/17 Javascript
使用puppeteer破解极验的滑动验证码
2018/02/24 Javascript
在js代码拼接dom对象到页面上的模板总结
2018/10/21 Javascript
详解微信小程序实现仿微信聊天界面(各种细节处理)
2019/02/17 Javascript
js核心基础之构造函数constructor用法实例分析
2019/05/11 Javascript
javascript自定义日期比较函数用法示例
2019/07/22 Javascript
[01:45]DOTA2新英雄“神谕者”全方位展示
2014/11/21 DOTA
在Python中编写数据库模块的教程
2015/04/29 Python
Python使用进程Process模块管理资源
2020/03/05 Python
Python用类实现扑克牌发牌的示例代码
2020/06/01 Python
8款使用 CSS3 实现超炫的 Loading(加载)的动画效果
2015/03/17 HTML / CSS
美国波西米亚风格服装品牌:Show Me Your Mumu
2018/01/05 全球购物
ETO男装官方网店:ETO Jeans
2019/02/28 全球购物
Overload和Override的区别
2012/09/02 面试题
学习交流会主持词
2014/04/01 职场文书
企业宣传工作方案
2014/06/02 职场文书
2014医学院领导干部四风对照检查材料思想汇报
2014/09/16 职场文书
介绍信格式样本
2015/05/05 职场文书
2015秋季开学典礼新闻稿
2015/07/17 职场文书
2016年5月份红领巾广播稿
2015/12/21 职场文书
怎么用Python识别手势数字
2021/06/07 Python
mysql5.6主从搭建以及不同步问题详解
2021/12/04 MySQL