Search code examples
javascriptwebpackpluginsdocumentationloader

Webpack plugin API - update content of an asset and rebuild hash


I am writing a plugin that needs to swap contents of a certain JSON file after all of the modules were bundled. I implemented it in 2 steps: loader replaces content with a placeholder, and the plugin replaces the placeholder.

the loader looks like this:

const loader = function(source) {
  this.clearDependencies();
  return JSON.stringify('REGENERATED_JSON');
};

the plugin looks roughly like this:

compilation.hooks.optimizeChunkAssets.tapAsync(PLUGIN_NAME, (chunks, callback) => {

  chunks.forEach((chunk) => {
    chunk.files.forEach((filePath) => {
      const asset = compilation.assets[filePath];
      const source = asset.source();

      replacements.forEach((id) => {
        const pattern = 'JSON.parse("\\"REGENERATED_JSON\\"")';
        const index = source.indexOf(pattern);

        if (index < 0) return;

        const content = JSON.stringify(json_content, null, 2);
        const updatedSource = new ReplaceSource(asset);

        updatedSource.replace(index, index + pattern.length, content);
        compilation.assets[filePath] = updatedSource;
      });
    });
  });

  callback();
});

This code has several issues:

  1. Fragile because it's tied to JSON.parse call. I wasn't able to trick webpack into treating file as javascript after it was imported as JSON.
  2. The content hash isn't being rebuilt, neither is the file size assessment, the JSON might be very large but Webpack wouldn't know.

Is there a way to solve these problems within webpack?


Solution

  • This comment helped me solve the issue: https://github.com/webpack/webpack/issues/8830#issuecomment-580095801

    In short:

    1. use compilation.hooks.finishModules.tap to get to the modules after every module was parsed
    2. inject a new loader there and then, or add information to the module(s) in question (right to the module object)
    3. trigger a rebuild of those module(s) with compilation.rebuildModule(module, callback), use promisify from built in utils package to convert to a promise to handle multiple parallel rebuilds