带你学习webpack-webpack源码解析一

2022-10-28 17:24:04 浏览数 (1)

先上一张流程图,小伙伴可以跟着这个流程图结合代码往下走:

再来一张大神画的图:

开始

代码语言:javascript复制
git clone https://github.com/webpack/webpack.git
yarn
yarn setup

打包

代码语言:javascript复制
node ./bin/webpack.js

bin/webpack.js

代码语言:javascript复制
//判断有没有安装webpack-cli,没有话的就提示是否需要安装
const question = `Do you want to install 'webpack-cli' (yes/no): `;
//确定有webpack-cli之后开始执行
require(path.resolve(path.dirname(pkgPath), pkg.bin[cli.binName]));

webpack-cli/bin/cli.js

代码语言:javascript复制
//创建compiler编译引擎
let compiler;
			try {
				compiler = webpack(options);
			} catch (err) {
			}
   compiler.run((err, stats) => {}

整个made过程

lib/webpack.js

代码语言:javascript复制
const webpack = (options, callback) => {
	let compiler;
		compiler = createCompiler(options);
		watch = options.watch;
		watchOptions = options.watchOptions || {};
	return compiler;
};
//创建编译器
const createCompiler = options => {
    //获取默认的webpack参数
	options = new WebpackOptionsDefaulter().process(options);
	const compiler = new Compiler(options.context);
	compiler.options = options;
    //配置全局api插件比如(fs文件api、infrastructureLogger日志log、)
	new NodeEnvironmentPlugin({
		infrastructureLogging: options.infrastructureLogging
	}).apply(compiler);
    // 执行我们在配置文件中配置的所有插件
	if (Array.isArray(options.plugins)) {
		for (const plugin of options.plugins) {
			if (typeof plugin === "function") {
				plugin.call(compiler, compiler);
			} else {
				plugin.apply(compiler);
			}
		}
	}
    //开启默认的所有插件(比较重要)
	compiler.options = new WebpackOptionsApply().process(options, compiler);
	return compiler;
};

lib/WebpackOptionsApply.js

代码语言:javascript复制
    //设置默认的一些插件
	process(options, compiler) {
        //入口文件插件注册
        new EntryOptionPlugin().apply(compiler);
        //调用入口文件插件注册钩子函数
		compiler.hooks.entryOption.call(options.context, options.entry);
    }

lib/EntryOptionPlugin.js

代码语言:javascript复制
module.exports = class EntryOptionPlugin {
	/**
	 * @param {Compiler} compiler the compiler instance one is tapping into
	 * @returns {void}
	 */
	apply(compiler) {
        //注册入口文件插件注册钩子函数
		compiler.hooks.entryOption.tap("EntryOptionPlugin", (context, entry) => {
            
			const applyEntryPlugins = (entry, name) => {
				if (typeof entry === "string") {
                    //调用入口插件
					new EntryPlugin(context, entry, name).apply(compiler);
				} else if (Array.isArray(entry)) {
					for (const item of entry) {
						applyEntryPlugins(item, name);
					}
				}
			};
            //options的entry支持string||array比如:(entry: "src/main.js")||(entry: ["pollyfill.js","src/main.js"])
			if (typeof entry === "string" || Array.isArray(entry)) {
				applyEntryPlugins(entry, "main");
             //options的entry支持object类型比如:(entry:{main: "src/main.js"})
			} else if (typeof entry === "object") {
				for (const name of Object.keys(entry)) {
					applyEntryPlugins(entry[name], name);
				}
                //options的entry支持function类型比如:(entry:()=>"src/main.js")})
			} else if (typeof entry === "function") {
				new DynamicEntryPlugin(context, entry).apply(compiler);
			}
			return true;
		});
	}
};

EntryPlugin.js

代码语言:javascript复制
apply(compiler) {
        //注册compiler的make钩子函数(后面会用到)
		compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => {
			const { entry, name, context } = this;

			const dep = EntryPlugin.createDependency(entry, name);
			compilation.addEntry(context, dep, name, err => {
				callback(err);
			});
		});
	}

