seal阶段完成后,对于每一个chunk下的每一个module都已经生成了相应的代码片段。接下来就需要将这些片段进行拼接,形成可以执行的完整代码,并将代码生成到相应的文件当中。

createChunkAssets

createChunkAssets会在codeGeneration回调中调用:

  1. asyncLib.forEachLimit(
  2. this.chunks,
  3. 15,
  4. (chunk, callback) => {
  5. manifest = this.getRenderManifest({
  6. chunk,
  7. hash: this.hash,
  8. fullHash: this.fullHash,
  9. outputOptions,
  10. codeGenerationResults: this.codeGenerationResults,
  11. moduleTemplates: this.moduleTemplates,
  12. dependencyTemplates: this.dependencyTemplates,
  13. chunkGraph: this.chunkGraph,
  14. moduleGraph: this.moduleGraph,
  15. runtimeTemplate: this.runtimeTemplate
  16. });
  17. // ...
  18. })
  19. )

首先会对每一个chunk创建manifest

  1. getRenderManifest(options) {
  2. return this.hooks.renderManifest.call([], options);
  3. }
  4. compilation.hooks.renderManifest.tap(
  5. "JavascriptModulesPlugin",
  6. (result, options) => {
  7. // ...
  8. render = () =>
  9. this.renderMain(
  10. {
  11. hash,
  12. chunk,
  13. dependencyTemplates,
  14. runtimeTemplate,
  15. moduleGraph,
  16. chunkGraph,
  17. codeGenerationResults,
  18. strictMode: runtimeTemplate.isModule()
  19. },
  20. hooks,
  21. compilation
  22. );
  23. // ...
  24. result.push({
  25. render,
  26. filenameTemplate,
  27. pathOptions: {
  28. hash,
  29. runtime: chunk.runtime,
  30. chunk,
  31. contentHashType: "javascript"
  32. },
  33. identifier: hotUpdateChunk
  34. ? `hotupdatechunk${chunk.id}`
  35. : `chunk${chunk.id}`,
  36. hash: chunk.contentHash.javascript
  37. });
  38. return result;
  39. }
  40. );

此时会进入到JavascriptModulesPlugin插件当中,对chunk类型进行检验,并生成带有不同render的任务,添加到result当中。完后遍历manifest,生成文件。精简后的代码如下:

  1. asyncLib.forEach(
  2. manifest,
  3. (fileManifest, callback) => {
  4. // 1. 解析 chunk 的文件等信息
  5. if ("filename" in fileManifest) {
  6. file = fileManifest.filename;
  7. assetInfo = fileManifest.info;
  8. } else {
  9. filenameTemplate = fileManifest.filenameTemplate;
  10. const pathAndInfo = this.getPathWithInfo(
  11. filenameTemplate,
  12. fileManifest.pathOptions
  13. );
  14. file = pathAndInfo.path;
  15. assetInfo = fileManifest.info
  16. ? {
  17. ...pathAndInfo.info,
  18. ...fileManifest.info
  19. }
  20. : pathAndInfo.info;
  21. }
  22. // 2. 生成 chunk 代码
  23. source = fileManifest.render();
  24. // 3. 输出文件
  25. this.emitAsset(file, source, assetInfo);
  26. chunk.files.add(file);
  27. this.hooks.chunkAsset.call(chunk, file);
  28. });
  29. )

主要流程有三部分:首先解析文件的信息,然后调用render生成代码,最后根据文件信息将生成的代码进行输出。render函数(renderMainrenderChunk)均在webpack/lib/javascript/JavascriptModulesPlugin插件中定义,其中renderMain相较于renderChunk更加复杂,因为它是作为入口文件,通常会加入一些runtime相关的执行函数等。

renderMain

最终生成的chunk代码由各个部分组成,包括我们自己写的和引用的模块代码,程序执行的一些runtime代码,程序启动代码,以及一些其他注释、立即执行等辅助结构代码。

模块代码

  1. const chunkModules = Template.renderChunkModules(
  2. chunkRenderContext,
  3. inlinedModules
  4. ? allModules.filter(m => !inlinedModules.has(m))
  5. : allModules,
  6. module => this.renderModule(module, chunkRenderContext, hooks, true),
  7. prefix
  8. );

调用Template.renderChunkModules函数遍历chunk中的所有module,然后将这些module形成键值对结构代码(实际上是一行一行代码组成的数组结构,但是执行时是对象结构),存放到__webpack_modules__变量当中。例如:

  1. /***/ "./src/moduleA.js":
  2. /*!************************!*\
  3. !*** ./src/moduleA.js ***!
  4. \************************/
  5. /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
  6. eval("/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"A\": () => (/* binding */ A)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n\nconsole.log(react__WEBPACK_IMPORTED_MODULE_0__)\n\nfunction A() {\n console.log('==> module A');\n}\n\n//# sourceURL=webpack://study-webpack/./src/moduleA.js?");
  7. /***/ })
  8. /******/ });