详解webpack2异步加载套路


Posted in Javascript onSeptember 14, 2018

webpack提供的一个非常强大的功能就是code spliting(代码切割)。

在webpack 1.x中提供了

require.ensure([], () => {
    let module = require('./page1/module');
    // do something
  }, 'module1')

利用require.ensure这个API使得webpack单独将这个文件打包成一个可以异步加载的chunk.

具体的套路见我写的另一篇blog: webpack分包及异步加载套路

一句话总结就是:

在输出的runtime代码中,包含了异步chunk的id及chunk name的映射关系。需要异步加载相应的chunk时,通过生成script标签,然后插入到DOM中完成chunk的加载。通过JSONP,runtime中定义好函数,chunk加载完成后即会立即执行这个函数。

从编译生成后的代码来看,webpack 1.x从chunk的加载到执行的过程处理的比较粗糙,仅仅是通过添加script标签,异步加载chunk后,完成函数的执行。

这个过程当中,如果出现了chunk加载不成功时,这种情况下应该如何去容错呢?

在webpack2中相比于webpack1.x在这个点的处理上是将chunk的加载包裹在了promise当中,那么这个过程变的可控起来。具体的webpack2实现套路也是本文想要去说明的地方。

webpack提供的异步加载函数是

/******/   // This file contains only the entry chunk.
/******/   // The chunk loading function for additional chunks
      // runtime代码里面只包含了入口的chunk
      // 这个函数的主要作用:
      // 1. 异步加载chunk
      // 2. 提供对于chunk加载失败或者处于加载中的处理
      // 其中chunk加载状态的判断是根据installedChunks对象chunkId是数字0还是数组来进行判断的
/******/   __webpack_require__.e = function requireEnsure(chunkId) {
        // 数字0代表chunk加载成功
/******/     if(installedChunks[chunkId] === 0) 
/******/       return Promise.resolve();

/******/     // an Promise means "currently loading".
        // 如果installedChunks[chunkId]为一个数组
/******/     if(installedChunks[chunkId]) {
          // 返回一个promise对象
/******/       return installedChunks[chunkId][2];
/******/     }
/******/     // start chunk loading
        // 通过生成script标签来异步加载chunk.文件名是根据接受的chunkId来确认的
/******/     var head = document.getElementsByTagName('head')[0];
/******/     var script = document.createElement('script');
/******/     script.type = 'text/javascript';
/******/     script.charset = 'utf-8';
/******/     script.async = true;
        // 超时时间为120s
/******/     script.timeout = 120000;

/******/     if (__webpack_require__.nc) {
/******/       script.setAttribute("nonce", __webpack_require__.nc);
/******/     }
        // 需要加载的文件名
/******/     script.src = __webpack_require__.p + "js/register/" + ({"2":"index"}[chunkId]||chunkId) + ".js";
        // 120s的定时器,超时后触发onScriptComplete回调
/******/     var timeout = setTimeout(onScriptComplete, 120000);
        // chunk加载完毕后的回调
/******/     script.onerror = script.onload = onScriptComplete;
/******/     function onScriptComplete() {
/******/       // avoid mem leaks in IE.
/******/       script.onerror = script.onload = null;
          // 清空定时器
/******/       clearTimeout(timeout);
          // 获取这个chunk的加载状态
          // 若为数字0,表示加载成功
          // 若为一个数组, 调用数组的第2个元素(第二个元素为promise内传入的reject函数),使得promise捕获抛出的错误。reject(new Error('xxx'))
/******/       var chunk = installedChunks[chunkId];
/******/       if(chunk !== 0) {
/******/         if(chunk) chunk[1](new Error('Loading chunk ' + chunkId + ' failed.'));
/******/         installedChunks[chunkId] = undefined;
/******/       }
/******/     };
        
        // 每次需要进行异步加载chunk时,会将这个chunk的加载状态进行初始化为一个数组,并以key/value的形式保存在installedChunks里
        // 这个数组为[resolve, reject, promise];
/******/     var promise = new Promise(function(resolve, reject) {
/******/       installedChunks[chunkId] = [resolve, reject];
/******/     });
/******/     installedChunks[chunkId][2] = promise;

/******/     head.appendChild(script);
        //返回promise
/******/     return promise;
/******/   };