lib/Compiler.js:

代码语言:javascript复制
//开始编译
run(callback) {
        //整个打包编译执行完毕回调
		const finalCallback = (err, stats) => {
			if (logger) logger.time("beginIdle");
			this.cache.beginIdle();
			if (logger) logger.timeEnd("beginIdle");
			this.running = false;
			if (err) {
				this.hooks.failed.call(err);
			}
			if (callback !== undefined) callback(err, stats);
			this.hooks.afterDone.call(stats);
		};
        //编译完毕回调
		const onCompiled = (err, compilation) => {
			if (err) return finalCallback(err);
				logger = compilation.getLogger("webpack.Compiler");
				logger.time("emitAssets");
                //执行打包操作
				this.emitAssets(compilation, err => {
					logger.timeEnd("emitAssets");
					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();
						logger.time("done hook");
						this.hooks.done.callAsync(stats, err => {
							logger.timeEnd("done hook");
							if (err) return finalCallback(err);

							this.hooks.additionalPass.callAsync(err => {
								if (err) return finalCallback(err);
								this.compile(onCompiled);
							});
						});
						return;
					}
			});
		};

		this.cache.endIdle(err => {
			if (err) return finalCallback(err);
            //调用编译打包前钩子函数
			this.hooks.beforeRun.callAsync(this, err => {
				if (err) return finalCallback(err);
                //调用运行中钩子函数
				this.hooks.run.callAsync(this, err => {
					if (err) return finalCallback(err);
                    //读取Records文件
					this.readRecords(err => {
						if (err) return finalCallback(err);
                        //开始编译
						this.compile(onCompiled);
					});
				});
			});
		});
}

