webpack.js 小尾巴
const webpack = (options, callback) => {
//…
if (callback) {
if (typeof callback !== “function”) {
throw new Error(“Invalid argument: callback”);
}
if (
options.watch === true ||
(Array.isArray(options) && options.some(o => o.watch))
) {
const watchOptions = Array.isArray(options)
? options.map(o => o.watchOptions || {})
: options.watchOptions || {};
return compiler.watch(watchOptions, callback);
}
compiler.run(callback);
}
return compiler;
}
最终返回了 compiler
exports.version = version;
// … 属性挂载, 把引入的函数模块全部暴露出来
webpack.WebpackOptionsDefaulter = WebpackOptionsDefaulter;
webpack.WebpackOptionsApply = WebpackOptionsApply;
webpack.Compiler = Compiler;
webpack.MultiCompiler = MultiCompiler;
webpack.NodeEnvironmentPlugin = NodeEnvironmentPlugin;
// @ts-ignore Global @this directive is not supported
webpack.validate = validateSchema.bind(this, webpackOptionsSchema);
webpack.validateSchema = validateSchema;
webpack.WebpackOptionsValidationError = WebpackOptionsValidationError;
下面暴露了一些插件
const exportPlugins = (obj, mappings) => {
for (const name of Object.keys(mappings)) {
Object.defineProperty(obj, name, {
configurable: false,
enumerable: true,
get: mappings[name]
});
}
};
exportPlugins(exports, {
AutomaticPrefetchPlugin: () => require(“./AutomaticPrefetchPlugin”),
BannerPlugin: () => require(“./BannerPlugin”),
CachePlugin: () => require(“./CachePlugin”)}
)
再通俗一点的解释:比如当你 api.AutomaticPrefetchPlugin 你能调用 AutomaticPrefetchPlugin 文件下的方法 这个和上面的不同在于上面的是挂在 webpack 函数对象上的
Compiler.js 正题
要想理解 Compiler.js 必须要理解 tapable 再写一遍 git 地址
我们先简单的理解它为一个通过 tap 注册插件 call 是 run 插件的事件流,里面还有一些异步的操作
Compiler 整理如下
class Compiler extends Tapable {
constructor(context) {
super();
this.hooks = {
// 罗列一些出现频率比较高的
shouldEmit: new SyncBailHook([“compilation”]),
done: new AsyncSeriesHook([“stats”]),
beforeRun: new AsyncSeriesHook([“compiler”]),
run: new AsyncSeriesHook([“compiler”]),
emit: new AsyncSeriesHook([“compilation”]),
afterEmit: new AsyncSeriesHook([“compilation”]),
thisCompilation: new SyncHook([“compilation”, “params”]),
compilation: new SyncHook([“compilation”, “params”]),
beforeCompile: new AsyncSeriesHook([“params”]),
compile: new SyncHook([“params”]),
make: new AsyncParallelHook([“compilation”]),
afterCompile: new AsyncSeriesHook([“compilation”]),
watchRun: new AsyncSeriesHook([“compiler”]),
//…
}
// 添加事件流
this._pluginCompat.tap(“Compiler”, options => {
switch (options.name) {
case “additional-pass”:
case “before-run”:
case “run”:
case “emit”:
case “after-emit”:
case “before-compile”:
case “make”:
case “after-compile”:
case “watch-run”:
options.async = true;
break;
}
});
}
watch(){
//…
}
run(callback) {
//…
}
// 清除输入文件系统
purgeInputFileSystem() {
if (this.inputFileSystem && this.inputFileSystem.purge) {
this.inputFileSystem.purge();
}
}
emitAssets(compilation, callback) {
//…
}
createChildCompiler(
compilation,
compilerName,
compilerIndex,
outputOptions,
plugins
) {
//…
}
//…
compile(callback){
//…
}
}
和 tapable 用法一个模子里刻出来的,我们仔细的研究 run 函数
compiler.js 是 webpack 的核心
run(callback) {
// 如果正在 running,返回报错处理
if (this.running) return callback(new ConcurrentCompilationError());
//running 调用 finalCallback
const finalCallback = (err, stats) => {
this.running = false;
if (callback !== undefined) return callback(err, stats);
};
// 记录初始化 running 时间
const startTime = Date.now();
// 设置 running 标志,防止多次 running
this.running = true;
// 正在编译
const onCompiled = (err, compilation) => {
// 如果报错,编译结束
if (err) return finalCallback(err);
// 如果没有编译完成
if (this.hooks.shouldEmit.call(compilation) === false) {
// Stats 模块有 1400 行,
// compiler.js 是 webpack 的核心,new Stats(compilation) 是 compiler.js 的核心
const stats = new Stats(compilation);
// stats 对象挂载 startTime,endTime
stats.startTime = startTime;
stats.endTime = Date.now();
// 异步调用完成事件流,running 结束
this.hooks.done.callAsync(stats, err => {
if (err) return finalCallback(err);
return finalCallback(null, stats);
});
return;
}
// 调用 emitAsset 方法,emitAsset 主要负责写入文件输出文件,不影响我们先看编译
this.emitAssets(compilation, err => {
// 类似同上, 如果报错,编译结束
if (err) return finalCallback(err);
// 如果需要额外的编译,上次的没编译完成吗????
if (compilation.hooks.needAdditionalPass.call()) {
compilation.needAdditionalPass = true;
// 再来一遍 new Stats
const stats = new Stats(compilation);
stats.startTime = startTime;
stats.endTime = Date.now();
// 继续异步调用时间流
this.hooks.done.callAsync(stats, err => {
if (err) return finalCallback(err);
// 这次多了一个时间流, 调用额外编译,告知编译终于编完了
this.hooks.additionalPass.callAsync(err => {
if (err) return finalCallback(err);
// 调用 compile,把 onCompiled 的返回值传入 compile 函数,onCompiled 的返回值也就是 new Stats 的对象
this.compile(onCompiled);
});
});
return;
}
// 如果都不走上面的分支,即编译完成,不需要额外的编译
// 调用 emitRecords 方法,主要用来记录输出的
this.emitRecords(err => {
if (err) return finalCallback(err);
// 同样 new Stats
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);
});
});
// 最终总结,无论走那个分支都是 new Stats(compilation) 返回 stats 的回调函数, 按照目前的流程走的是最后一个分支,调用 this.emitRecords
});
};
// 调用 beforeRun 钩子
this.hooks.beforeRun.callAsync(this, err => {
if (err) return finalCallback(err);
// 调用 run 钩子
this.hooks.run.callAsync(this, err => {
if (err) return finalCallback(err);
// 读文件记录
this.readRecords(err => {
if (err) return finalCallback(err);
// 把 onCompiled 函数传入,调用 compile
this.compile(onCompiled);
});
});
});
}
new Stats(compilation) 是 compiler.js 的核心
compilation 和 Stats 分别对应两个模块 compilation.js 2500 行,Stats.js 1400 行
接下来我们看一下 compile 函数
newCompilationParams() {
const params = {
// 普通模块工厂
normalModuleFactory: this.createNormalModuleFactory(),
// 上下文模块工厂
contextModuleFactory: this.createContextModuleFactory(),
// 编译依赖
compilationDependencies: new Set()
};
return params;
}
compile(callback) {
// params 值如下
const params = this.newCompilationParams();
// 异步调用 beforeCompile 钩子
this.hooks.beforeCompile.callAsync(params, err => {
if (err) return callback(err);
// 调用 compile 钩子
this.hooks.compile.call(params);
// 终于出现了 compilation,之前一直没有讲了 compilation 是什么,newCompilation 我们看如下函数
const compilation = this.newCompilation(params);
this.hooks.make.callAsync(compilation, err => {
if (err) return callback(err);
compilation.finish();
compilation.seal(err => {
if (err) return callback(err);
// 异步调用 afterCompile,返回回调函数
this.hooks.afterCompile.callAsync(compilation, err => {
if (err) return callback(err);
return callback(null, compilation);
});
});
});
});
}
newCompilation(params) {
// compilation 是 this.createCompilation(),继续往下
const compilation = this.createCompilation();
// 给 compilation 对象挂载属性
compilation.fileTimestamps = this.fileTimestamps;
compilation.contextTimestamps = this.contextTimestamps;
compilation.name = this.name;
compilation.records = this.records;
compilation.compilationDependencies = params.compilationDependencies;
// 告知钩子调用完毕
this.hooks.thisCompilation.call(compilation, params);
this.hooks.compilation.call(compilation, params);
return compilation;
}
createCompilation() {
// 原来 compilation 是 newCompilation 而来,Compilation 一共 2500 行,堪称整个 compiler.js 的核心
return new Compilation(this);
}
params 如下
{normalModuleFactory:
NormalModuleFactory {
_pluginCompat:
SyncBailHook {
// key 是 tapable 方法名
_args: [Array],
taps: [Array],
interceptors: [],
call: [Function: lazyCompileHook],
promise: [Function: lazyCompileHook],
callAsync: [Function: lazyCompileHook],
_x: undefined },
hooks:
{resolver: [SyncWaterfallHook],
factory: [SyncWaterfallHook],
beforeResolve: [AsyncSeriesWaterfallHook],
afterResolve: [AsyncSeriesWaterfallHook],
createModule: [SyncBailHook],
module: [SyncWaterfallHook],
createParser: [HookMap],
parser: [HookMap],
createGenerator: [HookMap],
generator: [HookMap] },
resolverFactory:
ResolverFactory {
_pluginCompat: [SyncBailHook],
hooks: [Object],
cache1: [WeakMap],
cache2: Map {}},
ruleSet: RuleSet {references: {}, rules: [Array] },
cachePredicate: [Function: bound Boolean],
// 文件路径
context: ‘/Users/orion/Desktop/react-beauty-highcharts’,
parserCache: {},
generatorCache: {}},
contextModuleFactory:
ContextModuleFactory {
_pluginCompat:
SyncBailHook {
_args: [Array],
taps: [Array],
interceptors: [],
call: [Function: lazyCompileHook],
promise: [Function: lazyCompileHook],
callAsync: [Function: lazyCompileHook],
_x: undefined },
hooks:
{beforeResolve: [AsyncSeriesWaterfallHook],
afterResolve: [AsyncSeriesWaterfallHook],
contextModuleFiles: [SyncWaterfallHook],
alternatives: [AsyncSeriesWaterfallHook] },
resolverFactory:
ResolverFactory {
_pluginCompat: [SyncBailHook],
hooks: [Object],
cache1: [WeakMap],
cache2: Map {}} },
compilationDependencies: Set {}}
终极总结
Compiler 构造函数 ->
run 方法 ->
this.compile(onCompiled) ->
this.compile() 执行有了 compilation ->
onCompiled 执行 const stats = new Stats(compilation)
最后返回 finalCallback(null, stats);
this.compile(onCompiled) 是个高阶函数,可以简单的理解为参数是函数,并且返回一个函数
撒花???? ???????? 我要买瓶 skii 犒赏自己