likes
comments
collection
share

Webpack 5 Compilation processAssets Hook 使用

作者站长头像
站长
· 阅读数 73

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:插件名称。
  • stagehook 的执行顺序。
  • additionalAssets:添加资源时的回调。

一直对 stageadditionalAssets 参数的使用存在疑惑(官方解释也是摸棱两可),今天抽空研究了下,把相关结论记录在此,以供大家参考。

stage

Webpack 提供了 PROCESS_ASSETS_STAGE_ADDITIONALPROCESS_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_DERIVEDOnePlugin PROCESS_ASSETS_STAGE_ADDITIONS 之间多出了以下几个输出:

OnePlugin PROCESS_ASSETS_STAGE_PRE_PROCESS
OnePlugin PROCESS_ASSETS_STAGE_DERIVED
OnePlugin PROCESS_ASSETS_STAGE_ADDITIONAL additionalAssets callback

这是因为在 TwoPluginprocessAssets hook 中我们通过 compilation.emitAsset 新增加了一个 hello.txt 资源,并且 OnePlugin 中已执行的几个 processAssets hook 均设置了 additionalAssets 参数,因此此刻 Webpack 先回过头触发相关的回调,然后再按照 stage 规则继续执行其它的 processAssets hook

根据上面的分析可知,参数 additionalAssets 的主要作用是为了监听后续要执行的 processAssets hook 所新增的资源,以便对其进行处理。

转载自:https://juejin.cn/post/7047003342971043876
评论
请登录