//编译
compile(callback) {
            //创建编译器的参数(重要)
    		const params = this.newCompilationParams();
            //创建一个编译器
            const compilation = this.newCompilation(params);
                //执行前面## EntryPlugin.js注册的make钩子函数并把编译器传递给插件
    			this.hooks.make.callAsync(compilation, err => {
}

//创建编译器的参数
newCompilationParams() {
		const params = {
			normalModuleFactory: this.createNormalModuleFactory(), //创建一个模块工厂(主要用于模块的构建,也就是我们说的js文件等等)
			contextModuleFactory: this.createContextModuleFactory() //创建一个上下文工厂(主要用于整个工程的文件路径加载)
		};
		return params;
	}
//创建模块工厂
createNormalModuleFactory() {
		const normalModuleFactory = new NormalModuleFactory({
			context: this.options.context, 
			fs: this.inputFileSystem,
			resolverFactory: this.resolverFactory,
			options: this.options.module || {}
		});
		this.hooks.normalModuleFactory.call(normalModuleFactory);
		return normalModuleFactory;
}
    

前面注册的钩子函数 lib/EntryPlugin.js

代码语言:javascript复制
compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => {
            //执行编译器的addEntry方法加载入口文件
			compilation.addEntry(context, dep, name, err => {
				callback(err);
		});

lib/Compilation.js

代码语言:javascript复制
//加载入口文件
addEntry(context, entry, name, callback) {
		this.addModuleChain(context, entry, (err, module) => {
			if (err) {
				this.hooks.failedEntry.call(entry, name, err);
				return callback(err);
			}
			this.hooks.succeedEntry.call(entry, name, module);
			return callback(null, module);
		});
	}
    
addModuleChain(context, dependency, callback) {
    	this.handleModuleCreation(
			{
				factory: moduleFactory,
				dependencies: [dependency],
				originModule: null,
				context
			},
}

handleModuleCreation(
		{ factory, dependencies, originModule, context },
		callback
	) { 
            //遍历工程中所有的模块并且创建模块
        	this.factorizeModule(
                //创建模块
        	    this.buildModule(module, err => {
}

//开始创建模块
_buildModule(module, callback) {
    //开始执行模块的构建方法
    	module.build(
					this.options,
					this,
					this.resolverFactory.get("normal", module.resolveOptions),
					this.inputFileSystem,
					err => {
}
 //遍历工程中所有的模块
_factorizeModule(
		{ currentProfile, factory, dependencies, originModule, context },
		callback
	) {
		if (currentProfile !== undefined) {
			currentProfile.markFactoryStart();
		}
        //调用前面创建的NormalModuleFactory的create方法
		factory.create(
			{

lib/NormalModuleFactory.js

代码语言:javascript复制
//构造方法
constructor({ context, fs, resolverFactory, options }) {
        //注册开始构建模块钩子函数
    		this.hooks.factorize.tapAsync(
			{
				name: "NormalModuleFactory",
				stage: 100
			},
			(resolveData, callback) => {
                //触发开始解析模块钩子函数(注册“解析模块钩子函数”也在NormalModuleFactory.js里面,主要就是获取我们配置的“loader”,重要)
				this.hooks.resolve.callAsync(resolveData, (err, result) => {
					//模块解析完毕后
					this.hooks.afterResolve.callAsync(resolveData, (err, result) => {
							if (err) return callback(err);

						if (typeof result === "object")
							throw new Error(deprecationChangedHookMessage("afterResolve"));

						// Ignored
						if (result === false) return callback();

						const createData = resolveData.createData;

						let createdModule = this.hooks.createModule.call(createData);
						if (!createdModule) {
							if (!resolveData.request) {
								return callback(new Error("Empty dependency (no request)"));
							}

							createdModule = new NormalModule(createData);
						}
                        //创建一个新的模块(包含模块的各种信息,比如模块的路径、能被哪些loader加载器加载等等)
						createdModule = this.hooks.module.call(
							createdModule,
							createData,
							resolveData
						);

						return callback(null, createdModule);
					});
				});
			}
		);
}
//创建工程的所有模块
create(data, callback) {
        //获取所有的模块
		const dependencies = /** @type {ModuleDependency[]} */ (data.dependencies);
		if (this.unsafeCache) {
			const cacheEntry = dependencyCache.get(dependencies[0]);
			if (cacheEntry) return callback(null, cacheEntry);
		}
            //触发开始构建模块钩子函数(在构造函数中注册)
			this.hooks.factorize.callAsync(resolveData, (err, module) => {
				
			});
		});
	}

lib/NormalModule.js

代码语言:javascript复制
    //被上面的Compilation.js中的_buildModule方法中的module.build调用
	build(options, compilation, resolver, fs, callback) {
        	return this.doBuild(options, compilation, resolver, fs, err => {

            }
    }
    //开始构建
    doBuild(options, compilation, resolver, fs, callback) {
        //处理loader加载过后的源码
        const processResult = (err, result) => {
            this._source = this.createSource(
				options.context,
				this.binary ? asBuffer(source) : asString(source),
				sourceMap,
				compilation.compiler.root
			);
        }
        //运行我们在配置文件中配置的所有loader,比如babel-loader,然后返回处理完毕后的源码
        runLoaders(
			{
				resource: this.resource,
				loaders: this.loaders,
				context: loaderContext,
				readResource: fs.readFile.bind(fs)
			},
			(err, result) => {
				if (!result) {
					processResult(
						err || new Error("No result from loader-runner processing"),
						null
					);
				}
                processResult(err, result.result);
			}
		);
    }
    //创建源码
    createSource(context, content, sourceMap, associatedObjectForCache) {
		if (Buffer.isBuffer(content)) {
            //获取构建过后的源码
			return new RawSource(content);
		}
	}

整个made过程结束

seal封装过程开始(1、优化我们编译过后的代码(代码混淆、分包优化等等)、2、生成我们最后需要的打包过后的文件)

Compiler.js文件

代码语言:javascript复制
compile(callback) {
    	logger.time("seal compilation");
        //开始封装并打包文件
		compilation.seal(err => {
                            
        }
}

lib/Compilation.js(重要,了解webpack的插件)

代码语言:javascript复制
/**
	 * @param {Callback} callback signals when the call finishes
	 * @returns {void}
	 */
	seal(callback) {
		const chunkGraph = new ChunkGraph(this.moduleGraph);
		this.chunkGraph = chunkGraph;

		for (const module of this.modules) {
			ChunkGraph.setChunkGraphForModule(module, chunkGraph);
		}

		this.hooks.seal.call();

		while (this.hooks.optimizeDependencies.call(this.modules)) {
			/* empty */
		}
		this.hooks.afterOptimizeDependencies.call(this.modules);

		this.hooks.beforeChunks.call();
		for (const [name, dependencies] of this.entryDependencies) {
			const chunk = this.addChunk(name);
			chunk.name = name;
			const entrypoint = new Entrypoint(name);
			entrypoint.setRuntimeChunk(chunk);
			this.namedChunkGroups.set(name, entrypoint);
			this.entrypoints.set(name, entrypoint);
			this.chunkGroups.push(entrypoint);
			connectChunkGroupAndChunk(entrypoint, chunk);

			for (const dep of dependencies) {
				entrypoint.addOrigin(null, { name }, dep.request);

				const module = this.moduleGraph.getModule(dep);
				if (module) {
					chunkGraph.connectChunkAndModule(chunk, module);
					chunkGraph.connectChunkAndEntryModule(chunk, module, entrypoint);
					this.assignDepth(module);
				}
			}
		}
		buildChunkGraph(
			this,
			/** @type {Entrypoint[]} */ (this.chunkGroups.slice())
		);
		this.hooks.afterChunks.call(this.chunks);

		this.hooks.optimize.call();

		while (this.hooks.optimizeModules.call(this.modules)) {
			/* empty */
		}
		this.hooks.afterOptimizeModules.call(this.modules);

		while (this.hooks.optimizeChunks.call(this.chunks, this.chunkGroups)) {
			/* empty */
		}
		this.hooks.afterOptimizeChunks.call(this.chunks, this.chunkGroups);

		this.hooks.optimizeTree.callAsync(this.chunks, this.modules, err => {
			if (err) {
				return callback(
					makeWebpackError(err, "Compilation.hooks.optimizeTree")
				);
			}

			this.hooks.afterOptimizeTree.call(this.chunks, this.modules);

			while (this.hooks.optimizeChunkModules.call(this.chunks, this.modules)) {
				/* empty */
			}
			this.hooks.afterOptimizeChunkModules.call(this.chunks, this.modules);

			const shouldRecord = this.hooks.shouldRecord.call() !== false;

			this.hooks.reviveModules.call(this.modules, this.records);
			this.hooks.beforeModuleIds.call(this.modules);
			this.hooks.moduleIds.call(this.modules);
			this.hooks.optimizeModuleIds.call(this.modules);
			this.hooks.afterOptimizeModuleIds.call(this.modules);

			this.hooks.reviveChunks.call(this.chunks, this.records);
			this.hooks.beforeChunkIds.call(this.chunks);
			this.hooks.chunkIds.call(this.chunks);
			this.hooks.optimizeChunkIds.call(this.chunks);
			this.hooks.afterOptimizeChunkIds.call(this.chunks);

			this.sortItemsWithChunkIds();

			if (shouldRecord) {
				this.hooks.recordModules.call(this.modules, this.records);
				this.hooks.recordChunks.call(this.chunks, this.records);
			}

			this.hooks.optimizeCodeGeneration.call(this.modules);

			this.hooks.beforeModuleHash.call();
			this.createModuleHashes();
			this.hooks.afterModuleHash.call();

			this.hooks.beforeCodeGeneration.call();
			this.codeGenerationResults = this.codeGeneration();
			this.hooks.afterCodeGeneration.call();

			this.hooks.beforeRuntimeRequirements.call();
			this.processRuntimeRequirements(this.entrypoints.values());
			this.hooks.afterRuntimeRequirements.call();

			this.hooks.beforeHash.call();
			this.createHash();
			this.hooks.afterHash.call();

			if (shouldRecord) {
				this.hooks.recordHash.call(this.records);
			}

			this.clearAssets();

			this.hooks.beforeModuleAssets.call();
			this.createModuleAssets();

			const cont = () => {
				this.hooks.additionalChunkAssets.call(this.chunks);
				this.summarizeDependencies();
				if (shouldRecord) {
					this.hooks.record.call(this, this.records);
				}

				this.hooks.additionalAssets.callAsync(err => {
					if (err) {
						return callback(
							makeWebpackError(err, "Compilation.hooks.additionalAssets")
						);
					}
					this.hooks.optimizeChunkAssets.callAsync(this.chunks, err => {
						if (err) {
							return callback(
								makeWebpackError(err, "Compilation.hooks.optimizeChunkAssets")
							);
						}
						this.hooks.afterOptimizeChunkAssets.call(this.chunks);
						this.hooks.optimizeAssets.callAsync(this.assets, err => {
							if (err) {
								return callback(
									makeWebpackError(err, "Compilation.hooks.optimizeAssets")
								);
							}
							this.hooks.afterOptimizeAssets.call(this.assets);
							if (this.hooks.needAdditionalSeal.call()) {
								this.unseal();
								return this.seal(callback);
							}
							this.hooks.finishAssets.callAsync(this.assets, err => {
								if (err) {
									return callback(
										makeWebpackError(err, "Compilation.hooks.finishAssets")
									);
								}
								this.hooks.afterFinishAssets.call(this.assets);
								this.cache.storeBuildDependencies(
									this.buildDependencies,
									err => {
										if (err) {
											return callback(err);
										}
										return this.hooks.afterSeal.callAsync(callback);
									}
								);
							});
						});
					});
				});
			};

			if (this.hooks.shouldGenerateChunkAssets.call() !== false) {
				this.hooks.beforeChunkAssets.call();
				this.createChunkAssets(err => {
					if (err) {
						return callback(err);
					}
					cont();
				});
			} else {
				cont();
			}
		});
	}

lib/Compilation.js

代码语言:javascript复制
	seal(callback) {
		//根据每个模块创建asset文件
		this.createModuleAssets();
		//提交所有的asset
		this.emitAsset(
						fileName,
						module.buildInfo.assets[assetName],
						assetsInfo ? assetsInfo.get(assetName) : undefined
					);
	}
	//提交asset
	emitAsset(file, source, assetInfo = {}) {
		//把所有的asset存在compilation对象的assets属性中
		this.assets[file] = source;
		this.assetsInfo.set(file, assetInfo);
	}

整个seal过程结束

lib/Compiler.js(emitAssets生成打包过后的文件过程开始)

代码语言:javascript复制
compile(callback) {
		
	compilation.seal(err => {
		//整个seal过程执行完毕后开始执行callback方法
		return callback(null, compilation);
	}
	run(callback) {
		//seal过程执行完毕后执行onCompiled方法
		const onCompiled = (err, compilation) => {
			//如果有错误的话,就直接结束
			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;
			}

			process.nextTick(() => {
				//开始提交assets
				this.emitAssets(compilation, err => {
						this.hooks.done.callAsync(stats, err => {
							//webpack整个过程结束
							return finalCallback(null, stats);
				});
			});
		};

		this.readRecords(err => {
			//整个seal过程执行完毕后开始执行onCompiled方法
			this.compile(onCompiled);
		});
	//提交并生成打包出来的所有文件
	emitAssets(compilation, callback) {
		let outputPath;
		//开始提交(生成打包过后的文件)
		this.hooks.emit.callAsync(compilation, err => {
			//获取输出文件的目标地址
			outputPath = compilation.getPath(this.outputPath, {});
			//生成目标文件夹
			mkdirp(this.outputFileSystem, outputPath, emitFiles);
		});
	}
	//输入所有的文件到指定文件夹
	const emitFiles = err => {
			asyncLib.forEachLimit(
				//获取所有的文件
				compilation.getAssets(),
				15,
				//列出打包过后的文件
				({ name: file, source, info }, callback) => {
					if (targetFile.match(//|\/)) {
						const fs = this.outputFileSystem;
						const dir = dirname(fs, join(fs, outputPath, targetFile));
						mkdirp(fs, dir, writeOut);
					} else {
						//输出文件
						writeOut();
					}
				}
	}

}

0 人点赞