diff --git a/packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts b/packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts index b8f7510d12dfe1..8891b8d5b040b6 100644 --- a/packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts +++ b/packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts @@ -51,48 +51,61 @@ test('namespace import', async () => { }) test('export function declaration', async () => { - expect(await ssrTransformSimpleCode(`export function foo() {}`)) - .toMatchInlineSnapshot(` - "function foo() {} - Object.defineProperty(__vite_ssr_exports__, "foo", { enumerable: true, configurable: true, get(){ return foo }});" - `) + expect( + await ssrTransformSimpleCode(`export function foo() {}`), + ).toMatchInlineSnapshot( + ` + "Object.defineProperty(__vite_ssr_exports__, "foo", { enumerable: true, configurable: true, get(){ return foo }}); + function foo() {}" + `, + ) }) test('export class declaration', async () => { - expect(await ssrTransformSimpleCode(`export class foo {}`)) - .toMatchInlineSnapshot(` - "class foo {} - Object.defineProperty(__vite_ssr_exports__, "foo", { enumerable: true, configurable: true, get(){ return foo }});" - `) + expect( + await ssrTransformSimpleCode(`export class foo {}`), + ).toMatchInlineSnapshot( + ` + "Object.defineProperty(__vite_ssr_exports__, "foo", { enumerable: true, configurable: true, get(){ return foo }}); + class foo {}" + `, + ) }) test('export var declaration', async () => { - expect(await ssrTransformSimpleCode(`export const a = 1, b = 2`)) - .toMatchInlineSnapshot(` - "const a = 1, b = 2 - Object.defineProperty(__vite_ssr_exports__, "a", { enumerable: true, configurable: true, get(){ return a }}); - Object.defineProperty(__vite_ssr_exports__, "b", { enumerable: true, configurable: true, get(){ return b }});" - `) + expect( + await ssrTransformSimpleCode(`export const a = 1, b = 2`), + ).toMatchInlineSnapshot( + ` + "Object.defineProperty(__vite_ssr_exports__, "a", { enumerable: true, configurable: true, get(){ return a }}); + Object.defineProperty(__vite_ssr_exports__, "b", { enumerable: true, configurable: true, get(){ return b }}); + const a = 1, b = 2" + `, + ) }) test('export named', async () => { expect( await ssrTransformSimpleCode(`const a = 1, b = 2; export { a, b as c }`), - ).toMatchInlineSnapshot(` - "const a = 1, b = 2; - Object.defineProperty(__vite_ssr_exports__, "a", { enumerable: true, configurable: true, get(){ return a }}); - Object.defineProperty(__vite_ssr_exports__, "c", { enumerable: true, configurable: true, get(){ return b }});" - `) + ).toMatchInlineSnapshot( + ` + "Object.defineProperty(__vite_ssr_exports__, "a", { enumerable: true, configurable: true, get(){ return a }}); + Object.defineProperty(__vite_ssr_exports__, "c", { enumerable: true, configurable: true, get(){ return b }}); + const a = 1, b = 2; " + `, + ) }) test('export named from', async () => { expect( await ssrTransformSimpleCode(`export { ref, computed as c } from 'vue'`), - ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["ref","computed"]}); - Object.defineProperty(__vite_ssr_exports__, "ref", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__.ref }}); - Object.defineProperty(__vite_ssr_exports__, "c", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__.computed }});" - `) + ).toMatchInlineSnapshot( + ` + "Object.defineProperty(__vite_ssr_exports__, "ref", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__.ref }}); + Object.defineProperty(__vite_ssr_exports__, "c", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__.computed }}); + const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["ref","computed"]});" + `, + ) }) test('named exports of imported binding', async () => { @@ -100,10 +113,12 @@ test('named exports of imported binding', async () => { await ssrTransformSimpleCode( `import {createApp} from 'vue';export {createApp}`, ), - ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["createApp"]}); - Object.defineProperty(__vite_ssr_exports__, "createApp", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__.createApp }});" - `) + ).toMatchInlineSnapshot( + ` + "Object.defineProperty(__vite_ssr_exports__, "createApp", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__.createApp }}); + const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["createApp"]});" + `, + ) }) test('export * from', async () => { @@ -120,11 +135,14 @@ test('export * from', async () => { }) test('export * as from', async () => { - expect(await ssrTransformSimpleCode(`export * as foo from 'vue'`)) - .toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue"); - Object.defineProperty(__vite_ssr_exports__, "foo", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__ }});" - `) + expect( + await ssrTransformSimpleCode(`export * as foo from 'vue'`), + ).toMatchInlineSnapshot( + ` + "Object.defineProperty(__vite_ssr_exports__, "foo", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__ }}); + const __vite_ssr_import_0__ = await __vite_ssr_import__("vue");" + `, + ) }) test('re-export by imported name', async () => { @@ -134,9 +152,9 @@ import * as foo from 'foo' export * as foo from 'foo' `), ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("foo"); + "Object.defineProperty(__vite_ssr_exports__, "foo", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_1__ }}); + const __vite_ssr_import_0__ = await __vite_ssr_import__("foo"); const __vite_ssr_import_1__ = await __vite_ssr_import__("foo"); - Object.defineProperty(__vite_ssr_exports__, "foo", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_1__ }}); " `) @@ -146,11 +164,11 @@ import { foo } from 'foo' export { foo } from 'foo' `), ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["foo"]}); - const __vite_ssr_import_1__ = await __vite_ssr_import__("foo", {"importedNames":["foo"]}); - Object.defineProperty(__vite_ssr_exports__, "foo", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_1__.foo }}); - " - `) + "Object.defineProperty(__vite_ssr_exports__, "foo", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_1__.foo }}); + const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["foo"]}); + const __vite_ssr_import_1__ = await __vite_ssr_import__("foo", {"importedNames":["foo"]}); + " + `) expect( await ssrTransformSimpleCode(`\ @@ -158,20 +176,22 @@ import { foo } from 'foo' export { foo as foo } from 'foo' `), ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["foo"]}); - const __vite_ssr_import_1__ = await __vite_ssr_import__("foo", {"importedNames":["foo"]}); - Object.defineProperty(__vite_ssr_exports__, "foo", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_1__.foo }}); - " - `) + "Object.defineProperty(__vite_ssr_exports__, "foo", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_1__.foo }}); + const __vite_ssr_import_0__ = await __vite_ssr_import__("foo", {"importedNames":["foo"]}); + const __vite_ssr_import_1__ = await __vite_ssr_import__("foo", {"importedNames":["foo"]}); + " + `) }) test('export * as from arbitrary module namespace identifier', async () => { expect( await ssrTransformSimpleCode(`export * as "arbitrary string" from 'vue'`), - ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue"); - Object.defineProperty(__vite_ssr_exports__, "arbitrary string", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__ }});" - `) + ).toMatchInlineSnapshot( + ` + "Object.defineProperty(__vite_ssr_exports__, "arbitrary string", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__ }}); + const __vite_ssr_import_0__ = await __vite_ssr_import__("vue");" + `, + ) }) test('export as arbitrary module namespace identifier', async () => { @@ -179,10 +199,12 @@ test('export as arbitrary module namespace identifier', async () => { await ssrTransformSimpleCode( `const something = "Something";export { something as "arbitrary string" };`, ), - ).toMatchInlineSnapshot(` - "const something = "Something"; - Object.defineProperty(__vite_ssr_exports__, "arbitrary string", { enumerable: true, configurable: true, get(){ return something }});" - `) + ).toMatchInlineSnapshot( + ` + "Object.defineProperty(__vite_ssr_exports__, "arbitrary string", { enumerable: true, configurable: true, get(){ return something }}); + const something = "Something";" + `, + ) }) test('export as from arbitrary module namespace identifier', async () => { @@ -190,10 +212,12 @@ test('export as from arbitrary module namespace identifier', async () => { await ssrTransformSimpleCode( `export { "arbitrary string2" as "arbitrary string" } from 'vue';`, ), - ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["arbitrary string2"]}); - Object.defineProperty(__vite_ssr_exports__, "arbitrary string", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__["arbitrary string2"] }});" - `) + ).toMatchInlineSnapshot( + ` + "Object.defineProperty(__vite_ssr_exports__, "arbitrary string", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__["arbitrary string2"] }}); + const __vite_ssr_import_0__ = await __vite_ssr_import__("vue", {"importedNames":["arbitrary string2"]});" + `, + ) }) test('export default', async () => { @@ -319,10 +343,12 @@ test('dynamic import', async () => { const result = await ssrTransformSimple( `export const i = () => import('./foo')`, ) - expect(result?.code).toMatchInlineSnapshot(` - "const i = () => __vite_ssr_dynamic_import__('./foo') - Object.defineProperty(__vite_ssr_exports__, "i", { enumerable: true, configurable: true, get(){ return i }});" - `) + expect(result?.code).toMatchInlineSnapshot( + ` + "Object.defineProperty(__vite_ssr_exports__, "i", { enumerable: true, configurable: true, get(){ return i }}); + const i = () => __vite_ssr_dynamic_import__('./foo')" + `, + ) expect(result?.deps).toEqual([]) expect(result?.dynamicDeps).toEqual(['./foo']) }) @@ -479,10 +505,10 @@ test('should declare variable for imported super class', async () => { `export class B extends Foo {}`, ), ).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("./dependency", {"importedNames":["Foo"]});const Foo = __vite_ssr_import_0__.Foo; + "Object.defineProperty(__vite_ssr_exports__, "B", { enumerable: true, configurable: true, get(){ return B }}); + const __vite_ssr_import_0__ = await __vite_ssr_import__("./dependency", {"importedNames":["Foo"]});const Foo = __vite_ssr_import_0__.Foo; class A extends Foo {}; class B extends Foo {} - Object.defineProperty(__vite_ssr_exports__, "B", { enumerable: true, configurable: true, get(){ return B }}); Object.defineProperty(__vite_ssr_exports__, "default", { enumerable: true, configurable: true, value: A });" `) }) @@ -518,9 +544,9 @@ test('should handle default export variants', async () => { `export default class A {}\n` + `export class B extends A {}`, ), ).toMatchInlineSnapshot(` - "class A {}; + "Object.defineProperty(__vite_ssr_exports__, "B", { enumerable: true, configurable: true, get(){ return B }}); + class A {}; class B extends A {} - Object.defineProperty(__vite_ssr_exports__, "B", { enumerable: true, configurable: true, get(){ return B }}); Object.defineProperty(__vite_ssr_exports__, "default", { enumerable: true, configurable: true, value: A });" `) }) @@ -958,12 +984,12 @@ export function fn1() { `, ), ).toMatchInlineSnapshot(` - " + "Object.defineProperty(__vite_ssr_exports__, "fn1", { enumerable: true, configurable: true, get(){ return fn1 }}); + Object.defineProperty(__vite_ssr_exports__, "fn2", { enumerable: true, configurable: true, get(){ return fn2 }}); + function fn1() { + };function fn2() { } - Object.defineProperty(__vite_ssr_exports__, "fn1", { enumerable: true, configurable: true, get(){ return fn1 }});;function fn2() { - } - Object.defineProperty(__vite_ssr_exports__, "fn2", { enumerable: true, configurable: true, get(){ return fn2 }}); " `) }) @@ -1062,7 +1088,8 @@ export class Test { };`.trim() expect(await ssrTransformSimpleCode(code)).toMatchInlineSnapshot(` - "const __vite_ssr_import_0__ = await __vite_ssr_import__("foobar", {"importedNames":["foo","bar"]}); + "Object.defineProperty(__vite_ssr_exports__, "Test", { enumerable: true, configurable: true, get(){ return Test }}); + const __vite_ssr_import_0__ = await __vite_ssr_import__("foobar", {"importedNames":["foo","bar"]}); if (false) { const foo = 'foo'; console.log(foo) @@ -1086,8 +1113,7 @@ export class Test { console.log((0,__vite_ssr_import_0__.bar)) } } - } - Object.defineProperty(__vite_ssr_exports__, "Test", { enumerable: true, configurable: true, get(){ return Test }});;;" + };;" `) }) @@ -1252,11 +1278,11 @@ export * as bar from './bar' console.log(bar) `), ).toMatchInlineSnapshot(` - " + "Object.defineProperty(__vite_ssr_exports__, "bar", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_1__ }}); + const __vite_ssr_import_0__ = await __vite_ssr_import__("./foo", {"importedNames":["foo"]}); __vite_ssr_exports__.default = (0,__vite_ssr_import_0__.foo)(); - const __vite_ssr_import_1__ = await __vite_ssr_import__("./bar"); - Object.defineProperty(__vite_ssr_exports__, "bar", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_1__ }});; + const __vite_ssr_import_1__ = await __vite_ssr_import__("./bar");; console.log(bar) " `) diff --git a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/README.md b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/README.md new file mode 100644 index 00000000000000..c48be22eee9283 --- /dev/null +++ b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/README.md @@ -0,0 +1,22 @@ +- `test1` - `test5` + +Cyclic import example based on https://github.com/vitejs/vite/issues/14048#issuecomment-2354774156 + +```mermaid +flowchart TD + B(dep1.js) -->|dep1| A(index.js) + A -->|dep1| C(dep2.js) + C -->|dep2| A + A -->|dep1, dep2| entry.js +``` + +--- + +- `test6` + +```mermaid +flowchart TD + A(dep1.js) -->|dep1| B + B(dep2.js) -->|dep2| A + A -->|dep1| C(index.js) +``` diff --git a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/package.json b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/package.json new file mode 100644 index 00000000000000..3dbc1ca591c055 --- /dev/null +++ b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test1/dep1.js b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test1/dep1.js new file mode 100644 index 00000000000000..052c10ef6e602c --- /dev/null +++ b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test1/dep1.js @@ -0,0 +1 @@ +export const dep1 = { ok: true }; diff --git a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test1/dep2.js b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test1/dep2.js new file mode 100644 index 00000000000000..be498e991723fb --- /dev/null +++ b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test1/dep2.js @@ -0,0 +1,2 @@ +import { dep1 } from "./index.js" +export const dep2 = { ok: dep1.ok } diff --git a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test1/index.js b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test1/index.js new file mode 100644 index 00000000000000..49fa0342503c71 --- /dev/null +++ b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test1/index.js @@ -0,0 +1,5 @@ +import { dep1 } from './dep1.js' +export { dep1 } + +import { dep2 } from './dep2.js' +export { dep2 } diff --git a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test2/dep1.js b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test2/dep1.js new file mode 100644 index 00000000000000..052c10ef6e602c --- /dev/null +++ b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test2/dep1.js @@ -0,0 +1 @@ +export const dep1 = { ok: true }; diff --git a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test2/dep2.js b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test2/dep2.js new file mode 100644 index 00000000000000..be498e991723fb --- /dev/null +++ b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test2/dep2.js @@ -0,0 +1,2 @@ +import { dep1 } from "./index.js" +export const dep2 = { ok: dep1.ok } diff --git a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test2/index.js b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test2/index.js new file mode 100644 index 00000000000000..49f8047b941b02 --- /dev/null +++ b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test2/index.js @@ -0,0 +1,2 @@ +export { dep1 } from './dep1.js' +export { dep2 } from "./dep2.js" diff --git a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test3/dep1.js b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test3/dep1.js new file mode 100644 index 00000000000000..052c10ef6e602c --- /dev/null +++ b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test3/dep1.js @@ -0,0 +1 @@ +export const dep1 = { ok: true }; diff --git a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test3/dep2.js b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test3/dep2.js new file mode 100644 index 00000000000000..be498e991723fb --- /dev/null +++ b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test3/dep2.js @@ -0,0 +1,2 @@ +import { dep1 } from "./index.js" +export const dep2 = { ok: dep1.ok } diff --git a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test3/index.js b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test3/index.js new file mode 100644 index 00000000000000..e032a766eab42d --- /dev/null +++ b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test3/index.js @@ -0,0 +1,4 @@ +import { dep1 } from './dep1.js' +import { dep2 } from './dep2.js' +export { dep1 } +export { dep2 } diff --git a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test4/dep1.js b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test4/dep1.js new file mode 100644 index 00000000000000..052c10ef6e602c --- /dev/null +++ b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test4/dep1.js @@ -0,0 +1 @@ +export const dep1 = { ok: true }; diff --git a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test4/dep2.js b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test4/dep2.js new file mode 100644 index 00000000000000..be498e991723fb --- /dev/null +++ b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test4/dep2.js @@ -0,0 +1,2 @@ +import { dep1 } from "./index.js" +export const dep2 = { ok: dep1.ok } diff --git a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test4/index.js b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test4/index.js new file mode 100644 index 00000000000000..bcbfe36b7660f7 --- /dev/null +++ b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test4/index.js @@ -0,0 +1,2 @@ +export * from './dep1.js' +export * from './dep2.js' diff --git a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test5/dep1.js b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test5/dep1.js new file mode 100644 index 00000000000000..052c10ef6e602c --- /dev/null +++ b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test5/dep1.js @@ -0,0 +1 @@ +export const dep1 = { ok: true }; diff --git a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test5/dep2.js b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test5/dep2.js new file mode 100644 index 00000000000000..be498e991723fb --- /dev/null +++ b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test5/dep2.js @@ -0,0 +1,2 @@ +import { dep1 } from "./index.js" +export const dep2 = { ok: dep1.ok } diff --git a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test5/index.js b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test5/index.js new file mode 100644 index 00000000000000..f748a394a7eb77 --- /dev/null +++ b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test5/index.js @@ -0,0 +1,5 @@ +import { dep2 } from './dep2.js' +export { dep2 } + +import { dep1 } from './dep1.js' +export { dep1 } diff --git a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test6/dep1.js b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test6/dep1.js new file mode 100644 index 00000000000000..a4116979f0f4aa --- /dev/null +++ b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test6/dep1.js @@ -0,0 +1,2 @@ +import { dep2 } from "./dep2.js" +export const dep1 = "dep1: " + dep2 diff --git a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test6/dep2.js b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test6/dep2.js new file mode 100644 index 00000000000000..a3c7564baaeb54 --- /dev/null +++ b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test6/dep2.js @@ -0,0 +1,2 @@ +import { dep1 } from "./dep1.js" +export const dep2 = "dep2: " + dep1 diff --git a/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test6/index.js b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test6/index.js new file mode 100644 index 00000000000000..d0da7b8089226b --- /dev/null +++ b/packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test6/index.js @@ -0,0 +1,2 @@ +import { dep1 } from './dep1.js' +export { dep1 } diff --git a/packages/vite/src/node/ssr/runtime/__tests__/server-runtime.spec.ts b/packages/vite/src/node/ssr/runtime/__tests__/server-runtime.spec.ts index 0dcf439589e150..f74132c1b674c7 100644 --- a/packages/vite/src/node/ssr/runtime/__tests__/server-runtime.spec.ts +++ b/packages/vite/src/node/ssr/runtime/__tests__/server-runtime.spec.ts @@ -246,6 +246,42 @@ describe('module runner initialization', async () => { const mod = await runner.import('/fixtures/no-this/importer.js') expect(mod.result).toBe(undefined) }) + + it.for([ + '/fixtures/cyclic2/test1/index.js', + '/fixtures/cyclic2/test2/index.js', + '/fixtures/cyclic2/test3/index.js', + '/fixtures/cyclic2/test4/index.js', + ] as const)(`cyclic %s`, async (entry, { runner }) => { + const mod = await runner.import(entry) + expect({ ...mod }).toEqual({ + dep1: { + ok: true, + }, + dep2: { + ok: true, + }, + }) + }) + + it(`cyclic invalid 1`, async ({ runner }) => { + // Node also fails but with a different message + // $ node packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2/test5/index.js + // ReferenceError: Cannot access 'dep1' before initialization + await expect(() => + runner.import('/fixtures/cyclic2/test5/index.js'), + ).rejects.toMatchInlineSnapshot( + `[ReferenceError: Cannot access '__vite_ssr_import_1__' before initialization]`, + ) + }) + + it(`cyclic invalid 2`, async ({ runner }) => { + await expect(() => + runner.import('/fixtures/cyclic2/test6/index.js'), + ).rejects.toMatchInlineSnapshot( + `[ReferenceError: Cannot access 'dep1' before initialization]`, + ) + }) }) describe('optimize-deps', async () => { diff --git a/packages/vite/src/node/ssr/ssrTransform.ts b/packages/vite/src/node/ssr/ssrTransform.ts index c5e79e3d3b75e4..925e2e037d6b7c 100644 --- a/packages/vite/src/node/ssr/ssrTransform.ts +++ b/packages/vite/src/node/ssr/ssrTransform.ts @@ -178,11 +178,11 @@ async function ssrTransformScript( return importId } - function defineExport(position: number, name: string, local = name) { + function defineExport(name: string, local = name) { s.appendLeft( - position, - `\nObject.defineProperty(${ssrModuleExportsKey}, ${JSON.stringify(name)}, ` + - `{ enumerable: true, configurable: true, get(){ return ${local} }});`, + fileStartIndex, + `Object.defineProperty(${ssrModuleExportsKey}, ${JSON.stringify(name)}, ` + + `{ enumerable: true, configurable: true, get(){ return ${local} }});\n`, ) } @@ -251,13 +251,13 @@ async function ssrTransformScript( node.declaration.type === 'ClassDeclaration' ) { // export function foo() {} - defineExport(node.end, node.declaration.id!.name) + defineExport(node.declaration.id!.name) } else { // export const foo = 1, bar = 2 for (const declaration of node.declaration.declarations) { const names = extractNames(declaration.id as any) for (const name of names) { - defineExport(node.end, name) + defineExport(name) } } } @@ -281,14 +281,9 @@ async function ssrTransformScript( ) as string if (spec.local.type === 'Identifier') { - defineExport( - node.end, - exportedAs, - `${importId}.${spec.local.name}`, - ) + defineExport(exportedAs, `${importId}.${spec.local.name}`) } else { defineExport( - node.end, exportedAs, `${importId}[${JSON.stringify(spec.local.value as string)}]`, ) @@ -304,7 +299,7 @@ async function ssrTransformScript( spec.exported, ) as string - defineExport(node.end, exportedAs, binding || local) + defineExport(exportedAs, binding || local) } } } @@ -344,7 +339,7 @@ async function ssrTransformScript( const exportedAs = getIdentifierNameOrLiteralValue( node.exported, ) as string - defineExport(node.end, exportedAs, `${importId}`) + defineExport(exportedAs, `${importId}`) } else { s.appendLeft(node.end, `${ssrExportAllKey}(${importId});\n`) } diff --git a/playground/hmr-ssr/__tests__/hmr-ssr.spec.ts b/playground/hmr-ssr/__tests__/hmr-ssr.spec.ts index d41ca3c196555b..2dc8404f1bc3a9 100644 --- a/playground/hmr-ssr/__tests__/hmr-ssr.spec.ts +++ b/playground/hmr-ssr/__tests__/hmr-ssr.spec.ts @@ -851,13 +851,14 @@ if (!isBuild) { editFile('self-accept-within-circular/c.js', (code) => code.replace(`export const c = 'c'`, `export const c = 'cc'`), ) + // it throws a same error as browser case, + // but it doesn't auto reload and it calls `hot.accept(nextExports)` with `nextExports = undefined` + await untilUpdated(() => el(), '') + + // test reloading manually for now + server.moduleGraph.invalidateAll() // TODO: why is `runner.clearCache()` not enough? + await runner.import('/self-accept-within-circular/index') await untilUpdated(() => el(), 'cc') - await vi.waitFor(() => { - expect(serverLogs.length).greaterThanOrEqual(1) - // Should still keep hmr update, but it'll error on the browser-side and will refresh itself. - // Match on full log not possible because of color markers - expect(serverLogs.at(-1)!).toContain('hmr update') - }) }) test('hmr should not reload if no accepted within circular imported files', async (ctx) => { diff --git a/playground/hmr-ssr/self-accept-within-circular/c.js b/playground/hmr-ssr/self-accept-within-circular/c.js index 47b6d494969917..7fe30c447965a2 100644 --- a/playground/hmr-ssr/self-accept-within-circular/c.js +++ b/playground/hmr-ssr/self-accept-within-circular/c.js @@ -8,5 +8,5 @@ function render(content) { render(c) import.meta.hot?.accept((nextExports) => { - render(nextExports.c) + render(nextExports?.c) }) diff --git a/playground/ssr/__tests__/ssr.spec.ts b/playground/ssr/__tests__/ssr.spec.ts index 87ee58ae2b58c1..0e4cb525ac9b4c 100644 --- a/playground/ssr/__tests__/ssr.spec.ts +++ b/playground/ssr/__tests__/ssr.spec.ts @@ -12,7 +12,7 @@ test(`circular dependencies modules doesn't throw`, async () => { ) }) -test(`circular import doesn't throw`, async () => { +test(`circular import doesn't throw (1)`, async () => { await page.goto(`${url}/circular-import`) expect(await page.textContent('.circ-import')).toMatchInlineSnapshot( @@ -20,6 +20,14 @@ test(`circular import doesn't throw`, async () => { ) }) +test(`circular import doesn't throw (2)`, async () => { + await page.goto(`${url}/circular-import2`) + + expect(await page.textContent('.circ-import')).toMatchInlineSnapshot( + '"A is: __A__"', + ) +}) + test(`deadlock doesn't happen for static imports`, async () => { await page.goto(`${url}/forked-deadlock-static-imports`) diff --git a/playground/ssr/src/app.js b/playground/ssr/src/app.js index adb225215a68af..5124b45b06075b 100644 --- a/playground/ssr/src/app.js +++ b/playground/ssr/src/app.js @@ -4,6 +4,7 @@ const pathRenderers = { '/': renderRoot, '/circular-dep': renderCircularDep, '/circular-import': renderCircularImport, + '/circular-import2': renderCircularImport2, '/forked-deadlock-static-imports': renderForkedDeadlockStaticImports, '/forked-deadlock-dynamic-imports': renderForkedDeadlockDynamicImports, } @@ -41,6 +42,11 @@ async function renderCircularImport(rootDir) { return `
${escapeHtml(logA())}
` } +async function renderCircularImport2(rootDir) { + const { logA } = await import('./circular-import2/index.js') + return `
${escapeHtml(logA())}
` +} + async function renderForkedDeadlockStaticImports(rootDir) { const { commonModuleExport } = await import('./forked-deadlock/common-module') commonModuleExport() diff --git a/playground/ssr/src/circular-import2/a.js b/playground/ssr/src/circular-import2/a.js new file mode 100644 index 00000000000000..00c8645fd78b12 --- /dev/null +++ b/playground/ssr/src/circular-import2/a.js @@ -0,0 +1,5 @@ +import { getB } from './b' + +export const A = '__A__' + +export const B = getB() diff --git a/playground/ssr/src/circular-import2/b.js b/playground/ssr/src/circular-import2/b.js new file mode 100644 index 00000000000000..772e68833f300b --- /dev/null +++ b/playground/ssr/src/circular-import2/b.js @@ -0,0 +1,5 @@ +export { A } from './a' + +export function getB() { + return '__B__' +} diff --git a/playground/ssr/src/circular-import2/index.js b/playground/ssr/src/circular-import2/index.js new file mode 100644 index 00000000000000..c9ec0fc50238b4 --- /dev/null +++ b/playground/ssr/src/circular-import2/index.js @@ -0,0 +1,5 @@ +import { A } from './b' + +export function logA() { + return `A is: ${A}` +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2a1d25b244cd79..73685f4c52b7fd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -499,6 +499,8 @@ importers: packages/vite/src/node/ssr/runtime/__tests__/fixtures/cjs-external: {} + packages/vite/src/node/ssr/runtime/__tests__/fixtures/cyclic2: {} + packages/vite/src/node/ssr/runtime/__tests__/fixtures/esm-external: {} playground: diff --git a/vitest.config.ts b/vitest.config.ts index 919e0a30d650bd..e75058c0d91930 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -19,6 +19,10 @@ export default defineConfig({ }, testTimeout: 20000, isolate: false, + // importing non entry files (e.g. config.ts, build.ts, server/index.ts) is broken due to cyclic import + // as it can be seen from tsx (try pnpm exec tsx packages/vite/src/node/server/index.ts). + // we can use `setupFiles` to ensure the modules are evaluated via main node entry. + setupFiles: ['./packages/vite/src/node/index.ts'], }, esbuild: { target: 'node18',