详解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中匿名函数,函数直接量和闭包
May 08 Javascript
利用JS进行图片的切换即特效展示图片
Dec 03 Javascript
同域jQuery(跨)iframe操作DOM(实例讲解)
Dec 19 Javascript
手写的一个兼容各种浏览器的javascript getStyle函数(获取元素的样式)
Jun 06 Javascript
JavaScript前端图片加载管理器imagepool使用详解
Dec 29 Javascript
jQuery实现div拖拽效果实例分析
Feb 20 Javascript
Vue2.0利用vue-resource上传文件到七牛的实例代码
Jul 28 Javascript
jQuery实现base64前台加密解密功能详解
Aug 29 jQuery
js实现时分秒倒计时
Dec 03 Javascript
JavaScript canvas实现文字时钟
Jan 10 Javascript
解决Vue+SpringBoot+Shiro跨域问题
Jun 09 Vue.js
vue-cli3.x配置全局的scss的时候报错问题及解决
Apr 30 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
关于查看MSSQL 数据库 用户每个表 占用的空间大小
2013/06/21 PHP
PHP实现视频文件上传完整实例
2014/08/28 PHP
THinkPHP获取客户端IP与IP地址查询的方法
2016/11/14 PHP
PHP pthreads v3下同步处理synchronized用法示例
2020/02/21 PHP
javascript 拖放效果实现代码
2010/01/22 Javascript
extjs中grid中嵌入动态combobox的应用
2011/01/01 Javascript
javascript 进阶篇2 CSS XML学习
2012/03/14 Javascript
非html5实现js版弹球游戏示例代码
2013/09/22 Javascript
举例详解Python中smtplib模块处理电子邮件的使用
2015/06/24 Javascript
js文字横向滚动特效
2015/11/11 Javascript
基于jQuery代码实现圆形菜单展开收缩效果
2017/02/13 Javascript
jQuery插件HighCharts绘制2D带有Legend的饼图效果示例【附demo源码下载】
2017/03/10 Javascript
基于ES6 Array.of的用法(实例讲解)
2017/09/05 Javascript
详解使用create-react-app快速构建React开发环境
2018/05/16 Javascript
jQuery轻量级表单模型验证插件
2018/10/15 jQuery
Javascript原生ajax请求代码实例
2020/02/20 Javascript
使用webpack5从0到1搭建一个react项目的实现步骤
2020/12/16 Javascript
python爬虫框架scrapy实战之爬取京东商城进阶篇
2017/04/24 Python
Python设计实现的计算器功能完整实例
2017/08/18 Python
python3.5 email实现发送邮件功能
2018/05/22 Python
Python实现FTP弱口令扫描器的方法示例
2019/01/31 Python
pandas数据拼接的实现示例
2020/04/16 Python
Python小白垃圾回收机制入门
2020/06/09 Python
解决python和pycharm安装gmpy2 出现ERROR的问题
2020/08/28 Python
canvas 实现 github404动态效果的示例代码
2017/11/15 HTML / CSS
html5 canvas简单封装一个echarts实现不了的饼图
2018/06/12 HTML / CSS
如何使用canvas绘制可移动网格的示例代码
2020/12/14 HTML / CSS
美国一家著名的儿童鞋制造商:Stride Rite
2017/01/02 全球购物
Europcar英国:英国汽车和货车租赁
2017/01/21 全球购物
巴西最大的巴士票务门户:Quero Passagem
2020/11/21 全球购物
手机被没收检讨书
2014/02/22 职场文书
财产公证书样本
2014/04/04 职场文书
学习党的群众路线对照检查材料
2014/09/29 职场文书
2014年党小组工作总结
2014/12/20 职场文书
文艺节目主持词
2015/07/06 职场文书
生日寿星公答谢词
2015/09/29 职场文书