详解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学习笔记8 用JSON做原型
Jan 11 Javascript
精选的10款用于构建良好易用性网站的jQuery插件
Jan 23 Javascript
jQuery中需要注意的细节问题小结
Dec 06 Javascript
JS防止用户多次提交的简单代码
Aug 01 Javascript
JavaScript 事件对象介绍
Apr 13 Javascript
解决angular的$http.post()提交数据时后台接收不到参数值问题的方法
Dec 10 Javascript
BootStrap3中模态对话框的使用
Jan 06 Javascript
微信小程序实现图片上传功能实例(前端+PHP后端)
Jan 10 Javascript
JS实现二维数组元素的排列组合运算简单示例
Jan 28 Javascript
ios中视频的最后一桢问题解决
May 14 Javascript
jquery.pager.js分页实现详解
Jul 29 jQuery
Vue 中使用 typescript的方法详解
Feb 17 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
php中通过curl模拟登陆discuz论坛的实现代码
2012/02/16 PHP
php常用hash加密函数
2014/11/22 PHP
学习php设计模式 php实现状态模式
2015/12/07 PHP
Redis构建分布式锁
2017/03/28 PHP
PhpStorm本地断点调试的方法步骤
2018/05/21 PHP
php workerman定时任务的实现代码
2018/12/23 PHP
jQuery插件 tabBox实现代码
2010/02/09 Javascript
jquery 笔记 事件
2011/11/02 Javascript
jquery实现固定顶部导航效果(仿蘑菇街)
2013/03/21 Javascript
简介JavaScript中的setDate()方法的使用
2015/06/11 Javascript
JS实现可点击展开与关闭的左侧广告代码
2015/09/02 Javascript
jQuery采用连缀写法实现的折叠菜单效果
2015/09/18 Javascript
jQuery 自定义下拉框(DropDown)附源码下载
2016/07/22 Javascript
Bootstrap Table服务器分页与在线编辑应用总结
2016/08/08 Javascript
详解nodejs中express搭建权限管理系统
2017/09/15 NodeJs
利用canvas中toDataURL()将图片转为dataURL(base64)的方法详解
2017/11/20 Javascript
JS中原始值和引用值的储存方式示例详解
2018/03/23 Javascript
react-router-dom 嵌套路由的实现
2020/05/02 Javascript
[47:10]完美世界DOTA2联赛PWL S3 LBZS vs Rebirth 第二场 12.16
2020/12/18 DOTA
一个超级简单的python web程序
2014/09/11 Python
Python如何快速上手? 快速掌握一门新语言的方法
2017/11/14 Python
Python面向对象之继承代码详解
2018/01/29 Python
python 定义给定初值或长度的list方法
2018/06/23 Python
python将时分秒转换成秒的实例
2019/12/07 Python
python实现随机加减法生成器
2020/02/24 Python
利用python实现凯撒密码加解密功能
2020/03/31 Python
keras 自定义loss损失函数,sample在loss上的加权和metric详解
2020/05/23 Python
pycharm 使用anaconda为默认环境的操作
2021/02/05 Python
浅析数据存储的三种方式 cookie sessionstorage localstorage 的异同
2020/06/04 HTML / CSS
京东全球售:直邮香港,澳门,台湾,美国,澳大利亚等地区
2017/09/24 全球购物
介绍一下SQL注入攻击的种类和防范手段
2012/02/18 面试题
大学生预备党员自我评价分享
2013/11/16 职场文书
品质标语大全
2014/06/21 职场文书
2015法院个人工作总结范文
2015/05/25 职场文书
大学毕业谢师宴致辞
2015/07/27 职场文书
Zabbix对Kafka topic积压数据监控的解决方案
2022/07/07 Servers