Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions packages/vite/src/node/__tests__/inspect-context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1509,3 +1509,48 @@ describe('vite inspect batched writes', () => {
])
})
})

describe('vite inspect modules list', () => {
it('reuses the list until a recorded change and then rebuilds it', async () => {
const { ctx, envCtx, vite } = await createFixture()
const plugin = vite.config.plugins[1]
const record = (id: string) => {
envCtx.recordTransform(id, { name: 'plugin-a', result: `export const id = '${id}'`, start: 0, end: 1 }, `const id = '${id}'`, plugin)
}

record('/src/a.ts')
const first = await envCtx.getModulesList()
expect(first.map(module => module.id)).toEqual(['/src/a.ts'])
expect(await envCtx.getModulesList()).toBe(first)

// Finishing a transform request doesn't change the recorded modules.
ctx.store.clearPendingLoadMetrics(envCtx.scope, {})
expect(await envCtx.getModulesList()).toBe(first)

record('/src/b.ts')
const second = await envCtx.getModulesList()
expect(second.map(module => module.id)).toEqual(['/src/a.ts', '/src/b.ts'])

envCtx.invalidate('/src/a.ts')
expect((await envCtx.getModulesList()).map(module => module.id)).toEqual(['/src/b.ts'])
})

it('builds the full sorted list when it takes several slices', async () => {
const { envCtx, vite } = await createFixture()
const plugin = vite.config.plugins[1]
const ids = Array.from({ length: 3000 }, (_, index) => `/src/m${String(index).padStart(4, '0')}.ts`)
for (const id of ids)
envCtx.recordTransform(id, { name: 'plugin-a', result: 'export {}', start: 0, end: 1 }, 'const a = 1', plugin)

// Make every module take longer than a slice, so the build yields between modules.
const now = vi.spyOn(performance, 'now')
let clock = 0
now.mockImplementation(() => (clock += 10))
try {
expect((await envCtx.getModulesList()).map(module => module.id)).toEqual(ids)
}
finally {
now.mockRestore()
}
})
})
23 changes: 20 additions & 3 deletions packages/vite/src/node/inspect/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,14 @@ 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
// Keep it well above the module count of large projects, or the cache keeps clearing and never hits.
const MAX_PUBLIC_MODULE_ID_CACHE_SIZE = 100_000

export class ViteInspectEnvironmentContext {
readonly scope: string
private pluginCallCount = 0
private graphModuleIdResolver?: ReturnType<typeof createGraphModuleIdResolver>
private modulesList?: { version: number, modules: ViteInspectModuleInfo[] }
// Every hook call records its module id, so the same id is normalized once per plugin per request.
private readonly publicModuleIds = new Map<string, string>()
private publicModuleIdsRoot?: string
Expand Down Expand Up @@ -405,12 +407,27 @@ export class ViteInspectEnvironmentContext {
}

async getModulesList(pluginCtx?: ModuleInfoProvider): Promise<ViteInspectModuleInfo[]> {
// The panel refetches the list after every edit, often twice; reuse it until something is recorded.
const store = this.inspectContext.store
await store.flush()
const version = store.getChangeVersion()
if (!pluginCtx && this.modulesList?.version === version) {
this.graphModuleIdResolver ??= this.createGraphModuleIdResolver(this.modulesList.modules)
return this.modulesList.modules
}

const modules = await getEnvironmentModulesList(this, pluginCtx)
this.graphModuleIdResolver = createGraphModuleIdResolver(
this.graphModuleIdResolver = this.createGraphModuleIdResolver(modules)
if (!pluginCtx)
this.modulesList = { version, modules }
return modules
}

private createGraphModuleIdResolver(modules: ViteInspectModuleInfo[]): ReturnType<typeof createGraphModuleIdResolver> {
return createGraphModuleIdResolver(
modules.map(module => module.id),
this.env.getTopLevelConfig().resolve?.extensions ?? [],
)
return modules
}

async getGraphModuleIdResolver(): Promise<ReturnType<typeof createGraphModuleIdResolver>> {
Expand Down
18 changes: 16 additions & 2 deletions packages/vite/src/node/inspect/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import type {
} from './types'
import { DUMMY_LOAD_PLUGIN_NAME } from './utils'

const MODULES_LIST_SLICE_MS = 4

export interface ModuleInfoProvider {
getModuleInfo: (id: string) => {
importedIds?: readonly string[]
Expand Down Expand Up @@ -53,7 +55,7 @@ export async function getModulesList(

const ids = new Set(Object.keys(transformsById).concat(Object.keys(transformedIdMap)))

return Array.from(ids).sort().map((id) => {
const toModuleInfo = (id: string): ViteInspectModuleInfo => {
let totalTime = 0
const transforms = transformsById[id] || []
const transformPlugins: ViteInspectModulePluginMetric[] = transforms.map((transform) => {
Expand Down Expand Up @@ -85,7 +87,19 @@ export async function getModulesList(
sourceSize: firstTransform?.hasResult ? firstTransform.resultSize : 0,
distSize: lastTransform?.hasResult ? lastTransform.resultSize : 0,
}
})
}

// Large projects take tens of milliseconds here; yield so Vite can keep serving requests meanwhile.
const modules: ViteInspectModuleInfo[] = []
let sliceStart = performance.now()
for (const id of Array.from(ids).sort()) {
modules.push(toModuleInfo(id))
if (performance.now() - sliceStart > MODULES_LIST_SLICE_MS) {
await new Promise(resolve => setImmediate(resolve))
sliceStart = performance.now()
}
}
return modules
}

export async function getPublicGraphModuleIds(ctx: ViteInspectEnvironmentContext): Promise<string[]> {
Expand Down
7 changes: 7 additions & 0 deletions packages/vite/src/node/inspect/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ class PayloadViteInspectStore implements ViteInspectStore {
private peakQueuedBytes = 0
private writeBatches = 0

private changeVersion = 0
private activeReaders = 0
private readerWaiters: Array<() => void> = []

Expand Down Expand Up @@ -318,6 +319,10 @@ class PayloadViteInspectStore implements ViteInspectStore {
return Array.from(moduleIds)
}

getChangeVersion(): number {
return this.changeVersion
}

getStats(): ViteInspectStoreStats {
return {
maxBatchItems: this.maxBatchItems,
Expand Down Expand Up @@ -354,6 +359,8 @@ class PayloadViteInspectStore implements ViteInspectStore {
private enqueue(write: QueuedWrite): void {
if (this.closing || this.closed || this.lastWriteError)
return
if (write.operation !== 'clearPendingLoadMetrics')
this.changeVersion += 1

const estimatedBytes = estimateWriteBytes(write)
this.queue.push({ write, estimatedBytes })
Expand Down
2 changes: 2 additions & 0 deletions packages/vite/src/node/inspect/store/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ export interface ViteInspectStore {
getFirstResolveResult: (scope: string, sourceId: string) => Promise<string | undefined>
findModuleId: (scope: string, publicModuleId: string) => Promise<string | undefined>
getModuleIds: (scope: string) => Promise<string[]>
/** Increases whenever a queued write can change recorded modules, so callers can reuse derived data until it changes. */
getChangeVersion: () => number
getStats: () => ViteInspectStoreStats
flush: () => Promise<void>
close: () => Promise<void>
Expand Down
Loading