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深入浅出理解前端相关技术栈