Skip to content

Commit

Permalink
fix: deadlock in ssrLoadModule with circular dependencies (fix vitejs…
Browse files Browse the repository at this point in the history
  • Loading branch information
fairbanksg authored and aleclarson committed Jul 29, 2021
1 parent ce1feff commit daef373
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 10 deletions.
9 changes: 9 additions & 0 deletions packages/playground/ssr-react/src/add.js
Original file line number Diff line number Diff line change
@@ -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)
}
9 changes: 9 additions & 0 deletions packages/playground/ssr-react/src/multiply.js
Original file line number Diff line number Diff line change
@@ -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)
}
11 changes: 10 additions & 1 deletion packages/playground/ssr-react/src/pages/About.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
import { addAndMultiply } from '../add'
import { multiplyAndAdd } from '../multiply'

export default function About() {
return <h1>About</h1>
return (
<>
<h1>About</h1>
<div>{addAndMultiply(1, 2, 3)}</div>
<div>{multiplyAndAdd(1, 2, 3)}</div>
</>
)
}
11 changes: 10 additions & 1 deletion packages/playground/ssr-react/src/pages/Home.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
import { addAndMultiply } from '../add'
import { multiplyAndAdd } from '../multiply'

export default function Home() {
return <h1>Home</h1>
return (
<>
<h1>Home</h1>
<div>{addAndMultiply(1, 2, 3)}</div>
<div>{multiplyAndAdd(1, 2, 3)}</div>
</>
)
}
42 changes: 34 additions & 8 deletions packages/vite/src/node/ssr/ssrModuleLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -42,6 +43,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
}

Expand Down Expand Up @@ -69,20 +78,16 @@ 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)

urlStack = urlStack.concat(url)

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

// Tolerate circular imports by ensuring the module can be
// referenced before it's been instantiated.
Expand Down Expand Up @@ -209,3 +214,24 @@ function resolve(id: string, importer: string | null, root: string) {
resolveCache.set(key, resolved)
return resolved
}

const pendingTransforms = new Map<string, Promise<TransformResult | null>>()

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
}

0 comments on commit daef373

Please sign in to comment.