Skip to content

fix(ssr)!: don't access Object variable in ssr transformed code #19996

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
"typescript": "~5.7.2",
"typescript-eslint": "^8.32.1",
"vite": "workspace:*",
"vitest": "^3.1.4"
"vitest": "3.2.0-beta.3"
},
"simple-git-hooks": {
"pre-commit": "pnpm exec lint-staged --concurrent false"
Expand Down
1 change: 1 addition & 0 deletions packages/vite/src/module-runner/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export const ssrModuleExportsKey = `__vite_ssr_exports__`
export const ssrImportKey = `__vite_ssr_import__`
export const ssrDynamicImportKey = `__vite_ssr_dynamic_import__`
export const ssrExportAllKey = `__vite_ssr_exportAll__`
export const ssrExportNameKey = `__vite_ssr_exportName__`
export const ssrImportMetaKey = `__vite_ssr_import_meta__`
3 changes: 3 additions & 0 deletions packages/vite/src/module-runner/esmEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
import {
ssrDynamicImportKey,
ssrExportAllKey,
ssrExportNameKey,
ssrImportKey,
ssrImportMetaKey,
ssrModuleExportsKey,
Expand All @@ -25,6 +26,7 @@ export class ESModulesEvaluator implements ModuleEvaluator {
ssrImportKey,
ssrDynamicImportKey,
ssrExportAllKey,
ssrExportNameKey,
// source map should already be inlined by Vite
'"use strict";' + code,
)
Expand All @@ -35,6 +37,7 @@ export class ESModulesEvaluator implements ModuleEvaluator {
context[ssrImportKey],
context[ssrDynamicImportKey],
context[ssrExportAllKey],
context[ssrExportNameKey],
)

Object.seal(context[ssrModuleExportsKey])
Expand Down
7 changes: 7 additions & 0 deletions packages/vite/src/module-runner/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
import {
ssrDynamicImportKey,
ssrExportAllKey,
ssrExportNameKey,
ssrImportKey,
ssrImportMetaKey,
ssrModuleExportsKey,
Expand Down Expand Up @@ -405,6 +406,12 @@ export class ModuleRunner {
[ssrDynamicImportKey]: dynamicRequest,
[ssrModuleExportsKey]: exports,
[ssrExportAllKey]: (obj: any) => exportAll(exports, obj),
[ssrExportNameKey]: (name, getter) =>
Object.defineProperty(exports, name, {
enumerable: true,
configurable: true,
get: getter,
}),
[ssrImportMetaKey]: meta,
}

Expand Down
2 changes: 2 additions & 0 deletions packages/vite/src/module-runner/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type { EvaluatedModuleNode, EvaluatedModules } from './evaluatedModules'
import type {
ssrDynamicImportKey,
ssrExportAllKey,
ssrExportNameKey,
ssrImportKey,
ssrImportMetaKey,
ssrModuleExportsKey,
Expand All @@ -38,6 +39,7 @@ export interface ModuleRunnerContext {
options?: ImportCallOptions,
) => Promise<any>
[ssrExportAllKey]: (obj: any) => void
[ssrExportNameKey]: (name: string, getter: () => unknown) => void
[ssrImportMetaKey]: ModuleRunnerImportMeta
}

Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/node/ssr/__tests__/ssrLoadModule.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ test('json', async () => {
null,
'/test.json',
)
expect(json?.code.length).toMatchInlineSnapshot(`225`)
expect(json?.code.length).toMatchInlineSnapshot(`165`)
})

test('file url', async () => {
Expand Down
70 changes: 35 additions & 35 deletions packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ test('export function declaration', async () => {
await ssrTransformSimpleCode(`export function foo() {}`),
).toMatchInlineSnapshot(
`
"Object.defineProperty(__vite_ssr_exports__, "foo", { enumerable: true, configurable: true, get(){ try { return foo } catch {} }});
"__vite_ssr_exportName__("foo", () => { try { return foo } catch {} });
function foo() {}"
`,
)
Expand All @@ -78,7 +78,7 @@ test('export class declaration', async () => {
await ssrTransformSimpleCode(`export class foo {}`),
).toMatchInlineSnapshot(
`
"Object.defineProperty(__vite_ssr_exports__, "foo", { enumerable: true, configurable: true, get(){ try { return foo } catch {} }});
"__vite_ssr_exportName__("foo", () => { try { return foo } catch {} });
class foo {}"
`,
)
Expand All @@ -89,8 +89,8 @@ test('export var declaration', async () => {
await ssrTransformSimpleCode(`export const a = 1, b = 2`),
).toMatchInlineSnapshot(
`
"Object.defineProperty(__vite_ssr_exports__, "a", { enumerable: true, configurable: true, get(){ try { return a } catch {} }});
Object.defineProperty(__vite_ssr_exports__, "b", { enumerable: true, configurable: true, get(){ try { return b } catch {} }});
"__vite_ssr_exportName__("a", () => { try { return a } catch {} });
__vite_ssr_exportName__("b", () => { try { return b } catch {} });
const a = 1, b = 2"
`,
)
Expand All @@ -101,8 +101,8 @@ test('export named', async () => {
await ssrTransformSimpleCode(`const a = 1, b = 2; export { a, b as c }`),
).toMatchInlineSnapshot(
`
"Object.defineProperty(__vite_ssr_exports__, "a", { enumerable: true, configurable: true, get(){ try { return a } catch {} }});
Object.defineProperty(__vite_ssr_exports__, "c", { enumerable: true, configurable: true, get(){ try { return b } catch {} }});
"__vite_ssr_exportName__("a", () => { try { return a } catch {} });
__vite_ssr_exportName__("c", () => { try { return b } catch {} });
const a = 1, b = 2; "
`,
)
Expand All @@ -113,8 +113,8 @@ test('export named from', async () => {
await ssrTransformSimpleCode(`export { ref, computed as c } from 'vue'`),
).toMatchInlineSnapshot(
`
"Object.defineProperty(__vite_ssr_exports__, "ref", { enumerable: true, configurable: true, get(){ try { return __vite_ssr_import_0__.ref } catch {} }});
Object.defineProperty(__vite_ssr_exports__, "c", { enumerable: true, configurable: true, get(){ try { return __vite_ssr_import_0__.computed } catch {} }});
"__vite_ssr_exportName__("ref", () => { try { return __vite_ssr_import_0__.ref } catch {} });
__vite_ssr_exportName__("c", () => { try { return __vite_ssr_import_0__.computed } catch {} });
const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["ref","computed"]});
"
`,
Expand All @@ -128,7 +128,7 @@ test('named exports of imported binding', async () => {
),
).toMatchInlineSnapshot(
`
"Object.defineProperty(__vite_ssr_exports__, "createApp", { enumerable: true, configurable: true, get(){ try { return __vite_ssr_import_0__.createApp } catch {} }});
"__vite_ssr_exportName__("createApp", () => { try { return __vite_ssr_import_0__.createApp } catch {} });
const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["createApp"]});
"
`,
Expand All @@ -155,7 +155,7 @@ test('export * as from', async () => {
await ssrTransformSimpleCode(`export * as foo from 'vue'`),
).toMatchInlineSnapshot(
`
"Object.defineProperty(__vite_ssr_exports__, "foo", { enumerable: true, configurable: true, get(){ try { return __vite_ssr_import_0__ } catch {} }});
"__vite_ssr_exportName__("foo", () => { try { return __vite_ssr_import_0__ } catch {} });
const __vite_ssr_import_0__ = await __vite_ssr_import__("vue");
"
`,
Expand All @@ -169,7 +169,7 @@ import * as foo from 'foo'
export * as foo from 'foo'
`),
).toMatchInlineSnapshot(`
"Object.defineProperty(__vite_ssr_exports__, "foo", { enumerable: true, configurable: true, get(){ try { return __vite_ssr_import_1__ } catch {} }});
"__vite_ssr_exportName__("foo", () => { try { return __vite_ssr_import_1__ } catch {} });
const __vite_ssr_import_0__ = await __vite_ssr_import__("foo");
const __vite_ssr_import_1__ = await __vite_ssr_import__("foo");

Expand All @@ -183,7 +183,7 @@ import { foo } from 'foo'
export { foo } from 'foo'
`),
).toMatchInlineSnapshot(`
"Object.defineProperty(__vite_ssr_exports__, "foo", { enumerable: true, configurable: true, get(){ try { return __vite_ssr_import_1__.foo } catch {} }});
"__vite_ssr_exportName__("foo", () => { try { return __vite_ssr_import_1__.foo } catch {} });
const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["foo"]});
const __vite_ssr_import_1__ = await __vite_ssr_import__("foo", {"importedNames":["foo"]});

Expand All @@ -197,7 +197,7 @@ import { foo } from 'foo'
export { foo as foo } from 'foo'
`),
).toMatchInlineSnapshot(`
"Object.defineProperty(__vite_ssr_exports__, "foo", { enumerable: true, configurable: true, get(){ try { return __vite_ssr_import_1__.foo } catch {} }});
"__vite_ssr_exportName__("foo", () => { try { return __vite_ssr_import_1__.foo } catch {} });
const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["foo"]});
const __vite_ssr_import_1__ = await __vite_ssr_import__("foo", {"importedNames":["foo"]});

Expand All @@ -211,7 +211,7 @@ test('export * as from arbitrary module namespace identifier', async () => {
await ssrTransformSimpleCode(`export * as "arbitrary string" from 'vue'`),
).toMatchInlineSnapshot(
`
"Object.defineProperty(__vite_ssr_exports__, "arbitrary string", { enumerable: true, configurable: true, get(){ try { return __vite_ssr_import_0__ } catch {} }});
"__vite_ssr_exportName__("arbitrary string", () => { try { return __vite_ssr_import_0__ } catch {} });
const __vite_ssr_import_0__ = await __vite_ssr_import__("vue");
"
`,
Expand All @@ -225,7 +225,7 @@ test('export as arbitrary module namespace identifier', async () => {
),
).toMatchInlineSnapshot(
`
"Object.defineProperty(__vite_ssr_exports__, "arbitrary string", { enumerable: true, configurable: true, get(){ try { return something } catch {} }});
"__vite_ssr_exportName__("arbitrary string", () => { try { return something } catch {} });
const something = "Something";"
`,
)
Expand All @@ -238,7 +238,7 @@ test('export as from arbitrary module namespace identifier', async () => {
),
).toMatchInlineSnapshot(
`
"Object.defineProperty(__vite_ssr_exports__, "arbitrary string", { enumerable: true, configurable: true, get(){ try { return __vite_ssr_import_0__["arbitrary string2"] } catch {} }});
"__vite_ssr_exportName__("arbitrary string", () => { try { return __vite_ssr_import_0__["arbitrary string2"] } catch {} });
const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["arbitrary string2"]});
"
`,
Expand All @@ -248,7 +248,7 @@ test('export as from arbitrary module namespace identifier', async () => {
test('export default', async () => {
expect(await ssrTransformSimpleCode(`export default {}`))
.toMatchInlineSnapshot(`
"Object.defineProperty(__vite_ssr_exports__, "default", { enumerable: true, configurable: true, get(){ try { return __vite_ssr_export_default__ } catch {} }});
"__vite_ssr_exportName__("default", () => { try { return __vite_ssr_export_default__ } catch {} });
const __vite_ssr_export_default__ = {}"
`)
})
Expand Down Expand Up @@ -306,7 +306,7 @@ test('dynamic import', async () => {
)
expect(result?.code).toMatchInlineSnapshot(
`
"Object.defineProperty(__vite_ssr_exports__, "i", { enumerable: true, configurable: true, get(){ try { return i } catch {} }});
"__vite_ssr_exportName__("i", () => { try { return i } catch {} });
const i = () => __vite_ssr_dynamic_import__('./foo')"
`,
)
Expand Down Expand Up @@ -506,8 +506,8 @@ test('should declare variable for imported super class', async () => {
`export class B extends Foo {}`,
),
).toMatchInlineSnapshot(`
"Object.defineProperty(__vite_ssr_exports__, "default", { enumerable: true, configurable: true, get(){ try { return A } catch {} }});
Object.defineProperty(__vite_ssr_exports__, "B", { enumerable: true, configurable: true, get(){ try { return B } catch {} }});
"__vite_ssr_exportName__("default", () => { try { return A } catch {} });
__vite_ssr_exportName__("B", () => { try { return B } catch {} });
const __vite_ssr_import_0__ = await __vite_ssr_import__("./dependency", {"importedNames":["Foo"]});
const Foo = __vite_ssr_import_0__.Foo;
class A extends Foo {};
Expand All @@ -520,14 +520,14 @@ test('should handle default export variants', async () => {
// default anonymous functions
expect(await ssrTransformSimpleCode(`export default function() {}\n`))
.toMatchInlineSnapshot(`
"Object.defineProperty(__vite_ssr_exports__, "default", { enumerable: true, configurable: true, get(){ try { return __vite_ssr_export_default__ } catch {} }});
"__vite_ssr_exportName__("default", () => { try { return __vite_ssr_export_default__ } catch {} });
const __vite_ssr_export_default__ = function() {}
"
`)
// default anonymous class
expect(await ssrTransformSimpleCode(`export default class {}\n`))
.toMatchInlineSnapshot(`
"Object.defineProperty(__vite_ssr_exports__, "default", { enumerable: true, configurable: true, get(){ try { return __vite_ssr_export_default__ } catch {} }});
"__vite_ssr_exportName__("default", () => { try { return __vite_ssr_export_default__ } catch {} });
const __vite_ssr_export_default__ = class {}
"
`)
Expand All @@ -538,7 +538,7 @@ test('should handle default export variants', async () => {
`foo.prototype = Object.prototype;`,
),
).toMatchInlineSnapshot(`
"Object.defineProperty(__vite_ssr_exports__, "default", { enumerable: true, configurable: true, get(){ try { return foo } catch {} }});
"__vite_ssr_exportName__("default", () => { try { return foo } catch {} });
function foo() {};
foo.prototype = Object.prototype;"
`)
Expand All @@ -548,8 +548,8 @@ test('should handle default export variants', async () => {
`export default class A {}\n` + `export class B extends A {}`,
),
).toMatchInlineSnapshot(`
"Object.defineProperty(__vite_ssr_exports__, "default", { enumerable: true, configurable: true, get(){ try { return A } catch {} }});
Object.defineProperty(__vite_ssr_exports__, "B", { enumerable: true, configurable: true, get(){ try { return B } catch {} }});
"__vite_ssr_exportName__("default", () => { try { return A } catch {} });
__vite_ssr_exportName__("B", () => { try { return B } catch {} });
class A {};
class B extends A {}"
`)
Expand Down Expand Up @@ -1001,8 +1001,8 @@ export function fn1() {
`,
),
).toMatchInlineSnapshot(`
"Object.defineProperty(__vite_ssr_exports__, "fn1", { enumerable: true, configurable: true, get(){ try { return fn1 } catch {} }});
Object.defineProperty(__vite_ssr_exports__, "fn2", { enumerable: true, configurable: true, get(){ try { return fn2 } catch {} }});
"__vite_ssr_exportName__("fn1", () => { try { return fn1 } catch {} });
__vite_ssr_exportName__("fn2", () => { try { return fn2 } catch {} });

function fn1() {
};function fn2() {
Expand All @@ -1024,15 +1024,15 @@ export default (function getRandom() {
`.trim()

expect(await ssrTransformSimpleCode(code)).toMatchInlineSnapshot(`
"Object.defineProperty(__vite_ssr_exports__, "default", { enumerable: true, configurable: true, get(){ try { return __vite_ssr_export_default__ } catch {} }});
"__vite_ssr_exportName__("default", () => { try { return __vite_ssr_export_default__ } catch {} });
const __vite_ssr_export_default__ = (function getRandom() {
return Math.random();
});"
`)

expect(await ssrTransformSimpleCode(`export default (class A {});`))
.toMatchInlineSnapshot(`
"Object.defineProperty(__vite_ssr_exports__, "default", { enumerable: true, configurable: true, get(){ try { return __vite_ssr_export_default__ } catch {} }});
"__vite_ssr_exportName__("default", () => { try { return __vite_ssr_export_default__ } catch {} });
const __vite_ssr_export_default__ = (class A {});"
`)
})
Expand Down Expand Up @@ -1110,7 +1110,7 @@ export class Test {
};`.trim()

expect(await ssrTransformSimpleCode(code)).toMatchInlineSnapshot(`
"Object.defineProperty(__vite_ssr_exports__, "Test", { enumerable: true, configurable: true, get(){ try { return Test } catch {} }});
"__vite_ssr_exportName__("Test", () => { try { return Test } catch {} });
const __vite_ssr_import_0__ = await __vite_ssr_import__("foobar", {"importedNames":["foo","bar"]});

if (false) {
Expand Down Expand Up @@ -1309,8 +1309,8 @@ export * as bar from './bar'
console.log(bar)
`),
).toMatchInlineSnapshot(`
"Object.defineProperty(__vite_ssr_exports__, "default", { enumerable: true, configurable: true, get(){ try { return __vite_ssr_export_default__ } catch {} }});
Object.defineProperty(__vite_ssr_exports__, "bar", { enumerable: true, configurable: true, get(){ try { return __vite_ssr_import_1__ } catch {} }});
"__vite_ssr_exportName__("default", () => { try { return __vite_ssr_export_default__ } catch {} });
__vite_ssr_exportName__("bar", () => { try { return __vite_ssr_import_1__ } catch {} });
const __vite_ssr_import_0__ = await __vite_ssr_import__("./foo", {"importedNames":["foo"]});
const __vite_ssr_import_1__ = await __vite_ssr_import__("./bar");
;
Expand Down Expand Up @@ -1585,9 +1585,9 @@ import("e")
export * as A from "a";
`)
expect(result?.code).toMatchInlineSnapshot(`
"Object.defineProperty(__vite_ssr_exports__, "b", { enumerable: true, configurable: true, get(){ try { return __vite_ssr_import_1__.b } catch {} }});
Object.defineProperty(__vite_ssr_exports__, "d", { enumerable: true, configurable: true, get(){ try { return __vite_ssr_import_3__ } catch {} }});
Object.defineProperty(__vite_ssr_exports__, "A", { enumerable: true, configurable: true, get(){ try { return __vite_ssr_import_4__ } catch {} }});
"__vite_ssr_exportName__("b", () => { try { return __vite_ssr_import_1__.b } catch {} });
__vite_ssr_exportName__("d", () => { try { return __vite_ssr_import_3__ } catch {} });
__vite_ssr_exportName__("A", () => { try { return __vite_ssr_import_4__ } catch {} });
const __vite_ssr_import_0__ = await __vite_ssr_import__("a", {"importedNames":["default"]});
const __vite_ssr_import_1__ = await __vite_ssr_import__("b", {"importedNames":["b"]});
const __vite_ssr_import_2__ = await __vite_ssr_import__("c");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const Object = "my-object";
export { Object };
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,15 @@ describe('module runner initialization', async () => {
`,
)
})

it(`handle Object variable`, async ({ runner }) => {
const mod = await runner.import('/fixtures/top-level-object.js')
expect(mod).toMatchInlineSnapshot(`
{
"Object": "my-object",
}
`)
})
})

describe('optimize-deps', async () => {
Expand Down
4 changes: 2 additions & 2 deletions packages/vite/src/node/ssr/ssrTransform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const ssrModuleExportsKey = `__vite_ssr_exports__`
export const ssrImportKey = `__vite_ssr_import__`
export const ssrDynamicImportKey = `__vite_ssr_dynamic_import__`
export const ssrExportAllKey = `__vite_ssr_exportAll__`
export const ssrExportNameKey = `__vite_ssr_exportName__`
export const ssrImportMetaKey = `__vite_ssr_import_meta__`

const hashbangRE = /^#!.*\n/
Expand Down Expand Up @@ -156,8 +157,7 @@ async function ssrTransformScript(
// wrap with try/catch to fallback to `undefined` for backward compat.
s.appendLeft(
fileStartIndex,
`Object.defineProperty(${ssrModuleExportsKey}, ${JSON.stringify(name)}, ` +
`{ enumerable: true, configurable: true, get(){ try { return ${local} } catch {} }});\n`,
`${ssrExportNameKey}(${JSON.stringify(name)}, () => { try { return ${local} } catch {} });\n`,
)
}

Expand Down
Loading