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