js 异步执行_webpack模块化原理-异步加载模块

v2-fe3eec07da67bb9a2ca7a09c0689fadc_1440w.jpg?source=172ae18b

在上篇文章中,我们介绍了 webpack 同步加载模块的原理。这篇文章,我们来介绍一下 webpack 异步加载模块。

异步加载模块

还是先做一些准备工作。

首先定义一个依赖模块:math.js,math.js 采用 ES6 module 导出了两个函数 add 和 minus。

export function add(a, b) {
  return a + b;
}

export function minus(a, b) {
  return a - b;
}

然后定义一个入口模块:index.js,index.js 通过 import 函数导入了 math.js,然后调用了里面的 add 和 minus 方法。

import("./math").then(math => {
  console.log(math.add(2, 1));
  console.log(math.minus(2, 1));
});

最后再定义一个配置文件:webpack.config.js,内容和 webpack 同步加载模块中一样。

const path = require("path");

module.exports = {
  mode: "development",
  devtool: "source-map",
  entry: path.join(__dirname, 'index.js'),
  output: {
    filename: "main.js",
    path: path.join(__dirname, 'dist')
  }
};

在根目录下执行webpack --config webpack.config.js,就可以在 dist 目录下看到最终的生成产物了,可以看到 dist 目录下除了 main.js 外,还多了一个 0.main.js。我们通常把 main.js 称为同步 chunk,0.main.js 称为异步 chunk

先来看一下 main.js 文件中的代码。

v2-4b2ac3e0e5ca662b643269cb694e7c06_b.jpg
main.js 代码片段

首先是 webpackBootstrap 的函数体,与同步加载模块相比,它新增了很多内容:

  • webpackJsonpCallback 函数:异步加载 chunk 后的 jsonp 回调函数
  • installedChunks 对象:缓存 chunk 加载状态,main 代表入口模块,0 表示模块已加载,因为入口模块是同步 chunk,所以默认就是已加载的
  • jsonpScriptSrc 函数:拼接 chunk 请求地址
  • __webpack__require__.e 函数:异步加载 chunk 函数
  • jsonp 初始化代码:初始化 jsonp 相关配置

接下来是 webapackBootstrap 的参数部分,与同步加载模块不同的是,它移除了依赖模块 main.js(main.js 中的内容都移入到了异步 chunk 文件 0.main.js 中)。

最后是入口模块 index.js,它使用 __webpack_require__.e 来异步下载依赖模块 math.js,在 math.js 下载完毕后,再使用 __webpack_require__ 来同步加载 math.js

看到这里,大家应该有个初步感觉了:webpack 异步加载模块,其实是通过 jsonp 的方式来实现的。

接下来看一下 __webpack_require__.e_ 的函数实现。

v2-164f63c71bd542fc368e7e03f44a7ddf_b.jpg
__webpack_requiore__.e 函数实现

__webpack_require__.e 中的代码比较多,先看一头一尾:最前面定义了一个 promise 数组 promises,最后面用 Promise.all 的方式返回了 promises。因此 __webpack_require__.e 中间的代码,其实都是用来操作 promises 数组的。具体是如何操作的,我们一步步来看。

首先是缓存查找,在 installedChunks 对象中根据 chunkId 来判断对应的 chunk 是否已加载,chunk 主要有三种状态,分别对应不同的处理:

  • 0:表示 chunk 已加载,这种情况下,不做处理,直接返回
  • 数组:表示 chunk 正在加载,这种情况下,会将缓存的 promise 对象压入 promises 数组中,等待 chunk 加载完毕
  • undefined:表示 chunk 第一次加载,这种情况下的操作包括:
    • 创建一个新的 promise 对象
    • 将新创建 promise 对象的 reject、resolve 方法以及 promise 对象本身都存放到 installedChunks 对象中
    • 将 promise 对象压入 promises 数组中

接下来是模块加载,模块加载使用 jsonp 的方式,首先会动态创建一个 script 标签,src 指向异步 chunk 地址,然后将 script 标签添加到 head 中,实现异步加载 chunk 的功能。

最后是异常处理,给 script 标签添加了 onloade 和 onerror 事件处理函数onScriptCompleteonScriptComplete 在判断模块加载超时或是加载失败的情况下(缓存的 chunk 不为 0),会调用之前保存的 reject 方法返回模块加载失败的异常,同时还会将 chunk 的缓存标识设置为 undefined,标识未加载。

从前面的流程中我们可以看到,__webpack_require__.e 主要是通过 jsonp 的方式来加载异步 chunk,同时通过 promise 对象来控制异步 chunk 的加载情况:在加载失败的情况下会调用 promise 对象的 reject 方法,在加载成功的情况会执行异步 chunk 文件,也就是 0.main.js。

再来看一下 0.main.js 中的代码。

v2-579a66f6115b7fc7d549340eff88d8c6_b.jpg
0.main.js 代码片段

0.main.js 中的内容比较简单,主要是调用 window 上挂载的全局数组 webpackJsop 的 push 方法,push 的内容包括两部分:chunkId 数组和依赖模块函数,webpackJsonp 全局数组又是在哪里定义的呢?

v2-a519cfdcba7cd8ba2b703233ece019eb_b.jpg
jsonp 初始化代码

我们回到 webpackBootStrap 函数,可以看到,webpackJsonp 是在 jsonp 初始化代码中定义的,jsonp 初始化代码主要干了以下几件事:

  1. 定义了一个全局的 webpackJsonp 数组,用来存储所有的 jsonp 回调
  2. webpackJsonp 的原生 push 方法改写为了 webpackJsonpCallback
  3. webpackJsonp 原生的 push 方法保存为了 parentJsonpFunction

所以,在 0.main.js 中调用 webpackJsonp 的 push 方法,最终执行的是 webpackJsonpCallback 中的代码。

最后,看一下 webpackJsonpCallback 的函数实现。

v2-d61d5cc780a8cd44b7aab04e2caa442e_b.jpg
webpackJsonpCallback 函数实现

首先是函数参数,data 参数分为两部分:

  • chunkIds:一个数组,包含当前 chunk 文件依赖的 chunkId,以及自身的 chunkId
  • moreModule:代表当前 chunk 带来的新模块,也就是咱们前面看到的模块执行函数

接下来是执行逻辑:

  1. 遍历 chunkIds 数组,判断 installedChunks 对象中的 chunk 是否处于加载中状态,如果返回数组,则表示正在加载中,在这种情况下,会取出缓存的 resolve 方法(installedChunks[chunkId][0]),放到 resolves 数组中,等下统一执行,同时,还会将对应的 chuk 设置为已加载
  2. moreModules 合并到 modules 对象中,便于后续同步加载
  3. 将 data 参数保存到 webpackJsonp 全局数组中
  4. 遍历 resolves 数组,执行前面缓存的 resolve 方法并清空数组,保证该模块加载开始前所有前置依赖内容包括它自身都已经被加载完毕

总结

总结一下,webpack 采用 jsonp 的方式来实现模块的异步加载:

    • 通过 __webpack_require__.e 实现动态加载
    • 通过 webpackJsonpCallback 实现异步回调

最后,我们用流程图来总结一下 webpack 异步加载模块的流程。

v2-61c8b3f871f324f128cf8e3f617579c7_b.jpg
webpack 异步加载模块流程图
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值