Does Rollup tree-shaking remove modules with hidden side effects?
0 reputation · 18 Jul 2025, 20:27 UTC
0 reputation · 18 Jul 2025, 20:27 UTC
A low-traffic site wants its JavaScript payload kept as small as possible so per-visit transfer and deployment overhead stay low. Rollup's tree-shaking is the main lever for cutting unused code from the bundle, and the plan is to rely on its static analysis of ES modules rather than manually pruning imports across the dependency graph.
The unresolved point is how tree-shaking treats modules that look unused but still perform global mutations, such as registering polyfills, attaching window handlers, or importing styles. Such modules can be dropped unless their side effects are declared, while over-declaring side effects preserves dead code and defeats the size goal. Dynamic imports and circular dependencies can further narrow what the analyzer safely removes.
Assuming a Rollup 4.x project bundling ESM sources, with the treeshake options available for tuning:
34275 reputation · 19 Jul 2025, 05:24 UTC
With Rollup 4.x and tree-shaking enabled, a module is removed when Rollup can prove none of its exported bindings are used and the module is not marked as having side effects. A module that only performs global mutations, registers polyfills, attaches window handlers, or imports styles will be dropped unless you explicitly declare it side-effectful. This is the confirmed static-analysis behavior. The likely explanation for hidden removals is a missing side-effects declaration combined with dynamic imports or circular dependencies that limit what the analyzer can see.
Per package, set sideEffects to control preservation.
true – treat all modules in the package as side-effectful. Appropriate when the package always performs global mutations.false – declare the package side-effect free. Appropriate for pure utility libraries where unused exports can be safely removed.["**/polyfill.js", "**/*.css"]. Appropriate for fine-grained control when only specific files have hidden side effects.Rollup 4.x exposes treeshake tuning in the config. Assumptions apply as behavior can change across majors.
treeshake: { moduleSideEffects: true | false | 'no-external' } overrides the package.json signal for the build. Use true to force keep side-effectful modules for a one-off build, false to allow aggressive removal.Confirmed fact: the package.json sideEffects field is the standard signal Rollup respects. The exact names and defaults of treeshake sub-options should be verified against your Rollup 4.x release notes, as they are subject to change.
npx rollup -c with a production environment.Dynamic imports and circular dependencies can prevent static analysis from seeing usage, which may lead to over-removal even with correct flags. In those cases explicitly mark the module as side-effectful.
Does the module export any bindings at all? If it has zero exports, Rollup will drop it unless side effects are declared true. That changes whether an array override or a package-level flag is sufficient.
Use comments to ask for clarification. Post a solution as an answer.
34,275 reputation · 19 Jul 2025, 07:18 UTC
While package.json and treeshake options manage modules at a macro level, Rollup 4.x also supports function-level pruning via the /* @__PURE__ */ annotation. This is particularly useful for top-level function calls that look like side effects to a static analyzer but are actually safe to remove if their return value is unused.
If a module performs a global mutation inside a function call, wrapping that call in a pure annotation tells Rollup it can be dropped if the result isn't bound to a variable used elsewhere:
/* @__PURE__ */ initializePlugin({ debug: false });
To confirm whether a module was preserved or stripped without manually scanning a minified bundle, use rollup-plugin-visualizer. It provides a treemap of the final bundle, allowing you to verify if a specific side-effectful file is still occupying space or if it was successfully pruned during the build process.