Webpack 4 to 5: custom plugin: replacing compilation.assets mutation with compilation.hooks.processAssets

Viewed 240

I am having trouble using the compilation hook processAssets to mirror deprecated Webpack 4 functionality in a custom plugin.

The purpose of the plugin is to write the chunkhash for js/css files to a python file, which is used by a Flask server to separate the functionality of the webpage based on a user's session and roles (e.g. public, private, admin, etc).

The meat of the plugin (with direct mutation of the assets) is below.

compiler.hooks.emit.tapAsync("WriteHashesPlugin", (compilation, cb) => {
    //For each bundle, write the name and the corresponding hash.
    compilation.chunks.forEach(chunk => {
        lines.push("__" + chunk.name + "_hash__ = '" + chunk.renderedHash + "'")
    });
    const content = lines.join("\n");

    compilation.assets[this.filename] = { // <= this.filename = "{relative_path}/bundles.py"
        source: () => content,
        size: () => content.length
    }
    cb();
})

This code still "works" to create/update the python file, but of course I don't want to be using deprecated syntax. The warning message in the build output:

(node:38072) [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_*.

I have implemented the compilation.hooks.processAssets instead of directly accessing the assets; however, I have not been able to get it to work as expected.

compiler.hooks.emit.tapAsync("WriteHashesPlugin", (compilation, cb) => {
    //For each bundle, write the name and the corresponding hash.
    compilation.chunks.forEach(chunk => {
        lines.push("__" + chunk.name + "_hash__ = '" + chunk.renderedHash + "'")
    });
    const content = lines.join("\n");

    // The below code block compiles but doesn't get run
    compilation.hooks.processAssets.tap({
        name: 'WriteHashesPlugin',
        stage: compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
        additionalAssets: true
    }, () => {
        compilation.emitAsset(
            this.filename,
            content
        );
    });
    cb();
})

I've tried using compilation.updateAsset with the stage PROCESS_ASSETS_STAGE_ADDITIONS as well with no success.

I am not a webpack expert by any means, but have spent the past few days scouring the documentation and attempting to understand the source code. Some ideas I have of what I'm doing wrong:

  • compiler.hooks.emit is not the appropriate step in the compilation process to be modifying assets (perhaps at a certain point in the compilation process, assets are not to be altered?).
  • compilation.emitAsset is not the appropriate compilation hook to be using.
  • I have misunderstood the deprecation warning in the build output, and I should be doing something completely different.
  • There is a better way to make the chunkhashes available to Flask.
  • Something else that one of you could illuminate for me.
1 Answers

Thanks to the comment by @chiborg, I was able to get this code functioning with a few tweaks.

As written in this post,

First, you need to bring in the { sources } class from webpack, which will aid us in crafting raw source changes to an asset (regardless of its optimization or asset type):

const { sources } = require('webpack');

The appropriate hook to use was compiler.hooks.compilation.tap instead of compiler.hooks.emit.tapAsync; once that was changed, the rest of the code inside the hook had to be moved into compilation.hooks.processAssets:

/**
 * Writes build version and bundle hashes to a new file, with the dir
 * specified in this.filename.
 */
compiler.hooks.compilation.tap("WriteHashesPlugin", compilation => {
    compilation.hooks.processAssets.tap({
        name: 'WriteHashesPlugin',
        stage: compilation.PROCESS_ASSETS_STAGE_ADDITIONAL
    }, () => {
        const lines = [];
        // Write build version
        lines.push("__version__ = '" + this.version + "'");
        // For each bundle, write the name and the corresponding hash.
        compilation.chunks.forEach(chunk => {
            lines.push("__" + chunk.name + "_hash__ = '" + chunk.renderedHash + "'");
        });
        const content = lines.join("\n");
        // write bundles.py to assets
        compilation.emitAsset(
            this.filename,
            new sources.RawSource(content)
        );
    });
});

compilation.chunks was empty when accessed outside the processAssets hook, but moving everything into the hook made it work as expected.

Related