feat: resolve circular dependencies

This commit is contained in:
Anthony Fu 2021-12-08 19:37:26 +08:00
parent 576bd8ac3a
commit 0bc512400b
7 changed files with 60 additions and 19 deletions

View File

@ -7,21 +7,25 @@ import c from 'picocolors'
const { red, dim, yellow } = c
export interface ModuleCache {
promise?: Promise<any>
exports?: any
transformResult?: TransformResult
}
declare global {
namespace NodeJS {
interface Process {
__vite_node__: {
server: ViteDevServer
watch?: boolean
moduleCache: Map<string, Promise<any>>
modulesTransformResult: Map<string, TransformResult>
moduleCache: Map<string, ModuleCache>
}
}
}
}
const moduleCache = new Map<string, Promise<any>>()
const modulesTransformResult = new Map<string, TransformResult>()
const moduleCache = new Map<string, ModuleCache>()
export interface ViteNodeOptions {
silent?: boolean
@ -55,7 +59,6 @@ export async function run(argv: ViteNodeOptions) {
process.__vite_node__ = {
server,
moduleCache,
modulesTransformResult,
}
try {
@ -126,6 +129,13 @@ async function transform(server: ViteDevServer, id: string) {
}
}
function setCache(id: string, mod: Partial<ModuleCache>) {
if (!moduleCache.has(id))
moduleCache.set(id, mod)
else
Object.assign(moduleCache.get(id), mod)
}
async function execute(files: string[], server: ViteDevServer, options: ViteNodeOptions) {
const result = []
for (const file of files)
@ -136,10 +146,13 @@ async function execute(files: string[], server: ViteDevServer, options: ViteNode
callstack = [...callstack, id]
const request = async(dep: string) => {
if (callstack.includes(dep)) {
throw new Error(`${red('Circular dependency detected')}\nStack:\n${[...callstack, dep].reverse().map((i) => {
const path = relative(server.config.root, toFilePath(normalizeId(i), server))
return dim(' -> ') + (i === dep ? yellow(path) : path)
}).join('\n')}\n`)
if (!moduleCache.get(dep)) {
throw new Error(`${red('Circular dependency detected')}\nStack:\n${[...callstack, dep].reverse().map((i) => {
const path = relative(server.config.root, toFilePath(normalizeId(i), server))
return dim(' -> ') + (i === dep ? yellow(path) : path)
}).join('\n')}\n`)
}
return moduleCache.get(dep)!.exports
}
return cachedRequest(dep, callstack)
}
@ -151,11 +164,11 @@ async function execute(files: string[], server: ViteDevServer, options: ViteNode
if (!result)
throw new Error(`failed to load ${id}`)
modulesTransformResult.set(id, result)
const url = pathToFileURL(fsPath)
const exports = {}
setCache(id, { transformResult: result, exports })
const context = {
require: createRequire(url),
__filename: fsPath,
@ -187,10 +200,11 @@ async function execute(files: string[], server: ViteDevServer, options: ViteNode
if (options.shouldExternalize!(fsPath, server))
return import(fsPath)
if (moduleCache.has(fsPath))
return moduleCache.get(fsPath)
moduleCache.set(fsPath, directRequest(id, fsPath, callstack))
return await moduleCache.get(fsPath)
if (moduleCache.get(fsPath)?.promise)
return moduleCache.get(fsPath)?.promise
const promise = directRequest(id, fsPath, callstack)
setCache(fsPath, { promise })
return await promise
}
function exportAll(exports: any, sourceModule: any) {

View File

@ -18,15 +18,15 @@ export async function printError(error: unknown) {
return
}
const { modulesTransformResult } = process.__vite_node__
const { moduleCache } = process.__vite_node__
const e = error as ErrorWithDiff
let codeFramePrinted = false
const stacks = parseStack(e.stack || '')
const nearest = stacks.find(stack => modulesTransformResult.has(stack.file))
const nearest = stacks.find(stack => moduleCache.has(stack.file))
if (nearest) {
const transformResult = modulesTransformResult.get(nearest.file)
const transformResult = moduleCache.get(nearest.file)?.transformResult
const pos = await getOriginalPos(transformResult?.map, nearest)
if (pos && existsSync(nearest.file)) {
const sourceCode = await fs.readFile(nearest.file, 'utf-8')

View File

@ -0,0 +1,7 @@
import { circularB } from './circularB'
export const CalledB: number[] = []
export function circularA() {
return circularB()
}

View File

@ -0,0 +1,5 @@
import { CalledB } from './circularA'
export function circularB() {
return CalledB.push(CalledB.length)
}

View File

@ -1,5 +1,5 @@
import { expect, test, assert, suite } from 'vitest'
import { two } from '../test/submodule'
import { two } from '../src/submodule'
test('Math.sqrt()', () => {
assert.equal(Math.sqrt(4), two)

View File

@ -0,0 +1,15 @@
import { test, expect } from 'vitest'
import { CalledB, circularA } from '../src/circularA'
test('circular', () => {
CalledB.length = 0
circularA()
expect(CalledB.length).toBe(1)
circularA()
circularA()
expect(CalledB).toEqual([0, 1, 2])
})