From ad2ddcce3b66eaa83e204161c4acfaaf9b22bc4b Mon Sep 17 00:00:00 2001 From: Greg Fairbanks Date: Wed, 19 May 2021 13:42:06 -0400 Subject: [PATCH] fix: deadlock in ssrLoadModule with circular dependencies (fix #2491, fix #2258) --- packages/playground/ssr-react/src/add.js | 9 ++++ packages/playground/ssr-react/src/multiply.js | 9 ++++ .../playground/ssr-react/src/pages/About.jsx | 11 ++++- .../playground/ssr-react/src/pages/Home.jsx | 11 ++++- packages/vite/src/node/ssr/ssrModuleLoader.ts | 42 +++++++++++++++---- 5 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 packages/playground/ssr-react/src/add.js create mode 100644 packages/playground/ssr-react/src/multiply.js 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/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..41cf0ee173b015 100644 --- a/packages/playground/ssr-react/src/pages/Home.jsx +++ b/packages/playground/ssr-react/src/pages/Home.jsx @@ -1,3 +1,12 @@ +import { addAndMultiply } from '../add' +import { multiplyAndAdd } from '../multiply' + export default function Home() { - return

Home

+ return ( + <> +

Home

+
{addAndMultiply(1, 2, 3)}
+
{multiplyAndAdd(1, 2, 3)}
+ + ) } diff --git a/packages/vite/src/node/ssr/ssrModuleLoader.ts b/packages/vite/src/node/ssr/ssrModuleLoader.ts index 3ee706740f3c60..a6fc4008e56c52 100644 --- a/packages/vite/src/node/ssr/ssrModuleLoader.ts +++ b/packages/vite/src/node/ssr/ssrModuleLoader.ts @@ -10,7 +10,8 @@ import { ssrImportMetaKey, ssrDynamicImportKey } from './ssrTransform' -import { transformRequest } from '../server/transformRequest' +import { transformRequest, TransformResult } from '../server/transformRequest' +import { ModuleNode } from '../server/moduleGraph' interface SSRContext { global: NodeJS.Global @@ -41,6 +42,14 @@ export async function ssrLoadModule( // request to that module are simply waiting on the same promise. const pending = pendingModules.get(url) if (pending) { + const { moduleGraph } = server + const mod = await moduleGraph.ensureEntryFromUrl(url) + const transformResult = await transformModule(mod, server) + const deps = (transformResult.deps || []).map((d) => unwrapId(d)) + const circularDep = urlStack.find((u) => deps.includes(u)) + if (circularDep) { + return {} + } return pending } @@ -63,18 +72,14 @@ async function instantiateModule( return mod.ssrModule } - const result = - mod.ssrTransformResult || - (await transformRequest(url, server, { ssr: true })) - if (!result) { - // TODO more info? is this even necessary? - throw new Error(`failed to load module for ssr: ${url}`) - } + const result = await transformModule(mod, server) const ssrModule = { [Symbol.toStringTag]: 'Module' } Object.defineProperty(ssrModule, '__esModule', { value: true }) + // Set immediately so the module is available in case of circular dependencies. + mod.ssrModule = ssrModule const isExternal = (dep: string) => dep[0] !== '.' && dep[0] !== '/' @@ -184,3 +189,24 @@ function resolve(id: string, importer: string | null, root: string) { resolveCache.set(key, resolved) return resolved } + +const pendingTransforms = new Map>() + +async function transformModule(mod: ModuleNode, server: ViteDevServer) { + const url = mod.url + let transformResult = mod.ssrTransformResult + if (!transformResult) { + let transformPromise = pendingTransforms.get(url) + if (!transformPromise) { + transformPromise = transformRequest(url, server, { ssr: true }) + pendingTransforms.set(url, transformPromise) + transformPromise.catch(() => {}).then(() => pendingTransforms.delete(url)) + } + transformResult = await transformPromise + } + if (!transformResult) { + // TODO more info? is this even necessary? + throw new Error(`failed to load module for ssr: ${url}`) + } + return transformResult +}