diff --git a/packages/vite/src/node/__tests__/inspect-context.test.ts b/packages/vite/src/node/__tests__/inspect-context.test.ts index 76ad1518..f6e13a4a 100644 --- a/packages/vite/src/node/__tests__/inspect-context.test.ts +++ b/packages/vite/src/node/__tests__/inspect-context.test.ts @@ -1286,3 +1286,26 @@ describe('vite inspect hook timing', () => { }) }) }) + +describe('vite inspect public module ids', () => { + it('normalizes dependency ids relative to the current root', async () => { + const { envCtx, vite } = await createFixture() + const id = '/project/node_modules/dep/index.js?v=abc123' + + expect(envCtx.getPublicModuleId(id)).toBe('./node_modules/dep/index.js') + expect(envCtx.getPublicModuleId(id)).toBe('./node_modules/dep/index.js') + + // A cached id must follow a root change instead of returning the stale result. + ;(vite.config as { root: string }).root = '/project/node_modules' + expect(envCtx.getPublicModuleId(id)).toBe('./dep/index.js') + }) + + it('keeps returning correct ids after the cache is cleared at its size limit', async () => { + const { envCtx } = await createFixture() + for (let index = 0; index < 10_001; index++) + expect(envCtx.getPublicModuleId(`/project/node_modules/dep/m${index}.js`)).toBe(`./node_modules/dep/m${index}.js`) + + expect(envCtx.getPublicModuleId('/project/node_modules/dep/m0.js')).toBe('./node_modules/dep/m0.js') + expect(envCtx.getPublicModuleId('/src/main.ts')).toBe('/src/main.ts') + }) +}) diff --git a/packages/vite/src/node/inspect/context.ts b/packages/vite/src/node/inspect/context.ts index 588e10b8..71c599ac 100644 --- a/packages/vite/src/node/inspect/context.ts +++ b/packages/vite/src/node/inspect/context.ts @@ -180,10 +180,16 @@ export class ViteInspectViteContext { } } +// Ids can carry HMR timestamps, so cap the cache instead of letting it grow for the whole session. +const MAX_PUBLIC_MODULE_ID_CACHE_SIZE = 10_000 + export class ViteInspectEnvironmentContext { readonly scope: string private pluginCallCount = 0 private graphModuleIdResolver?: ReturnType + // Every hook call records its module id, so the same id is normalized once per plugin per request. + private readonly publicModuleIds = new Map() + private publicModuleIdsRoot?: string constructor( readonly inspectContext: ViteInspectContext, @@ -336,7 +342,17 @@ export class ViteInspectEnvironmentContext { } getPublicModuleId(id: string): string { - return normalizeModuleId(this.normalizeId(id), this.getModuleIdBaseRoot()) + const root = this.getModuleIdBaseRoot() + if (root !== this.publicModuleIdsRoot || this.publicModuleIds.size >= MAX_PUBLIC_MODULE_ID_CACHE_SIZE) { + this.publicModuleIds.clear() + this.publicModuleIdsRoot = root + } + let publicId = this.publicModuleIds.get(id) + if (publicId === undefined) { + publicId = normalizeModuleId(this.normalizeId(id), root) + this.publicModuleIds.set(id, publicId) + } + return publicId } async getModuleId(id: string): Promise {