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深入浅出理解前端相关技术栈
查看14道真题和解析