webpack 4 源码主流程分析(十):资源的构建
生成 module 资源
接上文,执行:
1 | this.hooks.beforeModuleAssets.call(); |
这一步用于生成 module
资源。
在 createModuleAssets
里获取每个 module.buildInfo.assets
,然后触发 this.emitAsset
生成资源。buildInfo.assets
相关数据可以在 loader
里调用 api
: this.emitFile
生成。
生成 chunk 资源
这一步用于创建 chunk
资源。
生成前的准备
manifest
执行:
1 | this.hooks.beforeChunkAssets.call(); |
在 createChunkAssets
里循环对每个 chunk
执行:
1 | //... |
判断 chunk
是否含有 runtime
代码后即同步异步后,获取到对应的 template
。异步 chunk
对应 chunkTemplate
,同步及含有 runtime
的 chunk
对应 mainTemplate
。
然后执行对应的 getRenderManifest
,触发 template.hooks:renderManifest
执行插件 JavascriptModulesPlugin
相关事件得到 render
所需要的全部信息:[{ render(), filenameTemplate, pathOptions, identifier, hash }]
。
如果是 chunkTemplate
还会触发插件 WebAssemblyModulesPlugin
的相关事件处理 WebAssembly
相关。
pathAndInfo
然后遍历 manifest
对象执行:
1 | const pathAndInfo = this.getPathWithInfo(filenameTemplate, fileManifest.pathOptions); |
this.getPathWithInfo
用于得到路径和相关信息。会触发 mainTemplate.hooks
:assetPath
,去执行插件 TemplatedPathPlugin
相关事件,使用若干 replace
将如 [name].[chunkhash:8].js
替换为 0.e3296d88.js
。
构建资源
然后判断有无 source
缓存后,若无则执行:
1 | source = fileManifest.render(); |
即执行对应 template
的 render
。
chunkTemplate
生成主体 chunk 代码
如果是异步 chunk
,render
会执行在文件 JavascriptModulesPlugin.js
里的 renderJavascript
。方法里先执行 Template.renderChunkModules
静态方法:
1 | const moduleSources = Template.renderChunkModules(chunk, (m) => typeof m.source === 'function', moduleTemplate, dependencyTemplates); |
生成每个 module 代码
方法里执行:
1 | const allModules = modules.map((module) => { |
这里循环对每一个 module
执行 render
,方法里执行:
1 | const moduleSource = module.source(dependencyTemplates, this.runtimeTemplate, this.type); |
module.source
里执行:
1 | const source = this.generator.generate(this, dependencyTemplates, runtimeTemplate, type); |
这个 generator
就是在 reslove 流程 -> getGenerator
所获得,即执行:
1 | this.sourceBlock(module, module, [], dependencyTemplates, source, runtimeTemplate); |
这里循环处理 module
的每个依赖(module.dependencies
):获得依赖所对应的 template
模板类,然后执行该类的 apply
:
1 | const template = dependencyTemplates.get(dependency.constructor); |
这里的 dependencyTemplates
就是在 reslove 流程前的准备 ->Compiler.compile ->实例化 compilation
里添加的依赖模板模块。
在 apply
里,会根据依赖不同做相应的源码转化的处理。但方法里并没有直接执行源码转化的工作,而是将其转化对象 push
到 ReplaceSource.replacements
里,转化对象的格式为:
注:
webpack-sources
提供若干类型的source
类,如CachedSource, PrefixSource, ConcatSource, ReplaceSource
等。它们可以组合使用,方便对代码进行添加、替换、连接等操作。同时又含有一些source-map
相关,updateHash
等api
供webpack
内部调用.
1 | //Replacement |
各模板的具体处理转化见 构建 module(下) -> parse 源码 -> 各依赖作用解释
。
包裹代码
收集完依赖相关的转化对象 Replacement
之后,回到 module.source
进行 cachedSource
缓存包装后,回到 moduleTemplate.render
方法得到 moduleSource
。
然后触发相关 ModuleTemplate.hooks:content,module,render,package
,前两个钩子主要是可以让我们完成对 module
源码的再次处理,然后在 render
钩子里执行插件 FunctionModuleTemplatePlugin
的相关事件,主要是给处理后的 module
源码进行包裹,即生成代码:
1 | /***/ |
添加注释
然后触发 package
钩子执行插件 FunctionModuleTemplatePlugin
的相关事件,主要作用是添加相关注释,即生成代码:
1 | /*!***************************************************************!*\ |
将所有的 module
都处理完毕后,回到 renderChunkModules
,继续处理生成代码,最终将每个 module
生成的代码串起来得到 moduleSources
回到了 renderJavascript
里。
生成异步包裹代码
方法里先触发 chunkTemplate.hooks : modules
为修改生成的 chunk
代码提供钩子,得到 core
后,触发 chunkTemplate.hooks
:render
执行插件 JsonpChunkTemplatePlugin
相关事件,该事件主要是添加 jsonp
异步包裹代码,得到:
1 | (window['webpackJsonp'] = window['webpackJsonp'] || []).push([ |
完成后,最后返回一个 new ConcatSource(source, ";")
。到此普通的异步 chunk
代码 chunkTemplate
的 fileManifest.render
代码构建完成。
mainTemplate
如果是同步 chunk
,render
会执行在文件 JavascriptModulesPlugin.js
里的 compilation.mainTemplate.render
即文件 MainTemplate.js
里的 render
。
生成 runtime 代码
方法里执行:
1 | const buf = this.renderBootstrap(hash, chunk, moduleTemplate, dependencyTemplates); |
该方法得到 webpack runtime bootstrap
代码数组,从中会判断是否有异步 chunk
,如果有,则代码里还会包含异步相关的 runtime
代码,如果还有其他什么延迟加载的模块,都会在这里处理为相应是 runtime
。
包裹 runtime 与 chunk 代码
然后执行:
1 | 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
里已注册,代码如下:
1 | const source = new ConcatSource(); |
该方法对 runtime bootstrap
代码进行了包装(bootstrapSource
即为前面生成的 runtime
代码),其中触发 MainTemplate.hooks: modules
得到 chunk
的生成代码,即最终返回一个包含了 runtime
代码和 chunk
代码的 ConcatSource
实例。
生成 chunk 代码
这里来看 chunk
代码的实现,如上文代码中:
1 | this.hooks.modules.call(new RawSource(''), chunk, hash, moduleTemplate, dependencyTemplates); |
这里 mainTemplate.hooks: modules
触发插件 JavascriptModulesPlugin
的相关事件,即执行 Template
类的静态方法 renderChunkModules
。与前文 chunkTemplate -> 生成主体 chunk 代码
的实现一致。
最终经过包裹后得到的代码大致如下:
1 | "/******/ (function(modules) { // webpackBootstrap |
完成后,最后返回一个 new ConcatSource(source, ";")
。到此普通的同步 chunk
代码 mainTemplate
的 fileManifest.render
代码构建完成。
文件名映射资源
无论是同步还是异步,最后都回到 Compilation.js
的 createChunkAssets
里,做了 source
缓存,然后执行:
1 | this.emitAsset(file, source, assetInfo); |
建立起了文件名与对应源码的联系,将该映射对象挂载到 compilation.assets
下。 然后设置了 alreadyWrittenFiles
这个 Map
对象,防止重复构建代码。到此一个 chunk
的资源构建结束。
chunk
遍历结束后,得到 compilation.assets
和 compilation.assetsInfo
:
1 | //compilation |
本章小结
- 通过
this.emitFile
可生成module
资源,如果有则直接调用this.emitAsset
生成资源; - 生成
chunk
资源时,先根据是否含有runtime
得到不同的template
,包括chunkTemplate
和mainTemplate
; - 通过不同的
template
得到不同的manifest
和pathAndInfo
,然后调用不同的render
渲染代码; - 最后建立文件名与资源之间的映射,最终一起挂载到
compilation.assets
即目标资源。