# webpack编译流程分析

通过上两节我们了解到了,webpack的编译阶段可以分为准备阶段,将options转换为webpack 可识别的options配置项;构建阶段,其中,Compiler提供了构建各阶段的钩子函数,代码优化阶段, 而Compilation则提供了模块优化构建的流程。

# 准备阶段

webpack准备阶段会将一些插件挂载到compiler实例上,同时进行EntryOptionPlugin的一些初始化。 接着进入Compiler,完成Compilation以及NormalModuleFactory、ContextModuleFactory工厂 函数的创建,最终进入compiler.run阶段。

//webpack.js
...
const webpack = (options, callback) => {
    let compiler;
    if (Array.isArray(options)) {
        compiler = new MultiCompiler(
            Array.from(options).map(options => webpack(options))
    	);
    } else if (typeof options === "object") {
        // options设置默认参数
        options = new WebpackOptionsDefaulter().process(options);
    
        compiler = new Compiler(options.context);
        compiler.options = options;
        // NodeEnvironmentPlugin :构建前,监听beforeRun钩子,执行缓存清理
        new NodeEnvironmentPlugin({
    		infrastructureLogging: options.infrastructureLogging
    	}).apply(compiler);
        // 遍历插件并将compiler对象传递给插件,当compiler钩子触发时候实现plugin内部监听
        if (options.plugins && Array.isArray(options.plugins)) {
            for (const plugin of options.plugins) {
                if (typeof plugin === "function") {
                    plugin.call(compiler, compiler);
                } else {
                    plugin.apply(compiler);
                }
            }
        }
        compiler.hooks.environment.call();
        compiler.hooks.afterEnvironment.call();
        //WebpackOptionsApply: 将所有的配置options参数转换成webpack内部插件,使用默认的插件列表
        // 例如,output.library则会使用LibraryTemplatePlugin;externals则使用ExternalsPlugin等
        // 另外,WebpackOptionsApply也完成了使用EntryOptionPlugin完成了entry options的初始化
        compiler.options = new WebpackOptionsApply().process(options, compiler);
    }
    ...
}

# 构建阶段

进入Compiler,实例化Compiler时候会创建Compilation对象,NormalModuleFactory、ContextModuleFactory工厂方法, 执行run方法,触发beforeRun后则执行compile。
其中,与Compiler流程相关的钩子有:beforeRun/run,beforeCompile/AfterCompile,make,afterEmit/emit,done等, 而与监听有关的钩子有watchRun,watchClose

