Skip to content

SSR transform: imported binding used as a computed key in a defaulted destructured parameter is not rewritten (ReferenceError) #23232

Description

@webbertakken

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions