使用方式
例如:
import('./A.js').then((res) => { console.log(res) })
ast解析
使用acorn
解析时会转换为ExpressionStatement
,进一步分析为MemberExpression
下的ImportExpression
:
hooks.importCall
解析完成后进行walkStatement
,此时会触发hooks.importCall
钩子。在webpack/lib/dependencies/ImportParserPlugin.js
文件中:
parser.hooks.importCall.tap("ImportParserPlugin", expr => {
let chunkName = null;
// ...省略
const depBlock = new AsyncDependenciesBlock(
{
...groupOptions,
name: chunkName
},
expr.loc,
param.string
);
const dep = new ImportDependency(param.string, expr.range, exports);
dep.loc = expr.loc;
depBlock.addDependency(dep);
parser.state.current.addBlock(depBlock);
return true;
});
最终会创建一个AsyncDependenciesBlock
,并通过addBlock
添加,最终添加到module.blocks
属性上。异步组件module
的parentBlock
指向的是AsyncDependenciesBlock
。
seal 阶段
在seal
阶段中解析下一层引用modules
时,调用webpack/buildChunkGraph.js
文件中的extractBlockModules
方法:
const queue = [module];
while (queue.length > 0) {
const block = queue.pop();
const arr = [];
arrays.push(arr);
blockModulesMap.set(block, arr);
for (const b of block.blocks) {
queue.push(b);
}
}
此时会遍历blocks
形成如下结构数组结构:不同的block
对应于不同的module
。
接着在processBlock
结尾处,会对module.block
进行遍历:
for (const b of block.blocks) {
iteratorBlock(b);
}
if (block.blocks.length > 0 && module !== block) {
blocksWithNestedBlocks.add(block);
}
此时如果没有对应的chunkGroup
,则会进行创建:
cgi = namedChunkGroups.get(chunkName);
if (!cgi) {
c = compilation.addChunkInGroup(
b.groupOptions || b.chunkName,
module,
b.loc,
b.request
);
}
因此最终compilation.chunks
和compilation.chunkGroups
的值中将不止一个chunk/chunkGroup
。最终也会根据chunk
生成单独的文件。
emit阶段
这里以这一段代码为例:
// index.js 文件
import('./moduleB.js').then((res) => {
console.log(res)
})
// moduleB.js 文件
export function B() {
console.log('==> module B');
}
此时会生成两个文件:
分包后的代码会独自生成一个文件,而在main
文件中,打包后的import()
语句则会变为:
// 整理后的代码大致如下:
__webpack_require__.e("src_moduleB_js")
.then(__webpack_require__.bind(__webpack_require__,"./src/moduleB.js"))
.then((res) => {console.log(res)}));
代码执行初始化
installedChunks
先找到/* webpack/runtime/jsonp chunk loading */
这个注释的闭包函数,首先会定义一个installedChunks
变量
var installedChunks = {
"main": 0
};
该变量是一个对象,key
为chunk
的key
,值为0
代表已经加载完成,否则值为[resolve, reject, Promise]
形式。
webpack_require.f.j
__webpack_require__.f.j
在.f
属性上定义了一个j
函数,在后面会用到。
webpackJsonpCallback
// 定义webpackJsonpCallback
var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
// ...
}
// 定义一个全局变量,该变量在其他chunk里同样能访问
var chunkLoadingGlobal = self["webpackChunkstudy_webpack"] = self["webpackChunkstudy_webpack"] || [];
chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
// 1. 将数组的 原生push 当做参数传入
// 2. 重写chunkLoadingGlobal.push 方法
chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
当初始化完成后,开始加载index.js
文件,此时会执行__webpack_require__.e("src_moduleB_js")
。
引入模块(webpack_require.e)
(() => {
__webpack_require__.f = {};
__webpack_require__.e = (chunkId) => {
return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {
__webpack_require__.f[key](chunkId, promises);
return promises;
}, []));
};
})();
该函数相当于遍历.f
对象,并执行各个函数,前面初始化时已经定义了.j
函数。因此会执行_webpack_require__.f.j