Skip to content

Commit daef373

Browse files
fairbanksgaleclarson
authored andcommitted
fix: deadlock in ssrLoadModule with circular dependencies (fix vitejs#2491, fix vitejs#2258)
1 parent ce1feff commit daef373

File tree

5 files changed

+72
-10
lines changed

5 files changed

+72
-10
lines changed
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { multiply } from './multiply'
2+
3+
export function add(a, b) {
4+
return a + b
5+
}
6+
7+
export function addAndMultiply(a, b, c) {
8+
return multiply(add(a, b), c)
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { add } from './add'
2+
3+
export function multiply(a, b) {
4+
return a * b
5+
}
6+
7+
export function multiplyAndAdd(a, b, c) {
8+
return add(multiply(a, b), c)
9+
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
import { addAndMultiply } from '../add'
2+
import { multiplyAndAdd } from '../multiply'
3+
14
export default function About() {
2-
return <h1>About</h1>
5+
return (
6+
<>
7+
<h1>About</h1>
8+
<div>{addAndMultiply(1, 2, 3)}</div>
9+
<div>{multiplyAndAdd(1, 2, 3)}</div>
10+
</>
11+
)
312
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
import { addAndMultiply } from '../add'
2+
import { multiplyAndAdd } from '../multiply'
3+
14
export default function Home() {
2-
return <h1>Home</h1>
5+
return (
6+
<>
7+
<h1>Home</h1>
8+
<div>{addAndMultiply(1, 2, 3)}</div>
9+
<div>{multiplyAndAdd(1, 2, 3)}</div>
10+
</>
11+
)
312
}

packages/vite/src/node/ssr/ssrModuleLoader.ts

+34-8
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
ssrImportMetaKey,
1111
ssrDynamicImportKey
1212
} from './ssrTransform'
13-
import { transformRequest } from '../server/transformRequest'
13+
import { transformRequest, TransformResult } from '../server/transformRequest'
14+
import { ModuleNode } from '../server/moduleGraph'
1415

1516
interface SSRContext {
1617
global: NodeJS.Global
@@ -42,6 +43,14 @@ export async function ssrLoadModule(
4243
// request to that module are simply waiting on the same promise.
4344
const pending = pendingModules.get(url)
4445
if (pending) {
46+
const { moduleGraph } = server
47+
const mod = await moduleGraph.ensureEntryFromUrl(url)
48+
const transformResult = await transformModule(mod, server)
49+
const deps = (transformResult.deps || []).map((d) => unwrapId(d))
50+
const circularDep = urlStack.find((u) => deps.includes(u))
51+
if (circularDep) {
52+
return {}
53+
}
4554
return pending
4655
}
4756

@@ -69,20 +78,16 @@ async function instantiateModule(
6978
return mod.ssrModule
7079
}
7180

72-
const result =
73-
mod.ssrTransformResult ||
74-
(await transformRequest(url, server, { ssr: true }))
75-
if (!result) {
76-
// TODO more info? is this even necessary?
77-
throw new Error(`failed to load module for ssr: ${url}`)
78-
}
81+
const result = await transformModule(mod, server)
7982

8083
urlStack = urlStack.concat(url)
8184

8285
const ssrModule = {
8386
[Symbol.toStringTag]: 'Module'
8487
}
8588
Object.defineProperty(ssrModule, '__esModule', { value: true })
89+
// Set immediately so the module is available in case of circular dependencies.
90+
mod.ssrModule = ssrModule
8691

8792
// Tolerate circular imports by ensuring the module can be
8893
// referenced before it's been instantiated.
@@ -209,3 +214,24 @@ function resolve(id: string, importer: string | null, root: string) {
209214
resolveCache.set(key, resolved)
210215
return resolved
211216
}
217+
218+
const pendingTransforms = new Map<string, Promise<TransformResult | null>>()
219+
220+
async function transformModule(mod: ModuleNode, server: ViteDevServer) {
221+
const url = mod.url
222+
let transformResult = mod.ssrTransformResult
223+
if (!transformResult) {
224+
let transformPromise = pendingTransforms.get(url)
225+
if (!transformPromise) {
226+
transformPromise = transformRequest(url, server, { ssr: true })
227+
pendingTransforms.set(url, transformPromise)
228+
transformPromise.catch(() => {}).then(() => pendingTransforms.delete(url))
229+
}
230+
transformResult = await transformPromise
231+
}
232+
if (!transformResult) {
233+
// TODO more info? is this even necessary?
234+
throw new Error(`failed to load module for ssr: ${url}`)
235+
}
236+
return transformResult
237+
}

0 commit comments

Comments
 (0)