背景
- 什么是tapable、hook,平时做vue开发时的webpack 配置一直都没弄懂,你也有这种情况吗?
- 还是看源码,闲来无聊又看一下webpack的源码,看看能否找到一些宝藏
- tapable和webpack没有特定关系,可以先看下这篇文章,了解下这个小型库
https://webpack.docschina.org/api/plugins/#tapable
https://blog.csdn.net/mafan121/article/details/113120081
4.下面记录下寻宝过程
开始
执行一次webpack经历了什么,先看一下代码
我们分析一下4点
- 引用了webpack
- 我们使用的配置文件
- 调用webpack函数,传入配置,返回一个compiler(编译器)
- 执行编译器的run方法
分析
引用webpack,先把这个函数找出来
https://github.com/webpack/webpack/blob/main/package.json
"main": "lib/index.js",
https://github.com/webpack/webpack/blob/main/lib/index.js
module.exports = mergeExports(fn, { get webpack() { return require("./webpack"); },
https://github.com/webpack/webpack/blob/main/lib/webpack.js
const webpack = /** @type {WebpackFunctionSingle & WebpackFunctionMulti} */ ( /** * @param {WebpackOptions | (ReadonlyArray<WebpackOptions> & MultiCompilerOptions)} options options * @param {Callback<Stats> & Callback<MultiStats>=} callback callback * @returns {Compiler | MultiCompiler} */ (options, callback) => { const create = () => { if (!asArray(options).every(webpackOptionsSchemaCheck)) { getValidateSchema()(webpackOptionsSchema, options); util.deprecate( () => {}, "webpack bug: Pre-compiled schema reports error while real schema is happy. This has performance drawbacks.", "DEP_WEBPACK_PRE_COMPILED_SCHEMA_INVALID" )(); } /** @type {MultiCompiler|Compiler} */ let compiler; let watch = false; /** @type {WatchOptions|WatchOptions[]} */ let watchOptions; if (Array.isArray(options)) { /** @type {MultiCompiler} */ compiler = createMultiCompiler( options, /** @type {MultiCompilerOptions} */ (options) ); watch = options.some(options => options.watch); watchOptions = options.map(options => options.watchOptions || {}); } else { const webpackOptions = /** @type {WebpackOptions} */ (options); /** @type {Compiler} */ compiler = createCompiler(webpackOptions); watch = webpackOptions.watch; watchOptions = webpackOptions.watchOptions || {}; } return { compiler, watch, watchOptions }; }; if (callback) { try { const { compiler, watch, watchOptions } = create(); if (watch) { compiler.watch(watchOptions, callback); } else { compiler.run((err, stats) => { compiler.close(err2 => { callback(err || err2, stats); }); }); } return compiler; } catch (err) { process.nextTick(() => callback(err)); return null; } } else { const { compiler, watch } = create(); if (watch) { util.deprecate( () => {}, "A 'callback' argument needs to be provided to the 'webpack(options, callback)' function when the 'watch' option is set. There is no way to handle the 'watch' option without a callback.", "DEP_WEBPACK_WATCH_WITHOUT_CALLBACK" )(); } return compiler; } } ); module.exports = webpack;
这里主要就是调用create创建一个Compiler(先不理watch)
在看一下create,这里是调用的createCompiler或者createMultiCompiler
在看一下createCompiler,这里主要就是new一个Compiler。这个时候已经开始了webpack编译的生命周期。
/** * @param {WebpackOptions} rawOptions options object * @returns {Compiler} a compiler */ 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; };
我们简单看一下Compiler类的一些hooks
const { SyncHook, SyncBailHook, AsyncParallelHook, AsyncSeriesHook } = require("tapable"); ...... class Compiler { /** * @param {string} context the compilation path * @param {WebpackOptions} options options */ constructor(context, options = /** @type {WebpackOptions} */ ({})) { this.hooks = Object.freeze({ /** @type {SyncHook<[]>} */ initialize: new SyncHook([]), /** @type {SyncBailHook<[Compilation], boolean>} */ shouldEmit: new SyncBailHook(["compilation"]), /** @type {AsyncSeriesHook<[Stats]>} */ done: new AsyncSeriesHook(["stats"]), /** @type {SyncHook<[Stats]>} */ afterDone: new SyncHook(["stats"]), /** @type {AsyncSeriesHook<[]>} */ additionalPass: new AsyncSeriesHook([]), /** @type {AsyncSeriesHook<[Compiler]>} */ beforeRun: new AsyncSeriesHook(["compiler"]), /** @type {AsyncSeriesHook<[Compiler]>} */ run: new AsyncSeriesHook(["compiler"]), /** @type {AsyncSeriesHook<[Compilation]>} */ emit: new AsyncSeriesHook(["compilation"]), /** @type {AsyncSeriesHook<[string, AssetEmittedInfo]>} */ assetEmitted: new AsyncSeriesHook(["file", "info"]), /** @type {AsyncSeriesHook<[Compilation]>} */ afterEmit: new AsyncSeriesHook(["compilation"]),
每个hook都是一个 tapable包里对应后hook的实例
在回到创建编译器那里,这时创建一个插件的实例,并且执行apply方法,插件就会向自己关系的hook添加事件处理函数(其实还是一个事件监听),NodeEnvironmentPlugin代码可以自行在源码中查看
new NodeEnvironmentPlugin({ infrastructureLogging: options.infrastructureLogging }).apply(compiler);
一切都准备好了之后,我们再看一下编译器的run方法
/** * @param {Callback<Stats>} callback signals when the call finishes * @returns {void} */ run(callback) { 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 = () => { 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(); } }
https://github.com/webpack/webpack/blob/main/lib/Compiler.js
这里简单分析一下,主要就是执行run相关生命周期,以及编译。并且编译完成后传入回调函数onCompiled
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); this.compile(onCompiled); }); }); }); };
整体逻辑不是很复杂,我们主要可以感受到webpack启动后对hook的一些使用方式。整体的逻辑差不多都是一样的。是不是很简单。
总结
- 想了解一个框架,一定要找到入口函数,一点一点向前探索。
- tapable 是个好东西,
- 关于webpack生命周期,有疑问的时候,除了看文档意外,还可以结合源码去理解,去感受
你学会了吗?欢迎留下你的感受!