我们再来看看路由配置文件编译后生成的代码index.js, 特别注意下__webpack_require__.e这个异步加载函数:

Router
.home('path1')
.addRoute({
  path: 'path1',
  animate: 'zoomIn',
  viewBox: '.public-path1-container',
  template: __webpack_require__(5),
  // 挂载controller
  pageInit: function pageInit() {
    var _this = this;

    console.time('route async path1');
    // 异步加载0.js(这个文件是webpack通过code spliting自己生成的文件名)
    // 具体异步加载代码的封装见?分析
    // 其中0.js包含了包含了path1这个路由下的业务代码
    // __webpack_require__.e(0) 起的作用仅为加载chunk以及提供对于chunk加载失败错误的抛出
    // 具体的业务代码的触发是通过__webpack_require_e(0).then(__webpack_require__.bind(null, 8)).then(function(module) { ... })进行触发
    // __webpack_require__.bind(null, 8) 返回的是module[8]暴露出来的module
    // 这段代码执行时,首先初始化一个module对象
    // module = {
    //    i: moduleId, // 模块id
    //    l: false,   // 加载状态
    //    exports: {}  // 需要暴露的对象
    //  }
    // 通过异步加载的chunk最后暴露出来的对象是作为了module.exports.default属性
    // 因此在第二个方法中传入的对象的default属性才是你模块8真正所暴露的对象
    __webpack_require__.e/* import() */(0).then(__webpack_require__.bind(null, 8)).then(function (module) {
      var controller = module.default;
      Router.registerCtrl('path1', new controller(_this.viewBox));
    // 添加错误处理函数,用以捕获前面可能抛出的错误
    }).catch(function (e) {
      return console.log('chunk loading failed');
    });
  },

  // 进入路由跳转之前
  beforeEnter: function beforeEnter() {},

  // 路由跳转前
  beforeLeave: function beforeLeave() {}
})
.addRoute({
  path: 'path2',
  viewBox: '.public-path2-container',
  animate: 'zoomIn',
  template: __webpack_require__(6),
  pageInit: function pageInit() {
    var _this2 = this;

    __webpack_require__.e/* import() */(1).then(__webpack_require__.bind(null, 9)).then(function (module) {
      console.time('route async path2');
      var controller = module.default;
      Router.registerCtrl('path2', new controller(_this2.viewBox));
    }).catch(function (e) {
      return console.log('chunk loading failed');
    });
  },
  beforeEnter: function beforeEnter() {},
  beforeLeave: function beforeLeave() {}
});

Router.bootstrap();

总结一下就是:

webpack2相比于webpack1.x将异步加载chunk的过程封装在了promise当中,如果chunk加载超时或者失败会抛出错误,这时我们可以针对抛出的错误做相应的错误处理。

此外还应该注意下,webpack2异步加载chunk是基于原生的promise。如果部分环境暂时还不支持原生promise时需要提供polyfill。另外就是require.ensure可以接受第三个参数用以给chunk命名,但是import这个API没有提供这个方法

更多的细节大家可以运行demo看下编译后的代码

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

