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});`) } } }