@@ -39,14 +39,13 @@ interface NodeImportResolveOptions
3939}
4040
4141const pendingModules = new Map < string , Promise < SSRModule > > ( )
42- const pendingImports = new Map < string , string [ ] > ( )
42+ const pendingModuleDependencyGraph = new Map < string , Set < string > > ( )
4343const importErrors = new WeakMap < Error , { importee : string } > ( )
4444
4545export async function ssrLoadModule (
4646 url : string ,
4747 server : ViteDevServer ,
4848 context : SSRContext = { global } ,
49- urlStack : string [ ] = [ ] ,
5049 fixStacktrace ?: boolean ,
5150) : Promise < SSRModule > {
5251 url = unwrapId ( url )
@@ -60,17 +59,11 @@ export async function ssrLoadModule(
6059 return pending
6160 }
6261
63- const modulePromise = instantiateModule (
64- url ,
65- server ,
66- context ,
67- urlStack ,
68- fixStacktrace ,
69- )
62+ const modulePromise = instantiateModule ( url , server , context , fixStacktrace )
7063 pendingModules . set ( url , modulePromise )
7164 modulePromise
7265 . catch ( ( ) => {
73- pendingImports . delete ( url )
66+ /* prevent unhandled promise rejection error from bubbling up */
7467 } )
7568 . finally ( ( ) => {
7669 pendingModules . delete ( url )
@@ -82,7 +75,6 @@ async function instantiateModule(
8275 url : string ,
8376 server : ViteDevServer ,
8477 context : SSRContext = { global } ,
85- urlStack : string [ ] = [ ] ,
8678 fixStacktrace ?: boolean ,
8779) : Promise < SSRModule > {
8880 const { moduleGraph } = server
@@ -122,9 +114,6 @@ async function instantiateModule(
122114 url : pathToFileURL ( mod . file ! ) . toString ( ) ,
123115 }
124116
125- urlStack = urlStack . concat ( url )
126- const isCircular = ( url : string ) => urlStack . includes ( url )
127-
128117 const {
129118 isProduction,
130119 resolve : { dedupe, preserveSymlinks } ,
@@ -150,38 +139,37 @@ async function instantiateModule(
150139 packageCache : server . config . packageCache ,
151140 }
152141
153- // Since dynamic imports can happen in parallel, we need to
154- // account for multiple pending deps and duplicate imports.
155- const pendingDeps : string [ ] = [ ]
156-
157142 const ssrImport = async ( dep : string , metadata ?: SSRImportBaseMetadata ) => {
158143 try {
159144 if ( dep [ 0 ] !== '.' && dep [ 0 ] !== '/' ) {
160145 return await nodeImport ( dep , mod . file ! , resolveOptions , metadata )
161146 }
162147 // convert to rollup URL because `pendingImports`, `moduleGraph.urlToModuleMap` requires that
163148 dep = unwrapId ( dep )
164- if ( ! isCircular ( dep ) && ! pendingImports . get ( dep ) ?. some ( isCircular ) ) {
165- pendingDeps . push ( dep )
166- if ( pendingDeps . length === 1 ) {
167- pendingImports . set ( url , pendingDeps )
168- }
169- const mod = await ssrLoadModule (
170- dep ,
171- server ,
172- context ,
173- urlStack ,
174- fixStacktrace ,
175- )
176- if ( pendingDeps . length === 1 ) {
177- pendingImports . delete ( url )
178- } else {
179- pendingDeps . splice ( pendingDeps . indexOf ( dep ) , 1 )
149+
150+ // Handle any potential circular dependencies for static imports, preventing
151+ // deadlock scenarios when two modules are indirectly waiting on one another
152+ // to finish initializing. Dynamic imports are resolved at runtime, hence do
153+ // not contribute to the static module dependency graph in the same way
154+ if ( ! metadata ?. isDynamicImport ) {
155+ addPendingModuleDependency ( url , dep )
156+
157+ // If there's a circular dependency formed as a result of the dep import,
158+ // return the current state of the dependent module being initialized, in
159+ // order to avoid interlocking circular dependencies hanging indefinitely
160+ if ( checkModuleDependencyExists ( dep , url ) ) {
161+ const depSsrModule = moduleGraph . urlToModuleMap . get ( dep ) ?. ssrModule
162+ if ( ! depSsrModule ) {
163+ // Technically, this should never happen under normal circumstances
164+ throw new Error (
165+ '[vite] The dependency module is not yet fully initialized due to circular dependency. This is a bug in Vite SSR' ,
166+ )
167+ }
168+ return depSsrModule
180169 }
181- // return local module to avoid race condition #5470
182- return mod
183170 }
184- return moduleGraph . urlToModuleMap . get ( dep ) ?. ssrModule
171+
172+ return ssrLoadModule ( dep , server , context , fixStacktrace )
185173 } catch ( err ) {
186174 // tell external error handler which mod was imported with error
187175 importErrors . set ( err , { importee : dep } )
@@ -267,11 +255,52 @@ async function instantiateModule(
267255 )
268256
269257 throw e
258+ } finally {
259+ pendingModuleDependencyGraph . delete ( url )
270260 }
271261
272262 return Object . freeze ( ssrModule )
273263}
274264
265+ function addPendingModuleDependency ( originUrl : string , depUrl : string ) : void {
266+ if ( pendingModuleDependencyGraph . has ( originUrl ) ) {
267+ pendingModuleDependencyGraph . get ( originUrl ) ! . add ( depUrl )
268+ } else {
269+ pendingModuleDependencyGraph . set ( originUrl , new Set ( [ depUrl ] ) )
270+ }
271+ }
272+
273+ function checkModuleDependencyExists (
274+ originUrl : string ,
275+ targetUrl : string ,
276+ ) : boolean {
277+ const visited = new Set ( )
278+ const stack = [ originUrl ]
279+
280+ while ( stack . length ) {
281+ const currentUrl = stack . pop ( ) !
282+
283+ if ( currentUrl === targetUrl ) {
284+ return true
285+ }
286+
287+ if ( ! visited . has ( currentUrl ) ) {
288+ visited . add ( currentUrl )
289+
290+ const dependencies = pendingModuleDependencyGraph . get ( currentUrl )
291+ if ( dependencies ) {
292+ for ( const depUrl of dependencies ) {
293+ if ( ! visited . has ( depUrl ) ) {
294+ stack . push ( depUrl )
295+ }
296+ }
297+ }
298+ }
299+ }
300+
301+ return false
302+ }
303+
275304// In node@12+ we can use dynamic import to load CJS and ESM
276305async function nodeImport (
277306 id : string ,
0 commit comments