+
diff --git a/src/client/logic/rpc-static.ts b/src/client/logic/rpc-static.ts
index 06db1e0..3f750d1 100644
--- a/src/client/logic/rpc-static.ts
+++ b/src/client/logic/rpc-static.ts
@@ -22,5 +22,8 @@ export function createStaticRpcClient(): RpcFunctions {
resolveId: (query, id) => getModuleTransformInfo(query, id).then(r => r.resolvedId),
onModuleUpdated: async () => undefined,
getServerMetrics: async () => ({}),
+ getWaterfallInfo: async () => ({}),
+ getHmrEvents: async () => [],
+ list: () => null!,
}
}
diff --git a/src/client/pages/index.vue b/src/client/pages/index.vue
index 28134df..e7bd6ea 100644
--- a/src/client/pages/index.vue
+++ b/src/client/pages/index.vue
@@ -55,6 +55,9 @@ const isRoot = computed(() => route.path === '/')
+
+
+
diff --git a/src/client/pages/index/waterfall.vue b/src/client/pages/index/waterfall.vue
new file mode 100644
index 0000000..1fcfba1
--- /dev/null
+++ b/src/client/pages/index/waterfall.vue
@@ -0,0 +1,322 @@
+
+
+
+
+
+
+
+
+ Waterfall
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/client/stores/options.ts b/src/client/stores/options.ts
index 7c2952a..e24d3f8 100644
--- a/src/client/stores/options.ts
+++ b/src/client/stores/options.ts
@@ -12,6 +12,8 @@ export interface ViewState {
showBailout: boolean
showOneColumn: boolean
sort: 'default' | 'time-asc' | 'time-desc'
+ waterfallShowResolveId: boolean
+ waterfallStacking: boolean
}
export interface SearchState {
@@ -37,6 +39,8 @@ export const useOptionsStore = defineStore('options', () => {
showBailout: false,
showOneColumn: false,
sort: 'default',
+ waterfallShowResolveId: true,
+ waterfallStacking: true,
},
{ mergeDefaults: true },
)
diff --git a/src/node/context.ts b/src/node/context.ts
index 8eb4ed2..824a41d 100644
--- a/src/node/context.ts
+++ b/src/node/context.ts
@@ -1,8 +1,9 @@
import type { Environment, ResolvedConfig } from 'vite'
-import type { Metadata, ModuleInfo, PluginMetricInfo, QueryEnv, ResolveIdInfo, ServerMetrics, TransformInfo } from '../types'
+import type { HmrEventInfo, Metadata, ModuleInfo, PluginMetricInfo, QueryEnv, ResolveIdInfo, ServerMetrics, TransformInfo, WaterfallInfo } from '../types'
import type { ViteInspectOptions } from './options'
import { Buffer } from 'node:buffer'
import { resolve } from 'node:path'
+import { objectMap } from '@antfu/utils'
import { createFilter } from 'unplugin-utils'
import { DUMMY_LOAD_PLUGIN_NAME } from './constants'
import { removeVersionQuery, serializePlugin } from './utils'
@@ -111,10 +112,12 @@ export class InspectContextViteEnv {
transform: Record
resolveId: Record
transformCounter: Record
+ hmrEvents: HmrEventInfo[]
} = {
transform: {},
resolveId: {},
transformCounter: {},
+ hmrEvents: [],
}
recordTransform(id: string, info: TransformInfo, preTransformCode: string) {
@@ -147,6 +150,10 @@ export class InspectContextViteEnv {
this.data.resolveId[id].push(info)
}
+ recordHmrEvent(info: HmrEventInfo) {
+ this.data.hmrEvents.push(info)
+ }
+
invalidate(id: string) {
id = this.normalizeId(id)
delete this.data.transform[id]
@@ -294,6 +301,53 @@ export class InspectContextViteEnv {
}
}
+ async getWaterfallInfo() {
+ const resolveIdByResult: Record = {}
+ for (const id in this.data.resolveId) {
+ const info = this.data.resolveId[id][0]
+ resolveIdByResult[info.result] = {
+ ...info,
+ result: id,
+ }
+ }
+ return objectMap(this.data.transform, (id, transforms) => {
+ const result: WaterfallInfo[string] = []
+ let currentId = id
+ while (resolveIdByResult[currentId]) {
+ const info = resolveIdByResult[currentId]
+ result.push({
+ name: info.name,
+ start: info.start,
+ end: info.end,
+ isResolveId: true,
+ })
+ if (currentId === info.result)
+ break
+ currentId = info.result
+ }
+ for (const transform of transforms) {
+ result.push({
+ name: transform.name,
+ start: transform.start,
+ end: transform.end,
+ isResolveId: false,
+ })
+ }
+ result.sort((a, b) => a.start - b.start)
+ const filtered = result.filter(({ start, end }, i) => i === 0 || i === result.length - 1 || start < end)
+ return filtered.length
+ ? [
+ id,
+ filtered,
+ ]
+ : undefined
+ })
+ }
+
+ async getHmrEvents() {
+ return this.data.hmrEvents.sort((a, b) => a.timestamp - b.timestamp)
+ }
+
clearId(_id: string) {
const id = this.resolveId(_id)
if (id) {
diff --git a/src/node/index.ts b/src/node/index.ts
index 1d529f7..7af6d46 100644
--- a/src/node/index.ts
+++ b/src/node/index.ts
@@ -233,13 +233,16 @@ export default function PluginInspect(options: ViteInspectOptions = {}): Plugin
return null
},
},
- hotUpdate({ modules, environment }) {
+ hotUpdate({ modules, type, file, timestamp }) {
const ids = modules.map(module => module.id)
- environment.hot.send({
+ this.environment.hot.send({
type: 'custom',
event: 'vite-plugin-inspect:update',
data: { ids } as HMRData,
})
+
+ const env = ctx.getEnvContext(this.environment)
+ env?.recordHmrEvent({ type, file, timestamp })
},
async buildEnd() {
if (!build)
diff --git a/src/node/rpc.ts b/src/node/rpc.ts
index ccadc8f..621f834 100644
--- a/src/node/rpc.ts
+++ b/src/node/rpc.ts
@@ -25,6 +25,12 @@ export function createServerRpc(
.data
.serverMetrics || {}
},
+ async getWaterfallInfo(query) {
+ return ctx.queryEnv(query).getWaterfallInfo()
+ },
+ async getHmrEvents(query) {
+ return ctx.queryEnv(query).getHmrEvents()
+ },
async onModuleUpdated() {},
async list() {
diff --git a/src/node/utils.ts b/src/node/utils.ts
index 872d233..278bc11 100644
--- a/src/node/utils.ts
+++ b/src/node/utils.ts
@@ -34,3 +34,25 @@ export function removeVersionQuery(url: string) {
}
return url
}
+
+export function createFilter(pattern: string) {
+ pattern = pattern.trim()
+ if (!pattern) {
+ return () => true
+ }
+ const regex = new RegExp(pattern, 'i')
+ return (name: string) => regex.test(name)
+}
+
+export function generatorHashColorByString(str: string) {
+ let hash = 0
+ for (let i = 0; i < str.length; i++) {
+ hash = str.charCodeAt(i) + ((hash << 5) - hash)
+ }
+ let color = '#'
+ for (let i = 0; i < 3; i++) {
+ const value = (hash >> (i * 8)) & 0xFF
+ color += (`00${value.toString(16)}`).slice(-2)
+ }
+ return color
+}
diff --git a/src/types.ts b/src/types.ts
index aa7e831..0963391 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -19,6 +19,12 @@ export interface ResolveIdInfo {
error?: ParsedError
}
+export interface HmrEventInfo {
+ type: 'create' | 'update' | 'delete'
+ file: string
+ timestamp: number
+}
+
export interface ParsedError {
message: string
stack: StackFrame[]
@@ -44,6 +50,13 @@ export interface ModuleTransformInfo {
transforms: TransformInfo[]
}
+export type WaterfallInfo = Record
+
export interface PluginMetricInfo {
name: string
enforce?: string
@@ -107,6 +120,8 @@ export interface RpcFunctions {
getModuleTransformInfo: (query: QueryEnv, id: string, clear?: boolean) => Promise
getPluginMetrics: (query: QueryEnv) => Promise
getServerMetrics: (query: QueryEnv) => Promise
+ getWaterfallInfo: (query: QueryEnv) => Promise
+ getHmrEvents: (query: QueryEnv) => Promise
resolveId: (query: QueryEnv, id: string) => Promise
onModuleUpdated: () => Promise