webpack执行过程

webpack-cli

执行过程

1.webpack.config.js,shell options参数解析
2.new webpack(options)
3.run() 编译的入口方法
4.compile() 出发make事件
5.addEntry() 找到js文件,进行下一步模块绑定
6._addModuleChain() 解析js入口文件,创建模块
7.buildModule() 编译模块,loader处理与acorn处理AST语法树
8.seal() 每一个chunk对应一个入口文件
9.createChunkAssets() 生成资源文件
10.MainTemplate.render() __webpack__require()引入
11.ModuleTemplate.render() 生成模版
12.module.source() 将生成好的js保存在compilation.assets中
13.Compiler.emitAssets()通过emitAssets将最终的js输出到output的path中

参数解析

(function(){
    yargs.options({...})
    
    yargs.parse(process.argv.slice(2), (err, argv, output) => {...})
})()

加载webpack.config.js

(function(){
    ...
    yargs.parse(process.argv.slice(2), (err, argv, output) => {
        ...
        //解析argv,拿到配置文件参数
       let options =  require("./convert-argv")(argv);
       function processOptions(options){
           ...
       } 
        
       processOptions(options);
    })
})()

执行webpack()

(function(){
    ...
    yargs.parse(process.argv.slice(2), (err, argv, output) => {
        ...
        //解析argv,拿到配置文件参数
       let options =  require("./convert-argv")(argv);
       function processOptions(options){
           ...
           const webpack = require("webpack");
           compiler = webpack(options);   
       } 
        
       processOptions(options);
    })
})()

webpack.js

const webpack = (options, callback) => {
    
    //验证webpack.config.js合法性
    const webpackOptionsValidationErrors = validateSchema(
		webpackOptionsSchema,
		options
	);
	
	/*
	    [
          { entry: './index1.js', output: { filename: 'bundle1.js' } },
          { entry: './index2.js', output: { filename: 'bundle2.js' } }
        ]
	*/
	if (Array.isArray(options)) {
	    compiler = new MultiCompiler(options.map(options => webpack(options)));
	} else if(typeof options === "object"){
	    ...
	    //创建一个comiler对象
	    compiler = new Compiler(options.context);
	    
	    //往comiler中注册插件
	    new NodeEnvironmentPlugin().apply(compiler);
	    
	    //执行config中配置的插件
	    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);
				}
			}
		}
		
		//执行插件environment生命周期钩子方法
		compiler.hooks.environment.call();
		compiler.hooks.afterEnvironment.call();
		//执行webpack内置插件
		compiler.options = new
		WebpackOptionsApply().process(options, compiler);
	}else {
		throw new Error("Invalid argument: options");
	}
	
	if (callback) {
	    ...
	    //调用compiler.run开始编译
	    compiler.run(callback);
	}
	//将compiler对象返回
	return compiler
}

//NodeEnvironmentPlugin.js
class NodeEnvironmentPlugin {
	apply(compiler) {
        ...
		compiler.hooks.beforeRun.tap("NodeEnvironmentPlugin", compiler => {
			if (compiler.inputFileSystem === inputFileSystem) inputFileSystem.purge();
		});
	}
}
module.exports = NodeEnvironmentPlugin;

class WebpackOptionsApply extends OptionsApply {
	constructor() {
		super();
	}
    process(options, compiler) {
        //挂载配置,执行插件
        let ExternalsPlugin;
		compiler.outputPath = options.output.path;
		compiler.recordsInputPath = options.recordsInputPath || options.recordsPath;
		compiler.recordsOutputPath =
			options.recordsOutputPath || options.recordsPath;
		compiler.name = options.name;
		
		new EntryOptionPlugin().apply(compiler);
		new HarmonyModulesPlugin(options.module).apply(compiler);
		new LoaderPlugin().apply(compiler);
		...
	}
}

module.exports = WebpackOptionsApply;

compiler.run() 开始编译

