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 | 
webpack执行过程的更多相关文章
- ASP.NET Web API 过滤器创建、执行过程(二)
		
ASP.NET Web API 过滤器创建.执行过程(二) 前言 前面一篇中讲解了过滤器执行之前的创建,通过实现IFilterProvider注册到当前的HttpConfiguration里的服务容器 ...
 - ASP.NET Web API 过滤器创建、执行过程(一)
		
ASP.NET Web API 过滤器创建.执行过程(一) 前言 在上一篇中我们讲到控制器的执行过程系列,这个系列要搁置一段时间了,因为在控制器执行的过程中包含的信息都是要单独的用一个系列来描述的,就 ...
 - ASP.NET Web API 控制器执行过程(一)
		
ASP.NET Web API 控制器执行过程(一) 前言 前面两篇讲解了控制器的创建过程,只是从框架源码的角度去简单的了解,在控制器创建过后所执行的过程也是尤为重要的,本篇就来简单的说明一下控制器在 ...
 - Struts2拦截器的执行过程浅析
		
在学习Struts2的过程中对拦截器和动作类的执行过程一度陷入误区,特别读了一下Struts2的源码,将自己的收获分享给正在困惑的童鞋... 开始先上图: 从Struts2的图可以看出当浏览器发出请求 ...
 - 通过源码了解ASP.NET MVC 几种Filter的执行过程
		
一.前言 之前也阅读过MVC的源码,并了解过各个模块的运行原理和执行过程,但都没有形成文章(所以也忘得特别快),总感觉分析源码是大神的工作,而且很多人觉得平时根本不需要知道这些,会用就行了.其实阅读源 ...
 - Hadoop MapReduce执行过程详解(带hadoop例子)
		
https://my.oschina.net/itblog/blog/275294 摘要: 本文通过一个例子,详细介绍Hadoop 的 MapReduce过程. 分析MapReduce执行过程 Map ...
 - 高程(4):执行环境、作用域、上下文执行过程、垃圾收集、try...catch...
		
高程三 4.2.4.3 一.执行环境 1.全局执行环境是最外层的执行环境. 2.每个函数都有自己的执行环境,执行函数时,函数环境就会被推入一个当前环境栈中,执行完毕,栈将其环境弹出,把控制器返回给之前 ...
 - saltstack命令执行过程
		
saltstack命令执行过程 具体步骤如下 Salt stack的Master与Minion之间通过ZeroMq进行消息传递,使用了ZeroMq的发布-订阅模式,连接方式包括tcp,ipc salt ...
 - Web APi之过滤器执行过程原理解析【二】(十一)
		
前言 上一节我们详细讲解了过滤器的创建过程以及粗略的介绍了五种过滤器,用此五种过滤器对实现对执行Action方法各个时期的拦截非常重要.这一节我们简单将讲述在Action方法上.控制器上.全局上以及授 ...
 
随机推荐
- tf入门-tf.nn.conv2d是怎样实现卷积的?
			
转自:https://blog.csdn.net/mao_xiao_feng/article/details/78004522 实验环境:tensorflow版本1.2.0,python2.7 介绍 ...
 - python unittest框架理解与总结(二)
			
unittest基本原理: ♦整个平台的搭建使用的是python的unittest测试框架,这里简单介绍下unittest模块的简单应用. ♦unittest是python的标准测试库,相比于其他测试 ...
 - vue2.0-router的绑定
			
1.首先在‘components’文件夹里面创建想要切换的路由. 2.在index.js文件里面引入地址并进行路由的注入. 3.使用<router-link to="path地址&qu ...
 - react onclick传递参数
			
最近在做react项目的时候,被一个小问题绊了一脚,记录一下 onClick 传入参数 onClick={e=>{this.Mallclose(e,index)} onClick={this.M ...
 - FlowPortal-BPM——管理员、功能的权限设置
			
一.管理员设置 管理工具→安全组→安全组名称→管理授权→[添加管理人员]→[设置管理人员权限] 二.访问功能权限设置 (1)模块访问权限 (2)访问控制→[在需要的文件夹下]新建子资源→[资源名称]. ...
 - linux如何安装和启动mongdb
			
1.下载安装包 下载地址: https://www.mongodb.com/dr/fastdl.mongodb.org/linux/mongodb-linux-x86_64-4.0.9.tgz/dow ...
 - 了解到的Web攻击技术
			
(1)XSS(Cross-Site Scripting,跨站脚本攻击): 指通过存在安全漏洞的Web网站注册用户的浏览器内运行非法的HTML标签或者JavaScript进行的一种攻击. (2)SQL注 ...
 - 【Ubuntu】执行定时任务(cron)
			
1.打开定时任务配置文件 crontab -e 2.编写定时任务时间 命令和脚本例如: /3 * * * * /soft/config/test.sh 前5个字段为时间,后面的一个为命令 前5个含义为 ...
 - PHP 对目录下所有TXT进行遍历 并正则进行处理 preg_replace
			
<?php set_time_limit(); //遍历 指定目录下的所有 文件/ $pattern是要匹配的 文件规则 function file_list($dir,$pattern=&qu ...
 - split使用和特殊使用(包括截取第一个字符后的数据)
			
javaScript中关于split()的使用 1.一般使用对一个字符串使用split(),返回一个数组 例子: var testArr = "1,2,3,4,5": var ...