Skip to content
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

fix(ssr): hoist export to handle cyclic import better #18983

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
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 packages/vite/src/node/__tests__/build.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import colors from 'picocolors'
import { describe, expect, test, vi } from 'vitest'
import type { OutputChunk, OutputOptions, RollupOutput } from 'rollup'
import type { LibraryFormats, LibraryOptions } from '../build'
import { build } from '..'
import {
build,
createBuilder,
resolveBuildOutputs,
resolveLibFilename,
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/node/__tests__/environment.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import path from 'node:path'
import { describe, expect, onTestFinished, test } from 'vitest'
import type { RollupOutput } from 'rollup'
import { createServer } from '../server'
import { createServer } from '..'
import type { InlineConfig } from '../config'
import { createBuilder } from '../build'
import { createServerModuleRunner } from '../ssr/runtime/serverModuleRunner'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, test } from 'vitest'
import { parseAst } from 'rollup/parseAst'
import { resolveConfig } from '../..'
import { assetImportMetaUrlPlugin } from '../../plugins/assetImportMetaUrl'
import { resolveConfig } from '../../config'
import { PartialEnvironment } from '../../baseEnvironment'

async function createAssetImportMetaurlPluginTransform() {
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/node/__tests__/plugins/define.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, test } from 'vitest'
import { resolveConfig } from '../..'
import { definePlugin } from '../../plugins/define'
import { resolveConfig } from '../../config'
import { PartialEnvironment } from '../../baseEnvironment'

async function createDefinePluginTransform(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import { normalizePath } from '../../..'
import { transformDynamicImport } from '../../../plugins/dynamicImportVars'
import { normalizePath } from '../../../utils'
import { isWindows } from '../../../../shared/utils'

const __dirname = resolve(fileURLToPath(import.meta.url), '..')
Expand Down
1 change: 1 addition & 0 deletions packages/vite/src/node/__tests__/plugins/import.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { beforeEach, describe, expect, test, vi } from 'vitest'
import '../..'
import { transformCjsImport } from '../../plugins/importAnalysis'

describe('transformCjsImport', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, it } from 'vitest'
import type { ModuleFormat, RollupOutput } from 'rollup'
import { build } from '../../../build'
import { build } from '../../..'
import { modulePreloadPolyfillId } from '../../../plugins/modulePreloadPolyfill'

const buildProject = ({ format = 'es' as ModuleFormat } = {}) =>
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/node/__tests__/resolve.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { join } from 'node:path'
import { describe, expect, onTestFinished, test } from 'vitest'
import { createServer } from '../server'
import { createServer } from '..'
import { createServerModuleRunner } from '../ssr/runtime/serverModuleRunner'
import type { InlineConfig } from '../config'
import { build } from '../build'
Expand Down
1 change: 1 addition & 0 deletions packages/vite/src/node/__tests__/scan.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, expect, test } from 'vitest'
import '..'
import { commentRE, importsRE, scriptRE } from '../optimizer/scan'
import { multilineCommentsRE, singlelineCommentsRE } from '../utils'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, expect, it } from 'vitest'
import '../..'
import { EnvironmentModuleGraph } from '../moduleGraph'
import type { ModuleNode } from '../mixedModuleGraph'
import { ModuleGraph } from '../mixedModuleGraph'
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/node/server/__tests__/watcher.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { type ViteDevServer, createServer } from '../index'
import { type ViteDevServer, createServer } from '../..'

const stubGetWatchedCode = /\(\)\s*\{\s*return this;\s*\}/

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 @@ -3,7 +3,7 @@ import path from 'node:path'
import fs from 'node:fs'
import { stripVTControlCharacters } from 'node:util'
import { expect, test } from 'vitest'
import { createServer } from '../../server'
import { createServer } from '../..'
import { normalizePath } from '../../utils'

const root = fileURLToPath(new URL('./', import.meta.url))
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/node/ssr/__tests__/ssrStacktrace.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { fileURLToPath } from 'node:url'
import { test } from 'vitest'
import { createServer } from '../../server'
import { createServer } from '../..'

const root = fileURLToPath(new URL('./', import.meta.url))

Expand Down
88 changes: 44 additions & 44 deletions packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,46 +56,46 @@ 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 }});"
"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 }});"
"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 }});"
"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 }});"
"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 }});"
"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"]});
"
`)
})

Expand All @@ -105,9 +105,9 @@ test('named exports of imported binding', async () => {
`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 }});"
"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"]});
"
`)
})

Expand All @@ -129,19 +129,19 @@ 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__ }});"
"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('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__ }});"
"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");
"
`)
})

Expand All @@ -151,8 +151,8 @@ test('export as arbitrary module namespace identifier', async () => {
`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 }});"
"Object.defineProperty(__vite_ssr_exports__, "arbitrary string", { enumerable: true, configurable: true, get(){ return something }});
const something = "Something";"
`)
})

Expand All @@ -162,9 +162,9 @@ test('export as from arbitrary module namespace identifier', async () => {
`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"] }});"
"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"]});
"
`)
})

Expand Down Expand Up @@ -209,8 +209,8 @@ test('dynamic import', async () => {
`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 }});"
"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'])
Expand Down Expand Up @@ -382,11 +382,11 @@ 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"]});
"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 });"
`)
})
Expand Down Expand Up @@ -422,9 +422,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 });"
`)
})
Expand Down Expand Up @@ -875,12 +875,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 }});
"
`)
})
Expand Down Expand Up @@ -981,7 +981,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';
Expand All @@ -1006,8 +1007,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 }});;;"
};;"
`)
})

Expand Down Expand Up @@ -1180,13 +1180,13 @@ export * as bar from './bar'
console.log(bar)
`),
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__("./foo", {"importedNames":["foo"]});
"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__ }});;
;
console.log(bar)
"
`)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
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
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"type": "module"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const dep1 = { ok: true };
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { dep1 } from "./index.js"
export const dep2 = { ok: dep1.ok }
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { dep1 } from './dep1.js'
export { dep1 }

import { dep2 } from './dep2.js'
export { dep2 }
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const dep1 = { ok: true };
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { dep1 } from "./index.js"
export const dep2 = { ok: dep1.ok }
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { dep1 } from './dep1.js'
export { dep2 } from "./dep2.js"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const dep1 = { ok: true };
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { dep1 } from "./index.js"
export const dep2 = { ok: dep1.ok }
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { dep1 } from './dep1.js'
import { dep2 } from './dep2.js'
export { dep1 }
export { dep2 }
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const dep1 = { ok: true };
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { dep1 } from "./index.js"
export const dep2 = { ok: dep1.ok }
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './dep1.js'
export * from './dep2.js'
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const dep1 = { ok: true };
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { dep1 } from "./index.js"
export const dep2 = { ok: dep1.ok }
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { dep2 } from './dep2.js'
export { dep2 }

import { dep1 } from './dep1.js'
export { dep1 }
Loading
Loading