如何在 webpack 发出之前检查由 webpack 编译的文件

How to inspect files compiled by webpack before they are emitted

我正在使用 webpack (v4) 为我的 UI 应用程序打包资产。我想要 运行 一个函数,该函数最好在 将文件发送到输出目录之前 检查已编译资产的内容是否存在无效字符串。在提出最初的方法时,我很高兴地偶然发现了 webpack hooks,这似乎正是我完成任务所需要的。

不幸的是,文档有点缺乏,或者至少我在查看它之后仍然有一些不确定性。我的第一个挑战是确定使用哪个钩子。似乎我需要一个编译器挂钩(而不是编译挂钩),而 emit hook 似乎是我感兴趣的那个。接下来,这是我目前所拥有的:

plugins: [
  {
    apply(compiler: webpack.Compiler) {
      compiler.hooks.emit.tap('ValidateEmittedAssetsPlugin', (compilation) => {
        const { assets } = compilation;

        Object.entries(assets).forEach(([pathname, source]) => {
          const contents = (source as Buffer).buffer.toString();
          if (contents.indexOf(SOME_INVALID_STRING) > -1) {
            console.log('File contained invalid string. Failing build.');
            return false;
          }

          // Trying to toString() source.buffer hasn't worked so I've also tried 
          // the following also to no avail.
          //
          // const file = compilation.getAsset(pathname);
          // const contents = file.source.buffer.toString();
          }
        });
        return true;
      });
    },
  },
]

我能够成功地遍历要发出的文件,但我无法获取这些资产的内容。因为它们(我猜)在内存中,所以我不能使用 fs 之类的东西从文件系统中读取文件。我也尝试使用 compilation.getAsset() (如上所述)但无济于事。所以我的挑战是在 emit 挂钩中获取内存资产的内容。

如何使用 webpack hooks 在编译资源发出前检查它的内容?

如果你想控制发射资产你必须使用compiler.hooks.shouldEmit钩子,它的return值你可以实际控制。您无法使用 compiler.hooks.emit 挂钩控制。两者都在发射资产之前执行,但 shouldEmit 允许控制发射资产。

一旦你从 compilation.asset 获得条目,你可以调用 compilation.getAsset(<asset_name>) 来获取资产和 asset.source.source() return你的内容。

您可以根据自己的喜好使用 emitshouldEmit 挂钩。

我更喜欢使用单独的 js 文件来管理输出,而不是将逻辑插入到 module.plugin 数组中。您还可以将配置传递给构造函数以更好地控制输出。

AssetValidatorPlugin.js

const PLUGIN_NAME = 'AssetValidatorPlugin';

class AssetValidatorPlugin {

    constructor(options){
        this.options = options;
    }

    apply(compiler) {
        // to check content of emitted assets
        compiler.hooks.emit.tap(PLUGIN_NAME, (compilation) => {
            let assets = Object.entries(compilation.assets);
            assets.forEach(([fileName]) => {
                const asset = compilation.getAsset(fileName);
                const assetContent = asset.source.source();
                console.log(assetContent);
            })
        });

        // to check content of emitted assets and prevent when fail to pass checks
        compiler.hooks.shouldEmit.tap(PLUGIN_NAME, (compilation) => {
            let allCheckPassed = true;
            let assets = Object.entries(compilation.assets);
            const logger = compiler.getInfrastructureLogger(PLUGIN_NAME);

            logger.info('Asset check');

            for (let fileIndex = 0; fileIndex < assets.length; fileIndex++) {
                const [fileName] = assets[fileIndex];

                logger.info(`validating ${fileName}`)
                const asset = compilation.getAsset(fileName);
                const assetContent = asset.source.source();

                if (assetContent.indexOf('some inane string') > -1) {
                    logger.error(`invalid string found in ${fileName} `);
                    allCheckPassed = false;
                    break;
                }
            };

            logger.info('Asset check completed');
            if (allCheckPassed)
                logger.info('asset emit proceeded');
            else
                logger.warn('asset emit aborted');
            
            if(this.options.stopEmitOnError)
                return allCheckPassed;

            return true;
        });
    }
}

module.exports = AssetValidatorPlugin;

并将其添加到 webpack.config.js

const AssetValidatorPlugin = require('./AssetValidatorPlugin');

module.exports= {
    entry: {...},
    module:{...},
    plugins: [
        new AssetValidatorPlugin({stopEmitOnError: true}),
        ...
    ]
};

已在 Webpack 上测试 v4/v5 两个挂钩的工作原理相同。您也可以通过同样的方式获取资产内容。