webpack是如何实现动态导入的
前言
在单页应用中,经常使用 webpack 的 动态导入 功能来异步加载模块,从而减少部分文件的体积。我们可以通过webpack 提供的 import()
和 require.ensure
两个 API 来使用该功能。由于两个方法根本实现都是相同的,本文的示例都基于 import()
方法。
从一个例子开始
基本环境
webpack 4.35.3
代码
这边用一个最简单的例子,有两个文件,
index.js
为入口文件,该文件使用import()
来动态导入async.js
文件。
index.js

async.js

我们执行 webpack --mode=development
来获得编译后文件 main.js
与 0.js
。其中,main.js
包含 index.js
代码,0.js
包含 async.js
代码。
分析
主入口
首先,main.js
文件作为整个应用的入口,我们来看看里面有什么东西。

我们先忽略详细的代码逻辑,整体看下来,发现这个文件其实就是一个自执行函数,该函数把 转化后的 index.js
和 文件路径 组成一个 modules 传给主函数。
主函数中,会执行 __webpack_require__(__webpack_require__.s = "./src/index.js");
来初始化入口模块。

这个函数的作用就是加载并且执行指定的模块,并且返回模块的 module.exports
。
这边执行模块调用了 modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
来执行之前传入的 module,也就是 转换后的index.js
。

我们抽离当中的代码看一下。

发现import
被转化成了 __webpack_require__.e(/*! import() */ 0)
,我们顺藤摸瓜,继续来看看 __webpack_require__.e
做了些什么。
__webpack_require__.e

代码可能有点眼花,看下来无非就是做了这么一件事情。
- 根据
installedChunks
检查是否加载过该 chunk - 假如没加载过,则发起一个
JSONP
请求去加载 chunk - 设置一些请求的错误处理,然后返回一个 Promise。
当 Promise 返回之后,就会继续执行我们之前的异步请求回调
__webpack_require__.e(/*! import() */ 0)
.then(
__webpack_require__.bind(null, /*! ./async */ "./src/async.js")
)...
这里直接调用了 __webpack_require__
去加载我们的 异步模块
。
这里就有两个问题?
__webpack_require__
是根据我们之前传入的modules
来获取module
的,但是,在__webpack_require__.e
中并没有看到有对modules
执行操作的代码。那modules
到底是什么时候被更新的呢?promise
把resolve
和reject
全部存入了installedChunks
中, 并没有在获取异步chunk成功的onload
回调中执行resolve
,那么,resolve
是什么时候被执行的呢?
var promise = new Promise(function(resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
带着疑问,我们来看一下加载的 0.js
中的内容。
异步Chunk
0.js

这边逻辑也很简单,只是往window["webpackJsonp"]
里面 push chunkId
和 含有的modules
。我们搜一下 window["webpackJsonp"]
, 发现在 main.js
中有这么一段代码。

这边用自定义的 webpackJsonpCallback
函数替换了 window["webpackJsonp"]
的 push
方法。所以说,我们之前 0.js
执行的 push
其实就是执行了自定义的 webpackJsonpCallback
函数。
webpackJsonpCallback
可以看到,webpackJsonpCallback 做了2件事情。
- 执行
installedChunks
中的resolve
, 让import()
得以继续执行。 - 将
chunk
中含有的 模块全部注册到modules
变量中。
现在,我们终于理清了异步加载的全部流程。

more
其实,以上的代码只是最简单的情况,随着代码的不同,生成的函数具体内容也会有所差异。比如,我们添加预加载代码 import(/* webpackPrefetch: true */'./preload');
, 生成的 main.js
文件中才会有支持该功能的代码,不难看出,webpack此举是为了控制生成对文件大小,对具体细节感兴趣的同学,可以本地尝试一下。
转载自:https://juejin.cn/post/6844903888319954952