Last active
June 16, 2025 20:36
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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