diff --git a/packages/playground/ssr-react/__tests__/ssr-react.spec.ts b/packages/playground/ssr-react/__tests__/ssr-react.spec.ts
index bf161e03e5143c..d7c3313b38e57a 100644
--- a/packages/playground/ssr-react/__tests__/ssr-react.spec.ts
+++ b/packages/playground/ssr-react/__tests__/ssr-react.spec.ts
@@ -1,4 +1,4 @@
-import { editFile, getColor, isBuild, untilUpdated } from '../../testUtils'
+import { editFile, untilUpdated } from '../../testUtils'
import { port } from './serve'
import fetch from 'node-fetch'
@@ -46,3 +46,10 @@ test('client navigation', async () => {
)
await untilUpdated(() => page.textContent('h1'), 'changed')
})
+
+test(`circular dependecies modules doesn't throw`, async () => {
+ await page.goto(url)
+ expect(await page.textContent('.circ-dep-init')).toMatch(
+ 'circ-dep-init-a circ-dep-init-b'
+ )
+})
diff --git a/packages/playground/ssr-react/src/add.js b/packages/playground/ssr-react/src/add.js
new file mode 100644
index 00000000000000..a0e419e9cfcacf
--- /dev/null
+++ b/packages/playground/ssr-react/src/add.js
@@ -0,0 +1,9 @@
+import { multiply } from './multiply'
+
+export function add(a, b) {
+ return a + b
+}
+
+export function addAndMultiply(a, b, c) {
+ return multiply(add(a, b), c)
+}
diff --git a/packages/playground/ssr-react/src/circular-dep-init/README.md b/packages/playground/ssr-react/src/circular-dep-init/README.md
new file mode 100644
index 00000000000000..339eddf210acf8
--- /dev/null
+++ b/packages/playground/ssr-react/src/circular-dep-init/README.md
@@ -0,0 +1 @@
+This test aim to find out wherever the modules with circular dependencies are correctly initialized
\ No newline at end of file
diff --git a/packages/playground/ssr-react/src/circular-dep-init/circular-dep-init.js b/packages/playground/ssr-react/src/circular-dep-init/circular-dep-init.js
new file mode 100644
index 00000000000000..8867d64ec45091
--- /dev/null
+++ b/packages/playground/ssr-react/src/circular-dep-init/circular-dep-init.js
@@ -0,0 +1,2 @@
+export * from './module-a'
+export { getValueAB } from './module-b'
diff --git a/packages/playground/ssr-react/src/circular-dep-init/module-a.js b/packages/playground/ssr-react/src/circular-dep-init/module-a.js
new file mode 100644
index 00000000000000..335b3ac26ab3b5
--- /dev/null
+++ b/packages/playground/ssr-react/src/circular-dep-init/module-a.js
@@ -0,0 +1 @@
+export const valueA = 'circ-dep-init-a'
diff --git a/packages/playground/ssr-react/src/circular-dep-init/module-b.js b/packages/playground/ssr-react/src/circular-dep-init/module-b.js
new file mode 100644
index 00000000000000..cb16d7e9be4a30
--- /dev/null
+++ b/packages/playground/ssr-react/src/circular-dep-init/module-b.js
@@ -0,0 +1,8 @@
+import { valueA } from './circular-dep-init'
+
+export const valueB = 'circ-dep-init-b'
+export const valueAB = valueA.concat(` ${valueB}`)
+
+export function getValueAB() {
+ return valueAB
+}
diff --git a/packages/playground/ssr-react/src/forked-deadlock/README.md b/packages/playground/ssr-react/src/forked-deadlock/README.md
new file mode 100644
index 00000000000000..54cb6e133e601a
--- /dev/null
+++ b/packages/playground/ssr-react/src/forked-deadlock/README.md
@@ -0,0 +1,45 @@
+This test aim to check for a particular type of circular dependency that causes tricky deadlocks, **deadlocks with forked imports stack**
+
+```
+A -> B means: B is imported by A and B has A in its stack
+A ... B means: A is waiting for B to ssrLoadModule()
+
+H -> X ... Y
+H -> X -> Y ... B
+H -> A ... B
+H -> A -> B ... X
+```
+
+### Forked deadlock description:
+```
+[X] is waiting for [Y] to resolve
+ ↑ ↳ is waiting for [A] to resolve
+ │ ↳ is waiting for [B] to resolve
+ │ ↳ is waiting for [X] to resolve
+ └────────────────────────────────────────────────────────────────────────┘
+```
+
+This may seems a traditional deadlock, but the thing that makes this special is the import stack of each module:
+```
+[X] stack:
+ [H]
+```
+```
+[Y] stack:
+ [X]
+ [H]
+```
+```
+[A] stack:
+ [H]
+```
+```
+[B] stack:
+ [A]
+ [H]
+```
+Even if `[X]` is imported by `[B]`, `[B]` is not in `[X]`'s stack because it's imported by `[H]` in first place then it's stack is only composed by `[H]`. `[H]` **forks** the imports **stack** and this make hard to be found.
+
+### Fix description
+Vite, when imports `[X]`, should check whether `[X]` is already pending and if it is, it must check that, when it was imported in first place, the stack of `[X]` doesn't have any module in common with the current module; in this case `[B]` has the module `[H]` is common with `[X]` and i can assume that a deadlock is going to happen.
+
diff --git a/packages/playground/ssr-react/src/forked-deadlock/common-module.js b/packages/playground/ssr-react/src/forked-deadlock/common-module.js
new file mode 100644
index 00000000000000..c73a3ee4b970c8
--- /dev/null
+++ b/packages/playground/ssr-react/src/forked-deadlock/common-module.js
@@ -0,0 +1,10 @@
+import { stuckModuleExport } from './stuck-module'
+import { deadlockfuseModuleExport } from './deadlock-fuse-module'
+
+/**
+ * module H
+ */
+export function commonModuleExport() {
+ stuckModuleExport()
+ deadlockfuseModuleExport()
+}
diff --git a/packages/playground/ssr-react/src/forked-deadlock/deadlock-fuse-module.js b/packages/playground/ssr-react/src/forked-deadlock/deadlock-fuse-module.js
new file mode 100644
index 00000000000000..4f31763ba2343f
--- /dev/null
+++ b/packages/playground/ssr-react/src/forked-deadlock/deadlock-fuse-module.js
@@ -0,0 +1,8 @@
+import { fuseStuckBridgeModuleExport } from './fuse-stuck-bridge-module'
+
+/**
+ * module A
+ */
+export function deadlockfuseModuleExport() {
+ fuseStuckBridgeModuleExport()
+}
diff --git a/packages/playground/ssr-react/src/forked-deadlock/fuse-stuck-bridge-module.js b/packages/playground/ssr-react/src/forked-deadlock/fuse-stuck-bridge-module.js
new file mode 100644
index 00000000000000..211ad7c3bc9f92
--- /dev/null
+++ b/packages/playground/ssr-react/src/forked-deadlock/fuse-stuck-bridge-module.js
@@ -0,0 +1,8 @@
+import { stuckModuleExport } from './stuck-module'
+
+/**
+ * module C
+ */
+export function fuseStuckBridgeModuleExport() {
+ stuckModuleExport()
+}
diff --git a/packages/playground/ssr-react/src/forked-deadlock/middle-module.js b/packages/playground/ssr-react/src/forked-deadlock/middle-module.js
new file mode 100644
index 00000000000000..0632eedeabd7a5
--- /dev/null
+++ b/packages/playground/ssr-react/src/forked-deadlock/middle-module.js
@@ -0,0 +1,8 @@
+import { deadlockfuseModuleExport } from './deadlock-fuse-module'
+
+/**
+ * module Y
+ */
+export function middleModuleExport() {
+ void deadlockfuseModuleExport
+}
diff --git a/packages/playground/ssr-react/src/forked-deadlock/stuck-module.js b/packages/playground/ssr-react/src/forked-deadlock/stuck-module.js
new file mode 100644
index 00000000000000..50b4d28063dc70
--- /dev/null
+++ b/packages/playground/ssr-react/src/forked-deadlock/stuck-module.js
@@ -0,0 +1,8 @@
+import { middleModuleExport } from './middle-module'
+
+/**
+ * module X
+ */
+export function stuckModuleExport() {
+ middleModuleExport()
+}
diff --git a/packages/playground/ssr-react/src/multiply.js b/packages/playground/ssr-react/src/multiply.js
new file mode 100644
index 00000000000000..94f43efbff58bd
--- /dev/null
+++ b/packages/playground/ssr-react/src/multiply.js
@@ -0,0 +1,9 @@
+import { add } from './add'
+
+export function multiply(a, b) {
+ return a * b
+}
+
+export function multiplyAndAdd(a, b, c) {
+ return add(multiply(a, b), c)
+}
diff --git a/packages/playground/ssr-react/src/pages/About.jsx b/packages/playground/ssr-react/src/pages/About.jsx
index 22354540091f04..0fe4de69078504 100644
--- a/packages/playground/ssr-react/src/pages/About.jsx
+++ b/packages/playground/ssr-react/src/pages/About.jsx
@@ -1,3 +1,12 @@
+import { addAndMultiply } from '../add'
+import { multiplyAndAdd } from '../multiply'
+
export default function About() {
- return
About
+ return (
+ <>
+ About
+ {addAndMultiply(1, 2, 3)}
+ {multiplyAndAdd(1, 2, 3)}
+ >
+ )
}
diff --git a/packages/playground/ssr-react/src/pages/Home.jsx b/packages/playground/ssr-react/src/pages/Home.jsx
index 3e62e6933192cd..d1f4944810cc98 100644
--- a/packages/playground/ssr-react/src/pages/Home.jsx
+++ b/packages/playground/ssr-react/src/pages/Home.jsx
@@ -1,3 +1,17 @@
+import { addAndMultiply } from '../add'
+import { multiplyAndAdd } from '../multiply'
+import { commonModuleExport } from '../forked-deadlock/common-module'
+import { getValueAB } from '../circular-dep-init/circular-dep-init'
+
export default function Home() {
- return Home
+ commonModuleExport()
+
+ return (
+ <>
+ Home
+ {addAndMultiply(1, 2, 3)}
+ {multiplyAndAdd(1, 2, 3)}
+ {getValueAB()}
+ >
+ )
}
diff --git a/packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts b/packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts
index b3cc856aa9ae44..d3320a06a9429e 100644
--- a/packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts
+++ b/packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts
@@ -11,7 +11,7 @@ test('default import', async () => {
)
).code
).toMatchInlineSnapshot(`
- "const __vite_ssr_import_0__ = __vite_ssr_import__(\\"vue\\")
+ "const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\");
console.log(__vite_ssr_import_0__.default.bar)"
`)
})
@@ -26,7 +26,7 @@ test('named import', async () => {
)
).code
).toMatchInlineSnapshot(`
- "const __vite_ssr_import_0__ = __vite_ssr_import__(\\"vue\\")
+ "const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\");
function foo() { return __vite_ssr_import_0__.ref(0) }"
`)
})
@@ -41,7 +41,7 @@ test('namespace import', async () => {
)
).code
).toMatchInlineSnapshot(`
- "const __vite_ssr_import_0__ = __vite_ssr_import__(\\"vue\\")
+ "const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\");
function foo() { return __vite_ssr_import_0__.ref(0) }"
`)
})
@@ -50,7 +50,7 @@ test('export function declaration', async () => {
expect((await ssrTransform(`export function foo() {}`, null, null)).code)
.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 }});"
`)
})
@@ -58,7 +58,7 @@ test('export class declaration', async () => {
expect((await ssrTransform(`export class foo {}`, null, null)).code)
.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 }});"
`)
})
@@ -66,8 +66,8 @@ test('export var declaration', async () => {
expect((await ssrTransform(`export const a = 1, b = 2`, null, null)).code)
.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 }});"
`)
})
@@ -77,8 +77,8 @@ test('export named', async () => {
.code
).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 }});"
`)
})
@@ -87,10 +87,10 @@ test('export named from', async () => {
(await ssrTransform(`export { ref, computed as c } from 'vue'`, null, null))
.code
).toMatchInlineSnapshot(`
- "const __vite_ssr_import_0__ = __vite_ssr_import__(\\"vue\\")
+ "const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\");
- 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 }});"
`)
})
@@ -104,27 +104,35 @@ test('named exports of imported binding', async () => {
)
).code
).toMatchInlineSnapshot(`
- "const __vite_ssr_import_0__ = __vite_ssr_import__(\\"vue\\")
+ "const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\");
- 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 }});"
`)
})
test('export * from', async () => {
- expect((await ssrTransform(`export * from 'vue'`, null, null)).code)
- .toMatchInlineSnapshot(`
- "const __vite_ssr_import_0__ = __vite_ssr_import__(\\"vue\\")
-
- __vite_ssr_exportAll__(__vite_ssr_import_0__)"
+ expect(
+ (
+ await ssrTransform(
+ `export * from 'vue'\n` + `export * from 'react'`,
+ null,
+ null
+ )
+ ).code
+ ).toMatchInlineSnapshot(`
+ "const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\");
+ __vite_ssr_exportAll__(__vite_ssr_import_0__);
+ const __vite_ssr_import_1__ = await __vite_ssr_import__(\\"react\\");
+ __vite_ssr_exportAll__(__vite_ssr_import_1__);"
`)
})
test('export * as from', async () => {
expect((await ssrTransform(`export * as foo from 'vue'`, null, null)).code)
.toMatchInlineSnapshot(`
- "const __vite_ssr_import_0__ = __vite_ssr_import__(\\"vue\\")
+ "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__ }});"
`)
})
@@ -146,7 +154,7 @@ test('dynamic import', async () => {
.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 }});"
`)
})
@@ -160,7 +168,7 @@ test('do not rewrite method definition', async () => {
)
).code
).toMatchInlineSnapshot(`
- "const __vite_ssr_import_0__ = __vite_ssr_import__(\\"vue\\")
+ "const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\");
class A { fn() { __vite_ssr_import_0__.fn() } }"
`)
})
@@ -175,7 +183,7 @@ test('do not rewrite catch clause', async () => {
)
).code
).toMatchInlineSnapshot(`
- "const __vite_ssr_import_0__ = __vite_ssr_import__(\\"./dependency\\")
+ "const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"./dependency\\");
try {} catch(error) {}"
`)
})
@@ -191,7 +199,7 @@ test('should declare variable for imported super class', async () => {
)
).code
).toMatchInlineSnapshot(`
- "const __vite_ssr_import_0__ = __vite_ssr_import__(\\"./dependency\\")
+ "const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"./dependency\\");
const Foo = __vite_ssr_import_0__.Foo;
class A extends Foo {}"
`)
@@ -209,12 +217,12 @@ test('should declare variable for imported super class', async () => {
)
).code
).toMatchInlineSnapshot(`
- "const __vite_ssr_import_0__ = __vite_ssr_import__(\\"./dependency\\")
+ "const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"./dependency\\");
const Foo = __vite_ssr_import_0__.Foo;
class A extends Foo {}
class B extends Foo {}
- Object.defineProperty(__vite_ssr_exports__, \\"default\\", { enumerable: true, value: A })
- Object.defineProperty(__vite_ssr_exports__, \\"B\\", { enumerable: true, configurable: true, get(){ return B }})"
+ Object.defineProperty(__vite_ssr_exports__, \\"default\\", { enumerable: true, value: A });
+ Object.defineProperty(__vite_ssr_exports__, \\"B\\", { enumerable: true, configurable: true, get(){ return B }});"
`)
})
@@ -246,7 +254,7 @@ test('should handle default export variants', async () => {
).toMatchInlineSnapshot(`
"function foo() {}
foo.prototype = Object.prototype;
- Object.defineProperty(__vite_ssr_exports__, \\"default\\", { enumerable: true, value: foo })"
+ Object.defineProperty(__vite_ssr_exports__, \\"default\\", { enumerable: true, value: foo });"
`)
// default named classes
expect(
@@ -260,8 +268,8 @@ test('should handle default export variants', async () => {
).toMatchInlineSnapshot(`
"class A {}
class B extends A {}
- Object.defineProperty(__vite_ssr_exports__, \\"default\\", { enumerable: true, value: A })
- Object.defineProperty(__vite_ssr_exports__, \\"B\\", { enumerable: true, configurable: true, get(){ return B }})"
+ Object.defineProperty(__vite_ssr_exports__, \\"default\\", { enumerable: true, value: A });
+ Object.defineProperty(__vite_ssr_exports__, \\"B\\", { enumerable: true, configurable: true, get(){ return B }});"
`)
})
@@ -288,7 +296,7 @@ test('overwrite bindings', async () => {
)
).code
).toMatchInlineSnapshot(`
- "const __vite_ssr_import_0__ = __vite_ssr_import__(\\"vue\\")
+ "const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\");
const a = { inject: __vite_ssr_import_0__.inject }
const b = { test: __vite_ssr_import_0__.inject }
function c() { const { test: inject } = { test: true }; console.log(inject) }
diff --git a/packages/vite/src/node/ssr/ssrModuleLoader.ts b/packages/vite/src/node/ssr/ssrModuleLoader.ts
index b68b104461c0b3..ee216d797687d8 100644
--- a/packages/vite/src/node/ssr/ssrModuleLoader.ts
+++ b/packages/vite/src/node/ssr/ssrModuleLoader.ts
@@ -19,6 +19,7 @@ interface SSRContext {
type SSRModule = Record
const pendingModules = new Map>()
+const pendingImports = new Map()
export async function ssrLoadModule(
url: string,
@@ -28,13 +29,6 @@ export async function ssrLoadModule(
): Promise {
url = unwrapId(url)
- if (urlStack.includes(url)) {
- server.config.logger.warn(
- `Circular dependency: ${urlStack.join(' -> ')} -> ${url}`
- )
- return {}
- }
-
// when we instantiate multiple dependency modules in parallel, they may
// point to shared modules. We need to avoid duplicate instantiation attempts
// by register every module as pending synchronously so that all subsequent
@@ -46,7 +40,13 @@ export async function ssrLoadModule(
const modulePromise = instantiateModule(url, server, context, urlStack)
pendingModules.set(url, modulePromise)
- modulePromise.catch(() => {}).then(() => pendingModules.delete(url))
+ modulePromise
+ .catch(() => {
+ pendingImports.delete(url)
+ })
+ .finally(() => {
+ pendingModules.delete(url)
+ })
return modulePromise
}
@@ -76,37 +76,46 @@ async function instantiateModule(
}
Object.defineProperty(ssrModule, '__esModule', { value: true })
- const isExternal = (dep: string) => dep[0] !== '.' && dep[0] !== '/'
-
- await Promise.all(
- result.deps!.map((dep) => {
- if (!isExternal(dep)) {
- return ssrLoadModule(dep, server, context, urlStack.concat(url))
- }
- })
- )
+ // Tolerate circular imports by ensuring the module can be
+ // referenced before it's been instantiated.
+ mod.ssrModule = ssrModule
const ssrImportMeta = { url }
- const ssrImport = (dep: string) => {
- if (isExternal(dep)) {
+ urlStack = urlStack.concat(url)
+ const isCircular = (url: string) => urlStack.includes(url)
+
+ // Since dynamic imports can happen in parallel, we need to
+ // account for multiple pending deps and duplicate imports.
+ const pendingDeps: string[] = []
+
+ const ssrImport = async (dep: string) => {
+ if (dep[0] !== '.' && dep[0] !== '/') {
return nodeRequire(dep, mod.file, server.config.root)
- } else {
- return moduleGraph.urlToModuleMap.get(unwrapId(dep))?.ssrModule
}
+ dep = unwrapId(dep)
+ if (!isCircular(dep) && !pendingImports.get(dep)?.some(isCircular)) {
+ pendingDeps.push(dep)
+ if (pendingDeps.length === 1) {
+ pendingImports.set(url, pendingDeps)
+ }
+ await ssrLoadModule(dep, server, context, urlStack)
+ if (pendingDeps.length === 1) {
+ pendingImports.delete(url)
+ } else {
+ pendingDeps.splice(pendingDeps.indexOf(dep), 1)
+ }
+ }
+ return moduleGraph.urlToModuleMap.get(dep)?.ssrModule
}
const ssrDynamicImport = (dep: string) => {
- if (isExternal(dep)) {
- return Promise.resolve(nodeRequire(dep, mod.file, server.config.root))
- } else {
- // #3087 dynamic import vars is ignored at rewrite import path,
- // so here need process relative path
- if (dep.startsWith('.')) {
- dep = path.posix.resolve(path.dirname(url), dep)
- }
- return ssrLoadModule(dep, server, context, urlStack.concat(url))
+ // #3087 dynamic import vars is ignored at rewrite import path,
+ // so here need process relative path
+ if (dep[0] === '.') {
+ dep = path.posix.resolve(path.dirname(url), dep)
}
+ return ssrImport(dep)
}
function ssrExportAll(sourceModule: any) {
@@ -124,7 +133,9 @@ async function instantiateModule(
}
try {
- new Function(
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
+ const AsyncFunction = async function () {}.constructor as typeof Function
+ const initModule = new AsyncFunction(
`global`,
ssrModuleExportsKey,
ssrImportMetaKey,
@@ -132,7 +143,8 @@ async function instantiateModule(
ssrDynamicImportKey,
ssrExportAllKey,
result.code + `\n//# sourceURL=${mod.url}`
- )(
+ )
+ await initModule(
context.global,
ssrModule,
ssrImportMeta,
@@ -153,8 +165,7 @@ async function instantiateModule(
throw e
}
- mod.ssrModule = Object.freeze(ssrModule)
- return ssrModule
+ return Object.freeze(ssrModule)
}
function nodeRequire(id: string, importer: string | null, root: string) {
diff --git a/packages/vite/src/node/ssr/ssrTransform.ts b/packages/vite/src/node/ssr/ssrTransform.ts
index 45d0a95b16d0dd..c5d87e8c9c6fc5 100644
--- a/packages/vite/src/node/ssr/ssrTransform.ts
+++ b/packages/vite/src/node/ssr/ssrTransform.ts
@@ -47,15 +47,16 @@ export async function ssrTransform(
const importId = `__vite_ssr_import_${uid++}__`
s.appendLeft(
node.start,
- `const ${importId} = ${ssrImportKey}(${JSON.stringify(source)})\n`
+ `const ${importId} = await ${ssrImportKey}(${JSON.stringify(source)});\n`
)
return importId
}
- function defineExport(name: string, local = name) {
- s.append(
+ function defineExport(position: number, name: string, local = name) {
+ s.appendRight(
+ position,
`\nObject.defineProperty(${ssrModuleExportsKey}, "${name}", ` +
- `{ enumerable: true, configurable: true, get(){ return ${local} }})`
+ `{ enumerable: true, configurable: true, get(){ return ${local} }});`
)
}
@@ -93,32 +94,37 @@ export async function ssrTransform(
node.declaration.type === 'ClassDeclaration'
) {
// export function foo() {}
- defineExport(node.declaration.id!.name)
+ defineExport(node.end, 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(name)
+ defineExport(node.end, name)
}
}
}
s.remove(node.start, (node.declaration as Node).start)
- } else if (node.source) {
- // export { foo, bar } from './foo'
- const importId = defineImport(node, node.source.value as string)
- for (const spec of node.specifiers) {
- defineExport(spec.exported.name, `${importId}.${spec.local.name}`)
- }
- s.remove(node.start, node.end)
} else {
- // export { foo, bar }
- for (const spec of node.specifiers) {
- const local = spec.local.name
- const binding = idToImportMap.get(local)
- defineExport(spec.exported.name, binding || local)
- }
s.remove(node.start, node.end)
+ if (node.source) {
+ // export { foo, bar } from './foo'
+ const importId = defineImport(node, node.source.value as string)
+ for (const spec of node.specifiers) {
+ defineExport(
+ node.end,
+ spec.exported.name,
+ `${importId}.${spec.local.name}`
+ )
+ }
+ } else {
+ // export { foo, bar }
+ for (const spec of node.specifiers) {
+ const local = spec.local.name
+ const binding = idToImportMap.get(local)
+ defineExport(node.end, spec.exported.name, binding || local)
+ }
+ }
}
}
@@ -132,7 +138,7 @@ export async function ssrTransform(
s.remove(node.start, node.start + 15 /* 'export default '.length */)
s.append(
`\nObject.defineProperty(${ssrModuleExportsKey}, "default", ` +
- `{ enumerable: true, value: ${name} })`
+ `{ enumerable: true, value: ${name} });`
)
} else {
// anonymous default exports
@@ -148,12 +154,12 @@ export async function ssrTransform(
if (node.type === 'ExportAllDeclaration') {
if (node.exported) {
const importId = defineImport(node, node.source.value as string)
- defineExport(node.exported.name, `${importId}`)
s.remove(node.start, node.end)
+ defineExport(node.end, node.exported.name, `${importId}`)
} else {
const importId = defineImport(node, node.source.value as string)
s.remove(node.start, node.end)
- s.append(`\n${ssrExportAllKey}(${importId})`)
+ s.appendLeft(node.end, `${ssrExportAllKey}(${importId});`)
}
}
}