You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Vite 8 + @vitejs/plugin-legacy with the default build.minify (oxc): browsers that execute the legacy chunks get a white screen at first render when the bundle contains a block-scoped function declaration inside a nested scope (antd 6 / @ant-design/cssinjsparseStyle hits this on every page mount). Modern browsers are unaffected, so the breakage is silent for teams that don't test old browsers.
Legacy chunks originate from ES modules (always strict-mode code) but the SystemJS output carries no "use strict" directive, so they run in sloppy mode.
In sloppy mode, Annex B.3.3 hoists the block-level function declaration to a var-like function-scope binding, which clobbers the outer binding. The forEach callback then reads an undefined binding and throws TypeError.
Additionally the minifier strips "use strict" directives (oxc-project/oxc#25897), so the directive cannot be injected at the babel/renderChunk stage — it must be added after all transforms.
Workaround:build.minify: 'terser' — terser neither creates the collision nor strips directives.
Proposed fix (PR incoming): legacy chunks should keep the strict-mode semantics of the ES modules they derive from. Injecting "use strict"; as the first line of every legacy chunk in generateBundle (after the minifier ran) fixes the crash, restores semantic parity between modern and legacy output, and is independent of the upstream oxc fixes. The emitted sourcemaps only need an empty line prepended to their mappings, which is the exact equivalent of the one-line insertion.
Describe the bug
Vite 8 +
@vitejs/plugin-legacywith the defaultbuild.minify(oxc): browsers that execute the legacy chunks get a white screen at first render when the bundle contains a block-scopedfunctiondeclaration inside a nested scope (antd 6 /@ant-design/cssinjsparseStylehits this on every page mount). Modern browsers are unaffected, so the breakage is silent for teams that don't test old browsers.Three layers stack up:
functiondeclaration and an outer binding to the same name ([minifier] mangler assigns the same name to a block-scoped function declaration and an outer binding — changes semantics under Annex B (sloppy mode) oxc-project/oxc#25896). Harmless under block scoping — which is why modern chunks work."use strict"directive, so they run in sloppy mode.var-like function-scope binding, which clobbers the outer binding. The forEach callback then reads anundefinedbinding and throwsTypeError.Additionally the minifier strips
"use strict"directives (oxc-project/oxc#25897), so the directive cannot be injected at the babel/renderChunk stage — it must be added after all transforms.Reproduction
https://github.com/liruisen/rolldown-legacy-strict-repro
15 lines of plain JS, zero runtime dependencies. The built legacy chunk is byte-identical to the one from our real app (React 19 + antd 6).
Steps to reproduce
pnpm install && pnpm build && pnpm previewLogs
Minified legacy chunk (note
obj→nandfunction appendStyle→n):System Info
Additional context
Workaround:
build.minify: 'terser'— terser neither creates the collision nor strips directives.Proposed fix (PR incoming): legacy chunks should keep the strict-mode semantics of the ES modules they derive from. Injecting
"use strict";as the first line of every legacy chunk ingenerateBundle(after the minifier ran) fixes the crash, restores semantic parity between modern and legacy output, and is independent of the upstream oxc fixes. The emitted sourcemaps only need an empty line prepended to their mappings, which is the exact equivalent of the one-line insertion.