Skip to content

Commit

Permalink
fix(vite-node): circular import stuck (#3480)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dunqing committed Jun 6, 2023
1 parent 7c687ad commit 50f0700
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 11 deletions.
5 changes: 5 additions & 0 deletions examples/mocks/src/main.js
@@ -1,5 +1,10 @@
import { funcA } from './A'
import { funcB } from './B'

export function main() {
return funcA()
}

export function mainB() {
return funcB()
}
11 changes: 10 additions & 1 deletion examples/mocks/test/circular.spec.ts
@@ -1,18 +1,27 @@
import { expect, test, vi } from 'vitest'
import { main } from '../src/main.js'
import { main, mainB } from '../src/main.js'
import x from '../src/export-default-circle-index'

vi.mock('../src/A', async () => ({
...(await vi.importActual<any>('../src/A')),
funcA: () => 'mockedA',
}))

vi.mock('../src/B', async () => ({
...(await vi.importActual<any>('../src/B')),
funcB: () => 'mockedB',
}))

vi.mock('../src/export-default-circle-b')

test('can import actual inside mock factory', () => {
expect(main()).toBe('mockedA')
})

test('can import in top level and inside mock factory', () => {
expect(mainB()).toBe('mockedB')
})

test('can mock a circular dependency', () => {
expect(x()).toBe(undefined)
})
32 changes: 22 additions & 10 deletions packages/vite-node/src/client.ts
Expand Up @@ -58,10 +58,10 @@ export class ModuleCacheMap extends Map<string, ModuleCache> {
/**
* Assign partial data to the map
*/
update(fsPath: string, mod: Partial<ModuleCache>) {
update(fsPath: string, mod: ModuleCache) {
fsPath = this.normalizePath(fsPath)
if (!super.has(fsPath))
super.set(fsPath, mod)
this.setByModuleId(fsPath, mod)
else
Object.assign(super.get(fsPath) as ModuleCache, mod)
return this
Expand All @@ -75,13 +75,21 @@ export class ModuleCacheMap extends Map<string, ModuleCache> {
return this.setByModuleId(this.normalizePath(fsPath), mod)
}

getByModuleId(modulePath: string): ModuleCache {
getByModuleId(modulePath: string) {
if (!super.has(modulePath))
super.set(modulePath, {})
return super.get(modulePath)!
this.setByModuleId(modulePath, {})

const mod = super.get(modulePath)!
if (!mod.imports) {
Object.assign(mod, {
imports: new Set(),
importers: new Set(),
})
}
return mod as ModuleCache & Required<Pick<ModuleCache, 'imports' | 'importers'>>
}

get(fsPath: string): ModuleCache {
get(fsPath: string) {
return this.getByModuleId(this.normalizePath(fsPath))
}

Expand All @@ -99,6 +107,7 @@ export class ModuleCacheMap extends Map<string, ModuleCache> {
delete mod.promise
delete mod.exports
mod.importers?.clear()
mod.imports?.clear()
return true
}

Expand Down Expand Up @@ -185,16 +194,15 @@ export class ViteNodeRunner {
const importee = callstack[callstack.length - 1]

const mod = this.moduleCache.get(fsPath)
const { imports, importers } = mod

if (!mod.importers)
mod.importers = new Set()
if (importee)
mod.importers.add(importee)
importers.add(importee)

const getStack = () => `stack:\n${[...callstack, fsPath].reverse().map(p => ` - ${p}`).join('\n')}`

// check circular dependency
if (callstack.includes(fsPath) || callstack.some(c => this.moduleCache.get(c).importers?.has(fsPath))) {
if (callstack.includes(fsPath) || Array.from(imports.values()).some(i => importers.has(i))) {
if (mod.exports)
return mod.exports
}
Expand Down Expand Up @@ -269,6 +277,10 @@ export class ViteNodeRunner {

const request = async (dep: string) => {
const [id, depFsPath] = await this.resolveUrl(`${dep}`, fsPath)
const depMod = this.moduleCache.getByModuleId(depFsPath)
depMod.importers.add(moduleId)
mod.imports.add(depFsPath)

return this.dependencyRequest(id, depFsPath, callstack)
}

Expand Down
1 change: 1 addition & 0 deletions packages/vite-node/src/types.ts
Expand Up @@ -66,6 +66,7 @@ export interface ModuleCache {
* Module ids that imports this module
*/
importers?: Set<string>
imports?: Set<string>
}

export interface ViteNodeRunnerOptions {
Expand Down

0 comments on commit 50f0700

Please sign in to comment.