webpack提供的一个非常强大的功能就是code spliting(代码切割)。
在webpack 1.x中提供了
require.ensure([], () => { let module = require('./page1/module'); // do something }, 'module1')
利用require.ensure这个api使得webpack单独将这个文件打包成一个可以异步加载的chunk.
具体的套路见我写的另一篇blog:
一句话总结就是:
在输出的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看下编译后的代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。
您可能感兴趣的文章:
如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!
网友评论