Note: This issue was written by my clanker. While I try to verify its steps as much as possible, it might not be 100% correct.
Describe the bug
Under SSR (ssrLoadModule / vite-node / any SSR build), when an imported binding is used as a computed key inside a destructured function parameter that has a default value (function f({ [KEY]: v } = {}) {}), the SSR transform fails to rewrite the reference to the import namespace. At runtime the identifier is a bare reference and throws ReferenceError: KEY is not defined.
The same code works when the parameter has no default (plain ObjectPattern), and when the destructuring is moved into the function body (const { [KEY]: v } = input). Only the defaulted-parameter form (AssignmentPattern) is affected.
Reproduction
Three files:
// data.js
export const KEY = 'k';
// mod.js
import { KEY } from './data.js';
export function make({ [KEY]: value = null } = {}) {
return { [KEY]: value };
}
// repro.mjs
import { createServer } from 'vite';
const server = await createServer({
root: new URL('.', import.meta.url).pathname,
server: { middlewareMode: true },
appType: 'custom',
logLevel: 'silent',
});
const mod = await server.ssrLoadModule('./mod.js');
console.log(mod.make()); // ReferenceError: KEY is not defined
await server.close();
node repro.mjs -> ReferenceError: KEY is not defined.
Expected
make() returns { k: null }. The computed key KEY should be rewritten to the import namespace access, exactly as it is in every non-parameter position.
Root cause
In the SSR transform's identifier walker (ssrTransform), function parameters are handled two ways:
- A parameter that is directly an
ObjectPattern/ArrayPattern goes through handlePattern, which recurses only into each property's value and correctly ignores computed keys.
- A parameter that is an
AssignmentPattern (i.e. has a default) is instead walked generically, calling setScope(...) for the identifiers it encounters. The only property-key guard there is isStaticPropertyKey(child, parent), which returns true only for non-computed keys. So a computed key ([KEY]) falls through and is registered as a locally-scoped binding.
Once KEY is in the function scope, the later rewrite pass treats it as a local and skips the import rewrite, leaving a bare KEY -> ReferenceError.
Proposed fix
In the parameter walker, skip any property key that is not the property value (both static and computed), mirroring handlePattern. For example, replace the isStaticPropertyKey(child, parent) guard with:
if (parent?.type === 'Property' && parent.key === child && parent.value !== child) return;
This keeps shorthand bindings ({ x } = {}, where key === value) correctly scoped, stops computed keys from being scoped, and lets the normal rewrite handle them. Verified against the minimal repro above (throws before, returns { k: null } after).
System info
- Reproduced at runtime on
vite@7.1.12; the same code path is present unchanged in vite@7.3.6 and vite@8.2.1.
- Node.js 24, SSR (
ssrLoadModule). Also surfaces via vite-node and Vitest.
Note: This issue was written by my clanker. While I try to verify its steps as much as possible, it might not be 100% correct.
Describe the bug
Under SSR (
ssrLoadModule/ vite-node / any SSR build), when an imported binding is used as a computed key inside a destructured function parameter that has a default value (function f({ [KEY]: v } = {}) {}), the SSR transform fails to rewrite the reference to the import namespace. At runtime the identifier is a bare reference and throwsReferenceError: KEY is not defined.The same code works when the parameter has no default (plain
ObjectPattern), and when the destructuring is moved into the function body (const { [KEY]: v } = input). Only the defaulted-parameter form (AssignmentPattern) is affected.Reproduction
Three files:
node repro.mjs->ReferenceError: KEY is not defined.Expected
make()returns{ k: null }. The computed keyKEYshould be rewritten to the import namespace access, exactly as it is in every non-parameter position.Root cause
In the SSR transform's identifier walker (
ssrTransform), function parameters are handled two ways:ObjectPattern/ArrayPatterngoes throughhandlePattern, which recurses only into each property's value and correctly ignores computed keys.AssignmentPattern(i.e. has a default) is instead walked generically, callingsetScope(...)for the identifiers it encounters. The only property-key guard there isisStaticPropertyKey(child, parent), which returnstrueonly for non-computed keys. So a computed key ([KEY]) falls through and is registered as a locally-scoped binding.Once
KEYis in the function scope, the later rewrite pass treats it as a local and skips the import rewrite, leaving a bareKEY->ReferenceError.Proposed fix
In the parameter walker, skip any property key that is not the property value (both static and computed), mirroring
handlePattern. For example, replace theisStaticPropertyKey(child, parent)guard with:This keeps shorthand bindings (
{ x } = {}, wherekey === value) correctly scoped, stops computed keys from being scoped, and lets the normal rewrite handle them. Verified against the minimal repro above (throws before, returns{ k: null }after).System info
vite@7.1.12; the same code path is present unchanged invite@7.3.6andvite@8.2.1.ssrLoadModule). Also surfaces via vite-node and Vitest.