详解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 相关文章推荐
JavaScript 调试器简介
Feb 21 Javascript
JavaScript 字符串乘法
Aug 20 Javascript
JS+CSS制作DIV层可(最小化/拖拽/排序)功能实现代码
Feb 25 Javascript
网页中可关闭的漂浮窗口实现可自行调节
Aug 20 Javascript
jQuery读取和设定KindEditor值的方法
Nov 22 Javascript
浅谈Angular6的服务和依赖注入
Jun 27 Javascript
JS实现HTML页面中动态显示当前时间完整示例
Jul 30 Javascript
详解ESLint在Vue中的使用小结
Oct 15 Javascript
JavaScript this绑定过程深入详解
Dec 07 Javascript
RxJS的入门指引和初步应用
Jun 15 Javascript
node.js使用fs读取文件出错的解决方案
Oct 23 Javascript
微信小程序实现吸顶特效
Jan 08 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
我的论坛源代码(八)
2006/10/09 PHP
一个简单的PHP验证码实现代码
2014/05/10 PHP
twig模板常用语句实例小结
2016/02/04 PHP
linux下为php添加iconv模块的方法
2016/02/28 PHP
JSON 教程 json入门学习笔记
2020/09/22 Javascript
纯JavaScript实现HTML5 Canvas六种特效滤镜示例
2013/06/28 Javascript
jQuery判断多个input file 都不能为空的例子
2015/06/23 Javascript
Sublime Text 3常用插件及安装方法
2015/12/16 Javascript
JS+CSS实现鼠标经过弹出一个DIV框完整实例(带缓冲动画渐变效果)
2016/03/25 Javascript
ionic实现带字的toggle滑动组件
2016/08/27 Javascript
JS验证全角与半角及相互转化的介绍
2017/05/18 Javascript
yarn的使用与升级Node.js的方法详解
2017/06/04 Javascript
二维码图片生成器QRCode.js简单介绍
2017/08/18 Javascript
vue-cli项目无法用本机IP访问的解决方法
2018/09/20 Javascript
VScode格式化ESlint方法(最全最好用方法)
2019/09/10 Javascript
JS在Array数组中按指定位置删除或添加元素对象方法示例
2019/11/19 Javascript
JavaScript运动原理基础知识详解
2020/04/02 Javascript
[52:09]2014 DOTA2华西杯精英邀请赛 5 25 NewBee VS DK第二场
2014/05/26 DOTA
[36:14]DOTA2上海特级锦标赛D组小组赛#1 EG VS COL第二局
2016/02/28 DOTA
[01:08]DOTA2“血战之命”预告片
2017/08/12 DOTA
python使用smtplib模块通过gmail实现邮件发送的方法
2015/05/08 Python
Windows下的Python 3.6.1的下载与安装图文详解(适合32位和64位)
2018/02/21 Python
Python MySQLdb 使用utf-8 编码插入中文数据问题
2018/03/13 Python
解决Pandas to_json()中文乱码,转化为json数组的问题
2018/05/10 Python
python的concat等多种用法详解
2018/11/28 Python
对Python中小整数对象池和大整数对象池的使用详解
2019/07/09 Python
python实现身份证实名认证的方法实例
2019/11/08 Python
在Django中实现添加user到group并查看
2019/11/18 Python
在线购买世界上最好的酒:BoozeBud
2018/06/07 全球购物
某/etc/fstab文件中的某行如下: /dev/had5 /mnt/dosdata msdos defaults,usrquota 1 2 请解释其含义
2013/09/18 面试题
用Python写一个for循环的例子
2016/07/19 面试题
工会换届选举方案
2014/05/21 职场文书
"9.18"国耻日演讲稿范文
2014/09/14 职场文书
党员“四风”方面存在问题及整改措施
2014/09/24 职场文书
镇党委书记群众路线整改措施思想汇报
2014/10/13 职场文书
pytorch fine-tune 预训练的模型操作
2021/06/03 Python