插件的概念
插件是一个具有apply方法的JavaScript对象,apply方法会被webpack compiler调用,并且在整个编译声明周期都可以访问compiler对象
插件是 webpack 生态的关键部分, 它为社区用户提供了一种强有力的方式来直接触及 webpack 的编译过程(compilation process)。 插件能够 hook 到每一个编译(compilation)中发出的关键事件中。 在编译的每个阶段中,插件都拥有对 compiler 对象的完全访问能力, 并且在合适的时机,还可以访问当前的 compilation 对象
运行机制
插件的运行建立在:基于tapable实现的事件发布订阅以及webpack打包的整个生命周期中对事件的发布
- 在webpack.config.js中注册插件
- webpack生成compiler对象,调用注册的插件的apply方法,传入compiler对象,完成webapck事件订阅
- webpack在打包过程中,在对应的生命周期触发钩子,实现事件的广播
Tapable
-
SyncHook
- 同步hook
- 事件
- 注册:tap
- 调用:call
-
AyncSeriesHook
- 异步串行hook,不同插件注册在同一hook上的事件串行执行
- 事件
- 注册:tap、tapAsync、tapPromise
- 调用:call、callAsync、promise
-
AysncParalleHook
- 异步并行hook,不同插件注册在同一hook上的事件并行执行
- 事件的注册与调用同 AsyncSeriesHook
-
例如下面自定义插件的代码,便可以将自定义事件注册到 webpack打包的生命周期中,当webpack打包过程中,例如在生成资源到output目录前,便会执行 compiler.hooks.emit.callAsync(compilation, err => {}),从而触发插件注册在 emit事件上的回调函数的执行
// HelloPlugin.js
const PluginName = 'HelloPlugin'
module.exports = class HelloPlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
compiler.hooks.entryOption.tap(PluginName, (context, entry) => {
// console.log('hello-entryOptions: ', context, entry);
})
// AsyncSeriesHook 生成资源到 output 目录之前。
compiler.hooks.emit.tapAsync('ddd', (compilation, cb) => {
console.log('hello-emit: ', compilation.chunks);
cb();
})
// AsyncSeriesHook 生成资源到 output 目录之后。
compiler.hooks.afterEmit.tapPromise(PluginName, (compilation) => {
return new Promise((resolve, reject) => {
console.log('hello-afterEmit-do');
resolve()
})
})
}
}
Webpack构建流程
webpack的构建流程中几个重要的阶段
- 校验配置文件,初始化本次构建的配置参数
- 生成Compiler对象,注册plugin,调用plugin实例的apply,为插件实例传入compiler对象
- 进入entryOption阶段:webpack开始读取配置的Entries,递归遍历所有的入口文件
- run/watch: 如果运行在watch模式下执行watch方法,否则执行run方法
- compilation:创建Compilation对象回调 compilation相关钩子,依次进入每一个入口文件(entry),使用loader对文件进行编译。通过compilation可以读取module的resource(资源路径)、loaders等信息,再将编译好的文件内容使用acorn解析生成AST静态语法树,然后递归重复的执行这个工程,所有模块和依赖分析完成后,执行compilation的seal方法对每个chunk进行整理、优化、封装 _webpack_require_来模拟模块化操作
- emit:所有文件的编译及转化都已经完成,包含了最终输出的资源,我们可以在传入事件回调的compilation.assets上拿到所需数据,其中包括即将输出的资源、代码块Chunk等信息
- afterEmit:文件已经写入磁盘完成
- done:完成编译。
引用自:滴滴云博客,见:blog.didiyun.com/index.php/2…
相关流程代码
webpack.js
初始化配置参数,生成Compiler对象,执行run/watch方法
const webpack = (
(options, callback) => {
const create = () => {
let compiler;
let watch = false;
let watchOptions;
// 创建compiler
compiler = createCompiler(webpackOptions);
watch = webpackOptions.watch;
watchOptions = webpackOptions.watchOptions || {};
return { compiler, watch, watchOptions };
};
// ...
const { compiler, watch, watchOptions } = create();
if (watch) {
compiler.watch(watchOptions, callback);
} else {
compiler.run((err, stats) => {
compiler.close(err2 => {
callback(err || err2, stats);
});
});
}
}
);
// 创建compiler对象
const createCompiler = rawOptions => {
// ... 初始化配置参数
const compiler = new Compiler(options.context, options);
// 调用自定义plugin的apply,将事件钩入生命周期中
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;
};
Compiler.js
创建compiler对象,执行 run 方法,创建compilation对象,comiler.hooks.make 触发 EntryPlugin addEntry,处理入口文件
class Compiler {
constructor(context, options = /** @type {WebpackOptions} */ ({})) {
// 生命周期钩子
this.hooks = Object.freeze({
initialize: new SyncHook([]),
shouldEmit: new SyncBailHook(["compilation"]),
done: new AsyncSeriesHook(["stats"]),
afterDone: new SyncHook(["stats"]),
beforeRun: new AsyncSeriesHook(["compiler"]),
run: new AsyncSeriesHook(["compiler"]),
emit: new AsyncSeriesHook(["compilation"]),
assetEmitted: new AsyncSeriesHook(["file", "info"]),
afterEmit: new AsyncSeriesHook(["compilation"]),
entryOption: new SyncBailHook(["context", "entry"])
...
});
}
}
Complier 的 run 方法,触发 compiler
run(callback) {
this.running = true;
const conCompiled = (err, compilation) => {...}
const run = () => {
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);
// 开始 compiler,参数为compiler完成后的调用触发 shouldEmit 、done等hooks
this.compile(onCompiled);
});
});
});
};
run();
}
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(() => {
// 输出文件到构建目录
this.emitAssets(compilation, err => {
if (err) return finalCallback(err);
if (compilation.hooks.needAdditionalPass.call()) {
compilation.needAdditionalPass = true;
const stats = new Stats(compilation);
this.hooks.done.callAsync(stats, err => {
if (err) return finalCallback(err);
this.hooks.additionalPass.callAsync(err => {
if (err) return finalCallback(err);
this.compile(onCompiled);
});
});
return;
}
this.emitRecords(err => {
if (err) return finalCallback(err);
const stats = new Stats(compilation);
this.hooks.done.callAsync(stats, err => {
if (err) return finalCallback(err);
this.cache.storeBuildDependencies(
compilation.buildDependencies,
err => {
if (err) return finalCallback(err);
return finalCallback(null, stats);
}
);
});
});
});
});
};
compiler方法,
- 创建compilation对象,调用compiler.hooks.compilation,很多内置插件钩入了此阶段,如 EntryPlugin
- 调用 make,内置插件 EntryPlugin 调用 addEntry, 解析入口文件,开始进行编译构建
compile(callback) {
const params = this.newCompilationParams();
this.hooks.beforeCompile.callAsync(params, err => {
if (err) return callback(err);
this.hooks.compile.call(params);
// 创建 compilation对象
const compilation = this.newCompilation(params);
// 触发 EntryPlugin 的 addEntry
this.hooks.make.callAsync(compilation, err => {
this.hooks.finishMake.callAsync(compilation, err => {
process.nextTick(() => {
compilation.finish(err => {
// 构建结果封装
compilation.seal(err => {
this.hooks.afterCompile.callAsync(compilation, err => {
return callback(null, compilation);
});
});
});
});
});
});
});
}
// 创建 compilation
newCompilation(params) {
const compilation = this.createCompilation(params);
compilation.name = this.name;
compilation.records = this.records;
this.hooks.thisCompilation.call(compilation, params);
this.hooks.compilation.call(compilation, params);
return compilation;
}
关键hooks
compiler
hooks | 介绍 | 类型 | 参数 | |
---|---|---|---|---|
run | 执行compiler之前调用 | AsyncSeriesHook | compiler | |
compilation | compilation 创建之后执行 | SyncHook | compilation, compilationParams | |
emit | 输出 asset 到 output 目录之前执行 | AsyncSeriesHook | compilation | |
done | 在 compilation 完成时执行 | AsyncSeriesHook | stats |
Compilation
hooks | 介绍 | 类型 | 参数 | |
---|---|---|---|---|
buildModule | 在模块构建开始之前触发,可以用来修改模块。 | SyncHook | module | |
seal | compilation 对象停止接收新的模块时触发 | SyncHook | compilation | 构建结果封装, 不可再更改,生成资源,这些资源保存在compilation.assets, compilation.chunk |
optimize | 优化阶段开始时触发。 | SyncHook | compilation |
参考文档: