上篇文章解释了webpack 是怎么加载拆包后的代码的,这篇文章解释下 webpack 如何处理 import()。
import() 包含的代码被 webpack 当作 chunk 处理,同样也是通过 window["webapckJsonp"] 来进行加载。有了上篇文章的讲解,本篇理解起来会很容易。讲解之前再回顾下 webpack bootstrap 代码中非常重要的四个缓存变量
- modules:缓存 module 代码块,每个 module 有一个 id,开发环境默认以 module 所在文件的文件名标识,生产环境默认以一个数字标识。modules 是一个 object, key 为 module id,value 为对应 module 的源代码块。
- installedModules:缓存已经加载过的 module,简单理解就是已经运行了源码中 import somemodule from 'xxx' 这样的语句。installedModules 是一个 object, key 为 module id,value 为对应 module 导出的变量。(跟 modules 的 value 是不一样的,这里的 value 保存的是 module 对应的代码中 export 的变量)
- installedChunks:缓存已经加载过的 chunk,简单理解就是把其他 js 文件中的 chunk 包含的 modules 同步到了当前文件中。每个 chunk 有一个 id,默认以一个数字标识。installedChunks 也是一个对象,key 为 chunk id,value 有四种情况:
- undefined:chunk not loaded
- null:chunk preloaded/prefetched
- Promise:chunk loading
- 0:chunk loaded
- deferredModules:缓存运行当前 web app 需要的 chunk id 以及入口 module id(截图中 299 标识入口 module 的 id,0 和 1 标识运行必需的另外两个 chunk 的 id),比如,react 和 react-dom 被单独打包到了另外的 js 中,入口文件需要等待 react 和 react-dom 加载成功之后才能运行(这篇文章不涉及)。
import() --> __webpack_require__.e
首先看一下 import() 被转换后的代码
文件路径被替换成了 chunkId,作为参数调用了__webpack_require__.e,重点研究下 __webpack_require__.e 的实现
再来重复下, installedChunks 一个对象,key 为 chunk id,value 有四种情况:
-
- undefined:chunk not loaded
- null:chunk preloaded/prefetched(本篇不涉及)
- Promise:chunk loading
- 0:chunk loaded
现在看一下 __webpack_require__.e 干了什么
- 判断 installedChunks[chunkId] 是否已经被加载,如果已经被加载,直接返回 Promise.all([])
- 判断 installedChunks[chunkId] 是否在加载中,如果在加载中,把表示加载中的 promise 添加到 promises 数组中
- 没有加载的话,创建 promise,并赋值:installedChunks[chunkId] = [resolve, reject],添加到promises 数组中
- 动态创建 script 标签,添加 onerror 以及 onload 事件,并进行加载超时的处理
-
- 加载成功或者失败都会清除加载超时的处理函数
- 如果加载没有成功,构造 error 信息。还记得 installedChunks[chunkId] = [resolve, reject] 吗?这个时候就派上用场了,chunk[1](error) 即为 reject(error),触发 promise 的 reject
- __webpack_require__.e 返回 prmises 数组,等待 chunk 加载完成
webpackJsonpCallback
什么时候 promise 会 resolve 呢?code spliting 加载代码时,webpack 的 bootstrap 已经运行过,也就是说 window["webapckJsonp"] 的 push 方法已经被改写成了 webpackJsonpCallback
现在以 code spliting 的角度再来走一遍下面的逻辑
- 参数 data 的形式为 [ chunkId[], modules[] ]
- 第一个 for 循环判断 installedChunks[chunkId] 是否在加载中,这个时候 installedChunks[chunkId] = [resolve, reject]。执行当前函数时,代表当前 chunk 已经被正确加载,因此 installedChunks[chunkId][0] 表示的即为 resolve
- 第二个 for 循环把当前 chunk 包含的 module 保存到入口文件的 modules 变量
- 执行 resolve(),__webpack_require__.e 返回 fullfilled 态的 promise,表示 chunk 加载完毕
执行 __webpack_require__(moduId),加载入口代码,从而 import() 逻辑加载完毕
依然画个流程图总结一下
推荐阅读
聊聊 webpack 异步加载(一):webpack 如何加载拆包后的代码
聊聊 webpack 异步加载(三):webpack 如何做到持久化缓存