使用方式
例如:
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
