webpack源码分析(七)

compiler.hooks.initialize.call();

createComplier 就剩下这一个函数了,这个initialize默认没有事件,所以直接返回,那我们回顾一下每个函数都做了什么。

const createCompiler = rawOptions => {
	const options = getNormalizedWebpackOptions(rawOptions);
	applyWebpackOptionsBaseDefaults(options);
	const compiler = new Compiler(options.context, options);
	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);
			}
		}
	}
	applyWebpackOptionsDefaults(options);
	compiler.hooks.environment.call();
	compiler.hooks.afterEnvironment.call();
	new WebpackOptionsApply().process(options, compiler);
	compiler.hooks.initialize.call();
	return compiler;
};

const options = getNormalizedWebpackOptions(rawOptions);

格式化配置参数

applyWebpackOptionsBaseDefaults(options);

配置context和日志

const compiler = new Compiler(options.context, options);

创建compiler对象

new NodeEnvironmentPlugin({infrastructureLogging: options.infrastructureLogging}).apply(compiler);

初始化流文件和日志的输出输入,没有用默认的fs,采用时间片+cache优化

然后注册插件(代码不扔了,就在上面)

applyWebpackOptionsDefaults(options); 如果我们不给options赋值,那么webpack给他赋默认值

触发environment afterEnvironment 两个钩子

new WebpackOptionsApply().process(options, compiler);

注册默认事件,触发三个钩子(核心注册了一个EntryOption的钩子)

触发initialize钩子

createCompiler终于讲完了。我们再讲下一话题

compiler.run(callback)

run(callback) {
	//执行run方法
	if (this.running) {
		return callback(new ConcurrentCompilationError());
	}

	let logger;

	const finalCallback = (err, stats) => {
		if (logger) logger.time("beginIdle");
		this.idle = true;
		this.cache.beginIdle();
		this.idle = true;
		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 startTime = Date.now();

	this.running = true;

	const onCompiled = (err, compilation) => {
		if (err) return finalCallback(err);

		if (this.hooks.shouldEmit.call(compilation) === false) {
			compilation.startTime = startTime;
			compilation.endTime = Date.now();
			const stats = new Stats(compilation);
			this.hooks.done.callAsync(stats, err => {
				if (err) return finalCallback(err);
				return finalCallback(null, stats);
			});
			return;
		}

		process.nextTick(() => {
			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;

					compilation.startTime = startTime;
					compilation.endTime = Date.now();
					logger.time("done hook");
					const stats = new Stats(compilation);
					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;
				}

				logger.time("emitRecords");
				this.emitRecords(err => {
					logger.timeEnd("emitRecords");
					if (err) return finalCallback(err);

					compilation.startTime = startTime;
					compilation.endTime = Date.now();
					logger.time("done hook");
					const stats = new Stats(compilation);
					this.hooks.done.callAsync(stats, err => {
						logger.timeEnd("done hook");
						if (err) return finalCallback(err);
						this.cache.storeBuildDependencies(
							compilation.buildDependencies,
							err => {
								if (err) return finalCallback(err);
								return finalCallback(null, stats);
							}
						);
					});
				});
			});
		});
	};

	const run = () => {
		// before nodeenviorment
		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);

					this.compile(onCompiled);
				});
			});
		});
	};

	if (this.idle) {
		this.cache.endIdle(err => {
			if (err) return finalCallback(err);

			this.idle = false;
			run();
		});
	} else {
		run();
	}
}

看着代码好像不少,实际上只走内部的run方法

	const run = () => {
		// before nodeenviorment
		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);

					this.compile(onCompiled);
				});
			});
		});
	};

this.hooks.beforeRun.callAsync

触发了一个钩子

还记得NodeEnvironment吗

if (compiler.inputFileSystem === inputFileSystem) { compiler.fsStartTime = Date.now();inputFileSystem.purge(); }

没错就是初始化了一下inputFileSystem

接着看回掉函数

this.hooks.run.callAsync

触发了一个钩子,没有绑定事件

this.readRecords(callback)

不想写没用的代码了,直接写他做了什么吧

this.records = {}; callback();

看起来非常简单,给compiler赋值records,执行回掉函数

this.compile(callback);

走到了Compiler.compile(callback)

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);
		const logger = compilation.getLogger("webpack.Compiler");
		logger.time("make hook");
		this.hooks.make.callAsync(compilation, err => {
			logger.timeEnd("make hook");
			if (err) return callback(err);
			logger.time("finish make hook");
			this.hooks.finishMake.callAsync(compilation, err => {
				logger.timeEnd("finish make hook");
				if (err) return callback(err);

				process.nextTick(() => {
					logger.time("finish compilation");
					compilation.finish(err => {
						logger.timeEnd("finish compilation");
						if (err) return callback(err);

						logger.time("seal compilation");
						compilation.seal(err => {
							logger.timeEnd("seal compilation");
							if (err) return callback(err);

							logger.time("afterCompile hook");
							this.hooks.afterCompile.callAsync(compilation, err => {
								logger.timeEnd("afterCompile hook");
								if (err) return callback(err);

								return callback(null, compilation);
							});
						});
					});
				});
			});
		});
	});
}

