#4536·esbuild

Minify does not DCE unused bindings in webpack-concatenated ESM barrel (terser does)

Author: AcongmCreated Sep 4, 2026Updated Sep 4, 2026

Summary When webpack 5 concatenates a large ESM barrel (e.g. @remixicon/[email protected] index.mjs, ~2.4 MB, 3188 named exports, "sideEffects": false) and uses esbuild as the production minifier, unused icon components are not dead-code-eliminated. The same pipeline with terser as the minifier removes them.

This matches the documented webpack behavior that optimization.usedExports relies on the minifier for statement-level DCE, and prior discussion in #639 / esbuild-loader#178 that esbuild minify only removes unused top-level bindings in bundle mode — which does not apply when webpack wraps/concatenates modules.

Environment @remixicon/[email protected] (single index.mjs barrel, static named exports) Webpack 5.88.2 (via Umi 4.6 umi build) esbuild used as minifier only (jsMinifier: 'esbuild', Umi 4 default) Consumer imports one named export transitively: import { RiLoader4Line } from '@remixicon/react' (from @backstage/ui compiled ESM) Observed behavior Webpack usedExports is correct for the concatenated module: SET(1): RiLoader4Line. Webpack still emits the entire concatenated index.mjs into the chunk (CONCATENATED MODULE); DCE is deferred to the minifier. esbuild minify leaves all 3188 icon factories in the production entry (e.g. unused RiZzzLine path data still present; remixicon className count = 3188). Entry chunk: ~3.9 MB / 923 KB gzip. Switching only jsMinifier to 'terser' (webpack's traditional default): unused icons removed, remixicon count drops to ~10, entry 375 KB gzip. Control: esbuild in isolation A direct production bundle of import { RiLoader4Line } from '@remixicon/react' with esbuild (react external) is ~530 B — tree-shaking works when esbuild is the bundler, not merely the minifier after webpack concatenation.

Similarly, isolated Webpack 5.105 + terser for a single RiAddLine import is ~319 B (see backstage/backstage#35398).

Question / request Is improved DCE for webpack-concatenated output in scope for esbuild's minifier, or is the recommended workaround still "use terser when webpack sets usedExports and concatenates large ESM barrels"?

This affects any host using webpack + esbuild-minify (notably Umi 4's default) that pulls in libraries with large single-file ESM barrels and named imports.

Related https://github.com/evanw/esbuild/issues/639 https://github.com/privatenumber/esbuild-loader/issues/178 https://webpack.js.org/guides/tree-shaking/ https://github.com/backstage/backstage/issues/35397 Happy to provide a minimal reproduction repository if that would help.