详细解析Webpack是怎么运行的(6)

看起来有点长,我们一步步剖析,先从第一行和最后一行来看,整个函数将异步加载的过程封装到了 promise 中,最终导出。

接着从第二行开始,installedChunkData 从缓存中取值,显然首次加载 chunk 时此处是 undefined。接下来,installedChunkData 的 undefined 值触发了第一层 if 语句的判断条件。紧接着进行到第二层 if 语句,此时根据判断条件走入 else块,这里 if 块里的内容我们先战略跳过,else里主要有两块内容,一是 chunk 脚本加载过程,这个过程创建了一个 script 标签,使其请求 chunk所在地址并执行chunk 内容;二是初始化 promise ,并用 promis 控制 chunk 文件加载过程。

不过,我们只在这段 else 代码块中找到了 reject 的使用处,也就是在 chunk 加载异常时chunk[1](error) 的地方,但并没发现更重要的 resolve的使用地点,仅仅是把 resolve 挂在了缓存上(installedChunks[chunkId] = [resolve, reject])。

这里的 chunk 文件加载下来会发生什么呢?让我们打开dist/0.js一探究竟:

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0], {
 "./src/utils/math.js":
 (function (module, __webpack_exports__, __webpack_require__) {
  "use strict";
  __webpack_require__.r(__webpack_exports__);
  /* harmony export (binding) */
  __webpack_require__.d(__webpack_exports__, "plus", function () {
  return plus;
  });
  const plus = (a, b) => {
  return a + b;
  };
 })
}]);

我们发现了:

久违的 ./src/utils/math.js 模块

window["webpackJsonp"] 数组的使用地点

这段代码开始执行,把异步加载相关的 chunk id 与模块传给push 函数。而前面已经提到过,window["webpackJsonp"]数组的 push 函数已被重写为 webpackJsonpCallback 函数,它的定义位置在 webpackBootstrap 中:

function webpackJsonpCallback(data) {
 var chunkIds = data[0];
 var moreModules = data[1];
 // then flag all "chunkIds" as loaded and fire callback
 var moduleId, chunkId, i = 0, resolves = [];
 // 将 chunk 标记为已加载
 for(;i < chunkIds.length; i++) {
 chunkId = chunkIds[i];
 if(installedChunks[chunkId]) {
  resolves.push(installedChunks[chunkId][0]);
 }
 installedChunks[chunkId] = 0;
 }
 // 把 "moreModules" 加到 webpackBootstrap 中的 modules 闭包变量中。
 for(moduleId in moreModules) {
 if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
  modules[moduleId] = moreModules[moduleId];
 }
 }
 // parentJsonpFunction 是 window["webpackJsonp"] 的原生 push
 // 将 data 加入全局数组,缓存 chunk 内容
 if(parentJsonpFunction) parentJsonpFunction(data);
 // 执行 resolve 后,加载 chunk 的 promise 状态变为 resolved,then 内的函数开始执行。
 while(resolves.length) {
 resolves.shift()();
 }
};

走进这个函数中,意味着异步加载的 chunk 内容已经拿到,这个时候我们要完成两件事,一是让依赖这次异步加载结果的模块继续执行,二是缓存加载结果。