class Compiler extends Tapable{
    constructor(context){
        ...
    }
    watch(){...}
    run(callback){
        ...
        const onCompiled = (err, compilation){
            ...
        } 
        //执行生命周期钩子
        this.hooks.beforeRun.callAsync(this, err => {
              ...
            this.hooks.run.callAsync(this, err =>{
                this.readRecords(err =>{
                    ...
                    //开始编译
                    this.compile(onCompiled);
                })
            }
        }
    }
    compile(callback) {
        //拿到参数
        const params = this.newCompilationParams();
        //执行编译前钩子
        this.hooks.beforeCompile.callAsync(params, err => {
            ...
            
            //创建compilation对象
            const compilation = this.newCompilation(params);
            
            //开始构建模块对象
            this.hooks.make.callAsync(compilation, err =>{
                
            })
        }
    }
    createCompilation() {
        //创建comilation对象
		return new Compilation(this);
	}
    newCompilation(params) {
        //调用创建compilation对象方法
        const compilation = this.createCompilation();
    }
}

module.exports = Compiler;

创建 Compilation()

class Compilation extends Tapable {
    constructor(compiler) {
        super();
        ...
        //初始化配置
        this.compiler = compiler;
		this.resolverFactory = compiler.resolverFactory;
		this.inputFileSystem = compiler.inputFileSystem;
		this.requestShortener = compiler.requestShortener;
        
        //初始化模版
        this.mainTemplate = new MainTemplate(this.outputOptions);
		this.chunkTemplate = new ChunkTemplate(this.outputOptions);
		this.hotUpdateChunkTemplate = new HotUpdateChunkTemplate(
			this.outputOptions
		);
    }
}
class MainTemplate extends Tapable {
	this.hooks.requireExtensions.tap("MainTemplate", (source, chunk, hash) => {
			const buf = [];
			const chunkMaps = chunk.getChunkMaps();
			// Check if there are non initial chunks which need to be imported using require-ensure
			if (Object.keys(chunkMaps.hash).length) {
				buf.push("// This file contains only the entry chunk.");
				buf.push("// The chunk loading function for additional chunks");
				buf.push(`${this.requireFn}.e = function requireEnsure(chunkId) {`);
				buf.push(Template.indent("var promises = [];"));
				buf.push(
					Template.indent(
						this.hooks.requireEnsure.call("", chunk, hash, "chunkId")
					)
				);
				buf.push(Template.indent("return Promise.all(promises);"));
				buf.push("};");
			} else if (
				chunk.hasModuleInGraph(m =>
					m.blocks.some(b => b.chunkGroup && b.chunkGroup.chunks.length > 0)
				)
			) {
				// There async blocks in the graph, so we need to add an empty requireEnsure
				// function anyway. This can happen with multiple entrypoints.
				buf.push("// The chunk loading function for additional chunks");
				buf.push("// Since all referenced chunks are already included");
				buf.push("// in this file, this function is empty here.");
				buf.push(`${this.requireFn}.e = function requireEnsure() {`);
				buf.push(Template.indent("return Promise.resolve();"));
				buf.push("};");
			}
			buf.push("");
			buf.push("// expose the modules object (__webpack_modules__)");
			buf.push(`${this.requireFn}.m = modules;`);

			buf.push("");
			buf.push("// expose the module cache");
			buf.push(`${this.requireFn}.c = installedModules;`);

			buf.push("");
			buf.push("// define getter function for harmony exports");
			buf.push(`${this.requireFn}.d = function(exports, name, getter) {`);
			buf.push(
				Template.indent([
					`if(!${this.requireFn}.o(exports, name)) {`,
					Template.indent([
						"Object.defineProperty(exports, name, { enumerable: true, get: getter });"
					]),
					"}"
				])
			);
			buf.push("};");

			buf.push("");
			buf.push("// define __esModule on exports");
			buf.push(`${this.requireFn}.r = function(exports) {`);
			buf.push(
				Template.indent([
					"if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {",
					Template.indent([
						"Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });"
					]),
					"}",
					"Object.defineProperty(exports, '__esModule', { value: true });"
				])
			);
			buf.push("};");

			buf.push("");
			buf.push("// create a fake namespace object");
			buf.push("// mode & 1: value is a module id, require it");
			buf.push("// mode & 2: merge all properties of value into the ns");
			buf.push("// mode & 4: return value when already ns object");
			buf.push("// mode & 8|1: behave like require");
			buf.push(`${this.requireFn}.t = function(value, mode) {`);
			buf.push(
				Template.indent([
					`if(mode & 1) value = ${this.requireFn}(value);`,
					`if(mode & 8) return value;`,
					"if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;",
					"var ns = Object.create(null);",
					`${this.requireFn}.r(ns);`,
					"Object.defineProperty(ns, 'default', { enumerable: true, value: value });",
					"if(mode & 2 && typeof value != 'string') for(var key in value) " +
						`${this.requireFn}.d(ns, key, function(key) { ` +
						"return value[key]; " +
						"}.bind(null, key));",
					"return ns;"
				])
			);
			buf.push("};");

			buf.push("");
			buf.push(
				"// getDefaultExport function for compatibility with non-harmony modules"
			);
			buf.push(this.requireFn + ".n = function(module) {");
			buf.push(
				Template.indent([
					"var getter = module && module.__esModule ?",
					Template.indent([
						"function getDefault() { return module['default']; } :",
						"function getModuleExports() { return module; };"
					]),
					`${this.requireFn}.d(getter, 'a', getter);`,
					"return getter;"
				])
			);
			buf.push("};");

			buf.push("");
			buf.push("// Object.prototype.hasOwnProperty.call");
			buf.push(
				`${
					this.requireFn
				}.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };`
			);

			const publicPath = this.getPublicPath({
				hash: hash
			});
			buf.push("");
			buf.push("// __webpack_public_path__");
			buf.push(`${this.requireFn}.p = ${JSON.stringify(publicPath)};`);
			return Template.asString(buf);
		});
}

make开始构建

 //开始构建模块对象
this.hooks.make.callAsync(compilation, err =>{
                
})
//SingleEntryPlugin 监听make
class SingleEntryPlugin {
    apply(compiler) {
        compiler.hooks.make.tapAsync(
			"SingleEntryPlugin",
			(compilation, callback) => {
				const { entry, name, context } = this;
                
                //创建依赖
				const dep = SingleEntryPlugin.createDependency(entry, name);
				//添加入口文件
				compilation.addEntry(context, dep, name, callback);
			}
		);
    }
}
//Compilation.js
class Compilation extends Tapable {
    addEntry(context, entry, name, callback) {
        ...
        	this._addModuleChain(
			context,
			entry,
			module => {
				this.entries.push(module);
			},
			(err, module) => {
			...
			}
		);
    }
    _addModuleChain(context, dependency, onModule, callback) {
        ...
        //获取模块工厂
        const moduleFactory = this.dependencyFactories.get(Dep);
        
        this.semaphore.acquire(() => {
            ...
            //创建模块
            moduleFactory.create(
				{
					contextInfo: {
						issuer: "",
						compiler: this.compiler.name
					},
					context: context,
					dependencies: [dependency]
				},...)
        }
    }
}
class NormalModuleFactory extends Tapable {
    ...
    create(data, callback) {
        ...
        this.buildModule(module, false, null, null, err => {
        }
    }
    buildModule(module, optional, origin, dependencies, thisCallback) {
        ...
        //开始编译
        module.build(
			this.options,
			this,
			this.resolverFactory.get("normal", module.resolveOptions),
			this.inputFileSystem,...)
    }
}

//NodmalModule
doBuild(options, compilation, resolver, fs, callback) {
		const loaderContext = this.createLoaderContext(
			resolver,
			options,
			compilation,
			fs
		);
        ...
        //开始运行loader
		runLoaders(
			{
				resource: this.resource,
				loaders: this.loaders,
				context: loaderContext,
				readResource: fs.readFile.bind(fs)
			},
			(err, result) => {
			
		    );
		 )	}

总结

初始化阶段

事件名 解释 代码位置
读取命令行参数 从命令行中读取用户输入的参数 require('./convert-argv')(argv)
实例化Compiler 1.用上一步得到的参数初始化Compiler实例2.Compiler负责文件监听和启动编译3.Compiler实例中包含了完整的Webpack配置,全局只有一个Compiler实例 compiler = webpack(options)
加载插件 1.依次调用插件的apply方法,让插件可以监听后续的所有事件节点,同时给插件传入Compiler实例的引用,以方便插件通过compiler调用webpack提供的API plugin.apply(compiler)
处理入口 读取配置的Entry,为每个Entry实例化一个对应的EntryPlugin,为后面该Entry的递归解析工作做准备 new EntryOptionsPlugin() new SingleEntryPlugin(context,item,name) compiler.hooks.make.tapAsync

编译阶段

事件名 解释 代码位置
run 启动一次新的编译 this.hooks.run.callAsync
compile 该事件是为了告诉插件一次新的编译将要启动,同时会给插件传入compiler对象 compiler(callback)
compilation 当webpack以开发模式运行时,每当监测到文件变化,一次新的,Compilation将被创建一个Compilation对象包含了当前的模块资源,编译生成资源,变化的文件,Compilation对象也提供了很多事件回调供插件扩展 newCompilation(params)
make 一个新的Compilation创建完毕开始编译 this.hooks.make.callAsync
addEntry 即将从Entry开始读取文件 compilation.addEntry this._addModuleChain
moduleFactory 创建模块工厂 const moduleFactory = this.dependencyFactories.get(Dep)
create 开始创建模块 factory(result,(err,module) this.hooks.resolver.tap("NormalModule")
resolveRequestArray 解析loader路径 resolveRequestArray
resolve 解析资源文件路径 resolve
userRequest 得到包括loader在内的资源文件的绝对路径用!拼起来的字符串 userRequest
ruleSet.exec 它可以根据模块路径名,匹配出模块所需的loader this.ruleSet.exec
_run 它可以根据模块路径名,匹配出模块所需的loader _run
loaders 得到所有的loader数组 results[0].concat(loaders,results[1],results[2])
getParser 获取AST解析器 this.getParser(type,setting.parser)
buildModule 开始编译模块 thislbuildModule(module) buildModule(module,optional,origin,dependencies,thisCallback)
build 开始编译 build
doBuild 开始编译 doBuild
loader 使用loader进行转换 runLoaders
iteratePitchingLoaders 开始递归执行pitchloader iteratePitchingLoaders
loadLoader 加载loader loadLoader
runSyncOrAsync 执行loader runSyncOrAsync
processResource 开始处理资源 processResource options.readResource iterateNormalLoaders
createSource 创建源码对象 this.createSource
parse 使用parser转换抽象语法树 this.parser.parse
parse 继续抽象语法树 parse(source,initialState)
acorn.parse 继续语法树 acorn.parse(code,parserOptions)
ImportDependency 遍历添加依赖 parser.state.module.addDependency
succeedModule 生成语法树后就表示一个模块编译完成 this.hooks.successdModule.call(module)
processModuleDependencies 递归编译依赖模块 this.processModuleDependencies
make后 结束make this.hooks.make.callAsync
finish 编译完成 compilation.finishi()

结束阶段

事件名 解释 代码位置
seal 封装 compilation.seal
addChunk 生成资源 addChunk(name)
createChunkAssets 创建资源 this.createChunkAssets()
getRenderManifest 获取要渲染的描述文件 getRenderManifest(options)
render 渲染源码 source = fileManifest.render()
afterCompile 编译结束 this.hooks.afterCompile
shouldemit 所有属性输出的文件已经生成好,询问插件哪些文件需要输出,哪些不需要 this.hooks.shouldEmit
emit 确定后要输出哪些文件后,执行文件输出,可以在这里获取和修改输出内容 this.emitAssets(compilation,this.hooks.emit.callAsync) const emitFiles = err this.outputFileSystem.writeFile
this.emitRecords 写入记录 this.emitRecords
done 全部完成 this.hooks.done.callAsync
原文地址:https://www.cnblogs.com/pluslius/p/10271537.html