Skip to content

Commit

Permalink
fix(vite-node): circular imports (#3196)
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Apr 15, 2023
1 parent ac04717 commit cbb593a
Show file tree
Hide file tree
Showing 10 changed files with 61 additions and 38 deletions.
48 changes: 18 additions & 30 deletions packages/vite-node/src/client.ts
Expand Up @@ -188,22 +188,31 @@ export class ViteNodeRunner {
if (importee)
mod.importers.add(importee)

// the callstack reference itself circularly
if (callstack.includes(fsPath) && mod.exports)
return mod.exports
const getStack = () => `stack:\n${[...callstack, fsPath].reverse().map(p => ` - ${p}`).join('\n')}`

// cached module
if (mod.promise)
return mod.promise
// check circular dependency
if (callstack.includes(fsPath) || callstack.some(c => this.moduleCache.get(c).importers?.has(fsPath))) {
if (mod.exports)
return mod.exports
}

const promise = this.directRequest(id, fsPath, callstack)
Object.assign(mod, { promise, evaluated: false })
let debugTimer: any
if (this.debug)
debugTimer = setTimeout(() => console.warn(`[vite-node] module ${fsPath} takes over 2s to load.\n${getStack()}`), 2000)

try {
// cached module
if (mod.promise)
return await mod.promise

const promise = this.directRequest(id, fsPath, callstack)
Object.assign(mod, { promise, evaluated: false })
return await promise
}
finally {
mod.evaluated = true
if (debugTimer)
clearTimeout(debugTimer)
}
}

Expand Down Expand Up @@ -245,28 +254,7 @@ export class ViteNodeRunner {

/** @internal */
async dependencyRequest(id: string, fsPath: string, callstack: string[]) {
const getStack = () => {
return `stack:\n${[...callstack, fsPath].reverse().map(p => `- ${p}`).join('\n')}`
}

let debugTimer: any
if (this.debug)
debugTimer = setTimeout(() => console.warn(() => `module ${fsPath} takes over 2s to load.\n${getStack()}`), 2000)

try {
if (callstack.includes(fsPath)) {
const depExports = this.moduleCache.get(fsPath)?.exports
if (depExports)
return depExports
throw new Error(`[vite-node] Failed to resolve circular dependency, ${getStack()}`)
}

return await this.cachedRequest(id, fsPath, callstack)
}
finally {
if (debugTimer)
clearTimeout(debugTimer)
}
return await this.cachedRequest(id, fsPath, callstack)
}

/** @internal */
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
5 changes: 5 additions & 0 deletions test/vite-node/src/circular2/a.ts
@@ -0,0 +1,5 @@
import { c } from './reg'

await new Promise(resolve => setTimeout(resolve, 10))

export const a = `a${c}`
6 changes: 6 additions & 0 deletions test/vite-node/src/circular2/b.ts
@@ -0,0 +1,6 @@
// eslint-disable-next-line unused-imports/no-unused-imports
import { a } from './a'

await new Promise(resolve => setTimeout(resolve, 10))

export const b = 'b'
3 changes: 3 additions & 0 deletions test/vite-node/src/circular2/c.ts
@@ -0,0 +1,3 @@
export const c = 'c'

await new Promise(resolve => setTimeout(resolve, 10))
11 changes: 11 additions & 0 deletions test/vite-node/src/circular2/index.ts
@@ -0,0 +1,11 @@
/* eslint-disable no-console */
import { a } from './a'
import { b } from './b'

/**
* index -> a -> b
* ^ ^v
* reg -> c
*/

console.log(a, b)
6 changes: 6 additions & 0 deletions test/vite-node/src/circular2/reg.ts
@@ -0,0 +1,6 @@
export { a } from './a'

await new Promise(resolve => setTimeout(resolve, 20))

export { b } from './b'
export { c } from './c'
20 changes: 12 additions & 8 deletions test/vite-node/test/circular.test.ts
@@ -1,13 +1,17 @@
import { describe, expect, test } from 'vitest'
import { expect, test } from 'vitest'
import { execa } from 'execa'
import { resolve } from 'pathe'

const cliPath = resolve(__dirname, '../../../packages/vite-node/src/cli.ts')
const entryPath = resolve(__dirname, '../src/circular/index.ts')

describe('circular', async () => {
test('should works', async () => {
const result = await execa('npx', ['esno', cliPath, entryPath], { reject: true })
expect(result.stdout).toMatchInlineSnapshot('"A Bindex index"')
}, 60_000)
})
test('circular 1', async () => {
const entryPath = resolve(__dirname, '../src/circular1/index.ts')
const result = await execa('npx', ['esno', cliPath, entryPath], { reject: true })
expect(result.stdout).toMatchInlineSnapshot('"A Bindex index"')
}, 60_000)

test('circular 2', async () => {
const entryPath = resolve(__dirname, '../src/circular2/index.ts')
const result = await execa('npx', ['esno', cliPath, entryPath], { reject: true })
expect(result.stdout).toMatchInlineSnapshot('"ac b"')
}, 60_000)

0 comments on commit cbb593a

Please sign in to comment.