//Compiler.js
...
const {
	Tapable,
	SyncHook,
	SyncBailHook,
	AsyncParallelHook,
	AsyncSeriesHook
} = require("tapable");
// NormalModule在构建时使用loader-runner运行loaders,
// 生成js代码则通过Parser解析依赖,最后通过parserPlugins添加依赖
const NormalModuleFactory = require("./NormalModuleFactory");
const ContextModuleFactory = require("./ContextModuleFactory");
const Compilation = require("./Compilation");
...
class Compiler extends Tapable {
	constructor(context) {
		super();
		this.hooks = {
			shouldEmit: new SyncBailHook(["compilation"]),
			// 构建完成后
			done: new AsyncSeriesHook(["stats"]),
			// 开启构建前
			beforeRun: new AsyncSeriesHook(["compiler"]),
			// 开始构建
			run: new AsyncSeriesHook(["compiler"]),
			emit: new AsyncSeriesHook(["compilation"]),
			afterEmit: new AsyncSeriesHook(["compilation"]),
			// 类似HtmlWebpackPlugin等一些插件会有独立的构建流程,则执行thisCompilation
			thisCompilation: new SyncHook(["compilation", "params"]),
			compilation: new SyncHook(["compilation", "params"]),
			beforeCompile: new AsyncSeriesHook(["params"]),
			compile: new SyncHook(["params"]),
			make: new AsyncParallelHook(["compilation"]),
			afterCompile: new AsyncSeriesHook(["compilation"]),
			watchRun: new AsyncSeriesHook(["compiler"]),
			failed: new SyncHook(["error"]),
			watchClose: new SyncHook([]),
			// 初始化options
			entryOption: new SyncBailHook(["context", "entry"])...
		};
        ...
	}
	createCompilation() {
	    return new Compilation(this);
	}
	run(callback){
	    ...
	    // 开始真正构建
	    const onCompiled = (err, compilation) => {
	        if (err) return finalCallback(err);
	        // 最终编译完成判断是否生成资源
	        if (this.hooks.shouldEmit.call(compilation) === false) {
	            const stats = new Stats(compilation);
	            stats.startTime = startTime;
	            stats.endTime = Date.now();
	            this.hooks.done.callAsync(stats, err => {
	                if (err) return finalCallback(err);
	                return finalCallback(null, stats);
	            });
	            return;
	        }
	        this.emitAssets(compilation, err => {
	            if (err) return finalCallback(err);
            
	            if (compilation.hooks.needAdditionalPass.call()) {
	                compilation.needAdditionalPass = true;
            
	                const stats = new Stats(compilation);
	                stats.startTime = startTime;
	                stats.endTime = Date.now();
	                this.hooks.done.callAsync(stats, err => {
	                    if (err) return finalCallback(err);
	                    this.hooks.additionalPass.callAsync(err => {
	                        if (err) return finalCallback(err);
	                        this.compile(onCompiled);
	                    });
	                });
	                return;
	            }
           ...
	    };
	    this.hooks.beforeRun.callAsync(this, err => {
	        if (err) return finalCallback(err);
	        this.hooks.run.callAsync(this, err => {
	            if (err) return finalCallback(err);
	            this.readRecords(err => {
	                if (err) return finalCallback(err);
	                // 进入onCompiled
	                this.compile(onCompiled);
	            });
	        });
	    });
	}
	compile(callback) {
    	const params = this.newCompilationParams();
    	this.hooks.beforeCompile.callAsync(params, err => {
    		if (err) return callback(err);
    		this.hooks.compile.call(params);
    		const compilation = this.newCompilation(params);
    		// hooks.make会调用compilation的addEntry钩子
    		//从entry开始递归的分析依赖,对每个依赖模块进行build
    		this.hooks.make.callAsync(compilation, err => {
    		    if (err) return callback(err);
    		    // compilation.finish则模块构建完成后,会触发hooks.finishModules钩子
    		    // 获取到经过loader转换后的源码
    		    compilation.finish(err => {
    		        if (err) return callback(err);
    		        compilation.seal(err => {
    		            if (err) return callback(err);
    		            this.hooks.afterCompile.callAsync(compilation, err => {
    		                if (err) return callback(err);
    		                return callback(null, compilation);
    		            });
    		        });
    		    });
    		});
    	});
	}
	...
}

module.exports = Compiler;

Compilation就是负责模块的构建和构建优化的过程。 其中,Compilation相关的钩子有: 模块构建相关:buildModule,failModule,succeedModule 资源生成相关:moduleAsset,chunkAsset 优化相关的:optimize,afterOptimzeModules等。

//Compilation.js
...
const {
	Tapable,
	SyncHook,
	SyncBailHook,
	SyncWaterfallHook,
	AsyncSeriesHook
} = require("tapable");
...
class Compilation extends Tapable {
	constructor(compiler) {
		super();
		this.hooks = {
		    // 模块构建相关的钩子
			buildModule: new SyncHook(["module"]),
			rebuildModule: new SyncHook(["module"]),
			failedModule: new SyncHook(["module", "error"]),
			succeedModule: new SyncHook(["module"]),
			// entry相关的
			addEntry: new SyncHook(["entry", "name"]),
			failedEntry: new SyncHook(["entry", "name", "error"]),
			succeedEntry: new SyncHook(["entry", "name", "module"]),
			dependencyReference: new SyncWaterfallHook([
				"dependencyReference",
				"dependency",
				"module"
			]),

			finishModules: new AsyncSeriesHook(["modules"]),
			finishRebuildingModule: new SyncHook(["module"]),
			//seal相关的
			unseal: new SyncHook([]),
			seal: new SyncHook([]),
			// chunks相关的
			beforeChunks: new SyncHook([]),
			afterChunks: new SyncHook(["chunks"]),
			// 优化相关的
			optimizeDependenciesBasic: new SyncBailHook(["modules"]),
			optimizeDependencies: new SyncBailHook(["modules"]),
			optimizeDependenciesAdvanced: new SyncBailHook(["modules"]),
			afterOptimizeDependencies: new SyncHook(["modules"]),
			optimize: new SyncHook([]),
			optimizeModulesBasic: new SyncBailHook(["modules"]),
			optimizeModules: new SyncBailHook(["modules"]),
			optimizeModulesAdvanced: new SyncBailHook(["modules"]),
			afterOptimizeModules: new SyncHook(["modules"]),
			optimizeChunksBasic: new SyncBailHook(["chunks", "chunkGroups"]),
			optimizeChunks: new SyncBailHook(["chunks", "chunkGroups"]),
			//资源生成
			chunkHash: new SyncHook(["chunk", "chunkHash"]),
            moduleAsset: new SyncHook(["module", "filename"]),
		    ...
		};
		...
	}
	addEntry(context, entry, name, callback) {
	    this.hooks.addEntry.call(entry, name);
        ...
    	if (entry instanceof ModuleDependency) {
            slot.request = entry.request;
        }
        // 将模块添加到依赖列表,执行模块构建buildModule
        this._addModuleChain(
            context,
    		entry,
    		module => {
                this.entries.push(module);
        },
    	(err, module) => {
    		...
    		this.hooks.succeedEntry.call(entry, name, module);
    		return callback(null, module);
        });
    }
    // 最后会进入buildModule
    buildModule(module, optional, origin, dependencies, thisCallback) {
    	...
    	this.hooks.buildModule.call(module);
    	// 进入doBuild
    	//依赖loader-runner运行构建,完成后会运行parser.parse解析,最后把依赖模块添加到依赖列表modules
    	module.build(
    	    this.options,
    		this,
    		this.resolverFactory.get("normal", module.resolveOptions),
    		this.inputFileSystem,
    		error => {
    	        ...
    	        if (error) {
    	            this.hooks.failedModule.call(module, error);
    	            return callback(error);
    			}
    			this.hooks.succeedModule.call(module);
    	        return callback();
    	    }
    	);
	}
    ...
}

module.exports = Compilation;

# 文件生成

最后Compilation完成seal和优化之后,会回到Compiler执行emitAssets,将内容输出到磁盘上。

//Compiler.js
...
class Compiler extends Tapable {
    constructor(context){...}
    emitAssets(compilation, callback) {
        this.hooks.emit.callAsync(compilation, err => {
            if (err) return callback(err);
            outputPath = compilation.getPath(this.outputPath);
            this.outputFileSystem.mkdirp(outputPath, emitFiles);
        });
    }
    ...
}

# 总结

可以简单的总结为,webpack的编译按照钩子调用顺序执行的流程:
初始化EntryOptions,进入Compiler.run开始编译,hooks.make会调用Compilation的addEntry钩子,从entry开始递归的 分析依赖,对每个依赖模块进行build,对模块位置进行解析beforeResolve,然后开始构建模块buildModule,通过normalModuleLoader 将loader加载完成的module进行编译,生成AST抽象语法树,接着遍历AST,对require等一些调用进行依赖收集,最后将所有依赖构建 完成后,执行seal和优化,并回到Compiler.emit执行磁盘输出,完成编译流程。