Javascript 相关文章推荐
浅谈javascript的数据类型检测
Jul 10 Javascript
使用jQuery mobile库检测url绝对地址和相对地址的方法
Dec 04 Javascript
高性能JavaScript循环语句和条件语句
Jan 20 Javascript
如何使用PHP+jQuery+MySQL实现异步加载ECharts地图数据(附源码下载)
Feb 23 Javascript
Vue + Webpack + Vue-loader学习教程之功能介绍篇
Mar 14 Javascript
vue.js评论发布信息可插入QQ表情功能
Aug 08 Javascript
React + webpack 环境配置的方法步骤
Sep 07 Javascript
JS中‘hello’与new String(‘hello’)引出的问题详解
Aug 14 Javascript
JavaScript中concat复制数组方法浅析
Jan 20 Javascript
使用Webpack提升Vue.js应用程序的4种方法(翻译)
Oct 09 Javascript
手把手教您实现react异步加载高阶组件
Apr 07 Javascript
vue实现简易的双向数据绑定
Dec 29 Vue.js
关于在vue 中使用百度ueEditor编辑器的方法实例代码
Sep 14 #Javascript
react项目实践之webpack-dev-serve
Sep 14 #Javascript
javacript replace 正则取字符串中的值并替换【推荐】
Sep 13 #Javascript
vue删除html内容的标签样式实例
Sep 13 #Javascript
如何解决vue2.0下IE浏览器白屏问题
Sep 13 #Javascript
vue2.0获取鼠标位置的方法
Sep 13 #Javascript
vue实现动态列表点击各行换色的方法
Sep 13 #Javascript
You might like
php使用递归函数实现数字累加的方法
2015/03/16 PHP
PHP基于自定义类随机生成姓名的方法示例
2017/08/05 PHP
PHP中实现中文字串截取无乱码的解决方法
2018/05/29 PHP
yii2中关于加密解密的那些事儿
2018/06/12 PHP
PHP7 安装event扩展的实现方法
2019/10/08 PHP
PHP操作Redis常用命令的实例详解
2020/12/23 PHP
另类调用flash无须激活的方法
2006/12/27 Javascript
jQuery 1.5最新版本的改进细节分析
2011/01/19 Javascript
jQuery find和children方法使用
2011/01/31 Javascript
js 通过cookie实现刷新不变化树形菜单
2014/10/30 Javascript
jQuery实现渐变弹出层和弹出菜单的方法
2015/02/20 Javascript
PHP和NodeJs开发的应用如何共用Session
2015/04/16 NodeJs
JavaScript使用cookie实现记住账号密码功能
2015/04/27 Javascript
用JavaScript实现PHP的urlencode与urldecode函数
2015/08/13 Javascript
JavaScript队列、优先队列与循环队列
2016/11/14 Javascript
Node.js中用D3.js的方法示例
2017/01/16 Javascript
JavaScript实现简单的双色球(实例讲解)
2017/07/31 Javascript
javaScript canvas实现(画笔大小 颜色 橡皮的实例)
2017/11/28 Javascript
Vue响应式原理深入解析及注意事项
2017/12/11 Javascript
Vue 组件封装 并使用 NPM 发布的教程
2018/09/30 Javascript
JS中如何轻松遍历对象属性的方式总结
2019/08/06 Javascript
js实现车辆管理系统
2020/08/26 Javascript
Python 文件重命名工具代码
2009/07/26 Python
python分析nignx访问日志脚本分享
2015/02/26 Python
Python基于select实现的socket服务器
2016/04/13 Python
Python3.7实现中控考勤机自动连接
2018/08/28 Python
tensorflow 重置/清除计算图的实现
2020/01/19 Python
利用CSS3参考手册和CSS3代码生成工具加速来学习网页制
2012/07/11 HTML / CSS
关于赌博的检讨书
2014/01/24 职场文书
桥梁工程专业求职信
2014/04/21 职场文书
会计个人实习计划书
2014/08/15 职场文书
学校拾金不昧表扬信
2015/01/16 职场文书
人事专员岗位职责
2015/02/03 职场文书
Django实现drf搜索过滤和排序过滤
2021/06/21 Python
微信小程序scroll-view不能左右滑动问题的解决方法
2021/07/09 Javascript
Python内置类型集合set和frozenset的使用详解
2022/04/26 Python