Skip to content

Commit 5e6fb9f

Browse files
fix: reset variable render names between outputs in the same generate (#6350)
When the same input bundle is generated for multiple outputs in one `output` array, `Variable.renderBaseName` set during a chunked output's import deconfliction (e.g. `vendor` for `_` imported from a chunk) was left on the shared `Variable` instance. The next output's `deconflictTopLevelVariables` then skipped that variable because of the existing `renderBaseName`, so the function declaration was rendered as `function vendor._(...)` — invalid JavaScript. Reset `renderBaseName`/`renderName` for the chunk's top-level variables at the start of `setIdentifierRenderResolutions`, before either the exports loop or `deconflictChunk` reassigns them. Co-authored-by: Lukas Taegert-Atkinson <lukastaegert@users.noreply.github.com>
1 parent 7542834 commit 5e6fb9f

2 files changed

Lines changed: 45 additions & 0 deletions

File tree

src/Chunk.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1454,6 +1454,16 @@ export default class Chunk {
14541454
preserveModules,
14551455
externalLiveBindings
14561456
} = this.outputOptions;
1457+
// Reset stale render names from previous output renderings of the same
1458+
// module graph. Without this, variables that were renamed during a prior
1459+
// output's import deconfliction (e.g. given a chunk-prefixed
1460+
// `renderBaseName` like `vendor`) would carry that name into the next
1461+
// output, producing invalid identifiers such as `function vendor.foo()`.
1462+
for (const module of this.orderedModules) {
1463+
for (const variable of module.scope.variables.values()) {
1464+
variable.setRenderNames(null, null);
1465+
}
1466+
}
14571467
const syntheticExports = new Set<SyntheticNamedExportVariable>();
14581468
for (const exportName of this.getExportNames()) {
14591469
const exportVariable = this.exportsByName.get(exportName)!;

test/misc/misc.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,41 @@ describe('misc', () => {
209209
])
210210
));
211211

212+
it('does not leak chunk-prefixed render names from one output into another (#6296)', async () => {
213+
const bundle = await rollup.rollup({
214+
input: 'main',
215+
plugins: [
216+
loader({
217+
main: `import { _ } from 'helper';\nconsole.log(_);`,
218+
helper: `export function _(target, property) { return target[property]; }`
219+
})
220+
]
221+
});
222+
223+
// Generating the chunked CJS first sets `renderBaseName` on the `_`
224+
// variable to the chunk name (`vendor`). Without resetting render names
225+
// at the start of each output, the subsequent UMD generate would emit
226+
// `function vendor._(...)`, which is invalid JavaScript.
227+
const cjs = await bundle.generate({
228+
format: 'cjs',
229+
dir: 'dist',
230+
manualChunks: id => (id === 'helper' ? 'vendor' : undefined)
231+
});
232+
assert.ok(
233+
cjs.output.some(chunk => chunk.fileName.startsWith('vendor')),
234+
'CJS output should contain a vendor chunk'
235+
);
236+
237+
const umd = await bundle.generate({ format: 'umd', name: 'main' });
238+
const umdCode = umd.output[0].code;
239+
assert.ok(
240+
!/function\s+vendor\./.test(umdCode),
241+
`UMD output should not contain a dotted function declaration:\n${umdCode}`
242+
);
243+
// And the function should still parse — eval the wrapper to verify.
244+
assert.doesNotThrow(() => new Function(umdCode), 'UMD output should be valid JavaScript');
245+
});
246+
212247
it('allows passing the same object to `rollup` and `generate`', () => {
213248
const options = {
214249
input: 'input',

0 commit comments

Comments
 (0)