聊聊 webpack 异步加载(二):webpack 如何处理 import()

1,386 阅读3分钟

作者 | vayne
转载请注明出处: juejin.cn/user/272334…

上篇文章解释了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

  • 判断 installedChunks[chunkId] 是否已经被加载,如果已经被加载,直接返回 Promise.all([])

  • 判断 installedChunks[chunkId] 是否在加载中,如果在加载中,把表示加载中的 promise 添加到 promises 数组中

  • 没有加载的话,创建 promise,并赋值:installedChunks[chunkId] = [resolve, reject],添加到promises 数组中

  • 动态创建 script 标签,添加 onerror 以及 onload 事件,并进行加载超时的处理

  • 加载成功或者失败都会清除加载超时的处理函数

  • 如果加载没有成功,构造 error 信息。还记得 installedChunks[chunkId] = [resolve, reject] 吗?这个时候就派上用场了,chunk1 即为 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() 逻辑加载完毕

依然画个流程图总结一下