Webpack 5 Compilation processAssets Hook 使用
在 Webpack 5
之前的 plugin
中,我们常用 compiler.hooks.emit
对资源进行一些处理(比如删除注释、压缩文件等),在 Webpack 5
中使用该 hook
将会得到以下警告:
(node:3881) [DEP_WEBPACK_COMPILATION_ASSETS] DeprecationWarning: Compilation.assets will be frozen in future, all modifications are deprecated. BREAKING CHANGE: No more changes should happen to Compilation.assets after sealing the Compilation. Do changes to assets earlier, e. g. in Compilation.hooks.processAssets. Make sure to select an appropriate stage from Compilation.PROCESS_ASSETS_STAGE_*. (Use `node --trace-deprecation ...` to show where the warning was created) 复制代码
根据提示可知,我们需要使用 Compilation
中的 processAssets hook
来对资源进行再处理,根据官方的解释,该 hook
中有以下参数:
name
:插件名称。stage
:hook
的执行顺序。additionalAssets
:添加资源时的回调。
一直对 stage
和 additionalAssets
参数的使用存在疑惑(官方解释也是摸棱两可),今天抽空研究了下,把相关结论记录在此,以供大家参考。
stage
Webpack
提供了 PROCESS_ASSETS_STAGE_ADDITIONAL
、PROCESS_ASSETS_STAGE_PRE_PROCESS
等 15 种 stage
(点击查看详情),该参数主要用于控制 processAssets hook
的执行顺序,比如下面的例子:
//plugins.js const webpack = require('webpack'); class OnePlugin { apply(compiler) { compiler.hooks.compilation.tap('OnePlugin', (compilation) => { compilation.hooks.processAssets.tapAsync({ name: 'OnePlugin', stage: webpack.Compilation.PROCESS_ASSETS_STAGE_DERIVED, }, (_, callback) => { console.log('OnePlugin PROCESS_ASSETS_STAGE_DERIVED'); callback(); }); compilation.hooks.processAssets.tapAsync({ name: 'OnePlugin', stage: webpack.Compilation.PROCESS_ASSETS_STAGE_PRE_PROCESS, }, (_, callback) => { console.log('OnePlugin PROCESS_ASSETS_STAGE_PRE_PROCESS'); callback(); }); }); } } class TwoPlugin { apply(compiler) { compiler.hooks.compilation.tap('TwoPlugin', (compilation) => { compilation.hooks.processAssets.tapAsync({ name: 'TwoPlugin', stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL, }, (_, callback) => { console.log('TwoPlugin PROCESS_ASSETS_STAGE_ADDITIONAL'); callback(); }); }); } } module.exports = { OnePlugin, TwoPlugin, }; //webpack.config.js const path = require('path'); const { OnePlugin, TwoPlugin } = require('./plugins'); module.exports = { entry: './src/index.js', mode: process.env.NODE_ENV ?? 'development', output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js' }, plugins: [ new OnePlugin(), new TwoPlugin(), ] }; 复制代码
执行 npx webpack
命令:
TwoPlugin PROCESS_ASSETS_STAGE_ADDITIONAL OnePlugin PROCESS_ASSETS_STAGE_PRE_PROCESS OnePlugin PROCESS_ASSETS_STAGE_DERIVED 复制代码
通过上面的输出,可知 Webpack
在触发 processAssets hook
时,会根据指定的 stage
对应的执行顺序优先级执行相应回调,而忽略注册顺序,如果两个回调指定的 stage
相同,最先注册的最先执行,比如下面的例子:
//plugins.js const webpack = require('webpack'); class OnePlugin { apply(compiler) { compiler.hooks.compilation.tap('OnePlugin', (compilation) => { compilation.hooks.processAssets.tapAsync({ name: 'OnePlugin', stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL, }, (_, callback) => { console.log('OnePlugin PROCESS_ASSETS_STAGE_ADDITIONAL'); callback(); }); }); } } class TwoPlugin { apply(compiler) { compiler.hooks.compilation.tap('TwoPlugin', (compilation) => { compilation.hooks.processAssets.tapAsync({ name: 'TwoPlugin', stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL, }, (_, callback) => { console.log('TwoPlugin PROCESS_ASSETS_STAGE_ADDITIONAL'); callback(); }); }); } } module.exports = { OnePlugin, TwoPlugin, }; //webpack.config.js const path = require('path'); const { OnePlugin, TwoPlugin } = require('./plugins'); module.exports = { entry: './src/index.js', mode: process.env.NODE_ENV ?? 'development', output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js' }, plugins: [ new OnePlugin(), new TwoPlugin(), ] }; 复制代码
执行 npx webpack
命令:
OnePlugin PROCESS_ASSETS_STAGE_ADDITIONAL TwoPlugin PROCESS_ASSETS_STAGE_ADDITIONAL 复制代码
通过输出可知,在指定了相同的 stage
时,最先注册的先执行,后注册的后执行。
additionalAssets
先看下面的例子:
//plugins.js const webpack = require('webpack'); class OnePlugin { apply(compiler) { compiler.hooks.compilation.tap('OnePlugin', (compilation) => { compilation.hooks.processAssets.tapAsync({ name: 'OnePlugin', stage: webpack.Compilation.PROCESS_ASSETS_STAGE_PRE_PROCESS, additionalAssets: true, }, (_, callback) => { console.log('OnePlugin PROCESS_ASSETS_STAGE_PRE_PROCESS'); callback(); }); compilation.hooks.processAssets.tapAsync({ name: 'OnePlugin', stage: webpack.Compilation.PROCESS_ASSETS_STAGE_DERIVED, additionalAssets: true, }, (_, callback) => { console.log('OnePlugin PROCESS_ASSETS_STAGE_DERIVED'); callback(); }); compilation.hooks.processAssets.tapAsync({ name: 'OnePlugin', stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL, additionalAssets: (_, callback) => { console.log('OnePlugin PROCESS_ASSETS_STAGE_ADDITIONAL additionalAssets callback') callback(); }, }, (_, callback) => { console.log('OnePlugin PROCESS_ASSETS_STAGE_ADDITIONAL'); callback(); }); compilation.hooks.processAssets.tapAsync({ name: 'OnePlugin', stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS , additionalAssets: true, }, (_, callback) => { console.log('OnePlugin PROCESS_ASSETS_STAGE_ADDITIONS'); callback(); }); }); } } class TwoPlugin { apply(compiler) { compiler.hooks.compilation.tap('TwoPlugin', (compilation) => { compilation.hooks.processAssets.tapAsync({ name: 'TwoPlugin', stage: webpack.Compilation.PROCESS_ASSETS_STAGE_DERIVED, }, (_, callback) => { console.log('TwoPlugin PROCESS_ASSETS_STAGE_DERIVED'); compilation.emitAsset('hello.txt', new webpack.sources.RawSource('Hello World')); callback(); }); }); } } module.exports = { OnePlugin, TwoPlugin, }; //webpack.config.js const path = require('path'); const { OnePlugin, TwoPlugin } = require('./plugins'); module.exports = { entry: './src/index.js', mode: process.env.NODE_ENV ?? 'development', output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js' }, plugins: [ new OnePlugin(), new TwoPlugin(), ] }; 复制代码
执行 npx webpack
命令:
OnePlugin PROCESS_ASSETS_STAGE_ADDITIONAL OnePlugin PROCESS_ASSETS_STAGE_PRE_PROCESS OnePlugin PROCESS_ASSETS_STAGE_DERIVED TwoPlugin PROCESS_ASSETS_STAGE_DERIVED OnePlugin PROCESS_ASSETS_STAGE_PRE_PROCESS OnePlugin PROCESS_ASSETS_STAGE_DERIVED OnePlugin PROCESS_ASSETS_STAGE_ADDITIONAL additionalAssets callback OnePlugin PROCESS_ASSETS_STAGE_ADDITIONS 复制代码
根据前文对 stage
的介绍,该命令的输出应该为:
OnePlugin PROCESS_ASSETS_STAGE_ADDITIONAL OnePlugin PROCESS_ASSETS_STAGE_PRE_PROCESS OnePlugin PROCESS_ASSETS_STAGE_DERIVED TwoPlugin PROCESS_ASSETS_STAGE_DERIVED OnePlugin PROCESS_ASSETS_STAGE_ADDITIONS 复制代码
但根据输出可知,在 TwoPlugin PROCESS_ASSETS_STAGE_DERIVED
与 OnePlugin PROCESS_ASSETS_STAGE_ADDITIONS
之间多出了以下几个输出:
OnePlugin PROCESS_ASSETS_STAGE_PRE_PROCESS OnePlugin PROCESS_ASSETS_STAGE_DERIVED OnePlugin PROCESS_ASSETS_STAGE_ADDITIONAL additionalAssets callback 复制代码
这是因为在 TwoPlugin
的 processAssets hook
中我们通过 compilation.emitAsset
新增加了一个 hello.txt
资源,并且 OnePlugin
中已执行的几个 processAssets hook
均设置了 additionalAssets
参数,因此此刻 Webpack
先回过头触发相关的回调,然后再按照 stage
规则继续执行其它的 processAssets hook
。
根据上面的分析可知,参数 additionalAssets
的主要作用是为了监听后续要执行的 processAssets hook
所新增的资源,以便对其进行处理。
作者:泪已无痕
链接:https://juejin.cn/post/7047003342971043876