使用方式

例如:

  1. import('./A.js').then((res) => { console.log(res) })

ast解析

使用acorn解析时会转换为ExpressionStatement,进一步分析为MemberExpression下的ImportExpression
image.png

hooks.importCall

解析完成后进行walkStatement,此时会触发hooks.importCall钩子。在webpack/lib/dependencies/ImportParserPlugin.js文件中:

  1. parser.hooks.importCall.tap("ImportParserPlugin", expr => {
  2. let chunkName = null;
  3. // ...省略
  4. const depBlock = new AsyncDependenciesBlock(
  5. {
  6. ...groupOptions,
  7. name: chunkName
  8. },
  9. expr.loc,
  10. param.string
  11. );
  12. const dep = new ImportDependency(param.string, expr.range, exports);
  13. dep.loc = expr.loc;
  14. depBlock.addDependency(dep);
  15. parser.state.current.addBlock(depBlock);
  16. return true;
  17. });

最终会创建一个AsyncDependenciesBlock,并通过addBlock添加,最终添加到module.blocks属性上。异步组件moduleparentBlock指向的是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
image.png
接着在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.chunkscompilation.chunkGroups的值中将不止一个chunk/chunkGroup。最终也会根据chunk生成单独的文件。

emit阶段

这里以这一段代码为例:

// index.js 文件
import('./moduleB.js').then((res) => {
  console.log(res)
})

// moduleB.js 文件
export function B() {
  console.log('==> module B');
}

此时会生成两个文件:
image.png
分包后的代码会独自生成一个文件,而在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
};

该变量是一个对象,keychunkkey,值为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