Deterministic, readable chunk filenames in Rollup using output.plugins
Learn how to use Rollup’s output.plugins hook to generate deterministic, readable filenames for code‑split chunks while avoiding collisions and preserving cache‑busting benefits.
04 Nov 2025, 05:03 UTC

Why opaque chunk names hurt debugging and caching
When you enable code‑splitting in a Rollup build, each generated chunk receives a name like abcdef123.js. The hash is useful for cache‑busting, but it also makes the file list hard to read in browser devtools and prevents stable HTTP caching when the chunk’s content hasn’t changed.
Rollup provides an output.plugins hook that lets you influence the filename after the bundle graph is resolved but before the file is written. By returning a custom fileName from a plugin’s renderChunk method you can produce names that are both readable and still unique enough to avoid collisions.
How the hook works
The renderChunk function receives two arguments: the chunk’s source code (_code) and a chunkInfo object. The latter contains metadata such as:
name– the chunk’s logical name (often the entry point or dynamic import identifier)facadeModuleId– the absolute path of the module that caused the chunk to be createdmodules– an array of all modules included in the chunkisEntry– true for the initial entry chunk
Returning an object with a fileName property tells Rollup to use that string as the output file. Rollup still performs tree‑shaking, format conversion, and other optimizations; only the naming step is overridden.
Worked example: readable names with a safety hash
Below is a minimal rollup.config.js that gives each chunk a name based on its logical name and a short suffix taken from the end of facadeModuleId. This keeps names readable while guaranteeing uniqueness.
export default {
input: 'src/main.js',
output: {
dir: 'dist',
format: 'esm',
plugins: [
{
name: 'custom-chunk-names',
renderChunk(_code, chunk) {
// Use a 6‑character slice of the facade module id as a unique token
const token = chunk.facadeModuleId ? chunk.facadeModuleId.slice(-6) : '000000';
return { fileName: `${chunk.name}-${token}.js` };
}
}
]
}
};
If your project has a single entry point named main, the resulting files will look like:
main-a1b2c3.jsutils-d4e5f6.js(for a dynamically importedutilsmodule)
The suffix changes only when the underlying module path changes, so a chunk whose content is stable will keep the same filename across builds.
Verification steps
- Create a fresh directory and initialize a Node project:
npm init -y - Install Rollup as a dev dependency:
npm install --save-dev rollup - Add a simple source structure, e.g.,
src/main.jsthat dynamically imports another module. - Save the configuration above as
rollup.config.js. - Run the build:
npx rollup -c - List the output:
ls dist/and confirm each file follows the<name>-<6‑char>.jspattern. - Optionally, open the generated bundle in a browser or Node to ensure it executes correctly.
If you see a collision error like Duplicate file name: main-abc123.js, increase the length of the token or incorporate chunk.id to guarantee uniqueness.
Trade‑offs and limitations
- Readability vs. cache‑busting – Removing the hash entirely gives you stable names but defeats long‑term caching when a chunk’s content changes. Keeping a short hash (6‑8 hex characters) as shown above provides a compromise: names remain readable in devtools while still changing when the chunk’s actual code changes.
- Collision risk – Using only
chunk.namecan produce duplicate filenames when multiple chunks share the same logical name (common with dynamic imports). Always add a unique identifier such as a slice offacadeModuleId,chunk.id, or a content‑based hash. - Build‑time overhead – The
renderChunkhook runs for every chunk; the extra string manipulation is negligible for typical projects but worth noting for extremely large codebases.
Actionable closing
Add the output.plugins snippet to your Rollup configuration, run a development build, and inspect the dist/ folder. If the filenames match your pattern and the application works, you’ve gained clearer debugging output without sacrificing the ability to cache‑bust when needed. For production, consider swapping the short token for a full hash (e.g., crypto.createHash('sha256').update(code).digest('hex').slice(0,8)) while keeping the readable prefix for easier source‑map lookup.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.