Skip to content

Instantly share code, notes, and snippets.

@toverux
Last active June 16, 2025 20:36
Show Gist options
  • Save toverux/f6e45678f7756eb4bea2977bb7442bf1 to your computer and use it in GitHub Desktop.
Save toverux/f6e45678f7756eb4bea2977bb7442bf1 to your computer and use it in GitHub Desktop.
Example of custom Webpack plugin for embedding a build manifest inside your files at build-time. Customize to you needs.
import { Compilation, sources } from 'webpack';
/**
* This is a basic Webpack plugin producing an asset map similar to what WebpackManifestPlugin does,
* except instead of emitting a manifest.json, it inlines the manifest in the source, replacing a
* variable by name in the source.
* It was written to only cover our basic use case of referencing some "internally public" chunks by
* their basic name and map that to their built name with a hash.
*
* @example With an appropriate `filter` option, replaces a variable `__BUNDLING_MANIFEST__` with:
* {
* "chunk.js": "chunk.a6bcf52794.js",
* "fonts.css": "fonts.ca87ad3290.css",
* "styles.css": "styles.6990fbe22c.css"
* }
*/
export class InlineManifestPlugin {
opts;
constructor(opts = {}) {
this.opts = {
filter: opts.filter ?? (() => true),
placeholder: opts.placeholder ?? '__BUNDLING_MANIFEST__',
};
}
apply(compiler) {
compiler.hooks.thisCompilation.tap('InlineManifestPlugin', (compilation) => {
compilation.hooks.processAssets.tap(
{
name: 'InlineManifestPlugin',
// https://webpack.js.org/api/compilation-hooks/#list-of-asset-processing-stages
// Running early allows Terser to inline pre-compute expression, ex.
// `__BUNDLING_MANIFEST__['styles.css']` → yields `'styles.hash.css'`
stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONS,
},
() => {
const manifest = {};
for (const chunk of compilation.chunks.filter(this.opts.filter)) {
for (const file of chunk.files) {
manifest[`${chunk.name || chunk.id}${file.slice(file.lastIndexOf('.'))}`] = file;
}
}
const manifestJson = JSON.stringify(manifest)
// Replace double JSON quotes with single quotes so the text does not cause a syntax
// error in dev mode where webpack wraps the code in eval("...").
.replaceAll('"', '\'');
// Iterate over all emitted assets and replace the placeholder in JS files.
for (const asset of compilation.getAssets()) {
if (!asset.name.endsWith('.js')) {
continue;
}
const originalSource = asset.source;
const sourceCode = originalSource.source();
if (sourceCode.includes(this.opts.placeholder)) {
// Use ReplaceSource to maintain source map compatibility.
const replaceSource = new sources.ReplaceSource(originalSource);
// Find all occurrences of the placeholder and replace them.
let index = 0;
do {
index = sourceCode.indexOf(
this.opts.placeholder,
index + 1,
);
if (index !== -1) {
replaceSource.replace(
index,
index + this.opts.placeholder.length - 1,
manifestJson,
);
}
} while (index !== -1);
compilation.updateAsset(asset.name, replaceSource);
}
}
},
);
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment