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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM