原文首发于 blog.flqin.com。若有错误,请联系笔者。分析码字不易,转载请代表出处,谢谢!webpack
接上文,执行:git
this.hooks.beforeModuleAssets.call();
this.createModuleAssets();
复制代码
这一步用于生成 module
资源。在 createModuleAssets
里,获取每一个 module
属性上的 buildInfo.assets
,而后触发 this.emitAsset
生成资源。buildInfo.assets
相关数据能够在 loader
里调用 api
: this.emitFile
生成。github
这一步用于建立 chunk
资源。web
执行:json
this.hooks.beforeChunkAssets.call();
this.createChunkAssets();
复制代码
在 createChunkAssets
里循环对每一个 chunk
执行:bootstrap
//...
const template = chunk.hasRuntime() ? this.mainTemplate : this.chunkTemplate;
const manifest = template.getRenderManifest({
chunk,
hash: this.hash,
fullHash: this.fullHash,
outputOptions,
moduleTemplates: this.moduleTemplates,
dependencyTemplates: this.dependencyTemplates
}); //获得 `render` 所须要的所有信息:`[{ render(), filenameTemplate, pathOptions, identifier, hash }]`
//...
复制代码
在判断 chunk
是否含有 runtime
代码后即同步异步后,获取到对应的 template
。异步 chunk
对应 chunkTemplate
,同步及含有 runtime
的 chunk
对应 mainTemplate
。api
而后执行对应的 getRenderManifest
,触发 template.hooks:renderManifest
执行插件 JavascriptModulesPlugin
相关事件获得 render
所须要的所有信息:[{ render(), filenameTemplate, pathOptions, identifier, hash }]
。数组
若是是 chunkTemplate
还会触发插件 WebAssemblyModulesPlugin
的相关事件处理 WebAssembly
相关。缓存
而后遍历 manifest
对象执行:app
const pathAndInfo = this.getPathWithInfo(filenameTemplate, fileManifest.pathOptions);
复制代码
this.getPathWithInfo
用于获得路径和相关信息。会触发 mainTemplate.hooks
:assetPath
,去执行插件 TemplatedPathPlugin
相关事件,使用若干 replace
将如 [name].[chunkhash:8].js
替换为 0.e3296d88.js
。
而后判断有无 source
缓存后,若无则执行:
source = fileManifest.render();
复制代码
即执行对应 template
的 render
。
若是是异步 chunk
,render
会执行在文件 JavascriptModulesPlugin.js
里的 renderJavascript
。方法里先执行 Template.renderChunkModules
静态方法:
const moduleSources = Template.renderChunkModules(chunk, m => typeof m.source === 'function', moduleTemplate, dependencyTemplates);
复制代码
方法里执行:
const allModules = modules.map(module => {
return {
id: module.id,
source: moduleTemplate.render(module, dependencyTemplates, {
chunk
})
};
});
复制代码
这里循环对每个 module
执行 render
,方法里执行:
const moduleSource = module.source(dependencyTemplates, this.runtimeTemplate, this.type);
//...
复制代码
module.source
里执行:
const source = this.generator.generate(this, dependencyTemplates, runtimeTemplate, type);
复制代码
这个 generator
就是在 reslove 流程 -> getGenerator
所得到,即执行:
this.sourceBlock(module, module, [], dependencyTemplates, source, runtimeTemplate);
复制代码
这里循环处理 module
的每一个依赖(module.dependencies
):得到依赖所对应的 template
模板类,而后执行该类的 apply
:
const template = dependencyTemplates.get(dependency.constructor);
//...
template.apply(dependency, source, runtimeTemplate, dependencyTemplates);
复制代码
这里的 dependencyTemplates
就是在 reslove 流程前的准备 ->Compiler.compile ->实例化 compilation
里添加的依赖模板模块。
在 apply
里,会根据依赖不一样作相应的源码转化的处理。但方法里并无直接执行源码转化的工做,而是将其转化对象 push
到 ReplaceSource.replacements
里,转化对象的格式为:
注:
webpack-sources
提供若干类型的source
类,如CachedSource, PrefixSource, ConcatSource, ReplaceSource
等。它们能够组合使用,方便对代码进行添加、替换、链接等操做。同时又含有一些source-map
相关,updateHash
等api
供webpack
内部调用.
//Replacement
{
"content": "__webpack_require__.r(__webpack_exports__);\n", // 替换的内容
"end": -11, // 替换源码的终止位置
"insertIndex": 0, // 优先级
"name": "", // 名称
"start": -10 // 替换源码的起始位置
}
复制代码
各模板的具体处理转化见 构建 module(下) -> parse 源码 -> 各依赖做用解释
。
收集完依赖相关的转化对象 Replacement
以后,回到 module.source
进行 cachedSource
缓存包装后,回到 moduleTemplate.render
方法获得 moduleSource
。
而后触发相关 ModuleTemplate.hooks:content,module,render,package
,前两个钩子主要是可让咱们完成对 module
源码的再次处理,而后在 render
钩子里执行插件 FunctionModuleTemplatePlugin
的相关事件,主要是给处理后的 module
源码进行包裹,即生成代码:
/***/
(function(module, __webpack_exports__, __webpack_require__) {
'use strict';
//CachedSource 即为module源码,里面包含 replacements
/***/
});
复制代码
而后触发 package
钩子执行插件 FunctionModuleTemplatePlugin
的相关事件,主要做用是添加相关注释,即生成代码:
/*!***************************************************************!*\ !*** ./src/c.js ***! \***************************************************************/
/*! exports provided: sub */
/***/
(function(module, __webpack_exports__, __webpack_require__) {
'use strict';
//CachedSource 即为module源码,里面包含 replacements
/***/
});
复制代码
将全部的 module
都处理完毕后,回到 renderChunkModules
,继续处理生成代码,最终将每一个 module
生成的代码串起来获得 moduleSources
回到了 renderJavascript
里。
方法里先触发 chunkTemplate.hooks : modules
为修改生成的 chunk
代码提供钩子,获得 core
后,触发 chunkTemplate.hooks
:render
执行插件 JsonpChunkTemplatePlugin
相关事件,该事件主要是添加 jsonp
异步包裹代码,获得:
(window['webpackJsonp'] = window['webpackJsonp'] || []).push([
[0]
// 前面生成的 chunk 代码
]);
复制代码
完成后,最后返回一个 new ConcatSource(source, ";")
。到此普通的异步 chunk
代码 chunkTemplate
的 fileManifest.render
代码构建完成。
若是是同步 chunk
,render
会执行在文件 JavascriptModulesPlugin.js
里的 compilation.mainTemplate.render
即文件 MainTemplate.js
里的 render
。
方法里执行:
const buf = this.renderBootstrap(hash, chunk, moduleTemplate, dependencyTemplates);
复制代码
该方法获得 webpack runtime bootstrap
代码数组,从中会判断是否有异步 chunk
,若是有,则代码里还会包含异步相关的 runtime
代码,若是还有其余什么延迟加载的模块,都会在这里处理为相应是 runtime
。
而后执行:
let source = this.hooks.render.call(new OriginalSource(Template.prefix(buf, ' \t') + '\n', 'webpack/bootstrap'), chunk, hash, moduleTemplate, dependencyTemplates);
复制代码
先经过 Template.prefix
合并 runtime
代码字符串,获得 OriginalSource
的实例,而后将其做为参数执行 MainTemplate.hooks
: render
,该 hook
在 constructor
里已注册,代码以下:
const source = new ConcatSource();
source.add('/******/ (function(modules) { // webpackBootstrap\n');
source.add(new PrefixSource('/******/', bootstrapSource));
source.add('/******/ })\n');
source.add('/************************************************************************/\n');
source.add('/******/ (');
source.add(this.hooks.modules.call(new RawSource(''), chunk, hash, moduleTemplate, dependencyTemplates));
source.add(')');
return source;
复制代码
该方法对 runtime bootstrap
代码进行了包装(bootstrapSource
即为前面生成的 runtime
代码),其中触发 MainTemplate.hooks: modules
获得 chunk
的生成代码,即最终返回一个包含了 runtime
代码和 chunk
代码的 ConcatSource
实例。
这里来看 chunk
代码的实现,如上文代码中:
this.hooks.modules.call(new RawSource(''), chunk, hash, moduleTemplate, dependencyTemplates);
复制代码
这里 mainTemplate.hooks: modules
触发插件 JavascriptModulesPlugin
的相关事件,即执行 Template
类的静态方法 renderChunkModules
。与前文 chunkTemplate -> 生成主体 chunk 代码
的实现一致。
最终通过包裹后获得的代码大体以下:
"/******/ (function(modules) { // webpackBootstrap
// runtime 代码的 PrefixSource 实例
/******/ })
/************************************************************************/
/******/ ({
/***/ "../github/test-loader/loader.js?number=20!./src/d.js":
/*!************************************************************!*\
!*** ../github/test-loader/loader.js?number=20!./src/d.js ***!
\************************************************************/
/*! exports provided: mul */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
// module d 的 CachedSource 实例
/***/ }),
/***/ "./src/a.js":
/*!******************!*\
!*** ./src/a.js ***!
\******************/
/*! no exports provided */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
// module a 的 CachedSource 实例
/***/ }),
/***/ "./src/b.js":
/*!******************!*\
!*** ./src/b.js ***!
\******************/
/*! exports provided: add, addddd */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
// module b 的 CachedSource 实例
/***/ })
/******/ })"
复制代码
完成后,最后返回一个 new ConcatSource(source, ";")
。到此普通的同步 chunk
代码 mainTemplate
的 fileManifest.render
代码构建完成。
不管是同步仍是一部,最后都回到 Compilation.js
的 createChunkAssets
里,作了 source
缓存,而后执行:
this.emitAsset(file, source, assetInfo);
复制代码
创建起了文件名与对应源码的联系,将该映射对象挂载到 compilation.assets
下。 而后设置了 alreadyWrittenFiles
这个 Map
对象,防止重复构建代码。到此一个 chunk
的资源构建结束。
chunk
遍历结束后,获得 compilation.assets
和 compilation.assetsInfo
:
//compilation
{
//...
"assets": {
"0.3e.js": CachedSource, // CachedSource 里包含资源
"bundle.bf23.js": CachedSource
},
//...map结构
"assetsInfo": {
0: {
"key": '0.3e.js',
"value": {
immutable:true
}
},
1: {
"key": 'bundle.bf23.js',
"value": {
immutable:true
}
}
}
}
复制代码
this.emitFile
可生成 module
资源,若是有则直接调用 this.emitAsset
生成资源;chunk
资源时,先根据是否含有 runtime
获得不一样的 template
,包括 chunkTemplate
和 mainTemplate
;template
获得不一样的 manifest
和 pathAndInfo
,而后调用不一样的 render
渲染代码;compilation.assets
即目标资源。