一、demo 代码和 webpack 配置
首先我是 webpack 4+。源文件如下:
// src/a.js 入口
import { add } from '@/b'
import _ from 'lodash';
import('@/c.js').then(({default: sub}) => sub(2, 1))
const a = 1
add(3, 2 + a)
console.log('lodash,', _.join(['a', 'b', 'c'], '~'))
// src/b.js
import mul from '@/d'
export function add(n1, n2) {
return n1 + n2 + mul(10, 5)
}
// src/c.js
import mul from '@/d'
import('@/b.js').then(b => b.add(200, 100))
export default function sub(n1, n2) {
return n1 - n2 + mul(100, 50)
}
// src/d.js
export default function mul(n1, n2) {
const x = 10000
return n1 * n2 + x
}
webpack 配置:
const path = require('path')
module.exports = {
entry: {
index: './src/a'
},
output: {
path: __dirname + '/dist',
},
mode: 'development', // 开发环境比较便于观察打包后的代码
resolve: {
alias: {
'@': path.resolve(__dirname, 'src/'),
},
},
module: {
rules: [
{ test: /\.js$/, use: [{ loader: 'babel-loader' }] },
],
},
}
package.json 包版本:
{
"name": "webpack-demo",
"version": "1.0.0",
"description": "",
"main": "src/a.js",
"scripts": {
"build": "webpack",
"debug": "node --inspect-brk=9339 ./node_modules/.bin/webpack --watch",
"start": "webpack-dev-server --open"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.7.5",
"@babel/preset-env": "^7.7.6",
"babel-loader": "^8.0.6",
"html-webpack-plugin": "^3.2.0",
"webpack-dev-server": "^3.11.2"
},
"dependencies": {
"lodash": "^4.17.21",
"webpack": "^4.41.2",
"webpack-cli": "^3.3.10"
}
}
二、打包后的核心代码
我们运行npm run build
,输出 2 个文件:0.js
和index.js
index.js
是我们的入口 chunk,也就是首屏加载的同步 <script> 脚本。
让我们看看index.js
的内容(简化后):
(function(modules) { // webpackBootstrap
// The module cache
// 缓存加载过的 module
// key 是模块ID,值为模块路径(开发环境)或模块标识ID(生产环境),
// value 是一个对象,包含模块ID、模块加载状态(boolean) 和 模块导出对象(exports)
// 如 { 0: {i: 0, l: true, exports: Module} }
var installedModules = {};
// The require function
// webpack 实现的 import/require() 函数,也就是同步加载模块方法
function __webpack_require__(moduleId) {}
// ...
// 给 __webpack_require__ 添加一些必要的属性和方法
// Load entry module and return exports
// 加载入口模块,并返回模块对象
// __webpack_require__.s 表示入口模块的 id
return __webpack_require__(__webpack_require__.s = "./src/a.js");
}({
"./node_modules/lodash/lodash.js": (function() {...}),
// ...
"./src/a.js": (function() {...}),
"./src/b.js": (function() {...}),
"./src/d.js": (function() {...})
});
里面包含了 webpackBootstrap 代码,即所有项目运行时模块交互所需的加载和解析的逻辑
就是一个立即执行函数,传入的参数 ①modules
是一个以模块ID为 key,包装后的模块代码(模块函数)为 value 的对象,用于缓存应用的 modules,此时它包含了入口文件所有同步模块。
这个modules
为 webpackBootstrap 的闭包变量,在它内部都可以修改并获取到。
⚠️:模块ID的值是模块路径(开发环境)或模块标识ID(生产环境)
webpack 打包会把所有文件都转化成模块函数,项目运行时配合它的加载机制,加载对应的模块就会运行这些模块函数。
再看一下这个函数做了什么:
-
定义了一个模块缓存对象 ②
installedModules
(Object),用于缓存已经加载过的模块。其中 key 是模块ID,value 是一个包含:模块 ID、加载状态(boolean) 和 模块导出对象(exports) 三项的对象。- ⚠️:模块ID的值是模块路径(开发环境)或模块标识ID(生产环境)
- 举例:
installedModules: { 0: {i: 0, l: true, exports: Module} }
定义了一个同步模块加载函数
__webpack_require__
(核心)。在
__webpack_require__
上添加一些必要的(要用到的)属性和方法通过
__webpack_require__()
加载入口模块。
如果通过配置将 runtime 抽出了,那么传入的参数将是一个空数组。
三、webpack 模块的核心加载方法__webpack_require__
function __webpack_require__(moduleId) {
// Check if module is in cache
// 如果 module 已经加载过,直接返回缓存中该模块的导出对象
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
// 如未加载则创建一个新模块,并将其存入缓存对象 installedModules
var module = installedModules[moduleId] = {
i: moduleId, // 模块 ID
l: false, // 是否已加载
exports: {} // 模块导出值
};
// Execute the module function
// 执行模块函数,this 指向模块的 exports
// 传入的三个参数,分别为 module、module.exports、__webpack_require__
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// Flag the module as loaded
// 将模块标识为已加载
module.l = true;
// Return the exports of the module
// 返回该模块的 exports 对象
return module.exports;
}
__webpack_require__
的作用就是加载并执行指定的模块,然后返回模块的 module.exports。具体执行过程:
- 判断模块是否已加载过,是则直接返回 installedModules 中该模块的 exports 对象,到此结束(不会重复执行模块函数)。
- 没有则新建一个当前模块 ID为 key,{模块ID, 模块加载状态, 模块导出对象}为值的键值对,放入模块缓存对象。如
{ 0: {i: 0, l: false, exports: {}} }
,新建的情况这个exports
自然是空的。 - 执行模块函数,同时传入三个参数:
module
、module.exports
和__webpack_require__
自身。this指向模块的导出对象exports
。(此时传入的module.exports 同样是空对象) - 将这个新建模块标识为已加载。
- 最后返回该模块的 exports 对象。
应用到我们的实例上,执行的模块函数modules["./src/a.js"]
的内容如下:
(function(module, __webpack_exports__, __webpack_require__) {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _b__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @/b */ \"./src/b.js\");\n// src/a.js\n\n__webpack_require__.e(/*! import() */ 0).then(__webpack_require__.bind(null, /*! @/c.js */ \"./src/c.js\")).then(function (sub) {\n return sub(2, 1);\n});\nvar a = 1;\nObject(_b__WEBPACK_IMPORTED_MODULE_0__[\"add\"])(3, 2 + a);\n\n//# sourceURL=webpack:///./src/a.js?");
})
我们给它美化一下,把eval的字符串改成 js:
(function(module, __webpack_exports__, __webpack_require__) {
__webpack_require__.r(__webpack_exports__);
var _b__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/b.js");
__webpack_require__.e(0)
.then(__webpack_require__.bind(null,"./src/c.js"))
.then(function (sub) { return sub(2, 1);});
var a = 1;
Object(_b__WEBPACK_IMPORTED_MODULE_0__["add"])(3, 2 + a);
}
对比 demo 的a.js
,可以看出就是对源内容的转化和包装。可以直观看出的是同步去加载了b.js
,并把 b 的模块导出赋给一个变量(这里简化为_b_MODULE_
),然后用_b_MODULE_["add"]()
去调用方法;
除此之外还有__webpack_require__.r
和__webpack_require__.e
两个函数需要进一步去分析。我们接着往下看。
四、__webpack_require__
上挂载的属性/方法
(1) __webpack_require__.r
作用是给模块导出(__webpack_exports__
) 添加一个__esModule
为true
属性,表示这是一个 ES6 module。结合上面就是标记我们的a.js
为一个 ES6 模块。
// define __esModule on exports
// 在模块 exports 中 定义 __esModule 属性
__webpack_require__.r = function(exports) {
if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};
(2) __webpack_require__.d
作用是给模块导出(__webpack_exports__
)定义导出的变量
// define getter function for harmony exports
// 为模块导出定义 get 方法
__webpack_require__.d = function(exports, name, getter) {
if(!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};
我们的b.js
的内容被包装后有对此方法的调用:
__webpack_require__.d(__webpack_exports__, "add", function() { return add; });
以上为模块导出对象 exports 添加一个add
属性,这个属性的 get 方法被重写为一个函数,这样就可以通过 __webpack_exports__["add"]()
这种方式来获取模块导出的add
。如果用export default
导出,则会添加一个名为default
的属性。
(3) __webpack_require__.n
结合__webpack_require__.r
和__webpack_require__.d
,这三者负责解决不同模块类型的兼容性,比如混合使用 ES6 module 和 CommonJS 的情况。
// getDefaultExport function for compatibility with non-harmony modules
// 通过判断模块有无 __esModule 属性,决定获取模块导出变量的方式
__webpack_require__.n = function(module) {
var getter = module && module.__esModule ?
function getDefault() { return module['default']; } :
function getModuleExports() { return module; };
__webpack_require__.d(getter, 'a', getter);
return getter;
};
(4) __webpack_require__.t
当import()
动态引入一个CommonJS模块的时候,会创建一个对象,将这个对象标记为esModule,将CommonJS模块转换得到等价的esModule的内容输出
// create a fake namespace object
// mode & 1: value is a module id, require it
// mode & 2: merge all properties of value into the ns
// mode & 4: return value when already ns object
// mode & 8|1: behave like require
__webpack_require__.t = function(value, mode) {
if(mode & 1) value = __webpack_require__(value);
if(mode & 8) return value;
if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
var ns = Object.create(null);
__webpack_require__.r(ns);
Object.defineProperty(ns, 'default', { enumerable: true, value: value });
if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
return ns;
};
(5) 其他
用来加载异步模块的__webpack_require__.e
函数也很核心,放到后面结合异步加载单独说。
// the module id of the entry point
// 入口模块的ID
__webpack_require__.s = "./src/index.js"
// expose the modules object (__webpack_modules__)
// 将应用所有的模块挂载到 require() 函数的 m 属性上
__webpack_require__.m = modules;
// expose the module cache
// 将模块缓存对象挂载到 require() 函数的 c 属性上
__webpack_require__.c = installedModules;
// __webpack_public_path__
// 定义打包输出文件的 publicPath
__webpack_require__.p = "";
// on error function for async loading
// 捕获异步加载模块时的错误
__webpack_require__.oe = function(err) { console.error(err); throw err; };
五、异步加载
为了提高首屏性能,一个项目都会用到按需加载组件,比如Vue Router
的路由懒加载。
我们都知道动态导入的模块会被单独封装成一个 js 文件 (chunk),比如我们的 demo 打包后就把c.js
单独输出了一个0.js
,美化如下:
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],{
"./src/c.js":
(function(module, __webpack_exports__, __webpack_require__) {
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, "default", function() { return sub; });
var _d__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__"./src/d.js");
Promise.resolve).then(__webpack_require__.bind(null, "./src/b.js"))
.then(function (b) { return b.add(200, 100); });
function sub(n1, n2) {
return n1 - n2 + Object(_d__WEBPACK_IMPORTED_MODULE_0__["default"])(100, 50);
}
);
})
}]);
存在异步块的情况,webpackBootstrap 函数内会多出不少代码:
- 定义了 ③
installedChunks
变量(Object),用来缓存加载过的 chunk。key 为 chunk ID(也就是名称),value 是 chunk 对应的状态,有四种情况:- undefined:chunk 未加载
- null:chunk preloaded/prefetched
- Promise:chunk 正在加载中
- 0:表示 chunk 已经加载完成 ✅
在用__webpack_require__.e
加载异步 chunk 时会把 value 改为加载中 promise 的相关变量,而webpackJsonCallback
会把这个 value 标记成已完成。
- 两个核心函数
webpackJsonpCallback
和__webpack_require__.e
- 一个辅助函数
jsonpScriptSrc
,作用是根据 chunk ID 生成 chunk 的请求 URL。 - 定义全局变量
window["webpackJsonp"] = []
(Array),用来存储会用到webpackJsonpCallback
的 chunks 信息(不仅是异步 chunk、入口 chunk 中分出的同步 chunk 也会用到,下文会讲)。 - 重写
window["webpackJsonp"].push
方法为webpackJsonpCallback
函数。
简化代码如下:
// install a JSONP callback for chunk loading
function webpackJsonpCallback(data) {
// 加载异步 chunk 或其他被拆分的同步 chunk 后的回调函数
// 主要负责 chunk 的加载状态管理,并将 moreModules 装载到 modules 中
// 后面会展开详解
}
// object to store loaded and loading chunks
// undefined = chunk not loaded, null = chunk preloaded/prefetched
// Promise = chunk loading, 0 = chunk loaded
// 缓存已加载和加载中的 chunk
// key 为 chunk ID,值为 chunk 对应的状态
// undefined 未加载;null chunk preloaded/prefetched;Promise 正在加载;0 已加载完成
var installedChunks = {
"index": 0
};
// script path function
// 根据 chunk ID 生成模块文件的URL
function jsonpScriptSrc(chunkId) {
return __webpack_require__.p + "" + ({}[chunkId]||chunkId) + ".js"
// 打包文件名有配置 hash 的情况
// return __webpack_require__.p + "" + ({}[chunkId]||chunkId) + "." + {"0":"b1ab1cbc"}[chunkId] + ".js"
}
// This file contains only the entry chunk.
// The chunk loading function for additional chunks
__webpack_require__.e = function requireEnsure(chunkId) {
// 异步加载 chunk 的方法,返回封装加载过程的 Promise
// 后面会展开详解
}
// 全局变量 window["webpackJsonp"],存储动态导入/入口拆分出的同步 chunks 数据
var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
// 用 oldJsonpFunction 变量保存 jsonpArray 的原生 push 方法
var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
// 重写 window["webpackJsonp"].push 方法为 webpackJsonpCallback 函数
jsonpArray.push = webpackJsonpCallback;
// 把 jsonpArray 还原成普通数组
jsonpArray = jsonpArray.slice();
// jsonpArray 不为空时为每项循环执行 webpackJsonpCallback
for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
// jsonpArray 原生 push 方法赋给 parentJsonpFunction
var parentJsonpFunction = oldJsonpFunction;
代码量有点大,我们一点一点捋。通过 0.js 文件可以发现,它正是使用 window["webpackJsonp"].push()
来放入动态模块的。push 传入的参数有两个:
- 第一个是
[0]
,为 chunk ID 数组; - 第二个是 chunk 包含模块键值对组成的对象:key 为模块 ID(开发环境为模块原路径),value 为包裹后的模块代码 (模块函数) ,和 webpackBootstrap 自执行函数的参数格式一致。
再看回上面入口模块a.js
编译后的模块内容,有这样一句:
__webpack_require__.e(0)
.then(__webpack_require__.bind(null,"./src/c.js"))
.then(function (sub) { return sub(2, 1);});
也就是import('@/c.js').then(sub => sub(2, 1))
被转化的结果。显而易见__webpack_require__.e
就是用来加载异步 chunk 的。我们来看下它做了什么:
// 加载异步 chunk 的方法,返回封装加载过程的 Promise
// 传入的参数为 chunkID,比如 0
__webpack_require__.e = function requireEnsure(chunkId) {
var promises = [];
// JSONP chunk loading for javascript
// 创建一个 installedChunkData 对象,用来管理 chunk 在 installedChunks 缓存对象中的状态
var installedChunkData = installedChunks[chunkId];
// 如果这个 chunk 在缓存中的值不是 0,即表示未加载完成
if(installedChunkData !== 0) { // 0 means "already installed".
// a Promise means "currently loading".
// 不为 0 且 不为 undefined,即正在加载中
if(installedChunkData) {
// 将加载中的 promise 添加到 promises 数组
promises.push(installedChunkData[2]);
} else {
// setup Promise in chunk cache
// 创建一个新的 promise 对象,用于加载动态导入的 chunk
var promise = new Promise(function(resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
// 把新建的 promise 添加到 promise 数组,并且作为第三个参数放入 installedChunkData
// 由此可见加载中的异步 chunk 的状态, 即 installedChunkData 的结构为 [resolve,reject,promise]
promises.push(installedChunkData[2] = promise);
// start chunk loading
// 创建 script 标签
var script = document.createElement('script');
var onScriptComplete;
script.charset = 'utf-8';
script.timeout = 120;
if (__webpack_require__.nc) {
// 若脚本需要安全加载,CSP策略,则给 <script></script> 加上 nonce 属性
script.setAttribute("nonce", __webpack_require__.nc);
}
// 用 jsonpScriptSrc 生成异步 chunk 请求地址
script.src = jsonpScriptSrc(chunkId);
// create error before stack unwound to get useful stacktrace later
// 直译:在栈解旋前创建错误来方便调试
var error = new Error();
onScriptComplete = function (event) {
// avoid mem leaks in IE.
script.onerror = script.onload = null;
// 清除 timeout 定时器 (无论加载成功或失败)
clearTimeout(timeout);
var chunk = installedChunks[chunkId];
if(chunk !== 0) { // 如果 chunk 还未加载完成
if(chunk) { // 还在加载中,却出现问题,进行一系列错误处理
var errorType = event && (event.type === 'load' ? 'missing' : event.type);
var realSrc = event && event.target && event.target.src;
error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
error.name = 'ChunkLoadError';
error.type = errorType;
error.request = realSrc;
chunk[1](error); // reject(error)
}
installedChunks[chunkId] = undefined; // 把 chunk 标记为 未加载
}
};
// 开启一个延后 2 分钟的定时器进行超时处理
var timeout = setTimeout(function(){
onScriptComplete({ type: 'timeout', target: script });
}, 120000);
// 定义脚本 onerror 和 onload 为 onScriptComplete 函数
script.onerror = script.onload = onScriptComplete;
// 把 script 标签插入 <head></head> 中的最末
document.head.appendChild(script);
}
}
// 最终返回一个 promise,它的 then 触发时机是 promises 数组里所有 promise 对象都确定了完成状态。
// 也就是里面所有 promise 都 resolve 或 reject 了,此时 __webpack_require__.e(chunkId) 的 then() 里的回调就可以执行了
return Promise.all(promises);
};
__webpack_require__.e(chunkId)
的处理逻辑如下:
- 先根据传入的 chunk ID 判断 chunk 是否已加载完成,如没有则进一步判断 chunk 是否正在加载中。如果是则将这个加载中的 Promise push 到 promises 数组。
- 没加载过则创建一个新的 promise 对象,用于加载动态该 chunk。并且把
installedChunks
中该 chunk 的值改为[resolve, reject, promise]
,然后把这个 promise push 到 promise 数组。 - 动态创建一个 script 标签,将它的 onerror 和 onload 事件定义为
onScriptComplete
函数,将<script>
插入页面<head>
的最末,并进行加载超时错误处理。 - 最后返回一个 promise,它的 then 触发时机是当 promises 数组里所有 promise 都有了确定状态,也就是这些 promise 的 resolve 或 reject 被调用了。彼时
__webpack_require__.e(0).then()
里的回调就能接着执行了。
那么问题来了,加载 chunk 的 promise 只是把 resolve 和 reject 存入了 installedChunks,并没有在成功获取异步脚本的 onload 回调中执行 resolve,这些 promise 到底是什么时候 resolve() 的呢?
这就是webpackJsonpCallback
干的其中一件要事了:
// 该方法会记录管理 chunk 的加载状态,并将 moreModules 装载到 modules 中
// 如果是异步 chunk 会把它的 promise resolve 出去,也就是让`_webpack_require__.e(chunkId).then` 被触发
function webpackJsonpCallback(data) {
var chunkIds = data[0]; // window["webpackJsonp"].push 的第一个参数,也就是 chunkID 数组
var moreModules = data[1]; // chunk 里所有的模块对象,{ '模块ID': '模块函数', ... }
// add "moreModules" to the modules object,
// then flag all "chunkIds" as loaded and fire callback
var moduleId, chunkId, i = 0, resolves = [];
for(;i < chunkIds.length; i++) {
chunkId = chunkIds[i];
// installedChunks 已存在这个 chunk 且这个 chunk 在加载中
if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
// 那么把这个异步 chunk 的 resolve 方法添加到 resolves 数组 (此时 installedChunks[chunkId] 内容是 [resolve,reject,promise])
resolves.push(installedChunks[chunkId][0]);
}
installedChunks[chunkId] = 0; // 把这个 chunk 标记为已加载完成
}
// 遍历 moreModules,把内容深拷贝给 modules,也就是 webpackBootstrap 的参数指向的地址
// modules 为 webpackBootstrap 的闭包变量,作用域内的函数自然可以获取
for(moduleId in moreModules) {
if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
modules[moduleId] = moreModules[moduleId];
}
}
// 执行 window["webpackJsonp"] 原生的.push,那么 "webpackJsonp" 就有了这个 chunk 的所有信息
if(parentJsonpFunction) parentJsonpFunction(data);
// 将异步加载的 chunks 依次 resolve(),将会执行_webpack_require__.e.then() 里的回调
while(resolves.length) {
// resolves.shift() 会删除并返回它的第一个元素,也就是执行 _webpack_require__.e 里产生的 promise 对象的 resolve
resolves.shift()();
}
};
结合我们的实例,__webpack_require__
执行入口模块a.js
的模块函数时,处理到__webpack_require__.e(0)
会去异步下载动态资源,返回表示加载过程的 promises 数组。下载完成后执行0.js
里面的代码:window["webpackJsonp"].push()
,即 webpackJsonpCallback()
。执行到这个函数时,0.js
chunk 已经被正确加载,因此可以拿到它 promise 的 resolve,同时将installedChunks
中该 chunk 的值置为 0,标记为已加载完成。还有把当前 chunk 包含的 modules 保存到入口文件的 modules 变量。最后这个resolve()
执行,此时__webpack_require__.e().then()
里的回调便可以执行了。
再看一下入口模块异步加载 c 模块的代码: __webpack_require__.e(0).then(__webpack_require__.bind(null, "./src/c.js"))
,意味着加载执行完0
这个 chunk后,直接调用__webpack_require__()
去真正加载 c 模块的内容。
如果异步模块里再异步引入模块呢?
我们将 a.js 中的 lodash 部分注释掉,然后把 c.js 修改成:
// src/c.js
import mul from '@/d'
import _ from 'lodash';
import('@/b.js').then(b => b.add(200, 100))
import('@/e.js').then(e => console.log(e.default))
console.log("c 中引入 lodash,",_.join(['a', 'b', 'c'], '~'))
export default function sub(n1, n2) {
return n1 - n2 + mul(100, 50)
}
分两种情况,如果是同步块 (入口 chunk) 里已经存在的模块,则:
注:代码位于异步模块 c 打包的 chunk js 文件,./src/c.js
模块对应的模块函数中
Promise.resolve().then(__webpack_require__.bind(null, "./src/b.js")).then();
直接返回一个 resolved 状态的 Promise 对象。
若是之前没加载过的模块,那么显然又是另一个独立 chunk,其实就和任何同步模块加载异步 chunk 一样:
注:代码位于异步模块 c 打包的 chunk js 文件,./src/c.js
模块对应的模块函数中
__webpack_require__.e(2).then(__webpack_require__.bind(null, "./src/e.js")).then();
还有一种异步块同步依赖于其他 chunk(比如默认会被抽出的第三方库)的情况,加载这个异步模块(c)的语句就会变这样:
注:代码位于入口 chunk,./src/a.js
模块对应的模块函数中;0 是 第三方库 chunk、1 是 c 打包的 chunk
Promise.all([__webpack_require__.e(0), __webpack_require__.e(1)])
.then(__webpack_require__.bind(null,"./src/c.js"))
.then();
下一篇【webpack 模块加载原理及打包文件分析 (二)】 我们接着说入口文件拆包的情况
⚠️:标注 ①、②、③、④ 的四个变量需要重点理解,对理解 webpack 加载逻辑很有帮助。
参考文章:
Webpack 是怎样运行的?(一)
Webpack 是怎样运行的?(二)
聊聊 webpack 异步加载(二):webpack 如何处理 import()
webpack是如何实现动态导入的