likes
comments
collection
share

webpack是如何实现动态导入的

作者站长头像
站长
· 阅读数 12

前言

在单页应用中,经常使用 webpack 的 动态导入 功能来异步加载模块,从而减少部分文件的体积。我们可以通过webpack 提供的 import()require.ensure 两个 API 来使用该功能。由于两个方法根本实现都是相同的,本文的示例都基于 import() 方法

从一个例子开始

基本环境

webpack 4.35.3

代码

这边用一个最简单的例子,有两个文件,index.js 为入口文件,该文件使用 import() 来动态导入 async.js 文件。

index.js

webpack是如何实现动态导入的


async.js

webpack是如何实现动态导入的

我们执行 webpack --mode=development 来获得编译后文件 main.js0.js。其中,main.js 包含 index.js 代码,0.js 包含 async.js 代码。

分析

主入口

首先,main.js 文件作为整个应用的入口,我们来看看里面有什么东西。

webpack是如何实现动态导入的

我们先忽略详细的代码逻辑,整体看下来,发现这个文件其实就是一个自执行函数,该函数把 转化后的 index.js文件路径 组成一个 modules 传给主函数。

主函数中,会执行 __webpack_require__(__webpack_require__.s = "./src/index.js"); 来初始化入口模块。

webpack是如何实现动态导入的

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

webpack是如何实现动态导入的

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

webpack是如何实现动态导入的

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

__webpack_require__.e

webpack是如何实现动态导入的

代码可能有点眼花,看下来无非就是做了这么一件事情。

  1. 根据 installedChunks 检查是否加载过该 chunk
  2. 假如没加载过,则发起一个 JSONP 请求去加载 chunk
  3. 设置一些请求的错误处理,然后返回一个 Promise

当 Promise 返回之后,就会继续执行我们之前的异步请求回调

__webpack_require__.e(/*! import() */ 0)
    .then(
        __webpack_require__.bind(null, /*! ./async */ "./src/async.js")
    )...

这里直接调用了 __webpack_require__ 去加载我们的 异步模块

这里就有两个问题?

  1. __webpack_require__ 是根据我们之前传入的 modules 来获取 module 的,但是,在 __webpack_require__.e 中并没有看到有对 modules 执行操作的代码。那 modules 到底是什么时候被更新的呢?
  2. promiseresolvereject 全部存入了 installedChunks 中, 并没有在获取异步chunk成功的onload 回调中执行 resolve,那么,resolve 是什么时候被执行的呢?
var promise = new Promise(function(resolve, reject) {
    installedChunkData = installedChunks[chunkId] = [resolve, reject];
});

带着疑问,我们来看一下加载的 0.js 中的内容。

异步Chunk

0.js

webpack是如何实现动态导入的

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

webpack是如何实现动态导入的

这边用自定义的 webpackJsonpCallback 函数替换window["webpackJsonp"]push 方法。所以说,我们之前 0.js 执行的 push 其实就是执行了自定义的 webpackJsonpCallback 函数

webpackJsonpCallback

可以看到,webpackJsonpCallback 做了2件事情。

  1. 执行 installedChunks 中的 resolve , 让 import() 得以继续执行。
  2. chunk 中含有的 模块全部注册到 modules 变量中。

现在,我们终于理清了异步加载的全部流程。

webpack是如何实现动态导入的

more

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