详解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——表单应用范例
Feb 20 Javascript
jQuery 表格工具集
Apr 25 Javascript
JS封装cookie操作函数实例(设置、读取、删除)
Nov 17 Javascript
javascript动画之模拟拖拽效果篇
Sep 26 Javascript
Vue编写多地区选择组件
Aug 21 Javascript
移动端效果之Swiper详解
Oct 09 Javascript
在vue中给列表中的奇数行添加class的实现方法
Sep 05 Javascript
layui lay-verify form表单自定义验证规则详解
Sep 18 Javascript
JavaScript实现轮播图特效
Apr 10 Javascript
使用Webpack 搭建 Vue3 开发环境过程详解
Jul 28 Javascript
vue 封装面包屑组件教程
Nov 16 Javascript
vue+iview实现文件上传
Nov 17 Vue.js
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图像处理类代码分享
2012/01/19 PHP
php计算给定日期所在周的开始日期和结束日期示例
2017/02/06 PHP
js中将具有数字属性名的对象转换为数组
2011/03/06 Javascript
JavaScript高级程序设计 读书笔记之十一 内置对象Global
2012/03/07 Javascript
javascript中全局对象的parseInt()方法使用介绍
2013/12/19 Javascript
JS实现自动变化的导航菜单效果代码
2015/09/09 Javascript
3种js实现string的substring方法
2015/11/09 Javascript
jquery 实时监听输入框值变化的完美方法(必看)
2017/01/26 Javascript
原生js 封装get ,post, delete 请求的实例
2017/08/11 Javascript
详解vue 模拟后台数据(加载本地json文件)调试
2017/08/25 Javascript
用JavaScript做简易的购物车的代码示例
2017/10/20 Javascript
Vuejs 2.0 子组件访问/调用父组件的方法(示例代码)
2018/02/08 Javascript
JS扩展String.prototype.format字符串拼接的功能
2018/03/09 Javascript
Vue 页面跳转不用router-link的实现代码
2018/04/12 Javascript
javascript实现小型区块链功能
2019/04/03 Javascript
layui实现图片虚拟路径上传,预览和删除的例子
2019/09/25 Javascript
react用Redux中央仓库实现一个todolist
2019/09/29 Javascript
[03:07]完美世界DOTA2联赛PWL DAY10 决赛集锦
2020/11/11 DOTA
pycharm下打开、执行并调试scrapy爬虫程序的方法
2017/11/29 Python
Python双向循环链表实现方法分析
2018/07/30 Python
Python 调用 Windows API COM 新法
2019/08/22 Python
基于Python中isfile函数和isdir函数使用详解
2019/11/29 Python
pytorch 归一化与反归一化实例
2019/12/31 Python
基于 HTML5 WebGL 实现的医疗物流系统
2019/10/08 HTML / CSS
哥伦比亚最大的网上商店:Linio哥伦比亚
2016/09/25 全球购物
英国最大线上综合鞋类商城:Office
2017/12/08 全球购物
摩托车和ATV零件、配件和服装的首选在线零售商:MotoSport
2017/12/22 全球购物
New Era英国官网:美国棒球帽品牌
2018/03/21 全球购物
常用UNIX 命令(Linux的常用命令)
2015/12/26 面试题
Linux开机引导的步骤是什么
2014/02/26 面试题
爱我中华演讲稿
2014/05/20 职场文书
美国旅游签证工作证明
2014/10/14 职场文书
巴黎圣母院读书笔记
2015/06/26 职场文书
社区安全温馨提示语
2015/07/14 职场文书
大学军训通讯稿
2015/07/18 职场文书
分享五个Node.js开发的优秀实践 
2022/04/07 NodeJs