webpack是如何实现动态导入的

前言

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

从一个例子开始

基本环境

webpack 4.35.3java

代码

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

index.jsweb


async.jspromise

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

分析

主入口

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

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

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

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

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

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

__webpack_require__.e

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

  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

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

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

webpackJsonpCallback

能够看到,webpackJsonpCallback 作了2件事情。

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

如今,咱们终于理清了异步加载的所有流程。

more

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

相关文章
相关标签/搜索