1. 概览
该篇文章主要讲的是根据案例webpack源码的执行主流程,一共分为几个阶段来讲述,然后每个阶段大致做了什么,怎么做的,从而达到我们在实际应用中我们可以做什么事。
2. tapable模块
直接讲干货;这是关于webpack5.51.1的分析; 首先了解tapable这个模块;说下为什么:因为webpack是一个以多个插件组合(包括自定义插件)形成的流水线的工具,插件是怎样被注入到webpack里的呢,就是通过tapable这个模块实现的,通俗一点就是发布订阅的模式;在初始化插件时会调用tap方法,然后会把tap方法里的回调存在队列里,然后在webpack的某个阶段触发刚刚加入tap时的方法。从而达到了不同的阶段做该阶段的事;举个简单的例子(不要在意一些细节的判断):
let tapable= {
event:{},
_tap: (type, fn)=>{
this.event[type].push(fn)
},
_call: (type,...arg)=>{
this.event[type].foreach(fn=>fn(args))
}
}
// 注入插件:
class plugins{
constructor(){
tapable._tap('type', (arg)=>{console.log('回调执行',arg)})
}
}
// 触发插件:
new plugins();
tapable._call(111);
小结:tapable工作原理大致是上述这样,后面我们会跟着源码讲解tapable模块,我们了解了插件在webpack的工作原理,接下来我正式进入webpack源码讲解;后面根据webpack的各个阶段进行讲解:一共大致分为init->run->make->seal ->emit等主流程
前序工作和配置(webpack5.51.1、webpack-cli4.8.0):
webpack.config.js
const path = require('path');
import plugin from './plugin';
module.exports = {
mode: "production", // "production" | "development" | "none" // 告诉webpack是生产环境还是开发环境.
entry: path.resolve(__dirname, "../main.js"), // string | object | array // 默认 ./src
// 入口起点,可以指定多个入口起点
output: {
// 输出,只可指定一个输出配置
path: path.resolve(__dirname, "dist"), // string
// 所有输出文件所在的目录
filename: "bundle.js", // string // 输出文件的名称
},
module: {
rules: [
{
test: /\.js$/,
use:[{
loader: path.resolve(__dirname, './loader'),
}],
},
]
},
plugins:[
new plugin(),
]
}
main.js
import './index';
console.log('main');
index.js
console.log('index')
3. init阶段
首先打开webpack源码webpack.js:看到wepack是一个接受option和callback俩个参数的function;首先调用create方法创建启动编译的对象compiler;通过new Compiler()创建;然后通过compiler.run方法开始启动;
)
4. run阶段
run阶段以为正式进入编译阶段,这个阶段做了什么?接下来我们继续看源码,打开compiler模块我们看到run方法;
)
上图可以看到在run方法里定义了onCompiled
、run
俩个方法,onCompiled
通过命名可以猜测是编译完成调用的方法,那么我先看run
方法做了什么?首先看下this.hooks.beforeRun.callAsync()
这个方法,同样看下图
)
)
上图可以看出,该方法来自文章最开始提及的核心模块tapable,我想大家应该能猜测出这一步干了啥?this.hooks.beforeRun的值是AsyncSeriesHook的实例;打开AsyncSeriesHook模块,一看源码发现,相当于AsyncSeriesHook
继承Hook
,所以this.hooks.beforeRun.callAsync就是调用Hook模块里的callAsync
方法,接下来我们看hook模块,hook模块的callAsync方法是由this._createCall("async")得到,这个方法最终会返回一个函数function
(function anonymous(compiler, _callback
) {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
var _hasError0 = false;
try {
_fn0(compiler);
} catch(_err) {
_hasError0 = true;
_callback(_err);
}
if(!_hasError0) {
_callback();
}
})
表达式说明: compiler此时就是最开始调用时传入的this,callback就是回调;
_x这个属性值就是上文说的一些插件使用tap方法注册的回调,这里目前只有一个地方使用了tap方法,
所以只有_x[0],如果是多个地方使用了,那么生成的代码的就是再追加一个try的逻辑;
)
接下来 执行this.callAsync
这个函数就是执行上述function;那么执行try时就会执行到之前tap注册时的回调函数;这就是插件的原理;我们在后续调用call hooks的时候都可以自定义插件来处理系统的一些问题;比如编译之前copy一些文件等;说到这里我想大家已经很明白我最开始所说的发布订阅了,接下来我们继续讲流程(流程异常的情况不讲); 执行run流程注册的回调,接下来我们看this.compile()
这个流程,进入到该模块的compiler方法:
)
1、 首先new newCompilationParams()初始化; 会得到有俩个重要的属性分别是normalModuleFactor和contextModuleFactory的对象;首先normalModuleFactor属性值是一个继承于ModuleFactory
对象的NormalModuleFactory`的一个工厂生成的;normalModuleFactor同样也有自己的hook值等;
const NormalModuleFactory = {
cachePredicate:() => true,
ruleSet:{references: Map(0), exec: ƒ},
resolverFactory:ResolverFactory {hooks: {…}, cache: Map(0)},
context:'C:\\Users\\16422\\Desktop\\test',
fs:CachedInputFileSystem {fileSystem: {…}, _lstatBackend: CacheBackend, lstat: ƒ, lstatSync: ƒ, _statBackend: CacheBackend, …},
generatorCache:Map(0),
hooks:{resolve: Hook, resolveForScheme: HookMap, factorize: Hook, beforeResolve: Hook, afterResolve: Hook, …},
parserCache:Map(0),
}
这些属性先不做过多解释,后面调用再做详细解释;大致也能猜到将会处理我们业务代码中的依赖;contextModuleFactory
这属性也同样如此;到此很浅的分析完了new newCompilationParams();
2、接下来继续执行const compilation = this.newCompilation(params);
这句代码,compilation 是一个很重要的对象,compilation 是编译过程中的对象,后续module、chunk、assets等都挂到这个对象上;接下来看下this.newCompilation()
方法,这方法创建一个这样的对象:
{
buildQueue:AsyncQueue {_name: 'build', _parallelism: 100, _processor: ƒ, _getKey: ƒ, _entries: Map(0), …}
buildDependencies:LazySet {_set: Set(0), _toMerge: Set(0), _toDeepMerge: Array(0), _needMerge: false, _deopt: false}
bail:false
_assetsCache:CacheFacade {_cache: Cache, _name: 'Compilation/assets'}
_assetsRelatedIn:Map(0)
_codeGenerationCache:CacheFacade {_cache: Cache, _name: 'Compilation/codeGeneration'}
_modules:Map(0)
_modulesCache:CacheFacade {_cache: Cache, _name: 'Compilation/modules'}
_rebuildingModules:Map(0)
additionalChunkAssets:(0) []
addModuleQueue:AsyncQueue {_name: 'addModule', _parallelism: 100, _processor: ƒ, _getKey: ƒ, _entries: Map(0), …}
assets:{}
assetsInfo:Map(0)
asyncEntrypoints:(0) []
builtModules:WeakSet
asyncEntrypoints:(0) []
bail:false
buildDependencies:LazySet {_set: Set(0), _toMerge: Set(0), _toDeepMerge: Array(0), _needMerge: false, _deopt: false}
buildQueue:AsyncQueue {_name: 'build', _parallelism: 100, _processor: ƒ, _getKey: ƒ, _entries: Map(0), …}
cache (get):ƒ deprecated(...args) {\n if (!warned) {\n warned = true;\n if (code !== undefined) {\n if (!codesWarned[code]) {\n process.emitWarning(msg, 'DeprecationWarning', code, deprecated);\n codesWarned[code] = true;\n }\n } else {\n process.emitWarning(msg, 'DeprecationWarning', deprecated);\n }\n }\n if (new.target) {\n return Reflect.construct(fn, args, new.target);\n }\n return fn.apply(this, args);\n }
cache (set):ƒ deprecated(...args) {\n if (!warned) {\n warned = true;\n if (code !== undefined) {\n if (!codesWarned[code]) {\n process.emitWarning(msg, 'DeprecationWarning', code, deprecated);\n codesWarned[code] = true;\n }\n } else {\n process.emitWarning(msg, 'DeprecationWarning', deprecated);\n }\n }\n if (new.target) {\n return Reflect.construct(fn, args, new.target);\n }\n return fn.apply(this, args);\n }
children:(0) []
childrenCounters:{}
chunkGraph:undefined
chunkGroups:(0) []
chunks:Set(0) {concat: ƒ, entry: ƒ, filter: ƒ, find: ƒ, findIndex: ƒ, …}
chunkTemplate:ChunkTemplate {_outputOptions: {…}, hooks: {…}}
codeGeneratedModules:WeakSet
codeGenerationResults:undefined
comparedForEmitAssets:Set(0)
compilationDependencies:{add: ƒ}
compiler:Compiler {hooks: {…}, webpack: ƒ, name: undefined, parentCompilation: undefined, root: Compiler, …}
compilerPath:''
contextDependencies:LazySet {_set: Set(0), _toMerge: Set(0), _toDeepMerge: Array(0), _needMerge: false, _deopt: false}
creatingModuleDuringBuild:WeakMap
dependencyFactories:Map(26) {class … => NormalModuleFacto… …, class … => NormalModuleFacto… …, class … => NormalModuleFacto… …, class … => NormalModuleFacto… …, class … => NormalModuleFacto… …, …}
dependencyTemplates:DependencyTemplates {_map: Map(42), _hash: '31d6cfe0d16ae931b73c59d7e0c089c0'}
emittedAssets:Set(0)
endTime:undefined
entries:Map(0)
entrypoints:Map(0)
errors:(0) []
factorizeQueue:AsyncQueue {_name: 'factorize', _parallelism: 100, _processor: ƒ, _getKey: ƒ, _entries: Map(0), …}
fileDependencies:LazySet {_set: Set(0), _toMerge: Set(0), _toDeepMerge: Array(0), _needMerge: false, _deopt: false}
fileSystemInfo:FileSystemInfo {fs: CachedInputFileSystem, logger: WebpackLogger, _remainingLogs: 40, _loggedPaths: Set(0), _snapshotCache: WeakMap, …}
globalEntry:{dependencies: Array(0), includeDependencies: Array(0), options: {…}}
hooks:{buildModule: Hook, rebuildModule: Hook, failedModule: Hook, succeedModule: Hook, stillValidModule: Hook, …}
inputFileSystem:CachedInputFileSystem {fileSystem: {…}, _lstatBackend: CacheBackend, lstat: ƒ, lstatSync: ƒ, _statBackend: CacheBackend, …}
logger:WebpackLogger {getChildLogger: ƒ, Symbol(webpack logger raw log method): ƒ}
logging:Map(0)
mainTemplate:MainTemplate {_outputOptions: {…}, hooks: {…}, renderCurrentHashCode: ƒ, getPublicPath: ƒ, getAssetPath: ƒ, …}
missingDependencies:LazySet {_set: Set(0), _toMerge: Set(0), _toDeepMerge: Array(0), _needMerge: false, _deopt: false}
moduleGraph:ModuleGraph {_dependencyMap: Map(0), _moduleMap: Map(0), _originMap: Map(0), _metaMap: Map(0), _cacheModuleGraphModuleKey1: undefined, …}
modules:Set(0) {concat: ƒ, entry: ƒ, filter: ƒ, find: ƒ, findIndex: ƒ, …}
moduleTemplates:{javascript: ModuleTemplate, asset: <accessor>, webassembly: <accessor>}
name:undefined
namedChunkGroups:Map(0)
namedChunks:Map(0)
needAdditionalPass:false
options:{amd: undefined, bail: undefined, cache: false, context: 'C:\\Users\\16422\\Desktop\\test', dependencies: undefined, …}
outputOptions:{assetModuleFilename: '[hash][ext][query]', charset: true, chunkFilename: '[id].bundle.js', chunkFormat: 'array-push', chunkLoading: 'jsonp', …}
processDependenciesQueue:AsyncQueue {_name: 'processDependencies', _parallelism: 100, _processor: ƒ, _getKey: ƒ, _entries: Map(0), …}
profile:false
rebuildQueue:AsyncQueue {_name: 'rebuild', _parallelism: 100, _processor: ƒ, _getKey: ƒ, _entries: Map(0), …}
records:{}
requestShortener:RequestShortener {contextify: ƒ}
resolverFactory:ResolverFactory {hooks: {…}, cache: Map(0)}
runtimeTemplate:RuntimeTemplate {outputOptions: {…}, requestShortener: RequestShortener}
startTime:undefined
usedChunkIds:null
usedModuleIds:null
}
后续就是对该对象进行处理;完善其属性值,然后正式进入编译阶段……
5. make阶段
5.1 建立入口module
首先执行this.hooks.make.callAsync()
;调用EntryPlugin插件的回调;
)
回调执行compilation.addEntry()
传入三个参数context、dep、options;
1、context是工程webpack的绝对路径;
2、dep是由本模块初始化时执行EntryPlugin.createDependency(entry, options)
产生的对象,entry为webpackconfig配置的entry的值;执行该方法实际是调用 Dependency模块的constuctor来创建一个EntryDependency实例,包含属性为
entryDependency{
loc:_loc:{name: 'main'}
_parentModule:undefined
_parentDependenciesBlock:undefined
_locSL:0
_locSC:0
_locN:'main'
_locI:undefined
_locEL:0
assertions:undefined
optional:false
range:undefined
request: // 模块的绝对路径
userRequest: // 模块的绝对路径
weak:false
}
3、options就是调用webpack方法传入的第一个参数经过处理得到的一个对象
{
chunkLoading:undefined
dependOn:undefined
filename:undefined
layer:undefined
library:undefined
name:'main'
publicPath:undefined
runtime:undefined
wasmLoading:undefined
}
三个参数分析完了,回到compilation.addEntry()
方法,实际调用的compliation模块的_addEntryItem
方法;我们阅读这些this._addModuleTree
->this.addModuleTree
->this.handleModuleCreation
->this.factorizeModule()
->this.factorizeQueue.add()
流程;实际上是添加entry到factorizeQueue队列里,这里factorizeQueue实际是new AsyncQueue({ name: "build", parent: this.factorizeQueue,processor: this._buildModule.bind(this)})生成,调用add实际就是asyncQuen实例的add;那么最终会形成 asyncQuen._queued这个数组里对应着entry;然后通过Immediate
这个Api来调用_queued队列;
)
如图等待node执行Immediate该方法是就会调用root._ensureProcessing; root是asyncQuen的_root属性;
)
这些代码的含义实际就是为了使asyncQuen实例形成一个树形结构;_root永远都是整个树的根数据,和vue-roter源码类似;
{
_children: [AsyncQueue]
0:AsyncQueue {
_activeTasks:0
_children:undefined
_ensureProcessing:ƒ ()
_entries_entries:Map(1) {C:\\Users\\1…k\\main.js => AsyncQueueEntry …}
size (get):ƒ size()
[[Entries]]:Array(1)
0:{"C:\\Users\\16422\\Desktop\\webpack\\main.js" => AsyncQueueEntry}
key:'C:\\Users\\16422\\Desktop\\webpack\\main.js'
value:AsyncQueueEntry {
callback:(err, module) => {…} // 调用this.addModule方法的回调
callbacks:undefined
error:undefined
item:NormalModule {dependencies: Array(0), blocks: Array(0), type: 'javascript/auto', context:
'C:\\Users\\16422\\Desktop\\webpack', layer: null, …}
result:undefined
}
_name:'addModule'
_needProcessing:false
_parallelism:1
_processor:ƒ ()
_queued:ArrayQueue {_list: Array(1), _listReversed: Array(0)}
}
1:AsyncQueue {_name: 'factorize', _parallelism: 1, _processor: ƒ, _getKey: ƒ, _entries: Map(1), …}
2:AsyncQueue {_name: 'build', _parallelism: 1, _processor: ƒ, _getKey: ƒ, _entries: Map(0), …}
length:3 ]
}
在factorizeQueue这个实例里可以通过parent访问到addModuleQueue实例,这里的顺序processDependenciesQueue._children=addModuleQueue._children=factorizeQueue._children=buildQueue;这个对应关系清楚后;root._ensureProcessing这个方法就会遍历执行__queue队列和_children的队列;执行队列里的每一项实际是执行的什么呢?
)
从图中可以看出执行的是this._processor这个方法,这个方法前面可以知道是绑定的this._factorizeModule这个方法,最后又进入到buildModule的过程了,下面就是AsyncQueue 实例的结构
{
_activeTasks:1
_children:(3) [AsyncQueue, AsyncQueue, AsyncQueue]
_ensureProcessing:ƒ ()
_entries:Map(0)
_getKey:item => /** @type {any} */ (item)
_name:'processDependencies'
_needProcessing:true
_parallelism:100
_processor:ƒ ()
_queued:ArrayQueue {_list: Array(0), _listReversed: Array(0)}
_root:AsyncQueue {_name: 'processDependencies', _parallelism: 100, _processor: ƒ, _getKey: ƒ, _entries: Map(0), …}
_stopped:false
_willEnsureProcessing:false
}
继续调用this._processor()
方法,会根据entry入口构建module,回调里调用this.__handleResult()方法处理module,会调用ewntry.calback,也就是构建AsyncQuen时传入的callback,也就是this.factorizeModule的回调; 执行完__handleResult方法回继续调用this.addModule(newModule)
;newModule就是一个含有main.js路径的normalModule对象;该方法就是把创建的newModule加入到this.addModule实例里的_queued队列;和this.factorizeQueue一样的数据结构;同样的执行和this.factorizeQueue逻辑。
小结:这里也是一个订阅和发布的模式,new AsyncQueue()方法进行订阅,setImmediate(root._ensureProcessing)进行发布。
)
晓得上述大概流程的执行方式后,执行完addModule订阅AsyncQueue后会订阅buildModule的AsyncQueue,继续执行该订阅会调用_buildModule
方法里的module.build()
;该方法调用NormalModule实例的build方法,执行this.doBuild()
;然后执行 runLoaders;为甚么要说下这个方法,这个方法涉及平时我们配置的loader;该方法在runLoaders模块里;runLoaders方法接受来个参数一个是options,一个是callback;
options:{
context:{version: 2, getOptions: ƒ, emitWarning: ƒ, emitError: ƒ, getLogger: ƒ, …}
loaders:(1) [{…}]
0:{loader: 'C:\\Users\\16422\\Desktop\\webpack\\build\\loader.js', options: undefined, ident: undefined}
ident:undefined
loader:'C:\\Users\\16422\\Desktop\\webpack\\build\\loader.js'
options:undefined
__proto__:Object
length:1
__proto__:Array(0)
processResource:(loaderContext, resourcePath, callback) => {\n\t\t\t\t\tconst resource = loaderContext.resource;\n\t\t\t\t\tconst scheme = getScheme(resource);\n\t\t\t\t\tif (scheme) {\n\t\t\t\t\t\thooks.readResourceForScheme\n\t\t\t\t\t\t\t.for(scheme)\n\t\t\t\t\t\t\t.callAsync(resource, this, (err, result) => {\n\t\t\t\t\t\t\t\tif (err) return callback(err);\n\t\t\t\t\t\t\t\tif (typeof result !== "string" && !result) {\n\t\t\t\t\t\t\t\t\treturn callback(new UnhandledSchemeError(scheme, resource));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn callback(null, result);\n\t\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\tloaderContext.addDependency(resourcePath);\n\t\t\t\t\t\tfs.readFile(resourcePath, callback);\n\t\t\t\t\t}\n\t\t\t\t}
resource:'C:\\Users\\16422\\Desktop\\webpack\\main.js'
}
可以看到loaders属性值就是我最开始工程目录配置的l自定义loader;继续阅读到iteratePitchingLoaders
方法,进入该方法阅读到loadLoader;通过module = require(loader.path);获取到我写的自定义loader模块;然后进入teratePitchingLoaders
方法;调用processResource()
再调用options.processResource(loaderContext, resourcePath, function(err, buffer)
buffer就是main.js文件的内容;再调用runSyncOrAsync(fn, loaderContext, args);fn就是自定义loader方法;
)
图片最后一句代码fn(context);将会执行loader.js的方法;context就是main.js的内容,所以自定义loader方法接受的第一个参数就是loader正则匹配的文件的内容。
小结:上述流程完成了loader对文件的转换;所以这个流程阶段我们可以自定义改变文件的内容。
接下来我们思考一个问题?怎样进行下一个依赖index.js进行分析,我们大胆试想一下,是否是需要对main.js进行语法分析,然后再生成moudle依赖;
5.2 分析依赖
执行buildModule订阅的AsyncQueue时,执行是绑定的__buildModule方法;开始创建模块,然后执行module.build调用的是normalModule模块里的build;在this.doBuild
方法里获取main.js的source作为参数通过this.parser.parse()
方法进行语法分析;在JavascriptParser模块里调用parse方法;得到main.js的语法树ast;
body:(3) [Node, Node, Node]
Node {type: 'ImportDeclaration', start: 0, end: 20, loc: SourceLocation, range: Array(2), …}
end:20
loc:SourceLocation {start: Position, end: Position}
range:(2) [0, 20]
source:Node {type: 'Literal', start: 7, end: 19, loc: SourceLocation, range: Array(2), …}
specifiers:(0) []
start:0
type:'ImportDeclaration'
__proto__:Object
1:Node {type: 'ExpressionStatement', start: 22, end: 36, loc: SourceLocation, range: Array(2), …}
2:Node {type: 'ExpressionStatement', start: 38, end: 58, loc: SourceLocation, range: Array(2), …}
length:3
__proto__:Array(0)
end:58
然后调用this.blockPreWalkStatements()
进行语法树的分析;通过语法的type字段为ImportDeclaration来调用this.blockPreWalkImportDeclaration(statement)
,在方法里调用this.hooks.import.call(statement, source)
触发HarmonyImportDependencyParserPlugin模块里的hooks;通过new HarmonyImportSideEffectDependency()
创建依赖;最后加入到module的dependences队列里。
接下来进入到this.buildModule方法的回调里,可以看到会订阅processDependenciesQueue的AsyncQueue,然后执行这个订阅,这个订阅又会执行factorizeModule订阅;重复执行factorizeModule订阅->addmodule订阅->buildModule订阅->processDependenciesQueue订阅这个流程; 就会把创建依赖图谱执行完;最后会把生成module挂到compilation的_modules属性上
_modules:Map(2) {C:\\Users\\1…k\\main.js => NormalModule {…}, C:\\Users\\1…\\index.js => NormalModule {…}}
size (get):ƒ size()
[[Entries]]:Array(2)
0:{"C:\\Users\\16422\\Desktop\\webpack\\build\\loader.js!C:\\Users\\16422\\Desktop\\webpack\\main.js" => NormalModule}
key:'C:\\Users\\16422\\Desktop\\webpack\\build\\loader.js!C:\\Users\\16422\\Desktop\\webpack\\main.js'
value:NormalModule {dependencies: Array(1), blocks: Array(0), type: 'javascript/auto', context: 'C:\\Users\\16422\\Desktop\\webpack', layer: null, …}
1:{"C:\\Users\\16422\\Desktop\\webpack\\build\\loader.js!C:\\Users\\16422\\Desktop\\webpack\\index.js" => NormalModule}
length:2
类似这样的结构,到此依赖分析完成,并且make阶段也完成。
小结:make阶段进行了loader的解析、依赖分析生成依赖图谱、模块的hash值的生成等;生成modules;
6. seal 阶段
说哈大致流程: 1、通过module生成chunk 2、通过module、chunk生成assets 3、最后把assets输出到目录;
make阶段结束后进入compilation.seal方法;阅读源码发现触发了一系列的hooks,其中包括压缩等;然后通过this.entries生成chunk;看下chunk的结构:
{
groups:Set(0) {_sortFn: ƒ, _lastActiveSortFn: Symbol(not sorted), _cache: undefined, _cacheOrderIndependent: undefined}
auxiliaryFiles:Set(0)
files:Set(0)
filenameTemplate:undefined
chunkReason:undefined
contentHash:{}
debugId:1000
entryModule (get):ƒ entryModule() {\n\t\tconst entryModules = Array.from(\n\t\t\tChunkGraph.getChunkGraphForChunk(\n\t\t\t\tthis,\n\t\t\t\t"Chunk.entryModule",\n\t\t\t\t"DEP_WEBPACK_CHUNK_ENTRY_MODULE"\n\t\t\t).getChunkEntryModulesIterable(this)\n\t\t);\n\t\tif (entryModules.length === 0) {\n\t\t\treturn undefined;\n\t\t} else if (entryModules.length === 1) {\n\t\t\treturn entryModules[0];\n\t\t} else {\n\t\t\tthrow new Error(\n\t\t\t\t"Module.entryModule: Multiple entry modules are not supported by the deprecated API (Use the new ChunkGroup API)"\n\t\t\t);\n\t\t}\n\t}
extraAsync:false
hash:undefined
groupsIterable (get):ƒ groupsIterable() {\n\t\tthis._groups.sort();\n\t\treturn this._groups;\n\t}
files:Set(0)
modulesIterable (get):ƒ modulesIterable() {\n\t\tconst chunkGraph = ChunkGraph.getChunkGraphForChunk(\n\t\t\tthis,\n\t\t\t"Chunk.modulesIterable",\n\t\t\t"DEP_WEBPACK_CHUNK_MODULES_ITERABLE"\n\t\t);\n\t\treturn chunkGraph.getOrderedChunkModulesIterable(\n\t\t\tthis,\n\t\t\tcompareModulesByIdentifier\n\t\t);\n\t}
ids:null
idNameHints:Set(0) {_sortFn: undefined, _lastActiveSortFn: Symbol(not sorted), _cache: undefined, _cacheOrderIndependent: undefined}
id:null
hash:undefined
groupsIterable (get):ƒ groupsIterable() {\n\t\tthis._groups.sort();\n\t\treturn this._groups;\n\t}
files:Set(0)
filenameTemplate:undefined
extraAsync:false
name:'main'
preventIntegration:false
rendered:false
renderedHash:undefined
runtime:undefined
}
6.1 处理chunk和module得到 codeGenerationResults值:
接下来利用生成的chunk来建立chunkGraph、chunkGroups并挂到compilation上,我们最终的目的是要把模块的代码进行输出;看看是怎么进行处理得到我们想要的code;通过connectChunkGroupAndChunk这个方法把chunk与ChunkGroup关联起来,ChunkGroup的chunk属性里维护着chunk的队列,通过chunkGraph.connectChunkAndEntryModule方法建立chunk和module的关系,chunkGraph的chunk维护着chunk,module维护着module队列;接下来调用this.codeGeneration,该方法会对chunkGraph.module进行操作生成一个包含module的队列;同时也会初始化codeGenerationResults挂到compilation对象上results属性里,接下来继续对module进行处理;通过this.codeGenerationModule这个方法是把module经过分析处理后的结果保存在results中;那么module是怎么被处理的?首先是被concatenatedModule.codeGeneration处理一些需要输出的code,类似:"webpack_require"这样的code;还处理了作用域和遍历分析每个module,处理分析后的ast;并返回处理后的结果给results;怎么分析每个module?首先对module的codeGeneration方法获取module里的代码块;然后对代码块处理后放入到_children 数组里,最终数据可以在compailer对象的codeGenerationResults属性值里查看结构为:
_source:ConcatSource {_children: Array(4), _isOptimized: false}
_children:(4) ['\n;// CONCATENATED MODULE: ./index.js\n', ReplaceSource, '\n;// CONCATENATED MODULE: ./main.js\n', ReplaceSource]
0:'\n;// CONCATENATED MODULE: ./index.js\n'
1:ReplaceSource {_source: CachedSource, _name: undefined, _replacements: Array(1), _isSorted: false}
2:'\n;// CONCATENATED MODULE: ./main.js\n'
3:ReplaceSource {_source: CachedSource, _name: undefined, _replacements: Array(1), _isSorted: false}
length:4
6.2 生成assets
利用上面生成的codeGenerationResults值和其他值去生成asstes;调用createChunkAssets的方法就是把生成manifest处理成fileManifest,然后调用fileManifest.render()生成最终的source,然后缓存到compilation.assets里;首先把codeGenerationResults值传入到this.getRenderManifest这个方法里;这个方法会触发javascriptModulesPlugins模块里注册的hooks;给返回结果绑定render方法;然后执行render方法,此时会调用javascriptModulePlugins模块的renderMain方法,该方法会在source基础上添加renderBootstrap的代码块;
_children:(7) ['/******/ (() => { // webpackBootstrap\n', '/******/ \t"use strict";\n', 'var __webpack_exports__ = {};\n', CachedSource, '\n', '/******/ })()\n', ';']
0:'/******/ (() => { // webpackBootstrap\n'
1:'/******/ \t"use strict";\n'
2:'var __webpack_exports__ = {};\n'
3:CachedSource {_source: ConcatSource, _cachedSourceType: undefined, _cachedSource: undefined, _cachedBuffer: undefined, _cachedSize: undefined, …}
4:'\n'
5:'/******/ })()\n'
6:';'
length:7
最终生成的代码会调用this.emitAsset()方法把assets挂到compilation的assets属性上;数据结构为: )
6.3 处理assets
触发processAssets hook;会把this.assets的source进行处理最终输出,首先触发webpack-sources的CachedSource模块的sourceAndMap方法,整合assets里的source,遍历source得到合并后的code为:
/******/ (() => { // webpackBootstrap
/******/ "use strict";
var __webpack_exports__ = {};
;// CONCATENATED MODULE: ./index.js
/* harmony default export */ const index = ((name)=>{
alert(66666);
console.log(name);
});
;// CONCATENATED MODULE: ./main.js
alert(334445);
console.log('main');
index('index');
/******/ })()
;
这个流程最终生成了这样的asstes:
assets:{
bundle.js: {
_value: “上述代码块“
_valueAsBuffer:undefined
_valueIsBuffer:false
}
}
7. emit阶段
该阶段是输出bundle文件;回到最开始run方法里回调中的定义的onCompiled方法;在onCompiled方法里this.this.emitAssets方法会先根据配置创建输出更目录dist; ) emitFiles作为创建dist后的回调,该回调里定义了一系列方法;首先执行writeOut;然后依次执行processMissingFile ->getContent(得到buildle文件的buffer)->updateWithReplacementSource(修改文件的size等)->doWrite; ) 最后调用this.outputFileSystem.writeFile输出build.js文件。
总结:最后方便大家阅读:附上简略整个的流程图;待完……