const params = this.newCompilationParams();

第一行代码,赋值一个params

newCompilationParams() {
	const params = {
		normalModuleFactory: this.createNormalModuleFactory(),
		contextModuleFactory: this.createContextModuleFactory()
	};
	return params;
}

我们只讲 this.createNormalModuleFactory(), 不讲contextModuleFactory(都一样)

createNormalModuleFactory() {
	this._cleanupLastNormalModuleFactory();
	const normalModuleFactory = new NormalModuleFactory({
		context: this.options.context,
		fs: this.inputFileSystem,
		resolverFactory: this.resolverFactory,
		options: this.options.module,
		associatedObjectForCache: this.root,
		layers: this.options.experiments.layers
	});
	this._lastNormalModuleFactory = normalModuleFactory;
	this.hooks.normalModuleFactory.call(normalModuleFactory);
	return normalModuleFactory;
}

this._cleanupLastNormalModuleFactory(); 直接忽略,为啥忽略呢,实际上就是清除上一次_lastNormalModuleFactory的缓存。

一眼网上去,关键就是 new NormalModuleFactory 其他就是调用一个钩子函数

那先看参数

context: this.options.context 上下文不给就是process.cwd()

fs: this.inputFileSystem 加了缓存策略的fs

resolverFactory: this.resolverFactory 这个很关键,马上讲解

options: this.options.module 我们写的module:{rules:[{test:/\.js(x)$/,loader:'babel-loader'}]},外加默认的配置default(之前讲解createCompiler内部那个修改option的方法加上的)

associatedObjectForCache: this.root this.root=this本身也就是compiler

layers: this.options.experiments.layers 默认为false,除非我们自己传值

this.resolverFactory

this.resolverFactory = new ResolverFactory();

constructor() {

this.hooks = Object.freeze({

resolveOptions: new HookMap(

() => new SyncWaterfallHook(["resolveOptions"])

),

resolver: new HookMap(

() => new SyncHook(["resolver", "resolveOptions", "userResolveOptions"])

)

});

this.cache = new Map();

}

就是注册了两个钩子

再看看方法

get(type, resolveOptions = {}) {
	let typedCaches = this.cache.get(type);
	if (!typedCaches) {
		typedCaches = {
			direct: new WeakMap(),
			stringified: new Map()
		};
		this.cache.set(type, typedCaches);
	}
	const cachedResolver = typedCaches.direct.get(resolveOptions);
	if (cachedResolver) {
		return cachedResolver;
	}
	const ident = JSON.stringify(resolveOptions);
	const resolver = typedCaches.stringified.get(ident);
	if (resolver) {
		typedCaches.direct.set(resolveOptions, resolver);
		return resolver;
	}
	const newResolver = this._create(type, resolveOptions);
	typedCaches.direct.set(resolveOptions, newResolver);
	typedCaches.stringified.set(ident, newResolver);
	return newResolver;
}

别看好像行数挺多的,实际上不就是加了个缓存,找不到缓存就 this._create(type, resolveOptions);

	_create(type, resolveOptionsWithDepType) {
		/** @type {ResolveOptionsWithDependencyType} */
		const originalResolveOptions = { ...resolveOptionsWithDepType };

		const resolveOptions = convertToResolveOptions(
			this.hooks.resolveOptions.for(type).call(resolveOptionsWithDepType)
		);
		const resolver = /** @type {ResolverWithOptions} */ (
			Factory.createResolver(resolveOptions)
		);
		if (!resolver) {
			throw new Error("No resolver created");
		}
		/** @type {WeakMap<Partial<ResolveOptionsWithDependencyType>, ResolverWithOptions>} */
		const childCache = new WeakMap();
		resolver.withOptions = options => {
			const cacheEntry = childCache.get(options);
			if (cacheEntry !== undefined) return cacheEntry;
			const mergedOptions = cachedCleverMerge(originalResolveOptions, options);
			const resolver = this.get(type, mergedOptions);
			childCache.set(options, resolver);
			return resolver;
		};
		this.hooks.resolver
			.for(type)
			.call(resolver, resolveOptions, originalResolveOptions);
		return resolver;
	}

其实关键代码就是 Factory.createResolver(resolveOptions),代码都是围绕着它来转的,那么他又是什么呢

他是 enhanced-resolve 的 ResolverFactory

绕来绕去又跑到了我们以前曾经提到的一个包 enhanced-resolve

那下一张,我们继续讲 ResolverFactory

前端技术专栏分享 文章被收录于专栏

1. 包含常规前端面试题解析,和源码分析 2. 从0-1深入浅出理解前端相关技术栈

全部评论

相关推荐

02-24 17:39
门头沟学院 Java
神哥不得了:神哥来啦~专业技能的话建议不要前面空那么多,八股的话建议先把高频top 50的八股多巩固几遍,千万不要看那些假高频八股。项目的话,建议换两个高质量的项目上去
点赞 评论 收藏
分享
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务