diff --git a/docs/errors/VDT0001.md b/docs/errors/VDT0001.md
new file mode 100644
index 00000000..2066fab8
--- /dev/null
+++ b/docs/errors/VDT0001.md
@@ -0,0 +1,25 @@
+---
+outline: deep
+---
+
+# VDT0001: Inspect Context Unavailable
+
+## Message
+
+> Vite inspect context is not available for this DevTools context.
+
+## Cause
+
+A `vite:inspect:*` RPC function was called in a DevTools context that did not register the Vite inspect collector.
+
+## Example
+
+Calling `vite:inspect:get-modules-list` while only the base Vite metadata RPC functions are registered triggers this error.
+
+## Fix
+
+Register the Vite UI plugin with inspect collection enabled before calling `vite:inspect:*` RPC functions.
+
+## Source
+
+- [`packages/vite/src/node/inspect/context.ts`](https://github.com/vitejs/devtools/blob/main/packages/vite/src/node/inspect/context.ts) — `getViteInspectContext()` throws this when no inspect context was registered for the current DevTools context.
diff --git a/docs/errors/VDT0002.md b/docs/errors/VDT0002.md
new file mode 100644
index 00000000..ce16c0c7
--- /dev/null
+++ b/docs/errors/VDT0002.md
@@ -0,0 +1,25 @@
+---
+outline: deep
+---
+
+# VDT0002: Inspect Target Not Found
+
+## Message
+
+> Vite inspect target "`{id}`" was not found in `{target}`.
+
+## Cause
+
+An inspect RPC query referenced a Vite instance or environment that the collector has not recorded.
+
+## Example
+
+Calling `vite:inspect:get-module-transform-info` with an unknown `{ vite, env }` query triggers this error.
+
+## Fix
+
+Read `vite:inspect:get-metadata` first and pass one of the returned Vite instance IDs and environment names to follow-up inspect RPC calls.
+
+## Source
+
+- [`packages/vite/src/node/inspect/context.ts`](https://github.com/vitejs/devtools/blob/main/packages/vite/src/node/inspect/context.ts) — `getViteContext()` and `getEnvContext()` throw this when a query references an unknown inspect target.
diff --git a/docs/errors/index.md b/docs/errors/index.md
index 10004303..05772d60 100644
--- a/docs/errors/index.md
+++ b/docs/errors/index.md
@@ -9,7 +9,7 @@ Vite DevTools uses structured diagnostics to surface actionable warnings and err
## How error codes work
- Codes follow the pattern **prefix + 4-digit number** (e.g., `DF0001`, `DTK0008`, `RDDT0002`).
-- Each prefix maps to a package: `DTK` for `@vitejs/devtools` (Vite-specific pieces), `RDDT` for `@vitejs/devtools-rolldown`. The framework-neutral `devframe` package documents its own `DF`-prefixed codes at the [Devframe docs site](https://devfra.me/errors/).
+- Each prefix maps to a package: `DTK` for `@vitejs/devtools` (Vite-specific pieces), `RDDT` for `@vitejs/devtools-rolldown`, `VDT` for `@vitejs/devtools-vite`. The framework-neutral `devframe` package documents its own `DF`-prefixed codes at the [Devframe docs site](https://devfra.me/errors/).
- Every error page includes the cause, recommended fix, and a reference to the source file that emits it.
- The diagnostics system is powered by [`nostics`](https://github.com/vercel-labs/nostics), which provides structured diagnostic codes with docs URLs and ANSI-formatted console output.
@@ -42,3 +42,12 @@ Emitted by `@vitejs/devtools-rolldown`.
|------|-------|-------|
| [RDDT0001](./RDDT0001) | warn | Rolldown Logs Directory Not Found |
| [RDDT0002](./RDDT0002) | warn | Rolldown Log Reader Bad Line |
+
+## Vite DevTools (VDT)
+
+Emitted by `@vitejs/devtools-vite`.
+
+| Code | Level | Title |
+|------|-------|-------|
+| [VDT0001](./VDT0001) | error | Inspect Context Unavailable |
+| [VDT0002](./VDT0002) | error | Inspect Target Not Found |
diff --git a/docs/kit/diagnostics.md b/docs/kit/diagnostics.md
index cadb527e..5d1d6657 100644
--- a/docs/kit/diagnostics.md
+++ b/docs/kit/diagnostics.md
@@ -75,7 +75,7 @@ Prefixes used by the in-tree packages:
| `DF` | `devframe` |
| `DTK` | `@vitejs/devtools` |
| `RDDT` | `@vitejs/devtools-rolldown` |
-| `VDT` | `@vitejs/devtools-vite` (reserved) |
+| `VDT` | `@vitejs/devtools-vite` |
Each definition supports `why` (string or function returning a string) and an optional `fix` (string or function). A `docsBase` on the definition group auto-attaches a per-code URL to every emitted diagnostic.
diff --git a/packages/rolldown/src/app/components/assets/Sunburst.vue b/packages/rolldown/src/app/components/assets/Sunburst.vue
index 1792a71f..6f61b53e 100644
--- a/packages/rolldown/src/app/components/assets/Sunburst.vue
+++ b/packages/rolldown/src/app/components/assets/Sunburst.vue
@@ -1,6 +1,7 @@
@@ -101,7 +107,9 @@ function unselectToggle() {
@change="toggleRule(rule)"
>
- {{ rule.description || rule.name }}
+
+ {{ rule.description || rule.name }}
+
-import type { SessionContext } from '~~/shared/types'
import { useEventListener, useResizeObserver } from '@vueuse/core'
import { computed, onBeforeUnmount, onMounted, ref, unref, watch } from 'vue'
-import { generateModuleGraphLink, getModuleGraphLinkColor, useGraphDraggingScroll, useGraphZoom, useModuleGraph, useToggleGraphNodeExpanded } from '~/composables/module-graph'
+import { generateModuleGraphLink, getModuleGraphLinkColor, useGraphDraggingScroll, useGraphZoom, useModuleGraph, useToggleGraphNodeExpanded } from '../composables/module-graph'
+import DisplayTimeoutView from './DisplayTimeoutView.vue'
const props = withDefaults(defineProps<{
modules: T[]
- session: SessionContext
expandControls?: boolean
}>(), {
expandControls: true,
@@ -75,9 +74,8 @@ useEventListener(window, 'resize', scheduleReadViewport, { passive: true })
useResizeObserver(container, scheduleReadViewport)
onBeforeUnmount(() => {
- if (viewportFrame !== undefined) {
+ if (viewportFrame !== undefined)
cancelAnimationFrame(viewportFrame)
- }
})
onMounted(() => {
diff --git a/packages/rolldown/src/app/components/display/PluginName.vue b/packages/ui/src/components/DisplayPluginName.vue
similarity index 95%
rename from packages/rolldown/src/app/components/display/PluginName.vue
rename to packages/ui/src/components/DisplayPluginName.vue
index c5ce30d0..e773df6e 100644
--- a/packages/rolldown/src/app/components/display/PluginName.vue
+++ b/packages/ui/src/components/DisplayPluginName.vue
@@ -1,6 +1,6 @@
@@ -43,14 +51,9 @@ watchEffect(() => el.value?.append(props.graph.el))
{{ child.text }}
-
+
+ {{ child.text || child.id }}
+
el.value?.append(props.graph.el))
diff --git a/packages/rolldown/src/app/composables/graph-path-selector.ts b/packages/ui/src/composables/graph-path-selector.ts
similarity index 85%
rename from packages/rolldown/src/app/composables/graph-path-selector.ts
rename to packages/ui/src/composables/graph-path-selector.ts
index b493de51..92cdcbf8 100644
--- a/packages/rolldown/src/app/composables/graph-path-selector.ts
+++ b/packages/ui/src/composables/graph-path-selector.ts
@@ -17,14 +17,11 @@ export function useGraphPathSelector(options: {
searchKeys?: string[]
getModules: () => T[]
}): GraphPathSelector {
- const state = ref<{
- search: string
- selected: string | null
- }>({
+ const state = ref({
search: '',
- selected: null,
+ selected: null as string | null,
})
- const fuse = ref>>() as unknown as Ref>>
+ const fuse = ref>>()
const modules = computed(options.getModules)
@@ -59,10 +56,10 @@ export function useGraphPathSelector(options: {
}
}
-export function useGraphPathManager[] }>(
+export function useGraphPathManager(
options: {
onToggle: (visible: boolean) => void
- dataMap: ComputedRef>>
+ dataMap: ComputedRef>
list: ComputedRef
importIdKey: string
},
@@ -74,7 +71,7 @@ export function useGraphPathManager(false)
+ const pathSelectorVisible = ref(false)
const pathNodes = ref({
start: '',
end: '',
@@ -131,13 +128,15 @@ export function useGraphPathManager dataMap.value.get(id)?.imports!.map(i => i[importIdKey]) || [], end)
- }
- else if (start) {
- bfs(start, id => dataMap.value.get(id)?.imports!.map(i => i[importIdKey]) || [])
+ const getImportIds = (id: string) => {
+ return dataMap.value.get(id)?.imports?.map(i => `${(i as Record)[importIdKey]}`) || []
}
+ if (start && end)
+ bfs(start, getImportIds, end)
+ else if (start)
+ bfs(start, getImportIds)
+
return list.value.filter(x => linkedNodes.has(x.id)).map((m) => {
if (m.id === start) {
return {
diff --git a/packages/rolldown/src/app/composables/module-graph.ts b/packages/ui/src/composables/module-graph.ts
similarity index 79%
rename from packages/rolldown/src/app/composables/module-graph.ts
rename to packages/ui/src/composables/module-graph.ts
index a9c20faa..55ef3727 100644
--- a/packages/rolldown/src/app/composables/module-graph.ts
+++ b/packages/ui/src/composables/module-graph.ts
@@ -1,11 +1,8 @@
-import type { ComputedRefWithControl } from '@vueuse/core'
import type { HierarchyLink, HierarchyNode } from 'd3-hierarchy'
import type { ComputedRef, InjectionKey, MaybeRef, Ref, ShallowReactive, ShallowRef } from 'vue'
-import type { ModuleListItem } from '~~/shared/types'
-import { computedWithControl, onKeyPressed, useEventListener, useMagicKeys } from '@vueuse/core'
+import { onKeyPressed, useEventListener, useMagicKeys } from '@vueuse/core'
import { hierarchy, tree } from 'd3-hierarchy'
import { linkHorizontal, linkVertical } from 'd3-shape'
-import Fuse from 'fuse.js'
import { computed, inject, nextTick, provide, ref, shallowReactive, shallowRef, unref } from 'vue'
import { useZoomElement } from './zoom-element'
@@ -53,7 +50,6 @@ interface ModuleGraphOptions {
modulesMap: ComputedRef>
nodesMap: ShallowReactive>>>
linksMap: ShallowReactive>>
-
focusOn: (id: string, animated?: boolean) => void
}) => (focusOnFirstRootNode?: boolean) => void
}
@@ -77,9 +73,9 @@ export const createLinkVertical = linkVertical()
.y(d => d[1])
export function generateModuleGraphLink(link: ModuleGraphLink, spacing?: ModuleGraphSpacing) {
- if (!link.target || !link.source) {
+ if (!link.target || !link.source)
return null
- }
+
if (!spacing) {
if (link.target.x! <= link.source.x!) {
return createLinkVertical({
@@ -92,19 +88,19 @@ export function generateModuleGraphLink(link: ModuleGraphLink, spaci
target: [link.target.x!, link.target.y!],
})
}
- else {
- if (link.target.x! <= link.source.x!) {
- return createLinkVertical({
- source: [link.source.x! + unref(spacing.width) / 2 - unref(spacing.linkOffset), link.source.y!],
- target: [link.target.x! - unref(spacing.width) / 2 + unref(spacing.linkOffset), link.target.y!],
- })
- }
- return createLinkHorizontal({
+
+ if (link.target.x! <= link.source.x!) {
+ return createLinkVertical({
source: [link.source.x! + unref(spacing.width) / 2 - unref(spacing.linkOffset), link.source.y!],
target: [link.target.x! - unref(spacing.width) / 2 + unref(spacing.linkOffset), link.target.y!],
})
}
+ return createLinkHorizontal({
+ source: [link.source.x! + unref(spacing.width) / 2 - unref(spacing.linkOffset), link.source.y!],
+ target: [link.target.x! - unref(spacing.width) / 2 + unref(spacing.linkOffset), link.target.y!],
+ })
}
+
// @unocss-include
export function getModuleGraphLinkColor(_link: ModuleGraphLink) {
return 'stroke-#8885'
@@ -161,7 +157,7 @@ export function createModuleGraph(options: ModuleGr
})
}
- const _calculateGraph = options.generateGraph({
+ const calculateGraph = options.generateGraph({
isFirstCalculateGraph,
hierarchy,
tree,
@@ -183,7 +179,7 @@ export function createModuleGraph(options: ModuleGr
provide(ViteDevToolsModuleGraphStateSymbol, {
spacing: options.spacing,
- calculateGraph: _calculateGraph,
+ calculateGraph,
isFirstCalculateGraph,
container,
width,
@@ -202,8 +198,7 @@ export function createModuleGraph(options: ModuleGr
}
export function useModuleGraph() {
- const state = inject(ViteDevToolsModuleGraphStateSymbol)!
- return state
+ return inject(ViteDevToolsModuleGraphStateSymbol)!
}
export function useToggleGraphNodeExpanded(options: {
@@ -213,7 +208,6 @@ export function useToggleGraphNodeExpanded {
nextTick(() => {
const newNode = nodesRefMap.get(id)
@@ -240,7 +234,6 @@ export function useToggleGraphNodeExpanded {
- if (module.imports.length > 0) {
+ if (module.imports.length > 0)
collapsedNodes.add(module.id)
- }
})
calculateGraph()
@@ -340,14 +328,12 @@ export function useGraphDraggingScroll() {
function init() {
useEventListener(container, 'mousedown', (e) => {
- // prevent dragging when clicking on scrollbar
const rect = container.value!.getBoundingClientRect()
const distRight = rect.right - e.clientX
const distBottom = rect.bottom - e.clientY
- if (distRight <= SCROLLBAR_THICKNESS || distBottom <= SCROLLBAR_THICKNESS) {
+ if (distRight <= SCROLLBAR_THICKNESS || distBottom <= SCROLLBAR_THICKNESS)
return
- }
isGrabbing.value = true
x = container.value!.scrollLeft + e.pageX
@@ -370,57 +356,3 @@ export function useGraphDraggingScroll() {
isGrabbing,
}
}
-
-export interface ModulePathSelector {
- state: { value: { search: string, selected: string | null } }
- modules: ComputedRef
- fuse: Ref> | undefined>
- initSelector: (modules: ComputedRef) => void
- select: (module: ModuleListItem) => void
- clear: () => void
-}
-
-export function useModulePathSelector(options: {
- getModules: () => ModuleListItem[]
-}): ModulePathSelector {
- const state = ref<{
- search: string
- selected: string | null
- }>({
- search: '',
- selected: null,
- })
- const fuse = ref>>()
-
- const modules = computed(options.getModules)
-
- function initSelector(modules: ComputedRef) {
- fuse.value = computedWithControl(
- modules,
- () => new Fuse(modules.value, {
- includeScore: true,
- keys: ['id'],
- ignoreLocation: true,
- threshold: 0.4,
- }),
- )
- }
-
- function select(node: ModuleListItem) {
- state.value.selected = node.id
- state.value.search = ''
- }
-
- function clear() {
- state.value.selected = null
- }
-
- return {
- state,
- modules,
- fuse,
- initSelector,
- select,
- clear,
- }
-}
diff --git a/packages/rolldown/src/app/composables/zoom-element.ts b/packages/ui/src/composables/zoom-element.ts
similarity index 92%
rename from packages/rolldown/src/app/composables/zoom-element.ts
rename to packages/ui/src/composables/zoom-element.ts
index ea27758f..7e73f543 100644
--- a/packages/rolldown/src/app/composables/zoom-element.ts
+++ b/packages/ui/src/composables/zoom-element.ts
@@ -24,7 +24,6 @@ export function useZoomElement(
const { left, top, width, height } = el.getBoundingClientRect()
- // default to center
const x = clientX ?? (left + width / 2)
const y = clientY ?? (top + height / 2)
@@ -36,21 +35,18 @@ export function useZoomElement(
const ratio = scale.value / oldScale
- // Adjust scroll so that the zoom center is kept in place
el.scrollLeft = (el.scrollLeft + offsetX) * ratio - offsetX
el.scrollTop = (el.scrollTop + offsetY) * ratio - offsetY
}
function handleWheel(event: WheelEvent) {
if (toValue(wheel)) {
- // Use control + wheel
event.preventDefault()
const zoomFactor = 0.2
zoom(event.deltaY < 0 ? zoomFactor : zoomFactor * -1, event.clientX, event.clientY)
}
else if (event.ctrlKey) {
- // Use touchpad zoom
event.preventDefault()
const zoomFactor = 0.004
diff --git a/packages/ui/src/utils/color.ts b/packages/ui/src/utils/color.ts
index 951f85f4..306ff735 100644
--- a/packages/ui/src/utils/color.ts
+++ b/packages/ui/src/utils/color.ts
@@ -19,3 +19,18 @@ export function getHsla(
const lightness = isDark.value ? 60 : 40
return `hsla(${hue}, ${saturation}%, ${lightness}%, ${opacity})`
}
+
+export const predefinedPluginColorMap = {
+ error: 0,
+ client: 60,
+ ssr: 270,
+ vite: 250,
+ virtual: 160,
+} as Record
+
+export function getPluginColor(name: string, opacity = 1): string {
+ name = name.replace(/[^a-z]+/gi, '').toLowerCase()
+ if (name in predefinedPluginColorMap)
+ return getHsla(predefinedPluginColorMap[name]!, opacity)
+ return getHashColorFromString(name, opacity)
+}
diff --git a/packages/vite/package.json b/packages/vite/package.json
index 9c345145..2c59fa29 100644
--- a/packages/vite/package.json
+++ b/packages/vite/package.json
@@ -34,24 +34,36 @@
"dev:prepare": "nuxi prepare src",
"prepack": "pnpm build"
},
+ "peerDependencies": {
+ "vite": "*"
+ },
"dependencies": {
"@floating-ui/dom": "catalog:frontend",
"@vitejs/devtools-kit": "workspace:*",
- "birpc": "catalog:deps",
- "devframe": "catalog:deps",
+ "d3-shape": "catalog:frontend",
"envinfo": "catalog:deps",
- "get-port-please": "catalog:deps",
- "h3": "catalog:deps",
+ "nostics": "catalog:deps",
"pathe": "catalog:deps",
- "ws": "catalog:deps"
+ "perfect-debounce": "catalog:deps"
},
"devDependencies": {
+ "@types/d3-hierarchy": "catalog:types",
"@types/envinfo": "catalog:types",
+ "@types/splitpanes": "catalog:types",
"@unocss/nuxt": "catalog:build",
+ "@vueuse/components": "catalog:frontend",
"@vueuse/core": "catalog:frontend",
"@vueuse/nuxt": "catalog:build",
+ "comlink": "catalog:frontend",
+ "d3-hierarchy": "catalog:frontend",
+ "diff-match-patch-es": "catalog:frontend",
"floating-vue": "catalog:frontend",
+ "fuse.js": "catalog:frontend",
+ "modern-monaco": "catalog:frontend",
+ "nanovis": "catalog:frontend",
+ "splitpanes": "catalog:frontend",
"tsdown": "catalog:build",
- "unocss": "catalog:build"
+ "unocss": "catalog:build",
+ "vue-virtual-scroller": "catalog:frontend"
}
}
diff --git a/packages/vite/src/app/app.vue b/packages/vite/src/app/app.vue
index 6ca0c929..69757909 100644
--- a/packages/vite/src/app/app.vue
+++ b/packages/vite/src/app/app.vue
@@ -3,9 +3,11 @@ import PanelSideNav from '@vitejs/devtools-ui/components/PanelSideNav.vue'
import { useSideNav } from '@vitejs/devtools-ui/composables/nav'
import { useHead } from '#app/composables/head'
import { connect, rpcConnectionState } from './composables/rpc'
+import 'floating-vue/dist/style.css'
+import './styles/cm.css'
+import './styles/splitpanes.css'
import './styles/global.css'
import '@vitejs/devtools-ui/composables/dark'
-import 'floating-vue/dist/style.css'
useHead({
title: 'Vite DevTools',
@@ -20,6 +22,16 @@ useSideNav(() => {
icon: 'i-ph-house-duotone',
to: '/home',
},
+ {
+ title: 'Modules Graph',
+ icon: 'i-ph-graph-duotone',
+ to: '/graph',
+ },
+ {
+ title: 'Plugins',
+ icon: 'i-ph-plugs-duotone',
+ to: '/plugins',
+ },
]
})
diff --git a/packages/vite/src/app/components/chart/ModuleFlamegraph.vue b/packages/vite/src/app/components/chart/ModuleFlamegraph.vue
new file mode 100644
index 00000000..9aa66eba
--- /dev/null
+++ b/packages/vite/src/app/components/chart/ModuleFlamegraph.vue
@@ -0,0 +1,232 @@
+
+
+
+
+
+
+
+ {{ hoverNode.plugin_name }}
+
+
+
+ Start Time
+
+ {{ normalizeTimestamp(hoverNode.meta.timestamp_start) }}
+
+
+
+ End Time
+
+ {{ normalizeTimestamp(hoverNode.meta.timestamp_end) }}
+
+
+
+ Duration
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/vite/src/app/components/code/DiffEditor.vue b/packages/vite/src/app/components/code/DiffEditor.vue
new file mode 100644
index 00000000..e0e56970
--- /dev/null
+++ b/packages/vite/src/app/components/code/DiffEditor.vue
@@ -0,0 +1,270 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/vite/src/app/components/data/ModuleDetailsLoader.vue b/packages/vite/src/app/components/data/ModuleDetailsLoader.vue
new file mode 100644
index 00000000..eb0af74e
--- /dev/null
+++ b/packages/vite/src/app/components/data/ModuleDetailsLoader.vue
@@ -0,0 +1,151 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Build Flow
+
+
+
+ Charts
+
+
+
+ Imports
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/vite/src/app/components/data/ModuleImportRelationships.vue b/packages/vite/src/app/components/data/ModuleImportRelationships.vue
new file mode 100644
index 00000000..b24cb166
--- /dev/null
+++ b/packages/vite/src/app/components/data/ModuleImportRelationships.vue
@@ -0,0 +1,239 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/vite/src/app/components/data/PluginDetailsLoader.vue b/packages/vite/src/app/components/data/PluginDetailsLoader.vue
new file mode 100644
index 00000000..bb580821
--- /dev/null
+++ b/packages/vite/src/app/components/data/PluginDetailsLoader.vue
@@ -0,0 +1,403 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+ |
+
+ |
+
+
+
+
+
+ Build Flow
+
+
+
+ Sunburst
+
+
+
+
+
+
+
+ No data
+
+
+
+
+
+ {{ nodeHover.meta.title }}
+
+
+
+
+
+
+
+
+ Start Time
+
+ {{ normalizeTimestamp(nodeHover.meta.timestamp_start) }}
+
+
+
+ End Time
+
+ {{ normalizeTimestamp(nodeHover.meta.timestamp_end) }}
+
+
+
+ Duration
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/vite/src/app/components/data/PluginDetailsTable.vue b/packages/vite/src/app/components/data/PluginDetailsTable.vue
new file mode 100644
index 00000000..f28057bf
--- /dev/null
+++ b/packages/vite/src/app/components/data/PluginDetailsTable.vue
@@ -0,0 +1,221 @@
+
+
+
+
+
+
+
+ Hook name
+
+
+
+ Module
+
+
+
+
+
+
+
+
+
+ {{ rule.description || rule.name }}
+
+
+
+
+
+
+
+
+ Duration
+
+
+
+
+
+
+ Start Time
+
+
+ End Time
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ normalizeTimestamp(item.timestamp_start) }}
+
+
+ {{ normalizeTimestamp(item.timestamp_end) }}
+
+
+
+
+
+
+
+
+ No columns selected
+
+
+ No data
+
+
+
+
+
+
+
diff --git a/packages/vite/src/app/components/display/FileIcon.vue b/packages/vite/src/app/components/display/FileIcon.vue
new file mode 100644
index 00000000..925358c5
--- /dev/null
+++ b/packages/vite/src/app/components/display/FileIcon.vue
@@ -0,0 +1,18 @@
+
+
+
+
+
diff --git a/packages/vite/src/app/components/display/FileSizeBadge.vue b/packages/vite/src/app/components/display/FileSizeBadge.vue
new file mode 100644
index 00000000..9f9aafbf
--- /dev/null
+++ b/packages/vite/src/app/components/display/FileSizeBadge.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
+ {{ formatted[0] }}
{{ formatted[1] }}
+
+ {{ +(ratio.toFixed(1)) }}%
+
+
+
diff --git a/packages/vite/src/app/components/display/GraphHoverView.vue b/packages/vite/src/app/components/display/GraphHoverView.vue
new file mode 100644
index 00000000..c2e8e945
--- /dev/null
+++ b/packages/vite/src/app/components/display/GraphHoverView.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/vite/src/app/components/display/HighlightedPath.ts b/packages/vite/src/app/components/display/HighlightedPath.ts
new file mode 100644
index 00000000..21405e20
--- /dev/null
+++ b/packages/vite/src/app/components/display/HighlightedPath.ts
@@ -0,0 +1,99 @@
+import { defineComponent, h } from 'vue'
+import { getPluginColor } from '~/utils/color'
+
+// @unocss-include
+export default defineComponent({
+ name: 'HighlightedPath',
+ props: {
+ path: {
+ type: String,
+ required: true,
+ },
+ minimal: {
+ type: Boolean,
+ default: false,
+ },
+ },
+ setup(props) {
+ return () => {
+ const parts = props.path.split(/([?/&:=])/g)
+ let type: 'path' | 'query' = 'path'
+ const classes: string[][] = parts.map(() => [])
+ const styles: string[][] = parts.map(() => [])
+ const nodes = parts.map(part => h('span', { class: '' }, part))
+ const removeIndexes = new Set()
+
+ parts.forEach((part, index) => {
+ if (part === '?')
+ type = 'query'
+
+ if (type === 'path') {
+ if (/^\.+$/.test(part) || part === '/')
+ classes[index]?.push('op50')
+ else if (part === 'node_modules' || part === 'dist' || part === 'lib' || /^\.\w/.test(part))
+ classes[index]?.push('op60')
+
+ if (part === 'node_modules' && props.minimal) {
+ for (let i = 0; i < index + 2; i++)
+ removeIndexes.add(i)
+ }
+
+ if (part === '.pnpm') {
+ if (nodes[index]) {
+ nodes[index].children = '~'
+ classes[index]?.push('op25!')
+ classes[index - 1]?.push('op25!')
+ }
+ removeIndexes.add(index + 1)
+ removeIndexes.add(index + 2)
+ classes[index + 3]?.push('op25!')
+ if (nodes[index + 4]?.children === 'node_modules') {
+ removeIndexes.add(index + 3)
+ removeIndexes.add(index + 4)
+ classes[index + 5]?.push('op25!')
+ }
+ }
+ if (part === ':') {
+ if (nodes[index - 1])
+ styles[index - 1]?.push(`color: ${getPluginColor(parts[index - 1]!)}`)
+ classes[index]?.push('op50')
+ }
+ if (parts[index - 2] === 'node_modules' && !part.startsWith('.')) {
+ const color = `color: ${getPluginColor(parts[index]!)}`
+ styles[index]?.push(color)
+ if (part.startsWith('@')) {
+ styles[index + 1]?.push(color)
+ styles[index + 2]?.push(color)
+ }
+ }
+ }
+
+ if (type === 'query') {
+ if (part === '?')
+ classes[index]?.push('text-red-500 dark:text-red-400')
+ else if (part === '&')
+ classes[index]?.push('text-orange-500 dark:text-orange-400')
+ if (part === '=')
+ classes[index]?.push('text-orange-900 dark:text-orange-200 op50')
+ else if (parts[index + 1] === '=')
+ classes[index]?.push('text-amber-900 dark:text-amber-200')
+ else
+ classes[index]?.push('text-orange-900 dark:text-orange-200')
+ }
+ })
+
+ nodes.forEach((node, index) => {
+ if (node.props) {
+ node.props.class = classes[index]?.join(' ') ?? ''
+ node.props.style = styles[index]?.join(';') ?? ''
+ }
+ })
+
+ Array.from(removeIndexes)
+ .sort((a, b) => b - a)
+ .forEach(index => nodes.splice(index, 1))
+
+ return nodes
+ }
+ },
+})
diff --git a/packages/vite/src/app/components/display/ModuleId.vue b/packages/vite/src/app/components/display/ModuleId.vue
new file mode 100644
index 00000000..2b5b8a28
--- /dev/null
+++ b/packages/vite/src/app/components/display/ModuleId.vue
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ props.id }}
+
+
+
+
+
diff --git a/packages/vite/src/app/components/display/VirtualTree.vue b/packages/vite/src/app/components/display/VirtualTree.vue
new file mode 100644
index 00000000..cd966e3a
--- /dev/null
+++ b/packages/vite/src/app/components/display/VirtualTree.vue
@@ -0,0 +1,234 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/vite/src/app/components/flowmap/Expandable.vue b/packages/vite/src/app/components/flowmap/Expandable.vue
new file mode 100644
index 00000000..ee59d167
--- /dev/null
+++ b/packages/vite/src/app/components/flowmap/Expandable.vue
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/vite/src/app/components/flowmap/ModuleFlow.vue b/packages/vite/src/app/components/flowmap/ModuleFlow.vue
new file mode 100644
index 00000000..ce9cb207
--- /dev/null
+++ b/packages/vite/src/app/components/flowmap/ModuleFlow.vue
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/vite/src/app/components/flowmap/ModuleFlowDetails.vue b/packages/vite/src/app/components/flowmap/ModuleFlowDetails.vue
new file mode 100644
index 00000000..ff665ee0
--- /dev/null
+++ b/packages/vite/src/app/components/flowmap/ModuleFlowDetails.vue
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+ {{ codeDisplay?.type === 'load' ? 'Load' : 'Transform' }}
+
+
+
+
+
+
+
+
+
+ No data
+
+
+
diff --git a/packages/vite/src/app/components/flowmap/ModuleFlowTimeline.vue b/packages/vite/src/app/components/flowmap/ModuleFlowTimeline.vue
new file mode 100644
index 00000000..09794a82
--- /dev/null
+++ b/packages/vite/src/app/components/flowmap/ModuleFlowTimeline.vue
@@ -0,0 +1,394 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ module.importers?.length }} importers
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ module.imports?.length }} imports
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Resolve Id
+ ({{ resolveIds.length }})
+
+
+
+
+
+
+
+
+
+
+
Load
+ ({{ loadNodes.length }})
+
+
+
+
+
+
+
+
+
+
+
Transform
+
+ ({{ transformNodes.length }})
+
+
+
+
+
+
+
+
+
diff --git a/packages/vite/src/app/components/flowmap/Node.vue b/packages/vite/src/app/components/flowmap/Node.vue
new file mode 100644
index 00000000..1d70d36c
--- /dev/null
+++ b/packages/vite/src/app/components/flowmap/Node.vue
@@ -0,0 +1,51 @@
+
+
+
+
+
diff --git a/packages/vite/src/app/components/flowmap/NodeModuleInfo.vue b/packages/vite/src/app/components/flowmap/NodeModuleInfo.vue
new file mode 100644
index 00000000..d79481e3
--- /dev/null
+++ b/packages/vite/src/app/components/flowmap/NodeModuleInfo.vue
@@ -0,0 +1,200 @@
+
+
+
+
+
+
+
{{ item.count }} plugins did not change the content but cost
+
+
in total
+
+ Expand
+
+
+
+
+
+
+
{{ item.count }} plugins did not change the content but cost
+
+
in total
+
+ Hide
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ no changes
+
+
+
+ +{{ item.diff_added ?? 0 }}
+ -{{ item.diff_removed ?? 0 }}
+
+
+
+
+
+
+
+
+
diff --git a/packages/vite/src/app/components/flowmap/NodePluginInfo.vue b/packages/vite/src/app/components/flowmap/NodePluginInfo.vue
new file mode 100644
index 00000000..8263bc94
--- /dev/null
+++ b/packages/vite/src/app/components/flowmap/NodePluginInfo.vue
@@ -0,0 +1,34 @@
+
+
+
+
+
+ |
+
+ |
+
+
+
+
+
diff --git a/packages/vite/src/app/components/flowmap/PluginFlow.vue b/packages/vite/src/app/components/flowmap/PluginFlow.vue
new file mode 100644
index 00000000..4f262d24
--- /dev/null
+++ b/packages/vite/src/app/components/flowmap/PluginFlow.vue
@@ -0,0 +1,153 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ module unchanged, but cost
+
+
+
+ {{ showTypeText }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/vite/src/app/components/flowmap/PluginFlowTimeline.vue b/packages/vite/src/app/components/flowmap/PluginFlowTimeline.vue
new file mode 100644
index 00000000..4f383907
--- /dev/null
+++ b/packages/vite/src/app/components/flowmap/PluginFlowTimeline.vue
@@ -0,0 +1,176 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ new Date(startTime).toLocaleString() }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ new Date(endTime).toLocaleString() }}
+
+
+
+
+
diff --git a/packages/vite/src/app/components/modules/BuildMetrics.vue b/packages/vite/src/app/components/modules/BuildMetrics.vue
new file mode 100644
index 00000000..5f691869
--- /dev/null
+++ b/packages/vite/src/app/components/modules/BuildMetrics.vue
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
|
+
+
+
+
+ |
+
+
+
+
+
+
+
+
diff --git a/packages/vite/src/app/components/modules/DetailedList.vue b/packages/vite/src/app/components/modules/DetailedList.vue
new file mode 100644
index 00000000..83d8e9ef
--- /dev/null
+++ b/packages/vite/src/app/components/modules/DetailedList.vue
@@ -0,0 +1,86 @@
+
+
+
+
+
diff --git a/packages/vite/src/app/components/modules/FlatList.vue b/packages/vite/src/app/components/modules/FlatList.vue
new file mode 100644
index 00000000..dc89a91a
--- /dev/null
+++ b/packages/vite/src/app/components/modules/FlatList.vue
@@ -0,0 +1,48 @@
+
+
+
+
+
diff --git a/packages/vite/src/app/components/modules/Folder.vue b/packages/vite/src/app/components/modules/Folder.vue
new file mode 100644
index 00000000..3a243477
--- /dev/null
+++ b/packages/vite/src/app/components/modules/Folder.vue
@@ -0,0 +1,92 @@
+
+
+
+
+
diff --git a/packages/vite/src/app/components/modules/Graph.vue b/packages/vite/src/app/components/modules/Graph.vue
new file mode 100644
index 00000000..a456572b
--- /dev/null
+++ b/packages/vite/src/app/components/modules/Graph.vue
@@ -0,0 +1,199 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/vite/src/app/composables/chart.ts b/packages/vite/src/app/composables/chart.ts
new file mode 100644
index 00000000..d7900846
--- /dev/null
+++ b/packages/vite/src/app/composables/chart.ts
@@ -0,0 +1,234 @@
+import type { GraphBase, GraphBaseOptions, TreeNode } from 'nanovis'
+import type { ComputedRef, MaybeRef } from 'vue'
+import { isDark } from '@vitejs/devtools-ui/composables/dark'
+import { createColorGetterSpectrum } from 'nanovis'
+import { computed, nextTick, onUnmounted, ref, shallowRef, unref, watch } from 'vue'
+import { settings } from '~/state/settings'
+import { bytesToHumanSize } from '~/utils/format'
+
+export interface ChartGraphOptions {
+ data: ComputedRef | MaybeRef
+ nameKey: string
+ sizeKey: string
+ rootText?: string
+ nodeType?: string
+ graphOptions?: GraphBaseOptions
+ onUpdate?: () => void
+ tree?: ComputedRef<{
+ map: Map
+ root: N
+ maxDepth: number
+ }>
+}
+
+export function useChartGraph, I extends T & Record, N extends TreeNode>(options: ChartGraphOptions) {
+ const { data, nameKey, sizeKey, rootText, nodeType, graphOptions, onUpdate } = options
+ const nodeHover = shallowRef(undefined)
+ const nodeSelected = shallowRef(undefined)
+ const selectedNode = ref(undefined)
+ const graph = shallowRef> | undefined>(undefined)
+ let dispose: () => void | undefined
+
+ const tree = computed(() => {
+ if (options.tree)
+ return options.tree.value
+
+ const _data = unref(data)
+ const map = new Map()
+ let maxDepth = 0
+
+ const root = {
+ id: '~root',
+ text: rootText,
+ size: 0,
+ sizeSelf: 0,
+ children: [],
+ } as unknown as N
+
+ if (!_data?.length) {
+ return {
+ map,
+ root,
+ maxDepth,
+ }
+ }
+ const macrosTasks: (() => void)[] = []
+
+ macrosTasks.unshift(() => {
+ root.size += root.children.reduce((acc, item) => acc + item.size, 0)
+ root.subtext = bytesToHumanSize(root.size).join(' ')
+ root.children.sort((a, b) => b.size - a.size || a.id.localeCompare(b.id))
+ })
+
+ function dataToNode(data: T, path: string, name: string, parent: N, depth: number): N {
+ if (map.has(path))
+ return map.get(path)!
+
+ if (depth > maxDepth)
+ maxDepth = depth
+
+ const node = {
+ id: path,
+ text: name,
+ size: 0,
+ sizeSelf: 0,
+ children: [],
+ meta: {
+ ...data,
+ path: name,
+ type: 'folder',
+ },
+ parent,
+ } as unknown as N
+
+ map.set(path, node)
+ parent.children.push(node)
+
+ macrosTasks.unshift(() => {
+ const selfSize = node.sizeSelf
+ node.size += node.children.reduce((acc, item) => acc + item.size, 0)
+ node.subtext = bytesToHumanSize(node.size).join(' ')
+
+ if (node.children.length && selfSize / node.size > 0.1) {
+ node.children.push({
+ id: `${node.id}-self`,
+ text: '',
+ size: selfSize,
+ sizeSelf: selfSize,
+ subtext: bytesToHumanSize(selfSize).join(' '),
+ children: [],
+ meta: {
+ ...data,
+ path: '',
+ type: nodeType,
+ },
+ parent: node,
+ })
+ }
+
+ node.children.sort((a, b) => b.size - a.size || a.id.localeCompare(b.id))
+ })
+
+ return node
+ }
+
+ function processData(data: T) {
+ const parts: string[] = data[nameKey].split('/').filter(Boolean)
+ let current = root
+ let currentPath = ''
+ let depth = 0
+
+ parts.forEach((part, index) => {
+ currentPath += (currentPath ? '/' : '') + part
+ depth++
+
+ if (index === parts.length - 1) {
+ const fileNode = {
+ id: data[nameKey],
+ text: part,
+ size: data[sizeKey],
+ sizeSelf: data[sizeKey],
+ subtext: bytesToHumanSize(data[sizeKey]).join(' '),
+ children: [],
+ meta: {
+ ...data,
+ path: part,
+ type: nodeType,
+ },
+ } as unknown as N
+
+ current.children.push(fileNode)
+ map.set(data[nameKey], fileNode)
+ }
+ else {
+ current = dataToNode(data, currentPath, part, current, depth)
+ }
+ })
+ }
+
+ _data.forEach(processData)
+ macrosTasks.forEach(fn => fn())
+
+ return {
+ map,
+ root,
+ maxDepth,
+ }
+ })
+
+ const chartOptions = computed>(() => {
+ return {
+ animate: settings.value.chartAnimation,
+ palette: {
+ stroke: isDark.value ? '#222' : '#555',
+ fg: isDark.value ? '#fff' : '#000',
+ bg: isDark.value ? '#111' : '#fff',
+ },
+ getColor: createColorGetterSpectrum(
+ tree.value.root,
+ isDark.value ? 0.8 : 0.9,
+ isDark.value ? 1 : 1.1,
+ ),
+ getSubtext: (node) => {
+ return node.subtext
+ },
+ ...graphOptions,
+ }
+ })
+
+ function selectNode(node: N | null, animate?: boolean) {
+ selectedNode.value = node?.meta
+ if (!node?.children.length)
+ node = node?.parent as unknown as N | null
+ graph.value?.select(node, animate)
+ }
+
+ function buildGraph() {
+ dispose?.()
+
+ nodeSelected.value = tree.value.root
+ onUpdate?.()
+
+ nextTick(() => {
+ const selected = selectedNode.value ? tree.value.map.get(selectedNode.value[nameKey]) || null : null
+ if (selected)
+ selectNode(selected, false)
+ })
+
+ dispose = () => {
+ graph.value?.dispose()
+ graph.value = undefined
+ selectedNode.value = undefined
+ }
+ }
+
+ nextTick(() => {
+ watch(
+ () => [tree.value, chartOptions.value],
+ () => {
+ buildGraph()
+ },
+ {
+ deep: true,
+ immediate: false,
+ },
+ )
+
+ buildGraph()
+ })
+
+ onUnmounted(() => {
+ dispose?.()
+ })
+
+ return {
+ tree,
+ chartOptions,
+ graph,
+ nodeHover,
+ nodeSelected,
+ selectedNode,
+ selectNode,
+ buildGraph,
+ }
+}
diff --git a/packages/vite/src/app/composables/monaco.ts b/packages/vite/src/app/composables/monaco.ts
new file mode 100644
index 00000000..9c53c28e
--- /dev/null
+++ b/packages/vite/src/app/composables/monaco.ts
@@ -0,0 +1,194 @@
+import type { TextmateTheme } from 'modern-monaco'
+import type * as Monaco from 'modern-monaco/editor-core'
+import { isDark } from '@vitejs/devtools-ui/composables/dark'
+import { init } from 'modern-monaco'
+
+const lightTheme: TextmateTheme = {
+ type: 'light',
+ name: 'rolldown-light',
+ colors: {
+ 'editor.background': '#00000000',
+ 'editor.foreground': '#8e8f8b',
+ 'editor.selectionBackground': '#44444410',
+ 'editorLineNumber.foreground': '#8e8f8b80',
+ 'editorLineNumber.activeForeground': '#8e8f8b',
+ 'editorGutter.background': '#00000000',
+ 'editor.lineHighlightBackground': '#00000000',
+ },
+ tokenColors: [
+ { scope: 'comment', settings: { foreground: '#a0ada0' } },
+ { scope: 'string', settings: { foreground: '#b56959' } },
+ { scope: ['constant.numeric', 'number'], settings: { foreground: '#296aa3' } },
+ { scope: ['keyword', 'storage'], settings: { foreground: '#1c6b48' } },
+ { scope: ['entity.name.function', 'support.function'], settings: { foreground: '#6c7834' } },
+ { scope: ['entity.name.class', 'entity.name.type'], settings: { foreground: '#2993a3' } },
+ { scope: ['variable', 'identifier'], settings: { foreground: '#ad944c' } },
+ { scope: ['variable.other.property', 'meta.property-name'], settings: { foreground: '#b58451' } },
+ ],
+}
+
+const darkTheme: TextmateTheme = {
+ type: 'dark',
+ name: 'rolldown-dark',
+ colors: {
+ 'editor.background': '#00000000',
+ 'editor.foreground': '#858585',
+ 'editor.selectionBackground': '#44444450',
+ 'editorLineNumber.foreground': '#88888880',
+ 'editorLineNumber.activeForeground': '#888888',
+ 'editorGutter.background': '#00000000',
+ 'editor.lineHighlightBackground': '#00000000',
+ },
+ tokenColors: [
+ { scope: 'comment', settings: { foreground: '#758575' } },
+ { scope: 'string', settings: { foreground: '#d48372' } },
+ { scope: ['constant.numeric', 'number'], settings: { foreground: '#6394bf' } },
+ { scope: ['keyword', 'storage'], settings: { foreground: '#4d9375' } },
+ { scope: ['entity.name.function', 'support.function'], settings: { foreground: '#a1b567' } },
+ { scope: ['entity.name.class', 'entity.name.type'], settings: { foreground: '#54b1bf' } },
+ { scope: ['variable', 'identifier'], settings: { foreground: '#c2b36e' } },
+ { scope: ['variable.other.property', 'meta.property-name'], settings: { foreground: '#dd8e6e' } },
+ ],
+}
+
+const lightThemeId = lightTheme.name
+const darkThemeId = darkTheme.name
+
+function getThemeId() {
+ return isDark.value ? darkThemeId : lightThemeId
+}
+
+let monacoPromise: Promise | null = null
+
+export async function getMonaco() {
+ monacoPromise ??= init({
+ defaultTheme: isDark.value ? darkTheme : lightTheme,
+ themes: [lightTheme, darkTheme],
+ })
+ return monacoPromise
+}
+
+export function applyMonacoTheme(monaco: typeof Monaco) {
+ monaco.editor.setTheme(getThemeId())
+}
+
+export function getMonacoWordWrap(enabled: boolean): Monaco.editor.IStandaloneEditorConstructionOptions['wordWrap'] {
+ return enabled ? 'on' : 'off'
+}
+
+const readonlyEditorOptions: Monaco.editor.IStandaloneEditorConstructionOptions = {
+ automaticLayout: true,
+ fontFamily: '\'Input Mono\', \'FiraCode\', monospace',
+ fontSize: 13,
+ lineNumbers: 'on',
+ minimap: { enabled: false },
+ readOnly: true,
+ renderLineHighlight: 'none',
+ scrollBeyondLastLine: false,
+ scrollbar: {
+ alwaysConsumeMouseWheel: false,
+ horizontal: 'auto',
+ horizontalScrollbarSize: 6,
+ useShadows: false,
+ vertical: 'auto',
+ verticalScrollbarSize: 6,
+ },
+}
+
+export function createReadOnlyMonacoEditor(
+ monaco: typeof Monaco,
+ container: HTMLElement,
+ options: Monaco.editor.IStandaloneEditorConstructionOptions = {},
+) {
+ const scrollbar = {
+ ...readonlyEditorOptions.scrollbar,
+ ...options.scrollbar,
+ }
+ return monaco.editor.create(container, {
+ ...readonlyEditorOptions,
+ ...options,
+ scrollbar,
+ })
+}
+
+export function setModelLanguageIfNeeded(
+ monaco: typeof Monaco,
+ model: Monaco.editor.ITextModel,
+ language: string,
+) {
+ if (model.getLanguageId() !== language)
+ monaco.editor.setModelLanguage(model, language)
+}
+
+export function guessMonacoLanguage(code: string) {
+ if (code.trimStart().startsWith('<'))
+ return 'html'
+ if (/^import\s/.test(code))
+ return 'javascript'
+ if (/^[.#].+\{/.test(code))
+ return 'css'
+ return 'javascript'
+}
+
+function mapScroll(primaryPos: number, primaryMax: number, targetMax: number) {
+ if (primaryMax <= 0 || targetMax <= 0)
+ return 0
+ return (targetMax / primaryMax) * primaryPos
+}
+
+export function syncMonacoEditorScrolls(
+ primary: Monaco.editor.IStandaloneCodeEditor,
+ target: Monaco.editor.IStandaloneCodeEditor,
+) {
+ const pMaxX = Math.max(primary.getScrollWidth() - primary.getLayoutInfo().width, 0)
+ const pMaxY = Math.max(primary.getScrollHeight() - primary.getLayoutInfo().height, 0)
+ const tMaxX = Math.max(target.getScrollWidth() - target.getLayoutInfo().width, 0)
+ const tMaxY = Math.max(target.getScrollHeight() - target.getLayoutInfo().height, 0)
+
+ const scrollLeft = mapScroll(primary.getScrollLeft(), pMaxX, tMaxX)
+ const scrollTop = mapScroll(primary.getScrollTop(), pMaxY, tMaxY)
+
+ target.setScrollPosition({
+ scrollLeft: Number.isFinite(scrollLeft) ? scrollLeft : 0,
+ scrollTop: Number.isFinite(scrollTop) ? scrollTop : 0,
+ })
+}
+
+export function setupMonacoScrollSync(
+ editor1: Monaco.editor.IStandaloneCodeEditor,
+ editor2: Monaco.editor.IStandaloneCodeEditor,
+) {
+ let activeEditor = 1
+
+ const node1 = editor1.getDomNode()
+ const node2 = editor2.getDomNode()
+
+ const onMouseEnter1 = () => {
+ activeEditor = 1
+ }
+ const onMouseEnter2 = () => {
+ activeEditor = 2
+ }
+
+ node1?.addEventListener('mouseenter', onMouseEnter1)
+ node2?.addEventListener('mouseenter', onMouseEnter2)
+
+ const disposables: Monaco.IDisposable[] = [
+ editor1.onDidScrollChange(() => {
+ if (activeEditor === 1)
+ syncMonacoEditorScrolls(editor1, editor2)
+ }),
+ editor2.onDidScrollChange(() => {
+ if (activeEditor === 2)
+ syncMonacoEditorScrolls(editor2, editor1)
+ }),
+ editor1.onDidChangeCursorPosition(() => syncMonacoEditorScrolls(editor1, editor2)),
+ editor2.onDidChangeCursorPosition(() => syncMonacoEditorScrolls(editor2, editor1)),
+ ]
+
+ return () => {
+ node1?.removeEventListener('mouseenter', onMouseEnter1)
+ node2?.removeEventListener('mouseenter', onMouseEnter2)
+ disposables.forEach(d => d.dispose())
+ }
+}
diff --git a/packages/vite/src/app/composables/rpc.ts b/packages/vite/src/app/composables/rpc.ts
index 10f005ab..1c935d32 100644
--- a/packages/vite/src/app/composables/rpc.ts
+++ b/packages/vite/src/app/composables/rpc.ts
@@ -1,11 +1,16 @@
import type {} from '@vitejs/devtools'
import type { DevToolsRpcClient } from '@vitejs/devtools-kit/client'
-import type {} from '../../node/rpc'
+import type { ViteInspectModuleUpdatedState } from '../../node/rpc'
import { getDevToolsRpcClient } from '@vitejs/devtools-kit/client'
import { DEVTOOLS_MOUNT_PATH } from '@vitejs/devtools-kit/constants'
-import { reactive, shallowRef } from 'vue'
+import { createEventEmitter } from '@vitejs/devtools-kit/utils/events'
+import { getCurrentScope, onScopeDispose, reactive, shallowRef } from 'vue'
import { useRuntimeConfig } from '#app/nuxt'
+export interface InspectModuleUpdatedPayload {
+ ids?: string[]
+}
+
export const rpcConnectionState = reactive<{
connected: boolean
error: Error | null
@@ -15,6 +20,32 @@ export const rpcConnectionState = reactive<{
})
const rpc = shallowRef(undefined!)
+const moduleUpdated = createEventEmitter<{
+ updated: (payload: InspectModuleUpdatedPayload) => void
+}>()
+let unsubscribeInspectModuleUpdates: (() => void) | undefined
+
+function triggerModuleUpdated(payload: InspectModuleUpdatedPayload = {}) {
+ moduleUpdated.emit('updated', payload)
+}
+
+async function subscribeInspectModuleUpdates(client: DevToolsRpcClient) {
+ unsubscribeInspectModuleUpdates?.()
+
+ const state = await client.sharedState.get('vite:inspect:module-updated', {
+ initialValue: {
+ version: 0,
+ ids: null,
+ updatedAt: 0,
+ },
+ })
+
+ unsubscribeInspectModuleUpdates = state.on('updated', (value: ViteInspectModuleUpdatedState) => {
+ triggerModuleUpdated({
+ ids: value.ids ?? undefined,
+ })
+ })
+}
export async function connect() {
const runtimeConfig = useRuntimeConfig()
@@ -47,6 +78,7 @@ export async function connect() {
},
},
})
+ await subscribeInspectModuleUpdates(rpc.value)
rpcConnectionState.connected = true
}
@@ -58,3 +90,12 @@ export async function connect() {
export function useRpc() {
return rpc
}
+
+export function onInspectModuleUpdated(handler: (payload: InspectModuleUpdatedPayload) => void | Promise) {
+ const off = moduleUpdated.on('updated', handler)
+
+ if (getCurrentScope())
+ onScopeDispose(off)
+
+ return off
+}
diff --git a/packages/vite/src/app/pages/graph.vue b/packages/vite/src/app/pages/graph.vue
new file mode 100644
index 00000000..aeabfa38
--- /dev/null
+++ b/packages/vite/src/app/pages/graph.vue
@@ -0,0 +1,353 @@
+
+
+
+
+
+
+
+ {{ error.message }}
+
+
+
+ Retry
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
View as
+
+
+ {{ viewType.label }}
+
+
+
+
+
+
+
+
+
+ {{ searched.length }} of {{ modules.length }}
+
+
+
+
+
diff --git a/packages/vite/src/app/pages/plugins.vue b/packages/vite/src/app/pages/plugins.vue
new file mode 100644
index 00000000..61d6e62e
--- /dev/null
+++ b/packages/vite/src/app/pages/plugins.vue
@@ -0,0 +1,230 @@
+
+
+
+
+
+
+
+ {{ error.message }}
+
+
+
+ Retry
+
+
+
+
+
+
+
+
+ {{ searched.length }} of {{ plugins.length || 0 }}
+
+
+
+
+
diff --git a/packages/vite/src/app/state/flowmap.ts b/packages/vite/src/app/state/flowmap.ts
new file mode 100644
index 00000000..325bc1fa
--- /dev/null
+++ b/packages/vite/src/app/state/flowmap.ts
@@ -0,0 +1,10 @@
+import { useEventListener } from '@vueuse/core'
+import { shallowRef } from 'vue'
+
+export const isFlowmapSwapping = shallowRef(false)
+
+if (import.meta.client) {
+ useEventListener(window, ['pointerup', 'pointercancel', 'pointerleave'], () => {
+ isFlowmapSwapping.value = false
+ })
+}
diff --git a/packages/vite/src/app/state/settings.ts b/packages/vite/src/app/state/settings.ts
new file mode 100644
index 00000000..e64cd073
--- /dev/null
+++ b/packages/vite/src/app/state/settings.ts
@@ -0,0 +1,71 @@
+import type { RemovableRef } from '@vueuse/core'
+import type { Ref } from 'vue'
+import { useLocalStorage } from '@vueuse/core'
+import { computed } from 'vue'
+
+export interface ClientSettings {
+ codeviewerLineWrap: boolean
+ codeviewerDiffPanelSize: number
+ moduleGraphViewType: 'list' | 'detailed-list' | 'graph' | 'folder'
+ moduleDetailsViewType: 'flow' | 'charts' | 'imports'
+ chartAnimation: boolean
+ pluginDetailsViewType: 'flow' | 'sunburst'
+ pluginDetailsTableFields: string[] | null
+ pluginDetailsModuleTypes: string[] | null
+ pluginDetailsDurationSortType: string
+ pluginDetailSelectedHook: string
+ pluginDetailsShowType: 'changed' | 'unchanged' | 'all'
+ flowExpandResolveId: boolean
+ flowExpandTransforms: boolean
+ flowExpandLoads: boolean
+ flowShowAllTransforms: boolean
+ flowShowAllLoads: boolean
+}
+
+export const settings = useLocalStorage(
+ 'vite-devtools-settings',
+ {
+ codeviewerLineWrap: false,
+ codeviewerDiffPanelSize: 50,
+ moduleGraphViewType: 'list',
+ moduleDetailsViewType: 'flow',
+ chartAnimation: true,
+ pluginDetailsViewType: 'flow',
+ pluginDetailsTableFields: null,
+ pluginDetailsModuleTypes: null,
+ pluginDetailsDurationSortType: '',
+ pluginDetailSelectedHook: '',
+ pluginDetailsShowType: 'all',
+ flowExpandResolveId: true,
+ flowExpandTransforms: true,
+ flowExpandLoads: true,
+ flowShowAllTransforms: false,
+ flowShowAllLoads: false,
+ },
+ {
+ mergeDefaults: true,
+ },
+)
+
+export function objectRefToRefs(obj: RemovableRef): {
+ [K in keyof T]: Ref
+} {
+ const cache = new Map>()
+ return new Proxy(obj.value, {
+ get(target, prop) {
+ if (!cache.has(prop as keyof T)) {
+ cache.set(prop as keyof T, computed({
+ get() {
+ return target[prop as keyof T]
+ },
+ set(value) {
+ target[prop as keyof T] = value
+ },
+ }))
+ }
+ return cache.get(prop as keyof T)
+ },
+ }) as any
+}
+
+export const settingsRefs = objectRefToRefs(settings)
diff --git a/packages/vite/src/app/styles/cm.css b/packages/vite/src/app/styles/cm.css
new file mode 100644
index 00000000..09c337f3
--- /dev/null
+++ b/packages/vite/src/app/styles/cm.css
@@ -0,0 +1,23 @@
+:root {
+ --cm-font-family: 'Input Mono', 'FiraCode', monospace;
+}
+
+.code-viewer {
+ min-height: 80px;
+}
+
+.monaco-editor,
+.monaco-editor .margin,
+.monaco-editor .view-lines,
+.monaco-editor .view-line,
+.monaco-editor .line-numbers {
+ font-family: var(--cm-font-family) !important;
+ font-size: 13px !important;
+}
+
+.monaco-editor,
+.monaco-editor .monaco-editor-background,
+.monaco-editor .overflow-guard,
+.monaco-editor .margin {
+ background-color: transparent !important;
+}
diff --git a/packages/vite/src/app/styles/global.css b/packages/vite/src/app/styles/global.css
index f2b2ff40..9adbb432 100644
--- a/packages/vite/src/app/styles/global.css
+++ b/packages/vite/src/app/styles/global.css
@@ -1 +1,82 @@
@import '@vitejs/devtools-ui/styles/global.css';
+
+summary::-webkit-details-marker {
+ display: none;
+}
+
+/* Animations */
+@keyframes spin-reverse {
+ from {
+ transform: rotate(1080deg);
+ }
+ to {
+ transform: rotate(0deg);
+ }
+}
+.animate-spin-reverse,
+.hover\:animate-spin-reverse:hover {
+ animation: spin-reverse 2.5s cubic-bezier(0.37, 0, 0.63, 1) infinite;
+}
+
+/* Xterm */
+.terminal.xterm {
+ height: 100%;
+ padding: 0 1.5em;
+}
+
+.terminal.xterm .xterm-rows {
+ --uno: font-mono;
+}
+
+/* Overrides Floating Vue */
+.v-popper--theme-dropdown .v-popper__inner,
+.v-popper--theme-tooltip .v-popper__inner {
+ --uno: bg-tooltip color-base font-sans rounded border border-base shadow dark:shadow-2xl;
+ box-shadow: 0 6px 30px #0000001a;
+}
+
+.v-popper--theme-tooltip .v-popper__inner {
+ --uno: text-sm;
+}
+
+.v-popper--theme-dropdown .v-popper__inner {
+ max-width: 50vw;
+ max-height: 50vh;
+ overflow: auto;
+}
+
+.v-popper--theme-tooltip .v-popper__arrow-inner,
+.v-popper--theme-dropdown .v-popper__arrow-inner {
+ visibility: visible;
+ --uno: border-solid border-white dark:border-#111;
+}
+
+.v-popper--theme-tooltip .v-popper__arrow-outer,
+.v-popper--theme-dropdown .v-popper__arrow-outer {
+ --uno: border-solid border-base;
+}
+
+.v-popper--theme-tooltip.v-popper--shown,
+.v-popper--theme-tooltip.v-popper--shown * {
+ transition: none !important;
+}
+
+.v-popper__popper--shown {
+ overflow: visible !important;
+}
+
+/* Monaco */
+.monaco-scrollable-element > .scrollbar {
+ background: transparent !important;
+}
+
+.monaco-scrollable-element > .scrollbar > .slider {
+ background-color: var(--app-scrollbar-thumb) !important;
+ border-radius: var(--app-scrollbar-radius) !important;
+ transition: background 0.2s ease;
+}
+
+.monaco-scrollable-element > .scrollbar > .slider.active,
+.monaco-scrollable-element > .scrollbar > .slider:hover {
+ background-color: var(--app-scrollbar-thumb-hover) !important;
+}
diff --git a/packages/vite/src/app/styles/splitpanes.css b/packages/vite/src/app/styles/splitpanes.css
new file mode 100644
index 00000000..c6c73901
--- /dev/null
+++ b/packages/vite/src/app/styles/splitpanes.css
@@ -0,0 +1,53 @@
+@import 'splitpanes/dist/splitpanes.css';
+
+
+/* Splitpanes */
+.splitpanes__pane {
+ transition: none !important;
+}
+
+.splitpanes__splitter {
+ position: relative;
+}
+
+.splitpanes__splitter:before {
+ position: absolute;
+ left: 0;
+ top: 0;
+ transition: .2s ease;
+ content: '';
+ transition: opacity 0.4s;
+ z-index: 1;
+}
+
+.splitpanes__splitter:hover:before {
+ background: #8881;
+ opacity: 1;
+}
+
+.splitpanes--vertical>.splitpanes__splitter {
+ width: 0 !important;
+ min-width: 0 !important;
+}
+
+.splitpanes--horizontal>.splitpanes__splitter {
+ height: 0 !important;
+ min-height: 0 !important;
+}
+
+.splitpanes--vertical>.splitpanes__splitter:before {
+ left: -5px;
+ right: -5px;
+ height: 100%;
+}
+
+.splitpanes--horizontal>.splitpanes__splitter:before {
+ top: -5px;
+ bottom: -5px;
+ width: 100%;
+}
+
+.splitpanes__pane[style*="display: none"]+.splitpanes__splitter,
+.splitpanes__pane[hidden]+.splitpanes__splitter {
+ display: none;
+}
diff --git a/packages/vite/src/app/types/chart.ts b/packages/vite/src/app/types/chart.ts
new file mode 100644
index 00000000..e56c97c7
--- /dev/null
+++ b/packages/vite/src/app/types/chart.ts
@@ -0,0 +1,11 @@
+import type { TreeNode } from 'nanovis'
+import type { VitePluginBuildInfo } from './plugins'
+
+export type PluginChartInfo = Omit & {
+ type: 'module' | 'hook'
+ title: string
+ text: string
+ children?: any[]
+}
+
+export type PluginChartNode = TreeNode
diff --git a/packages/vite/src/app/types/modules.ts b/packages/vite/src/app/types/modules.ts
new file mode 100644
index 00000000..e902a70f
--- /dev/null
+++ b/packages/vite/src/app/types/modules.ts
@@ -0,0 +1,49 @@
+export interface ViteGraphModulePluginMetric {
+ name: string
+ transform?: number
+ resolveId?: number
+}
+
+export interface ViteModuleTransformMetric {
+ plugin_name: string
+ source_code_size: number
+ transformed_code_size: number
+ duration: number
+}
+
+export interface ViteModuleBuildMetrics {
+ resolve_ids: { duration: number }[]
+ loads: { duration: number }[]
+ transforms: ViteModuleTransformMetric[]
+}
+
+export interface ViteModuleImport {
+ module_id: string
+ kind?: string
+}
+
+export interface ViteModuleListItem {
+ id: string
+ deps: string[]
+ importers: string[]
+ imports: ViteModuleImport[]
+ plugins: ViteGraphModulePluginMetric[]
+ virtual: boolean
+ totalTime: number
+ invokeCount: number
+ sourceSize: number
+ distSize: number
+ path?: string
+ buildMetrics?: ViteModuleBuildMetrics
+}
+
+export interface ViteModuleDest {
+ full: string
+ path: string
+}
+
+export interface ViteModuleTreeNode {
+ name?: string
+ children: Record
+ items: ViteModuleDest[]
+}
diff --git a/packages/vite/src/app/types/plugins.ts b/packages/vite/src/app/types/plugins.ts
new file mode 100644
index 00000000..fa20cb43
--- /dev/null
+++ b/packages/vite/src/app/types/plugins.ts
@@ -0,0 +1,10 @@
+import type { DevToolsRpcServerFunctions } from '@vitejs/devtools-kit'
+
+export type VitePluginDetails = Awaited>
+export type VitePluginBuildInfo = VitePluginDetails['calls'][number]
+
+export interface VitePluginItem {
+ plugin_id: number
+ name: string
+ enforce?: 'pre' | 'post'
+}
diff --git a/packages/vite/src/app/utils/cache.ts b/packages/vite/src/app/utils/cache.ts
new file mode 100644
index 00000000..6f68ffc8
--- /dev/null
+++ b/packages/vite/src/app/utils/cache.ts
@@ -0,0 +1,9 @@
+export function makeCachedFunction any>(fn: T): T {
+ const cache = new Map>()
+ return ((...args: Parameters) => {
+ const key = JSON.stringify(args)
+ if (!cache.has(key))
+ cache.set(key, fn(...args))
+ return cache.get(key)
+ }) as T
+}
diff --git a/packages/vite/src/app/utils/color.ts b/packages/vite/src/app/utils/color.ts
new file mode 100644
index 00000000..b2bc84c4
--- /dev/null
+++ b/packages/vite/src/app/utils/color.ts
@@ -0,0 +1,16 @@
+import { getHashColorFromString, getHsla } from '@vitejs/devtools-ui/utils/color'
+
+export const predefinedColorMap = {
+ error: 0,
+ client: 60,
+ ssr: 270,
+ vite: 250,
+ virtual: 160,
+} as Record
+
+export function getPluginColor(name: string, opacity = 1): string {
+ name = name.replace(/[^a-z]+/gi, '').toLowerCase()
+ if (name in predefinedColorMap)
+ return getHsla(predefinedColorMap[name]!, opacity)
+ return getHashColorFromString(name, opacity)
+}
diff --git a/packages/vite/src/app/utils/filepath.ts b/packages/vite/src/app/utils/filepath.ts
new file mode 100644
index 00000000..50b72ff3
--- /dev/null
+++ b/packages/vite/src/app/utils/filepath.ts
@@ -0,0 +1,145 @@
+import { relative } from 'pathe'
+import { makeCachedFunction } from './cache'
+
+const NODE_MODULES = 'node_modules'
+
+export function normalizeModulePath(path: string) {
+ return path
+ .replace(/%2F/gi, '/')
+ .replace(/\\/g, '/')
+}
+
+export function isPackageName(name: string) {
+ return name[0] === '#' || !!name.match(/^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/)
+}
+
+export function getModuleNameFromPath(path: string) {
+ const normalizedPath = normalizeModulePath(path)
+ if (isPackageName(normalizedPath))
+ return normalizedPath
+
+ const segments = normalizedPath.split('/')
+ const packageRoot = findNodeModulesPackage(segments)
+
+ return packageRoot?.name ?? findPackageLocator(segments)?.name
+}
+
+function getModuleSubpathFromPath(path: string) {
+ return path.match(/.*\/node_modules\/(.*)$/)?.[1]
+}
+
+export const parseReadablePath = makeCachedFunction((path: string, root: string) => {
+ const parsedPath = normalizeModulePath(path)
+ if (isPackageName(parsedPath)) {
+ return {
+ moduleName: parsedPath,
+ path: parsedPath,
+ }
+ }
+
+ if (/^\w+:/.test(parsedPath) && !(/^[a-z]:\\/i.test(path))) {
+ return {
+ moduleName: parsedPath,
+ path: parsedPath,
+ }
+ }
+
+ const moduleName = getModuleNameFromPath(parsedPath)
+ const subpath = getModuleSubpathFromPath(parsedPath)
+ if (moduleName && subpath) {
+ return {
+ moduleName,
+ path: subpath,
+ }
+ }
+
+ try {
+ let result = relative(root, parsedPath)
+ if (!result.startsWith('./') && !result.startsWith('../'))
+ result = `./${result}`
+ return { path: result }
+ }
+ catch {
+ return { path: parsedPath }
+ }
+})
+
+function findNodeModulesPackage(segments: string[]) {
+ for (const nodeModulesIndex of getNodeModulesIndexes(segments)) {
+ const name = getPackageNameFromSegments(segments, nodeModulesIndex + 1)
+ if (!name)
+ continue
+
+ return {
+ name,
+ endIndex: nodeModulesIndex + 1 + (name.startsWith('@') ? 2 : 1),
+ }
+ }
+}
+
+function getPackageNameFromSegments(segments: string[], startIndex: number) {
+ const firstSegment = segments[startIndex]
+ if (!firstSegment)
+ return undefined
+
+ const packageName = firstSegment.startsWith('@')
+ ? `${firstSegment}/${segments[startIndex + 1] ?? ''}`
+ : firstSegment
+
+ return isPackageName(packageName) ? packageName : undefined
+}
+
+function findPackageLocator(segments: string[]) {
+ for (const nodeModulesIndex of getNodeModulesIndexes(segments)) {
+ if (getPackageNameFromSegments(segments, nodeModulesIndex + 1))
+ continue
+
+ const startIndex = nodeModulesIndex + 1
+ const nextNodeModulesIndex = segments.indexOf(NODE_MODULES, startIndex)
+ const boundaryIndex = nextNodeModulesIndex >= 0 ? nextNodeModulesIndex : segments.length
+
+ for (const candidateIndex of [startIndex, startIndex + 1]) {
+ if (candidateIndex >= boundaryIndex)
+ continue
+
+ const segment = segments[candidateIndex]
+ if (!segment)
+ continue
+
+ const packageLocator = parsePackageLocatorSegment(segment)
+ if (packageLocator)
+ return { ...packageLocator, index: candidateIndex }
+ }
+ }
+}
+
+function getNodeModulesIndexes(segments: string[]) {
+ return segments
+ .map((segment, index) => segment === NODE_MODULES ? index : -1)
+ .filter(index => index >= 0)
+ .reverse()
+}
+
+function parsePackageLocatorSegment(segment: string): { name: string, version: string } | undefined {
+ const versionIndex = segment.startsWith('@')
+ ? segment.indexOf('@', 1)
+ : segment.indexOf('@')
+
+ if (versionIndex <= 0)
+ return undefined
+
+ const name = segment
+ .slice(0, versionIndex)
+ .replace(/\+/g, '/')
+ const version = segment
+ .slice(versionIndex + 1)
+ .split('_')[0]
+
+ if (!isPackageName(name) || !version)
+ return undefined
+
+ return {
+ name,
+ version,
+ }
+}
diff --git a/packages/vite/src/app/utils/format.ts b/packages/vite/src/app/utils/format.ts
new file mode 100644
index 00000000..08f5482a
--- /dev/null
+++ b/packages/vite/src/app/utils/format.ts
@@ -0,0 +1,61 @@
+import type { ViteModuleDest, ViteModuleTreeNode } from '~/types/modules'
+
+export function bytesToHumanSize(bytes: number, digits = 2) {
+ const sizes = ['Bytes', 'kB', 'MB', 'GB', 'TB']
+ const i = Math.floor(Math.log(bytes || 1) / Math.log(1000))
+ if (i === 0)
+ return [bytes, 'B']
+ return [(+(bytes / 1000 ** i).toFixed(digits)).toLocaleString(), sizes[i]]
+}
+
+export function toTree(modules: ViteModuleDest[], name: string) {
+ const node: ViteModuleTreeNode = { name, children: {}, items: [] }
+
+ function add(mod: ViteModuleDest, parts: string[], current = node) {
+ if (parts.length <= 1) {
+ current.items.push(mod)
+ return
+ }
+
+ const first = parts.shift()!
+ if (!current.children[first])
+ current.children[first] = { name: first, children: {}, items: [] }
+ add(mod, parts, current.children[first])
+ }
+
+ modules.forEach((m) => {
+ const parts = m.path.split(/\//g).filter(Boolean)
+ add(m, parts)
+ })
+
+ function flat(node: ViteModuleTreeNode) {
+ const children = Object.values(node.children)
+ if (children.length === 1 && !node.items.length) {
+ const child = children[0]!
+ node.name = node.name ? `${node.name}/${child.name}` : child.name
+ node.items = child.items
+ node.children = child.children
+ flat(node)
+ }
+ else {
+ children.forEach(flat)
+ }
+ }
+
+ Object.values(node.children).forEach(flat)
+
+ return node
+}
+
+export function normalizeTimestamp(timestamp: number) {
+ return new Date(timestamp).toLocaleString(undefined, {
+ hour12: false,
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit',
+ fractionalSecondDigits: 3,
+ })
+}
diff --git a/packages/vite/src/app/utils/icon.ts b/packages/vite/src/app/utils/icon.ts
new file mode 100644
index 00000000..75d4ff63
--- /dev/null
+++ b/packages/vite/src/app/utils/icon.ts
@@ -0,0 +1,190 @@
+import { makeCachedFunction } from './cache'
+
+export interface FilterMatchRule {
+ match: RegExp
+ name: string
+ description: string
+ icon: string
+}
+
+// @unocss-include
+export const ModuleTypeRules: FilterMatchRule[] = [
+ {
+ match: /[\\/]node_modules[\\/]/i,
+ name: 'node_modules',
+ description: 'Node Modules',
+ icon: 'i-catppuccin-npm',
+ },
+ {
+ match: /virtual:|^\0/,
+ name: 'virtual',
+ description: 'Virtual',
+ icon: 'i-catppuccin-symlink',
+ },
+ {
+ match: /^@?[\w-]+\/?/,
+ name: 'package',
+ description: 'Package',
+ icon: 'i-catppuccin-java-class-abstract',
+ },
+ {
+ match: /\.vue(?:$|\?)/i,
+ name: 'vue',
+ description: 'Vue',
+ icon: 'i-catppuccin-vue',
+ },
+ {
+ match: /\.[cm]?[tj]sx(?:$|\?)/i,
+ name: 'jsx',
+ description: 'JavaScript React',
+ icon: 'i-catppuccin-javascript-react',
+ },
+ {
+ match: /\.[cm]?ts(?:$|\?)/i,
+ name: 'ts',
+ description: 'TypeScript',
+ icon: 'i-catppuccin-typescript',
+ },
+ {
+ match: /\.[cm]?js(?:$|\?)/i,
+ name: 'js',
+ description: 'JavaScript',
+ icon: 'i-catppuccin-javascript',
+ },
+ {
+ match: /\.svelte(?:$|\?)/i,
+ name: 'svelte',
+ description: 'Svelte',
+ icon: 'i-catppuccin-svelte',
+ },
+ {
+ match: /\.mdx(?:$|\?)/i,
+ name: 'mdx',
+ description: 'MDX',
+ icon: 'i-catppuccin-mdx',
+ },
+ {
+ match: /\.md(?:$|\?)/i,
+ name: 'md',
+ description: 'Markdown',
+ icon: 'i-catppuccin-markdown',
+ },
+ {
+ match: /\.astro(?:$|\?)/i,
+ name: 'astro',
+ description: 'Astro',
+ icon: 'i-catppuccin-astro',
+ },
+ {
+ match: /\.angular(?:$|\?)/i,
+ name: 'angular',
+ description: 'Angular',
+ icon: 'i-catppuccin-angular',
+ },
+ {
+ match: /\.html?(?:$|\?)/i,
+ name: 'html',
+ description: 'HTML',
+ icon: 'i-catppuccin-html',
+ },
+ {
+ match: /\.(?:css|scss|less|postcss)(?:$|\?)/i,
+ name: 'css',
+ description: 'CSS',
+ icon: 'i-catppuccin-css',
+ },
+ {
+ match: /\.jsonc?5?(?:$|\?)/i,
+ name: 'json',
+ description: 'JSON',
+ icon: 'i-catppuccin-json',
+ },
+ {
+ match: /\.(?:yaml|yml)(?:$|\?)/i,
+ name: 'yaml',
+ description: 'YAML',
+ icon: 'i-catppuccin-yaml',
+ },
+ {
+ match: /\.toml(?:$|\?)/i,
+ name: 'toml',
+ description: 'TOML',
+ icon: 'i-catppuccin-toml',
+ },
+ {
+ match: /\.svg(?:$|\?)/i,
+ name: 'svg',
+ description: 'SVG',
+ icon: 'i-catppuccin-svg',
+ },
+]
+
+// @unocss-include
+export const PluginTypeRules: FilterMatchRule[] = [
+ {
+ match: /^(replace|define|alias)$/,
+ name: 'rollup',
+ description: 'Rollup',
+ icon: 'i-catppuccin-rollup',
+ },
+ {
+ match: /^vite:/i,
+ name: 'vite',
+ description: 'Vite',
+ icon: 'i-catppuccin-vite',
+ },
+ {
+ match: /^unocss:/i,
+ name: 'unocss',
+ description: 'Unocss',
+ icon: 'i-catppuccin-unocss',
+ },
+ {
+ match: /^nuxt:/i,
+ name: 'nuxt',
+ description: 'Nuxt',
+ icon: 'i-catppuccin-nuxt',
+ },
+ {
+ match: /^builtin:/i,
+ name: 'builtin',
+ description: 'Builtin',
+ icon: 'i-catppuccin-folder-prisma',
+ },
+]
+
+export const DefaultFileTypeRule: FilterMatchRule = {
+ name: 'file',
+ match: /.*/,
+ description: 'File',
+ icon: 'i-catppuccin-file',
+}
+
+export const DefaultPluginType: FilterMatchRule = {
+ name: 'plugin',
+ match: /.*/,
+ description: 'User Plugins',
+ icon: 'i-catppuccin-folder-plugins',
+}
+
+export const getFileTypeFromModuleId = makeCachedFunction((moduleId: string): FilterMatchRule => {
+ moduleId = moduleId
+ .replace(/(\?|&)v=[^&]*/, '$1')
+ .replace(/\?$/, '')
+
+ for (const rule of ModuleTypeRules) {
+ if (rule.match.test(moduleId))
+ return rule
+ }
+
+ return DefaultFileTypeRule
+})
+
+export const getPluginTypeFromName = makeCachedFunction((name: string): FilterMatchRule => {
+ for (const rule of PluginTypeRules) {
+ if (rule.match.test(name))
+ return rule
+ }
+
+ return DefaultPluginType
+})
diff --git a/packages/vite/src/app/worker/diff.ts b/packages/vite/src/app/worker/diff.ts
new file mode 100644
index 00000000..3024edd2
--- /dev/null
+++ b/packages/vite/src/app/worker/diff.ts
@@ -0,0 +1,18 @@
+import type { Remote } from 'comlink'
+import type { Exports } from './diff.worker'
+import { wrap } from 'comlink'
+
+let diffWorker: Remote | undefined
+
+export async function calculateDiffWithWorker(left: string, right: string) {
+ if (!diffWorker) {
+ diffWorker = wrap(
+ new Worker(new URL('./diff.worker.ts', import.meta.url), {
+ type: 'module',
+ }),
+ )
+ }
+
+ const result = await diffWorker.calculateDiff(left, right)
+ return result
+}
diff --git a/packages/vite/src/app/worker/diff.worker.ts b/packages/vite/src/app/worker/diff.worker.ts
new file mode 100644
index 00000000..c0b09a81
--- /dev/null
+++ b/packages/vite/src/app/worker/diff.worker.ts
@@ -0,0 +1,16 @@
+import { expose } from 'comlink'
+import { diffCleanupSemantic, diffMain } from 'diff-match-patch-es'
+
+function calculateDiff(left: string, right: string) {
+ const changes = diffMain(left.replace(/\r\n/g, '\n'), right.replace(/\r\n/g, '\n'))
+ diffCleanupSemantic(changes)
+ return changes
+}
+
+const exports = {
+ calculateDiff,
+}
+
+export type Exports = typeof exports
+
+expose(exports)
diff --git a/packages/vite/src/index.ts b/packages/vite/src/index.ts
index 336ce12b..520f0e69 100644
--- a/packages/vite/src/index.ts
+++ b/packages/vite/src/index.ts
@@ -1 +1 @@
-export {}
+export * from './node'
diff --git a/packages/vite/src/modules/rpc.ts b/packages/vite/src/modules/rpc.ts
index c56c63d3..184d6790 100644
--- a/packages/vite/src/modules/rpc.ts
+++ b/packages/vite/src/modules/rpc.ts
@@ -1,6 +1,6 @@
import { addVitePlugin, defineNuxtModule } from '@nuxt/kit'
import { DevToolsServer } from '../../../core/src/node/plugins/server'
-import { rpcFunctions } from '../node/rpc'
+import { DevToolsViteInspect } from '../node/inspect/plugin'
export default defineNuxtModule({
meta: {
@@ -8,17 +8,7 @@ export default defineNuxtModule({
configKey: 'devtoolsRpc',
},
setup() {
- addVitePlugin({
- name: 'vite:devtools:vite',
- devtools: {
- setup(ctx) {
- for (const fn of rpcFunctions) {
- ctx.rpc.register(fn as any)
- }
- },
- },
- })
-
+ addVitePlugin(DevToolsViteInspect())
addVitePlugin(DevToolsServer())
},
})
diff --git a/packages/vite/src/node/__tests__/inspect-context.test.ts b/packages/vite/src/node/__tests__/inspect-context.test.ts
new file mode 100644
index 00000000..c74937d0
--- /dev/null
+++ b/packages/vite/src/node/__tests__/inspect-context.test.ts
@@ -0,0 +1,384 @@
+import type { Environment, Plugin, ResolvedConfig } from 'vite'
+import { describe, expect, it } from 'vitest'
+import { ViteInspectContext } from '../inspect/context'
+import { hijackPlugin } from '../inspect/hijack'
+
+function createFixture(options: {
+ root?: string
+} = {}) {
+ const config = {
+ root: options.root || '/project',
+ plugins: [
+ { name: 'vite:load-fallback' },
+ { name: 'plugin-a', enforce: 'pre' },
+ { name: 'plugin-b' },
+ ],
+ } as ResolvedConfig
+ const env = {
+ name: 'client',
+ mode: 'build',
+ getTopLevelConfig: () => config,
+ } as Environment
+ const ctx = new ViteInspectContext()
+ const vite = ctx.getViteContext(config)
+ const envCtx = vite.getEnvContext(env)
+
+ return {
+ ctx,
+ vite,
+ env,
+ envCtx,
+ }
+}
+
+describe('vite inspect context', () => {
+ it('records module transforms and normalizes version query by default', () => {
+ const { envCtx } = createFixture()
+
+ envCtx.recordTransform('/src/main.ts?v=123456', {
+ name: 'plugin-a',
+ result: 'export const value = 1',
+ start: 10,
+ end: 16,
+ order: 'pre',
+ }, 'const value = 1')
+
+ const modules = envCtx.getModulesList()
+
+ expect(modules).toMatchObject([
+ {
+ id: '/src/main.ts',
+ plugins: [
+ { name: '__load__', transform: 0 },
+ { name: 'plugin-a', transform: 6 },
+ ],
+ totalTime: 6,
+ invokeCount: 1,
+ sourceSize: 15,
+ distSize: 22,
+ },
+ ])
+ })
+
+ it('records resolveId chains and plugin metrics', () => {
+ const { envCtx } = createFixture()
+
+ envCtx.recordResolveId('/src/main.ts', {
+ name: 'plugin-a',
+ result: '/src/resolved.ts',
+ start: 0,
+ end: 4,
+ })
+ envCtx.recordTransform('/src/resolved.ts', {
+ name: 'plugin-b',
+ result: 'export {}',
+ start: 5,
+ end: 8,
+ }, '')
+
+ expect(envCtx.resolveId('/src/main.ts')).toBe('/src/resolved.ts')
+ expect(envCtx.getPluginMetrics()).toEqual(expect.arrayContaining([
+ expect.objectContaining({
+ name: 'plugin-a',
+ resolveId: {
+ invokeCount: 1,
+ totalTime: 4,
+ },
+ }),
+ expect.objectContaining({
+ name: 'plugin-b',
+ transform: {
+ invokeCount: 1,
+ totalTime: 3,
+ },
+ }),
+ ]))
+ })
+
+ it('records plugin call details by plugin id', () => {
+ const { envCtx, vite } = createFixture()
+
+ const pluginA = vite.config.plugins[1]!
+ const pluginB = vite.config.plugins[2]!
+
+ envCtx.recordResolveId('/src/main.ts', {
+ name: 'plugin-a',
+ result: '/src/resolved.ts',
+ start: 0,
+ end: 4,
+ }, pluginA)
+ envCtx.recordLoad('/src/resolved.ts', {
+ name: 'plugin-a',
+ result: 'export const value = 1',
+ start: 5,
+ end: 7,
+ }, pluginA)
+ envCtx.recordTransform('/src/resolved.ts', {
+ name: 'plugin-b',
+ result: 'export const value = 2',
+ start: 8,
+ end: 14,
+ }, 'export const value = 1', pluginB)
+
+ expect(envCtx.getPluginDetails(1)).toMatchObject({
+ plugin_name: 'plugin-a',
+ plugin_id: 1,
+ calls: [
+ {
+ type: 'resolve',
+ plugin_id: 1,
+ plugin_name: 'plugin-a',
+ module: '/src/resolved.ts',
+ duration: 4,
+ timestamp_start: 0,
+ timestamp_end: 4,
+ },
+ {
+ type: 'load',
+ plugin_id: 1,
+ plugin_name: 'plugin-a',
+ module: '/src/resolved.ts',
+ duration: 2,
+ unchanged: false,
+ },
+ ],
+ resolveIdMetrics: [
+ expect.objectContaining({ type: 'resolve' }),
+ ],
+ loadMetrics: [
+ expect.objectContaining({ type: 'load' }),
+ ],
+ transformMetrics: [],
+ })
+
+ expect(envCtx.getPluginDetails(2)).toMatchObject({
+ plugin_name: 'plugin-b',
+ transformMetrics: [
+ expect.objectContaining({
+ type: 'transform',
+ duration: 6,
+ unchanged: false,
+ }),
+ ],
+ })
+ })
+
+ it('clears module-related inspect data on invalidation', () => {
+ const { envCtx, vite } = createFixture()
+
+ const pluginA = vite.config.plugins[1]!
+ const pluginB = vite.config.plugins[2]!
+
+ envCtx.recordResolveId('/src/main.ts', {
+ name: 'plugin-a',
+ result: '/src/resolved.ts',
+ start: 0,
+ end: 4,
+ }, pluginA)
+ envCtx.recordLoad('/src/resolved.ts', {
+ name: 'plugin-a',
+ result: 'export const value = 1',
+ start: 5,
+ end: 7,
+ }, pluginA)
+ envCtx.recordTransform('/src/resolved.ts', {
+ name: 'plugin-b',
+ result: 'export const value = 2',
+ start: 8,
+ end: 14,
+ }, 'export const value = 1', pluginB)
+ envCtx.recordTransform('/src/other.ts', {
+ name: 'plugin-a',
+ result: 'export const other = 1',
+ start: 15,
+ end: 18,
+ }, '', pluginA)
+
+ envCtx.invalidate('/src/resolved.ts')
+
+ expect(envCtx.data.transform['/src/resolved.ts']).toBeUndefined()
+ expect(envCtx.data.transformCounter['/src/resolved.ts']).toBeUndefined()
+ expect(envCtx.data.resolveId['/src/main.ts']).toBeUndefined()
+ expect(envCtx.getModulesList().map(module => module.id)).toEqual(['/src/other.ts'])
+ expect(envCtx.getPluginDetails(1).calls.map(call => call.module)).toEqual(['/src/other.ts'])
+ expect(envCtx.getPluginDetails(2).calls).toEqual([])
+ expect(envCtx.getPluginMetrics()).toEqual(expect.arrayContaining([
+ expect.objectContaining({
+ name: 'plugin-a',
+ transform: {
+ invokeCount: 1,
+ totalTime: 3,
+ },
+ resolveId: {
+ invokeCount: 0,
+ totalTime: 0,
+ },
+ }),
+ ]))
+ })
+
+ it('records empty string load results as load output', async () => {
+ const { ctx, env, envCtx, vite } = createFixture()
+ const plugin = vite.config.plugins[1]! as Plugin & {
+ load: NonNullable
+ }
+ plugin.load = () => ''
+
+ hijackPlugin(plugin, ctx)
+ await (plugin.load as any).call({ environment: env }, '/src/empty.ts')
+
+ expect(envCtx.getPluginDetails(1)).toMatchObject({
+ loadMetrics: [
+ {
+ type: 'load',
+ plugin_id: 1,
+ plugin_name: 'plugin-a',
+ module: '/src/empty.ts',
+ unchanged: false,
+ },
+ ],
+ })
+ await expect(envCtx.getModuleTransformInfo('/src/empty.ts')).resolves.toMatchObject({
+ transforms: [
+ {
+ name: 'plugin-a',
+ result: '',
+ },
+ ],
+ })
+ })
+
+ it('keeps empty string transform results in module metrics', async () => {
+ const { envCtx, vite } = createFixture()
+ const pluginA = vite.config.plugins[1]!
+ const pluginB = vite.config.plugins[2]!
+
+ envCtx.recordLoad('/src/empty.ts', {
+ name: 'plugin-a',
+ result: '',
+ start: 0,
+ end: 2,
+ }, pluginA)
+ envCtx.recordTransform('/src/empty.ts', {
+ name: 'plugin-b',
+ result: 'export {}',
+ start: 3,
+ end: 8,
+ }, '', pluginB)
+
+ expect(envCtx.getModulesList()).toMatchObject([
+ {
+ id: '/src/empty.ts',
+ plugins: [
+ { name: 'plugin-a', transform: 2 },
+ { name: 'plugin-b', transform: 5 },
+ ],
+ totalTime: 7,
+ sourceSize: 0,
+ distSize: 9,
+ },
+ ])
+ await expect(envCtx.getModuleTransformInfo('/src/empty.ts')).resolves.toMatchObject({
+ transforms: [
+ {
+ name: 'plugin-a',
+ result: '',
+ },
+ {
+ name: 'plugin-b',
+ result: 'export {}',
+ },
+ ],
+ })
+ })
+
+ it('keeps same-name plugin metrics separated by plugin id', () => {
+ const config = {
+ root: '/project',
+ plugins: [
+ { name: 'plugin-a' },
+ { name: 'plugin-a' },
+ ],
+ } as ResolvedConfig
+ const env = {
+ name: 'client',
+ mode: 'build',
+ getTopLevelConfig: () => config,
+ } as Environment
+ const ctx = new ViteInspectContext()
+ const envCtx = ctx.getViteContext(config).getEnvContext(env)
+
+ envCtx.recordTransform('/src/a.ts', {
+ name: 'plugin-a',
+ result: 'export const a = 1',
+ start: 0,
+ end: 3,
+ }, '', config.plugins[0])
+ envCtx.recordTransform('/src/b.ts', {
+ name: 'plugin-a',
+ result: 'export const b = 1',
+ start: 4,
+ end: 9,
+ }, '', config.plugins[1])
+
+ expect(envCtx.getPluginMetrics().filter(metric => metric.name === 'plugin-a')).toMatchObject([
+ {
+ plugin_id: 0,
+ transform: {
+ invokeCount: 1,
+ totalTime: 3,
+ },
+ },
+ {
+ plugin_id: 1,
+ transform: {
+ invokeCount: 1,
+ totalTime: 5,
+ },
+ },
+ ])
+ })
+
+ it('normalizes absolute node_modules ids relative to project root', async () => {
+ const { envCtx } = createFixture({
+ root: '/workspace/packages/vite',
+ })
+ const rawId = '/workspace/node_modules/.pnpm/unhead@1.0.0/node_modules/unhead/dist/index.mjs'
+
+ envCtx.recordTransform(`${rawId}?v=123456`, {
+ name: 'plugin-a',
+ result: 'export const head = {}',
+ start: 10,
+ end: 16,
+ order: 'pre',
+ }, 'const head = {}')
+
+ const modules = envCtx.getModulesList()
+
+ expect(modules[0]?.id).toBe('../../node_modules/.pnpm/unhead@1.0.0/node_modules/unhead/dist/index.mjs')
+ await expect(envCtx.getModuleTransformInfo(modules[0]!.id)).resolves.toMatchObject({
+ resolvedId: rawId,
+ transforms: [
+ { name: '__load__' },
+ { name: 'plugin-a' },
+ ],
+ })
+ })
+
+ it('exposes metadata for vite instances and environments', () => {
+ const { ctx, vite } = createFixture()
+
+ expect(ctx.getMetadata()).toMatchObject({
+ instances: [
+ {
+ root: '/project',
+ vite: vite.id,
+ environments: ['client'],
+ environmentPlugins: {
+ client: [0, 1, 2],
+ },
+ },
+ ],
+ })
+ })
+})
diff --git a/packages/vite/src/node/__tests__/inspect-plugin.test.ts b/packages/vite/src/node/__tests__/inspect-plugin.test.ts
new file mode 100644
index 00000000..a941f1df
--- /dev/null
+++ b/packages/vite/src/node/__tests__/inspect-plugin.test.ts
@@ -0,0 +1,42 @@
+import type { Plugin, ResolvedConfig } from 'vite'
+import { describe, expect, it } from 'vitest'
+import { createDevToolsContext } from '../../../../core/src/node/context'
+import { DevToolsViteInspect } from '../inspect/plugin'
+
+function createConfig(plugin: Plugin, command: 'serve' | 'build'): ResolvedConfig {
+ return {
+ root: process.cwd(),
+ command,
+ plugins: [plugin],
+ environments: {
+ client: {},
+ },
+ createResolver: () => async (id: string) => id,
+ } as unknown as ResolvedConfig
+}
+
+async function createContext(command: 'serve' | 'build') {
+ const plugin = DevToolsViteInspect()
+ const config = createConfig(plugin, command)
+
+ await (plugin.configResolved as (config: ResolvedConfig) => void | Promise)(config)
+
+ return createDevToolsContext(config)
+}
+
+describe('devToolsViteInspect', () => {
+ it('registers inspect RPC in dev mode', async () => {
+ const ctx = await createContext('serve')
+
+ expect(ctx.rpc.definitions.has('vite:inspect:get-metadata')).toBe(true)
+ expect(ctx.rpc.definitions.has('vite:inspect:get-modules-list')).toBe(true)
+ expect(ctx.rpc.definitions.has('vite:inspect:get-plugin-details')).toBe(true)
+ })
+
+ it('keeps inspect RPC disabled in build mode', async () => {
+ const ctx = await createContext('build')
+
+ expect(ctx.rpc.definitions.has('vite:meta-info')).toBe(true)
+ expect(ctx.rpc.definitions.has('vite:inspect:get-metadata')).toBe(false)
+ })
+})
diff --git a/packages/vite/src/node/diagnostics.ts b/packages/vite/src/node/diagnostics.ts
new file mode 100644
index 00000000..28c25ef2
--- /dev/null
+++ b/packages/vite/src/node/diagnostics.ts
@@ -0,0 +1,14 @@
+import { createConsoleReporter, defineDiagnostics } from 'nostics'
+
+export const diagnostics = /* #__PURE__ */ defineDiagnostics({
+ docsBase: 'https://devtools.vite.dev/errors',
+ reporters: [createConsoleReporter()],
+ codes: {
+ VDT0001: {
+ why: 'Vite inspect context is not available for this DevTools context.',
+ },
+ VDT0002: {
+ why: (p: { target: string, id: string }) => `Vite inspect target "${p.id}" was not found in ${p.target}.`,
+ },
+ },
+})
diff --git a/packages/vite/src/node/index.ts b/packages/vite/src/node/index.ts
new file mode 100644
index 00000000..5ae282ef
--- /dev/null
+++ b/packages/vite/src/node/index.ts
@@ -0,0 +1,3 @@
+export { DevToolsViteInspect } from './inspect/plugin'
+export * from './inspect/types'
+export { DevToolsViteUI, DevToolsViteUIPlugin } from './plugin'
diff --git a/packages/vite/src/node/inspect/context.ts b/packages/vite/src/node/inspect/context.ts
new file mode 100644
index 00000000..cbf90e2a
--- /dev/null
+++ b/packages/vite/src/node/inspect/context.ts
@@ -0,0 +1,567 @@
+import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit'
+import type {
+ Environment,
+ Plugin,
+ ResolvedConfig,
+} from 'vite'
+import type {
+ ViteInspectModuleInfo,
+ ViteInspectModulePluginMetric,
+ ViteInspectModuleTransformInfo,
+ ViteInspectPluginCallInfo,
+ ViteInspectPluginDetails,
+ ViteInspectPluginMetric,
+ ViteInspectQuery,
+ ViteInspectResolveIdInfo,
+ ViteInspectServerMetrics,
+ ViteInspectTransformInfo,
+} from './types'
+import { resolve } from 'node:path'
+import { createFilter } from 'vite'
+import { diagnostics } from '../diagnostics'
+import {
+ DUMMY_LOAD_PLUGIN_NAME,
+ getUtf8Size,
+ normalizeModuleId,
+ removeVersionQuery,
+ serializePlugin,
+} from './utils'
+
+let viteCount = 0
+
+interface ModuleInfoProvider {
+ getModuleInfo: (id: string) => {
+ importedIds?: readonly string[]
+ importers?: readonly string[]
+ } | null | undefined
+}
+
+const contextMap = new WeakMap()
+
+export function setViteInspectContext(devtoolsCtx: ViteDevToolsNodeContext, ctx: ViteInspectContext): void {
+ contextMap.set(devtoolsCtx, ctx)
+}
+
+export function getViteInspectContext(devtoolsCtx: ViteDevToolsNodeContext): ViteInspectContext {
+ const ctx = contextMap.get(devtoolsCtx)
+ if (!ctx)
+ throw diagnostics.VDT0001()
+ return ctx
+}
+
+export class ViteInspectContext {
+ readonly filter = createFilter()
+ readonly configToInstances = new Map()
+ readonly idToInstances = new Map()
+
+ getMetadata() {
+ return {
+ instances: Array.from(this.idToInstances.values(), vite => ({
+ root: vite.config.root,
+ vite: vite.id,
+ plugins: vite.config.plugins.map(plugin => serializePlugin(plugin)),
+ environments: [...new Set([...vite.environmentNames, ...vite.environments.keys()])],
+ environmentPlugins: Object.fromEntries(Array.from(vite.environments.entries(), ([name, env]) => {
+ const plugins = env.env.getTopLevelConfig().plugins
+ return [name, plugins.map(plugin => vite.config.plugins.indexOf(plugin))]
+ })),
+ })),
+ embedded: false,
+ }
+ }
+
+ getViteContext(configOrId: ResolvedConfig | string): ViteInspectViteContext {
+ if (typeof configOrId === 'string') {
+ const vite = this.idToInstances.get(configOrId)
+ if (!vite)
+ throw diagnostics.VDT0002({ target: 'Vite inspect instances', id: configOrId })
+ return vite
+ }
+
+ const existing = this.configToInstances.get(configOrId)
+ if (existing)
+ return existing
+
+ const id = `vite${++viteCount}`
+ const vite = new ViteInspectViteContext(id, this, configOrId)
+ this.idToInstances.set(id, vite)
+ this.configToInstances.set(configOrId, vite)
+ return vite
+ }
+
+ getEnvContext(env: Environment | undefined): ViteInspectEnvironmentContext | undefined {
+ if (!env)
+ return undefined
+ return this.getViteContext(env.getTopLevelConfig()).getEnvContext(env)
+ }
+
+ queryEnv(query: ViteInspectQuery): ViteInspectEnvironmentContext {
+ return this.getViteContext(query.vite).getEnvContext(query.env)
+ }
+}
+
+export class ViteInspectViteContext {
+ readonly environmentNames = new Set()
+ readonly environments = new Map()
+ readonly data: {
+ serverMetrics: ViteInspectServerMetrics
+ } = {
+ serverMetrics: {
+ middleware: {},
+ },
+ }
+
+ constructor(
+ readonly id: string,
+ readonly context: ViteInspectContext,
+ readonly config: ResolvedConfig,
+ ) {}
+
+ registerEnvironmentNames(names: Iterable): void {
+ for (const name of names)
+ this.environmentNames.add(name)
+ }
+
+ getEnvContext(env: Environment | string): ViteInspectEnvironmentContext {
+ if (typeof env === 'string') {
+ const envContext = this.environments.get(env)
+ if (!envContext)
+ throw diagnostics.VDT0002({ target: `Vite inspect environments for ${this.id}`, id: env })
+ return envContext
+ }
+
+ if (env.getTopLevelConfig() !== this.config) {
+ throw diagnostics.VDT0002({
+ target: 'the current Vite config environments',
+ id: env.name,
+ })
+ }
+
+ this.environmentNames.add(env.name)
+ let envContext = this.environments.get(env.name)
+ if (!envContext) {
+ envContext = new ViteInspectEnvironmentContext(this.context, this, env)
+ this.environments.set(env.name, envContext)
+ }
+ return envContext
+ }
+}
+
+export class ViteInspectEnvironmentContext {
+ readonly data: {
+ transform: Record
+ resolveId: Record
+ pluginCalls: Record
+ transformCounter: Record
+ } = {
+ transform: {},
+ resolveId: {},
+ pluginCalls: {},
+ transformCounter: {},
+ }
+
+ private pluginCallCount = 0
+
+ constructor(
+ readonly contextMain: ViteInspectContext,
+ readonly contextVite: ViteInspectViteContext,
+ readonly env: Environment,
+ ) {}
+
+ recordTransform(id: string, info: ViteInspectTransformInfo, preTransformCode: string, plugin?: Plugin): void {
+ id = this.normalizeId(id)
+ const pluginId = this.getPluginId(plugin, info.name)
+
+ let transforms = this.data.transform[id]
+ if (!transforms || !transforms.some(transform => transform.result != null)) {
+ transforms = [{
+ name: DUMMY_LOAD_PLUGIN_NAME,
+ result: preTransformCode,
+ start: info.start,
+ end: info.start,
+ sourcemaps: info.sourcemaps,
+ }]
+ this.data.transform[id] = transforms
+ this.data.transformCounter[id] = (this.data.transformCounter[id] || 0) + 1
+ }
+
+ transforms.push({
+ ...info,
+ plugin_id: pluginId,
+ })
+
+ this.recordPluginCall({
+ type: 'transform',
+ pluginId,
+ pluginName: info.name,
+ module: this.getPublicModuleId(id),
+ start: info.start,
+ end: info.end,
+ unchanged: info.result == null || info.result === preTransformCode,
+ })
+ }
+
+ recordLoad(id: string, info: ViteInspectTransformInfo, plugin?: Plugin): void {
+ id = this.normalizeId(id)
+ const pluginId = this.getPluginId(plugin, info.name)
+ this.data.transform[id] = [{
+ ...info,
+ plugin_id: pluginId,
+ }]
+ this.data.transformCounter[id] = (this.data.transformCounter[id] || 0) + 1
+
+ this.recordPluginCall({
+ type: 'load',
+ pluginId,
+ pluginName: info.name,
+ module: this.getPublicModuleId(id),
+ start: info.start,
+ end: info.end,
+ unchanged: info.result == null,
+ })
+ }
+
+ recordLoadCall(id: string, info: ViteInspectTransformInfo, plugin?: Plugin): void {
+ id = this.normalizeId(id)
+ this.recordPluginCall({
+ type: 'load',
+ pluginId: this.getPluginId(plugin, info.name),
+ pluginName: info.name,
+ module: this.getPublicModuleId(id),
+ start: info.start,
+ end: info.end,
+ unchanged: true,
+ })
+ }
+
+ recordResolveId(id: string, info: ViteInspectResolveIdInfo, plugin?: Plugin): void {
+ id = this.normalizeId(id)
+ const pluginId = this.getPluginId(plugin, info.name)
+ const normalizedResult = this.normalizeId(info.result)
+ const resolveIds = this.data.resolveId[id] ||= []
+ resolveIds.push({
+ ...info,
+ plugin_id: pluginId,
+ result: normalizedResult,
+ })
+
+ this.recordPluginCall({
+ type: 'resolve',
+ pluginId,
+ pluginName: info.name,
+ module: this.getPublicModuleId(normalizedResult),
+ start: info.start,
+ end: info.end,
+ })
+ }
+
+ recordResolveIdCall(id: string, info: Omit & { result?: string | null }, plugin?: Plugin): void {
+ id = this.normalizeId(id)
+ const result = info.result ? this.normalizeId(info.result) : id
+ this.recordPluginCall({
+ type: 'resolve',
+ pluginId: this.getPluginId(plugin, info.name),
+ pluginName: info.name,
+ module: this.getPublicModuleId(result),
+ start: info.start,
+ end: info.end,
+ })
+ }
+
+ invalidate(id: string): void {
+ const normalizedId = this.normalizeId(id)
+ const invalidatedIds = new Set([normalizedId])
+ const invalidatedPublicIds = new Set([this.getPublicModuleId(normalizedId)])
+
+ for (const rawId of this.getRawModuleIds()) {
+ const publicId = this.getPublicModuleId(rawId)
+ if (invalidatedPublicIds.has(publicId)) {
+ invalidatedIds.add(rawId)
+ invalidatedPublicIds.add(publicId)
+ }
+ }
+
+ for (const [sourceId, resolveIds] of Object.entries(this.data.resolveId)) {
+ if (this.matchesInvalidatedModule(sourceId, invalidatedIds, invalidatedPublicIds)) {
+ invalidatedIds.add(this.normalizeId(sourceId))
+ invalidatedPublicIds.add(this.getPublicModuleId(sourceId))
+ }
+
+ for (const resolveId of resolveIds) {
+ if (this.matchesInvalidatedModule(resolveId.result, invalidatedIds, invalidatedPublicIds)) {
+ const normalizedResult = this.normalizeId(resolveId.result)
+ invalidatedIds.add(normalizedResult)
+ invalidatedPublicIds.add(this.getPublicModuleId(normalizedResult))
+ }
+ }
+ }
+
+ for (const rawId of invalidatedIds) {
+ delete this.data.transform[rawId]
+ delete this.data.transformCounter[rawId]
+ }
+
+ for (const [sourceId, resolveIds] of Object.entries(this.data.resolveId)) {
+ const remaining = resolveIds.filter((resolveId) => {
+ return !this.matchesInvalidatedModule(sourceId, invalidatedIds, invalidatedPublicIds)
+ && !this.matchesInvalidatedModule(resolveId.result, invalidatedIds, invalidatedPublicIds)
+ })
+
+ if (remaining.length)
+ this.data.resolveId[sourceId] = remaining
+ else
+ delete this.data.resolveId[sourceId]
+ }
+
+ for (const [pluginId, calls] of Object.entries(this.data.pluginCalls)) {
+ const remaining = calls.filter(call => !invalidatedPublicIds.has(call.module))
+ if (remaining.length)
+ this.data.pluginCalls[Number(pluginId)] = remaining
+ else
+ delete this.data.pluginCalls[Number(pluginId)]
+ }
+ }
+
+ private matchesInvalidatedModule(id: string, invalidatedIds: Set, invalidatedPublicIds: Set): boolean {
+ const normalizedId = this.normalizeId(id)
+ return invalidatedIds.has(normalizedId)
+ || invalidatedPublicIds.has(this.getPublicModuleId(normalizedId))
+ }
+
+ normalizeId(id: string): string {
+ return removeVersionQuery(id)
+ }
+
+ getModuleIdBaseRoot(): string {
+ return this.env.getTopLevelConfig().root
+ }
+
+ getPublicModuleId(id: string): string {
+ return normalizeModuleId(this.normalizeId(id), this.getModuleIdBaseRoot())
+ }
+
+ getRawModuleId(id: string): string {
+ const normalizedId = this.getPublicModuleId(id)
+ for (const rawId of this.getRawModuleIds()) {
+ if (this.getPublicModuleId(rawId) === normalizedId)
+ return rawId
+ }
+ if (normalizedId.startsWith('./') || normalizedId.startsWith('../'))
+ return resolve(this.getModuleIdBaseRoot(), normalizedId).replace(/\\/g, '/')
+ return normalizedId
+ }
+
+ getRawModuleIds(): string[] {
+ const ids = new Set(Object.keys(this.data.transform))
+ for (const resolveIds of Object.values(this.data.resolveId)) {
+ for (const id of resolveIds)
+ ids.add(this.normalizeId(id.result))
+ }
+ return Array.from(ids)
+ }
+
+ getPluginId(plugin: Plugin | undefined, name: string): number {
+ if (plugin) {
+ const index = this.env.getTopLevelConfig().plugins.indexOf(plugin)
+ if (index >= 0)
+ return index
+ }
+
+ return this.env.getTopLevelConfig().plugins.findIndex(item => item.name === name)
+ }
+
+ recordPluginCall(options: {
+ type: ViteInspectPluginCallInfo['type']
+ pluginId: number
+ pluginName: string
+ module: string
+ start: number
+ end: number
+ unchanged?: boolean
+ }): void {
+ if (options.pluginId < 0)
+ return
+
+ const calls = this.data.pluginCalls[options.pluginId] ||= []
+ calls.push({
+ type: options.type,
+ id: `${options.type}:${options.pluginId}:${this.pluginCallCount++}`,
+ duration: Math.max(0, options.end - options.start),
+ plugin_id: options.pluginId,
+ plugin_name: options.pluginName,
+ module: options.module,
+ timestamp_start: options.start,
+ timestamp_end: options.end,
+ unchanged: options.unchanged,
+ })
+ }
+
+ getModulesList(pluginCtx?: ModuleInfoProvider): ViteInspectModuleInfo[] {
+ const moduleGraph = this.env.mode === 'dev' ? this.env.moduleGraph : undefined
+ const getDeps = moduleGraph
+ ? (id: string) => Array.from(moduleGraph.getModuleById(id)?.importedModules || []).map(module => module.id || '').filter(Boolean)
+ : pluginCtx
+ ? (id: string) => Array.from(pluginCtx.getModuleInfo(id)?.importedIds || [])
+ : () => []
+ const getImporters = moduleGraph
+ ? (id: string) => Array.from(moduleGraph.getModuleById(id)?.importers || []).map(module => module.id || '').filter(Boolean)
+ : pluginCtx
+ ? (id: string) => Array.from(pluginCtx.getModuleInfo(id)?.importers || [])
+ : () => []
+
+ const transformedIdMap = Object.values(this.data.resolveId).reduce>((map, ids) => {
+ ids.forEach((id) => {
+ const result = this.normalizeId(id.result)
+ const resolvedIds = map[result] ||= []
+ resolvedIds.push(id)
+ })
+ return map
+ }, {})
+
+ const ids = new Set(Object.keys(this.data.transform).concat(Object.keys(transformedIdMap)))
+
+ return Array.from(ids).sort().map((id) => {
+ let totalTime = 0
+ const transformPlugins: ViteInspectModulePluginMetric[] = (this.data.transform[id] || [])
+ .filter(transform => transform.result != null)
+ .map((transform) => {
+ const delta = transform.end - transform.start
+ totalTime += delta
+ return {
+ name: transform.name,
+ transform: delta,
+ }
+ })
+ const resolveIdPlugins: ViteInspectModulePluginMetric[] = (transformedIdMap[id] || []).map(resolveId => ({
+ name: resolveId.name,
+ resolveId: resolveId.end - resolveId.start,
+ }))
+ const plugins = transformPlugins.concat(resolveIdPlugins)
+
+ return {
+ id: this.getPublicModuleId(id),
+ deps: getDeps(id).map(dep => this.getPublicModuleId(dep)),
+ importers: getImporters(id).map(importer => this.getPublicModuleId(importer)),
+ plugins,
+ virtual: isVirtual(plugins[0]?.name || '', this.data.transform[id]?.[0]?.name || ''),
+ totalTime,
+ invokeCount: this.data.transformCounter[id] || 0,
+ sourceSize: getUtf8Size(this.data.transform[id]?.[0]?.result),
+ distSize: getUtf8Size(this.data.transform[id]?.at(-1)?.result),
+ }
+ })
+ }
+
+ resolveId(id = ''): string {
+ id = this.getRawModuleId(id)
+ if (id.startsWith('./'))
+ id = resolve(this.getModuleIdBaseRoot(), id).replace(/\\/g, '/')
+ return this.resolveIdRecursive(id)
+ }
+
+ resolveIdRecursive(id: string): string {
+ const resolved = this.data.resolveId[id]?.[0]?.result
+ return resolved ? this.resolveIdRecursive(this.normalizeId(resolved)) : id
+ }
+
+ getPluginMetrics(): ViteInspectPluginMetric[] {
+ const map: Record = {}
+ const defaultMetricInfo = () => ({
+ transform: {
+ invokeCount: 0,
+ totalTime: 0,
+ },
+ resolveId: {
+ invokeCount: 0,
+ totalTime: 0,
+ },
+ })
+
+ this.env.getTopLevelConfig().plugins.forEach((plugin: Plugin, pluginId) => {
+ map[pluginId] = {
+ ...defaultMetricInfo(),
+ name: plugin.name,
+ plugin_id: pluginId,
+ enforce: plugin.enforce,
+ }
+ })
+
+ Object.values(this.data.transform).forEach((transformInfos) => {
+ transformInfos.forEach(({ name, plugin_id: pluginId, start, end }) => {
+ if (name === DUMMY_LOAD_PLUGIN_NAME)
+ return
+ const key = pluginId == null || pluginId < 0 ? name : String(pluginId)
+ map[key] ||= {
+ ...defaultMetricInfo(),
+ name,
+ plugin_id: pluginId,
+ }
+ map[key].transform.totalTime += end - start
+ map[key].transform.invokeCount += 1
+ })
+ })
+
+ Object.values(this.data.resolveId).forEach((resolveIdInfos) => {
+ resolveIdInfos.forEach(({ name, plugin_id: pluginId, start, end }) => {
+ const key = pluginId == null || pluginId < 0 ? name : String(pluginId)
+ map[key] ||= {
+ ...defaultMetricInfo(),
+ name,
+ plugin_id: pluginId,
+ }
+ map[key].resolveId.totalTime += end - start
+ map[key].resolveId.invokeCount += 1
+ })
+ })
+
+ return Object.values(map).filter(Boolean).sort((a, b) => a.name.localeCompare(b.name))
+ }
+
+ async getModuleTransformInfo(id: string): Promise {
+ const resolvedId = this.resolveId(id)
+ return {
+ resolvedId,
+ transforms: this.data.transform[resolvedId] || [],
+ }
+ }
+
+ getPluginDetails(pluginId: number): ViteInspectPluginDetails {
+ const plugin = this.env.getTopLevelConfig().plugins[pluginId]
+ const calls = this.data.pluginCalls[pluginId] ?? []
+ return {
+ plugin_name: plugin?.name ?? calls[0]?.plugin_name ?? '',
+ plugin_id: pluginId,
+ calls,
+ resolveIdMetrics: calls.filter(call => call.type === 'resolve'),
+ loadMetrics: calls.filter(call => call.type === 'load'),
+ transformMetrics: calls.filter(call => call.type === 'transform'),
+ }
+ }
+
+ async clearModuleTransform(id: string): Promise {
+ this.clearId(id)
+ try {
+ if (this.env.mode === 'dev')
+ await this.env.transformRequest(id)
+ }
+ catch {}
+ }
+
+ clearId(rawId: string): void {
+ const id = this.resolveId(rawId)
+ if (!id)
+ return
+
+ const moduleGraph = this.env.mode === 'dev' ? this.env.moduleGraph : undefined
+ const mod = moduleGraph?.getModuleById(id)
+ if (mod)
+ moduleGraph?.invalidateModule(mod)
+ this.invalidate(id)
+ }
+}
+
+function isVirtual(pluginName: string, transformName: string): boolean {
+ return pluginName !== DUMMY_LOAD_PLUGIN_NAME
+ && transformName !== 'vite:load-fallback'
+ && transformName !== 'vite:build-load-fallback'
+}
diff --git a/packages/vite/src/node/inspect/hijack.ts b/packages/vite/src/node/inspect/hijack.ts
new file mode 100644
index 00000000..cd349528
--- /dev/null
+++ b/packages/vite/src/node/inspect/hijack.ts
@@ -0,0 +1,191 @@
+import type { Plugin } from 'vite'
+import type { ViteInspectContext } from './context'
+import { parseError, stringifyError } from './utils'
+
+type PluginWithOrder = Plugin & {
+ order?: string
+}
+
+type HookWrapper = (
+ fn: (...args: any[]) => any,
+ context: any,
+ args: any[],
+ order?: string,
+) => any
+
+function hijackHook(plugin: PluginWithOrder, name: 'transform' | 'load' | 'resolveId', wrapper: HookWrapper): void {
+ const hook = plugin[name] as any
+ if (!hook)
+ return
+
+ let order = `${plugin.order || plugin.enforce || 'normal'}`
+
+ if (typeof hook === 'object' && 'handler' in hook) {
+ const oldFn = hook.handler
+ order += `-${hook.order || hook.enforce || 'normal'}`
+ hook.handler = function (...args: any[]) {
+ return wrapper(oldFn, this, args, order)
+ }
+ }
+ else if (typeof hook === 'object' && 'transform' in hook) {
+ const oldFn = hook.transform
+ order += `-${hook.order || hook.enforce || 'normal'}`
+ hook.transform = function (...args: any[]) {
+ return wrapper(oldFn, this, args, order)
+ }
+ }
+ else {
+ const oldFn = hook
+ const mutablePlugin = plugin as unknown as Record
+ mutablePlugin[name] = function (...args: any[]) {
+ return wrapper(oldFn, this, args, order)
+ }
+ }
+}
+
+const hijackedPlugins = new WeakSet()
+
+export function hijackPlugin(plugin: Plugin, ctx: ViteInspectContext): void {
+ if (hijackedPlugins.has(plugin))
+ return
+ hijackedPlugins.add(plugin)
+
+ hijackHook(plugin, 'transform', async (fn, context, args, order) => {
+ const code = args[0] as string
+ const id = args[1] as string
+ let resultValue: any
+ let error: unknown
+ const start = Date.now()
+
+ try {
+ resultValue = await fn.apply(context, args)
+ }
+ catch (err) {
+ error = err
+ }
+
+ const end = Date.now()
+ const result = error
+ ? '[Error]'
+ : typeof resultValue === 'string'
+ ? resultValue
+ : resultValue?.code?.toString()
+
+ if (ctx.filter(id)) {
+ const sourcemaps = typeof resultValue === 'string' ? null : resultValue?.map
+ ctx.getEnvContext(context?.environment)?.recordTransform(id, {
+ name: plugin.name,
+ result,
+ start,
+ end,
+ order,
+ sourcemaps,
+ error: error ? parseError(error) : undefined,
+ }, code, plugin)
+ }
+
+ if (error)
+ throw error
+ return resultValue
+ })
+
+ hijackHook(plugin, 'load', async (fn, context, args) => {
+ const id = args[0] as string
+ let resultValue: any
+ let error: unknown
+ const start = Date.now()
+
+ try {
+ resultValue = await fn.apply(context, args)
+ }
+ catch (err) {
+ error = err
+ }
+
+ const end = Date.now()
+ const result = error
+ ? '[Error]'
+ : typeof resultValue === 'string'
+ ? resultValue
+ : resultValue?.code
+ const sourcemaps = typeof resultValue === 'string' ? null : resultValue?.map
+
+ const info = {
+ name: plugin.name,
+ result,
+ start,
+ end,
+ sourcemaps,
+ error: error ? parseError(error) : undefined,
+ }
+
+ if (result != null) {
+ ctx.getEnvContext(context?.environment)?.recordLoad(id, {
+ ...info,
+ result,
+ }, plugin)
+ }
+ else {
+ ctx.getEnvContext(context?.environment)?.recordLoadCall(id, {
+ name: plugin.name,
+ start,
+ end,
+ error: error ? parseError(error) : undefined,
+ }, plugin)
+ }
+
+ if (error)
+ throw error
+ return resultValue
+ })
+
+ hijackHook(plugin, 'resolveId', async (fn, context, args) => {
+ const id = args[0] as string
+ let resultValue: any
+ let error: unknown
+ const start = Date.now()
+
+ try {
+ resultValue = await fn.apply(context, args)
+ }
+ catch (err) {
+ error = err
+ }
+
+ const end = Date.now()
+ if (!ctx.filter(id)) {
+ if (error)
+ throw error
+ return resultValue
+ }
+
+ const result = error
+ ? stringifyError(error)
+ : typeof resultValue === 'object'
+ ? resultValue?.id
+ : resultValue
+
+ if (result && result !== id) {
+ ctx.getEnvContext(context?.environment)?.recordResolveId(id, {
+ name: plugin.name,
+ result,
+ start,
+ end,
+ error,
+ }, plugin)
+ }
+ else {
+ ctx.getEnvContext(context?.environment)?.recordResolveIdCall(id, {
+ name: plugin.name,
+ result,
+ start,
+ end,
+ error,
+ }, plugin)
+ }
+
+ if (error)
+ throw error
+ return resultValue
+ })
+}
diff --git a/packages/vite/src/node/inspect/plugin.ts b/packages/vite/src/node/inspect/plugin.ts
new file mode 100644
index 00000000..75d9c339
--- /dev/null
+++ b/packages/vite/src/node/inspect/plugin.ts
@@ -0,0 +1,140 @@
+import type { PluginWithDevTools } from '@vitejs/devtools-kit'
+import type { SharedState } from '@vitejs/devtools-kit/utils/shared-state'
+import type { ResolvedConfig, ResolveFn } from 'vite'
+import type { ViteInspectModuleUpdatedState } from '../rpc'
+import { debounce } from 'perfect-debounce'
+import { diagnostics } from '../diagnostics'
+import { inspectRpcFunctions, VITE_INSPECT_MODULE_UPDATED_STATE_KEY, viteRpcFunctions } from '../rpc'
+import { setViteInspectContext, ViteInspectContext } from './context'
+import { hijackPlugin } from './hijack'
+import { setupEnvironmentInvalidation, setupMiddlewarePerformance } from './server'
+
+export function DevToolsViteInspect(): PluginWithDevTools {
+ let resolvedConfig: ResolvedConfig | undefined
+ let inspectContext: ViteInspectContext | undefined
+ let inspectModuleUpdatedState: SharedState | undefined
+
+ function notifyInspectModuleUpdated(ids: string[] | null = null) {
+ inspectModuleUpdatedState?.mutate((state) => {
+ state.version += 1
+ state.ids = ids
+ state.updatedAt = Date.now()
+ })
+ }
+
+ function getInspectContext(config: ResolvedConfig): ViteInspectContext | undefined {
+ if (config.command !== 'serve')
+ return undefined
+ inspectContext ||= new ViteInspectContext()
+ return inspectContext
+ }
+
+ return {
+ name: 'vite:devtools:vite-inspect',
+ enforce: 'pre',
+
+ devtools: {
+ async setup(ctx) {
+ ctx.diagnostics.register(diagnostics)
+
+ for (const fn of viteRpcFunctions)
+ ctx.rpc.register(fn as any)
+
+ if (inspectContext) {
+ setViteInspectContext(ctx, inspectContext)
+ for (const fn of inspectRpcFunctions)
+ ctx.rpc.register(fn as any)
+
+ inspectModuleUpdatedState = await ctx.rpc.sharedState.get(VITE_INSPECT_MODULE_UPDATED_STATE_KEY, {
+ initialValue: {
+ version: 0,
+ ids: null,
+ updatedAt: 0,
+ },
+ })
+
+ if (ctx.viteServer) {
+ const debouncedNotify = debounce(() => {
+ notifyInspectModuleUpdated()
+ }, 100)
+
+ ctx.viteServer.middlewares.use((_req: unknown, _res: unknown, next: () => void) => {
+ debouncedNotify()
+ next()
+ })
+ }
+ }
+ },
+ },
+
+ configResolved(config) {
+ resolvedConfig = config
+ const ctx = getInspectContext(config)
+ if (!ctx)
+ return
+
+ const vite = ctx.getViteContext(config)
+ vite.registerEnvironmentNames(Object.keys(config.environments))
+
+ config.plugins.forEach(plugin => hijackPlugin(plugin, ctx))
+
+ const mutableConfig = config as ResolvedConfig & {
+ createResolver: ResolvedConfig['createResolver']
+ }
+ const createResolver = mutableConfig.createResolver
+ mutableConfig.createResolver = function (this: ResolvedConfig, ...args: Parameters) {
+ const resolver = createResolver.apply(this, args)
+ return async (...resolverArgs: Parameters) => {
+ const [id, , aliasOnly, ssr] = resolverArgs
+ const start = Date.now()
+ const result = await resolver(...resolverArgs)
+ const end = Date.now()
+
+ if (result && result !== id) {
+ const pluginName = aliasOnly ? 'alias' : 'vite:resolve (+alias)'
+ const envName = ssr ? 'ssr' : 'client'
+ vite.environments.get(envName)?.recordResolveId(id, {
+ name: pluginName,
+ result,
+ start,
+ end,
+ })
+ }
+
+ return result
+ }
+ }
+ },
+
+ configureServer(server) {
+ const ctx = resolvedConfig ? getInspectContext(resolvedConfig) : inspectContext
+ if (!ctx)
+ return
+
+ const vite = ctx.getViteContext(server.config)
+ Object.values(server.environments).forEach(env => vite.getEnvContext(env))
+ setupEnvironmentInvalidation(server, vite)
+
+ return () => {
+ setupMiddlewarePerformance(vite, server.middlewares.stack)
+ }
+ },
+
+ load: {
+ order: 'pre',
+ handler(id) {
+ inspectContext?.getEnvContext(this.environment)?.invalidate(id)
+ return null
+ },
+ },
+
+ hotUpdate({ modules }) {
+ if (!inspectContext)
+ return
+
+ notifyInspectModuleUpdated(modules.map(module => module.id).filter(id => id != null))
+ },
+
+ sharedDuringBuild: true,
+ }
+}
diff --git a/packages/vite/src/node/inspect/server.ts b/packages/vite/src/node/inspect/server.ts
new file mode 100644
index 00000000..cad97c70
--- /dev/null
+++ b/packages/vite/src/node/inspect/server.ts
@@ -0,0 +1,67 @@
+import type { Connect, ViteDevServer } from 'vite'
+import type { ViteInspectViteContext } from './context'
+
+const timestampRE = /\bt=\d{13}&?\b/
+const trailingSeparatorRE = /[?&]$/
+
+interface MiddlewareLayer {
+ handle?: unknown
+}
+
+export function setupEnvironmentInvalidation(server: ViteDevServer, vite: ViteInspectViteContext): void {
+ Object.values(server.environments).forEach((env) => {
+ const envContext = vite.getEnvContext(env)
+ const invalidateModule = env.moduleGraph.invalidateModule
+
+ env.moduleGraph.invalidateModule = function (...args) {
+ const mod = args[0]
+ if (mod?.id)
+ envContext.invalidate(mod.id)
+ return invalidateModule.apply(this, args)
+ }
+ })
+}
+
+export function setupMiddlewarePerformance(vite: ViteInspectViteContext, middlewares: MiddlewareLayer[]): void {
+ let firstMiddlewareIndex = -1
+
+ middlewares.forEach((middleware, index) => {
+ const originalHandle = middleware.handle
+ if (typeof originalHandle !== 'function' || !originalHandle.name)
+ return
+
+ middleware.handle = function (this: unknown, ...middlewareArgs: Parameters) {
+ let req: Parameters[0]
+ if (middlewareArgs.length === 4)
+ req = middlewareArgs[1] as any
+ else
+ req = middlewareArgs[0]
+
+ const start = Date.now()
+ const url = req.url?.replace(timestampRE, '').replace(trailingSeparatorRE, '') || '/'
+ const metrics = vite.data.serverMetrics.middleware[url] ||= []
+ if (firstMiddlewareIndex < 0)
+ firstMiddlewareIndex = index
+ if (index === firstMiddlewareIndex)
+ metrics.length = 0
+
+ const result = originalHandle.apply(this as any, middlewareArgs as any)
+ Promise.resolve(result).then(() => {
+ const total = Date.now() - start
+ metrics.push({
+ self: metrics.length ? Math.max(total - (metrics.at(-1)?.total || 0), 0) : total,
+ total,
+ name: originalHandle.name,
+ })
+ })
+
+ return result
+ } as Connect.HandleFunction
+
+ Object.defineProperty(middleware.handle, 'name', {
+ value: originalHandle.name,
+ configurable: true,
+ enumerable: true,
+ })
+ })
+}
diff --git a/packages/vite/src/node/inspect/types.ts b/packages/vite/src/node/inspect/types.ts
new file mode 100644
index 00000000..8f4b7fd1
--- /dev/null
+++ b/packages/vite/src/node/inspect/types.ts
@@ -0,0 +1,100 @@
+export interface ViteInspectQuery {
+ vite: string
+ env: string
+}
+
+export interface ViteInspectErrorInfo {
+ message: string
+ stack: string[]
+ raw?: unknown
+}
+
+export interface ViteInspectTransformInfo {
+ name: string
+ plugin_id?: number
+ result?: string | null
+ start: number
+ end: number
+ order?: string
+ sourcemaps?: unknown
+ error?: ViteInspectErrorInfo
+}
+
+export interface ViteInspectResolveIdInfo {
+ name: string
+ plugin_id?: number
+ result: string
+ start: number
+ end: number
+ error?: unknown
+}
+
+export interface ViteInspectModulePluginMetric {
+ name: string
+ transform?: number
+ resolveId?: number
+}
+
+export interface ViteInspectModuleInfo {
+ id: string
+ deps: string[]
+ importers: string[]
+ plugins: ViteInspectModulePluginMetric[]
+ virtual: boolean
+ totalTime: number
+ invokeCount: number
+ sourceSize: number
+ distSize: number
+}
+
+export interface ViteInspectPluginMetric {
+ name: string
+ plugin_id?: number
+ enforce?: 'pre' | 'post'
+ transform: {
+ invokeCount: number
+ totalTime: number
+ }
+ resolveId: {
+ invokeCount: number
+ totalTime: number
+ }
+}
+
+export interface ViteInspectModuleTransformInfo {
+ resolvedId: string
+ transforms: ViteInspectTransformInfo[]
+}
+
+export type ViteInspectPluginCallType = 'resolve' | 'load' | 'transform'
+
+export interface ViteInspectPluginCallInfo {
+ type: ViteInspectPluginCallType
+ id: string
+ duration: number
+ plugin_id: number
+ plugin_name: string
+ module: string
+ timestamp_start: number
+ timestamp_end: number
+ unchanged?: boolean
+}
+
+export interface ViteInspectPluginDetails {
+ plugin_name: string
+ plugin_id: number
+ calls: ViteInspectPluginCallInfo[]
+ resolveIdMetrics: ViteInspectPluginCallInfo[]
+ loadMetrics: ViteInspectPluginCallInfo[]
+ transformMetrics: ViteInspectPluginCallInfo[]
+}
+
+export interface ViteInspectMiddlewareMetric {
+ self: number
+ total: number
+ name: string
+}
+
+export interface ViteInspectServerMetrics {
+ middleware: Record
+}
diff --git a/packages/vite/src/node/inspect/utils.ts b/packages/vite/src/node/inspect/utils.ts
new file mode 100644
index 00000000..623906fa
--- /dev/null
+++ b/packages/vite/src/node/inspect/utils.ts
@@ -0,0 +1,105 @@
+import type { Plugin } from 'vite'
+import type { ViteInspectContext } from './context'
+import type { ViteInspectQuery } from './types'
+import { Buffer } from 'node:buffer'
+import { relative } from 'pathe'
+
+export const DUMMY_LOAD_PLUGIN_NAME = '__load__'
+
+export function serializePlugin(plugin: Plugin): unknown {
+ return JSON.parse(JSON.stringify(plugin, (key, value) => {
+ if (typeof value === 'function') {
+ let name = value.name
+ if (name === 'anonymous')
+ name = ''
+ if (name === key)
+ name = ''
+ return name ? `[Function ${name}]` : '[Function]'
+ }
+ if (key === 'api' && value)
+ return '[Object API]'
+ return value
+ }))
+}
+
+export function removeVersionQuery(url: string): string {
+ if (!url.includes('v='))
+ return url
+ return url
+ .replace(/&v=\w+/, '')
+ .replace(/\?v=\w+/, '?')
+ .replace(/\?$/, '')
+}
+
+export function normalizeModuleId(id: string, root: string): string {
+ const normalizedId = id
+ .replace(/%2F/gi, '/')
+ .replace(/\\/g, '/')
+ const normalizedRoot = root
+ .replace(/%2F/gi, '/')
+ .replace(/\\/g, '/')
+ const isAbsolutePath = normalizedId.startsWith('/') || /^[a-z]:\//i.test(normalizedId)
+ if (!isAbsolutePath || !normalizedId.includes('/node_modules/'))
+ return normalizedId
+
+ let result = relative(normalizedRoot, normalizedId)
+ if (!result.startsWith('./') && !result.startsWith('../'))
+ result = `./${result}`
+ return result
+}
+
+export function getUtf8Size(value?: string | null): number {
+ return value ? Buffer.byteLength(value, 'utf8') : 0
+}
+
+export function parseError(error: unknown) {
+ if (error instanceof Error) {
+ return {
+ message: error.message || String(error),
+ stack: typeof error.stack === 'string'
+ ? error.stack.split('\n').map(line => line.trim()).filter(Boolean)
+ : [],
+ raw: error,
+ }
+ }
+
+ return {
+ message: String(error),
+ stack: [],
+ raw: error,
+ }
+}
+
+export function stringifyError(error: unknown): string {
+ if (error instanceof Error)
+ return error.stack || error.message
+ return String(error)
+}
+
+export function getAllQueryEnvs(ctx: ViteInspectContext): ViteInspectQuery[] {
+ const result: ViteInspectQuery[] = []
+ for (const vite of ctx.idToInstances.values()) {
+ for (const envName of vite.environments.keys()) {
+ result.push({
+ vite: vite.id,
+ env: envName,
+ })
+ }
+ }
+ return result
+}
+
+export function getAllModuleIds(ctx: ViteInspectContext): [ViteInspectQuery, string][] {
+ const result: [ViteInspectQuery, string][] = []
+ for (const vite of ctx.idToInstances.values()) {
+ for (const [envName, env] of vite.environments) {
+ const query = {
+ vite: vite.id,
+ env: envName,
+ }
+ for (const id of Object.keys(env.data.transform))
+ result.push([query, id])
+ }
+ }
+ return result
+}
diff --git a/packages/vite/src/node/plugin.ts b/packages/vite/src/node/plugin.ts
new file mode 100644
index 00000000..117cf577
--- /dev/null
+++ b/packages/vite/src/node/plugin.ts
@@ -0,0 +1,39 @@
+import type { PluginWithDevTools } from '@vitejs/devtools-kit'
+import type { PluginOption } from 'vite'
+import { DEVTOOLS_VITEPLUS_GROUP_ID } from '@vitejs/devtools-kit/constants'
+import { clientPublicDir } from '../dirs'
+import { DevToolsViteInspect } from './inspect/plugin'
+
+const VITE_DEVTOOLS_BASE = '/__devtools-vite/'
+
+export function DevToolsViteUI(): PluginOption {
+ return [
+ DevToolsViteInspect(),
+ DevToolsViteUIPlugin(),
+ ]
+}
+
+export function DevToolsViteUIPlugin(): PluginWithDevTools {
+ return {
+ name: 'vite:devtools:vite-ui',
+ enforce: 'pre',
+
+ devtools: {
+ setup(ctx) {
+ ctx.views.hostStatic(
+ VITE_DEVTOOLS_BASE,
+ clientPublicDir,
+ )
+
+ ctx.docks.register({
+ id: 'vite',
+ title: 'Vite',
+ icon: 'material-icon-theme:vite',
+ groupId: DEVTOOLS_VITEPLUS_GROUP_ID,
+ type: 'iframe',
+ url: VITE_DEVTOOLS_BASE,
+ })
+ },
+ },
+ }
+}
diff --git a/packages/vite/src/node/rpc/functions/vite-clear-module-transform.ts b/packages/vite/src/node/rpc/functions/vite-clear-module-transform.ts
new file mode 100644
index 00000000..1082ff1c
--- /dev/null
+++ b/packages/vite/src/node/rpc/functions/vite-clear-module-transform.ts
@@ -0,0 +1,17 @@
+import type { ViteInspectQuery } from '../../inspect/types'
+import { defineRpcFunction } from '@vitejs/devtools-kit'
+import { getViteInspectContext } from '../../inspect/context'
+
+export const viteClearModuleTransform = defineRpcFunction({
+ name: 'vite:inspect:clear-module-transform',
+ type: 'action',
+ jsonSerializable: true,
+ setup: (devtoolsCtx) => {
+ const ctx = getViteInspectContext(devtoolsCtx)
+ return {
+ handler: async (query: ViteInspectQuery, id: string) => {
+ await ctx.queryEnv(query).clearModuleTransform(id)
+ },
+ }
+ },
+})
diff --git a/packages/vite/src/node/rpc/functions/vite-get-metadata.ts b/packages/vite/src/node/rpc/functions/vite-get-metadata.ts
new file mode 100644
index 00000000..91fd0e91
--- /dev/null
+++ b/packages/vite/src/node/rpc/functions/vite-get-metadata.ts
@@ -0,0 +1,17 @@
+import { defineRpcFunction } from '@vitejs/devtools-kit'
+import { getViteInspectContext } from '../../inspect/context'
+
+export const viteGetMetadata = defineRpcFunction({
+ name: 'vite:inspect:get-metadata',
+ type: 'query',
+ jsonSerializable: true,
+ dump: async () => ({
+ inputs: [[] satisfies []],
+ }),
+ setup: (devtoolsCtx) => {
+ const ctx = getViteInspectContext(devtoolsCtx)
+ return {
+ handler: async () => ctx.getMetadata(),
+ }
+ },
+})
diff --git a/packages/vite/src/node/rpc/functions/vite-get-module-transform-info.ts b/packages/vite/src/node/rpc/functions/vite-get-module-transform-info.ts
new file mode 100644
index 00000000..dbf9a7ed
--- /dev/null
+++ b/packages/vite/src/node/rpc/functions/vite-get-module-transform-info.ts
@@ -0,0 +1,22 @@
+import type { ViteInspectQuery } from '../../inspect/types'
+import { defineRpcFunction } from '@vitejs/devtools-kit'
+import { getViteInspectContext } from '../../inspect/context'
+import { getAllModuleIds } from '../../inspect/utils'
+
+export const viteGetModuleTransformInfo = defineRpcFunction({
+ name: 'vite:inspect:get-module-transform-info',
+ type: 'query',
+ jsonSerializable: true,
+ dump: async (devtoolsCtx) => {
+ const ctx = getViteInspectContext(devtoolsCtx)
+ return {
+ inputs: getAllModuleIds(ctx).map(([query, id]) => [query, id] satisfies [ViteInspectQuery, string]),
+ }
+ },
+ setup: (devtoolsCtx) => {
+ const ctx = getViteInspectContext(devtoolsCtx)
+ return {
+ handler: async (query: ViteInspectQuery, id: string) => ctx.queryEnv(query).getModuleTransformInfo(id),
+ }
+ },
+})
diff --git a/packages/vite/src/node/rpc/functions/vite-get-modules-list.ts b/packages/vite/src/node/rpc/functions/vite-get-modules-list.ts
new file mode 100644
index 00000000..14f78b00
--- /dev/null
+++ b/packages/vite/src/node/rpc/functions/vite-get-modules-list.ts
@@ -0,0 +1,22 @@
+import type { ViteInspectQuery } from '../../inspect/types'
+import { defineRpcFunction } from '@vitejs/devtools-kit'
+import { getViteInspectContext } from '../../inspect/context'
+import { getAllQueryEnvs } from '../../inspect/utils'
+
+export const viteGetModulesList = defineRpcFunction({
+ name: 'vite:inspect:get-modules-list',
+ type: 'query',
+ jsonSerializable: true,
+ dump: async (devtoolsCtx) => {
+ const ctx = getViteInspectContext(devtoolsCtx)
+ return {
+ inputs: getAllQueryEnvs(ctx).map(query => [query] satisfies [ViteInspectQuery]),
+ }
+ },
+ setup: (devtoolsCtx) => {
+ const ctx = getViteInspectContext(devtoolsCtx)
+ return {
+ handler: async (query: ViteInspectQuery) => ctx.queryEnv(query).getModulesList(),
+ }
+ },
+})
diff --git a/packages/vite/src/node/rpc/functions/vite-get-plugin-details.ts b/packages/vite/src/node/rpc/functions/vite-get-plugin-details.ts
new file mode 100644
index 00000000..b53baf72
--- /dev/null
+++ b/packages/vite/src/node/rpc/functions/vite-get-plugin-details.ts
@@ -0,0 +1,15 @@
+import type { ViteInspectQuery } from '../../inspect/types'
+import { defineRpcFunction } from '@vitejs/devtools-kit'
+import { getViteInspectContext } from '../../inspect/context'
+
+export const viteGetPluginDetails = defineRpcFunction({
+ name: 'vite:inspect:get-plugin-details',
+ type: 'query',
+ jsonSerializable: true,
+ setup: (devtoolsCtx) => {
+ const ctx = getViteInspectContext(devtoolsCtx)
+ return {
+ handler: async (query: ViteInspectQuery, id: string) => ctx.queryEnv(query).getPluginDetails(Number(id)),
+ }
+ },
+})
diff --git a/packages/vite/src/node/rpc/functions/vite-get-plugin-metrics.ts b/packages/vite/src/node/rpc/functions/vite-get-plugin-metrics.ts
new file mode 100644
index 00000000..3f1a1bbb
--- /dev/null
+++ b/packages/vite/src/node/rpc/functions/vite-get-plugin-metrics.ts
@@ -0,0 +1,22 @@
+import type { ViteInspectQuery } from '../../inspect/types'
+import { defineRpcFunction } from '@vitejs/devtools-kit'
+import { getViteInspectContext } from '../../inspect/context'
+import { getAllQueryEnvs } from '../../inspect/utils'
+
+export const viteGetPluginMetrics = defineRpcFunction({
+ name: 'vite:inspect:get-plugin-metrics',
+ type: 'query',
+ jsonSerializable: true,
+ dump: async (devtoolsCtx) => {
+ const ctx = getViteInspectContext(devtoolsCtx)
+ return {
+ inputs: getAllQueryEnvs(ctx).map(query => [query] satisfies [ViteInspectQuery]),
+ }
+ },
+ setup: (devtoolsCtx) => {
+ const ctx = getViteInspectContext(devtoolsCtx)
+ return {
+ handler: async (query: ViteInspectQuery) => ctx.queryEnv(query).getPluginMetrics(),
+ }
+ },
+})
diff --git a/packages/vite/src/node/rpc/functions/vite-get-server-metrics.ts b/packages/vite/src/node/rpc/functions/vite-get-server-metrics.ts
new file mode 100644
index 00000000..f6c7af60
--- /dev/null
+++ b/packages/vite/src/node/rpc/functions/vite-get-server-metrics.ts
@@ -0,0 +1,22 @@
+import type { ViteInspectQuery } from '../../inspect/types'
+import { defineRpcFunction } from '@vitejs/devtools-kit'
+import { getViteInspectContext } from '../../inspect/context'
+import { getAllQueryEnvs } from '../../inspect/utils'
+
+export const viteGetServerMetrics = defineRpcFunction({
+ name: 'vite:inspect:get-server-metrics',
+ type: 'query',
+ jsonSerializable: true,
+ dump: async (devtoolsCtx) => {
+ const ctx = getViteInspectContext(devtoolsCtx)
+ return {
+ inputs: getAllQueryEnvs(ctx).map(query => [query] satisfies [ViteInspectQuery]),
+ }
+ },
+ setup: (devtoolsCtx) => {
+ const ctx = getViteInspectContext(devtoolsCtx)
+ return {
+ handler: async (query: ViteInspectQuery) => ctx.getViteContext(query.vite).data.serverMetrics,
+ }
+ },
+})
diff --git a/packages/vite/src/node/rpc/functions/vite-resolve-id.ts b/packages/vite/src/node/rpc/functions/vite-resolve-id.ts
new file mode 100644
index 00000000..68307ae5
--- /dev/null
+++ b/packages/vite/src/node/rpc/functions/vite-resolve-id.ts
@@ -0,0 +1,22 @@
+import type { ViteInspectQuery } from '../../inspect/types'
+import { defineRpcFunction } from '@vitejs/devtools-kit'
+import { getViteInspectContext } from '../../inspect/context'
+import { getAllModuleIds } from '../../inspect/utils'
+
+export const viteResolveId = defineRpcFunction({
+ name: 'vite:inspect:resolve-id',
+ type: 'query',
+ jsonSerializable: true,
+ dump: async (devtoolsCtx) => {
+ const ctx = getViteInspectContext(devtoolsCtx)
+ return {
+ inputs: getAllModuleIds(ctx).map(([query, id]) => [query, id] satisfies [ViteInspectQuery, string]),
+ }
+ },
+ setup: (devtoolsCtx) => {
+ const ctx = getViteInspectContext(devtoolsCtx)
+ return {
+ handler: async (query: ViteInspectQuery, id: string) => ctx.queryEnv(query).resolveId(id),
+ }
+ },
+})
diff --git a/packages/vite/src/node/rpc/index.ts b/packages/vite/src/node/rpc/index.ts
index b68ece44..941d7bd2 100644
--- a/packages/vite/src/node/rpc/index.ts
+++ b/packages/vite/src/node/rpc/index.ts
@@ -1,15 +1,51 @@
import type { RpcDefinitionsToFunctions } from '@vitejs/devtools-kit'
+import { viteClearModuleTransform } from './functions/vite-clear-module-transform'
import { viteEnvInfo } from './functions/vite-env-info'
+import { viteGetMetadata } from './functions/vite-get-metadata'
+import { viteGetModuleTransformInfo } from './functions/vite-get-module-transform-info'
+import { viteGetModulesList } from './functions/vite-get-modules-list'
+import { viteGetPluginDetails } from './functions/vite-get-plugin-details'
+import { viteGetPluginMetrics } from './functions/vite-get-plugin-metrics'
+import { viteGetServerMetrics } from './functions/vite-get-server-metrics'
import { viteMetaInfo } from './functions/vite-meta-info'
+import { viteResolveId } from './functions/vite-resolve-id'
import '@vitejs/devtools-kit'
-export const rpcFunctions = [
+export const VITE_INSPECT_MODULE_UPDATED_STATE_KEY = 'vite:inspect:module-updated'
+
+export interface ViteInspectModuleUpdatedState {
+ version: number
+ ids: string[] | null
+ updatedAt: number
+}
+
+export const viteRpcFunctions = [
viteMetaInfo,
viteEnvInfo,
] as const
+export const inspectRpcFunctions = [
+ viteClearModuleTransform,
+ viteGetMetadata,
+ viteGetModulesList,
+ viteGetPluginMetrics,
+ viteGetPluginDetails,
+ viteGetModuleTransformInfo,
+ viteResolveId,
+ viteGetServerMetrics,
+] as const
+
+export const rpcFunctions = [
+ ...viteRpcFunctions,
+ ...inspectRpcFunctions,
+] as const
+
export type ServerFunctions = RpcDefinitionsToFunctions
declare module '@vitejs/devtools-kit' {
export interface DevToolsRpcServerFunctions extends ServerFunctions {}
+
+ export interface DevToolsRpcSharedStates {
+ 'vite:inspect:module-updated': ViteInspectModuleUpdatedState
+ }
}
diff --git a/packages/vite/src/nuxt.config.ts b/packages/vite/src/nuxt.config.ts
index 9cd77c10..63bb99a3 100644
--- a/packages/vite/src/nuxt.config.ts
+++ b/packages/vite/src/nuxt.config.ts
@@ -91,6 +91,19 @@ export default defineNuxtConfig({
cssMinify: false,
},
optimizeDeps: {
+ include: [
+ '@vueuse/core',
+ '@floating-ui/dom',
+ 'd3-hierarchy',
+ 'd3-shape',
+ 'fuse.js',
+ 'modern-monaco',
+ 'comlink',
+ 'floating-vue',
+ 'splitpanes',
+ 'vue-virtual-scroller',
+ 'nanovis',
+ ],
exclude: [
'structured-clone-es',
'birpc',
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4e513389..b4b13ce1 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1025,6 +1025,21 @@ importers:
'@vueuse/core':
specifier: '*'
version: 14.3.0(vue@3.5.38(typescript@6.0.3))
+ d3-hierarchy:
+ specifier: '*'
+ version: 3.1.2
+ d3-shape:
+ specifier: '*'
+ version: 3.2.0
+ floating-vue:
+ specifier: '*'
+ version: 5.2.2(@nuxt/kit@4.4.8(magicast@0.5.2))(vue@3.5.38(typescript@6.0.3))
+ fuse.js:
+ specifier: '*'
+ version: 7.4.2
+ nanovis:
+ specifier: '*'
+ version: 1.0.0
nuxt:
specifier: ^4.4.8
version: 4.4.8(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.0.3)(@vitejs/devtools@0.3.3)(@vue/compiler-sfc@3.5.38)(cac@7.0.0)(db0@0.3.4)(esbuild@0.28.1)(eslint@10.5.0(jiti@2.7.0))(idb-keyval@6.2.5)(ioredis@5.10.1)(magicast@0.5.2)(optionator@0.9.4)(rolldown@1.1.3)(rollup-plugin-visualizer@7.0.1(rolldown@1.1.3)(rollup@4.60.2))(rollup@4.60.2)(terser@5.44.1)(tsx@4.22.4)(typescript@6.0.3)(vite@8.1.0)(vue-tsc@3.3.5(typescript@6.0.3))(yaml@2.9.0)
@@ -1046,49 +1061,79 @@ importers:
'@vitejs/devtools-kit':
specifier: workspace:*
version: link:../kit
- birpc:
- specifier: catalog:deps
- version: 4.0.0
- devframe:
- specifier: catalog:deps
- version: 0.5.4(typescript@6.0.3)
+ d3-shape:
+ specifier: catalog:frontend
+ version: 3.2.0
envinfo:
specifier: catalog:deps
version: 7.21.0
- get-port-please:
- specifier: catalog:deps
- version: 3.2.0
- h3:
+ nostics:
specifier: catalog:deps
- version: 2.0.1-rc.22
+ version: 1.1.4
pathe:
specifier: catalog:deps
version: 2.0.3
- ws:
+ perfect-debounce:
specifier: catalog:deps
- version: 8.21.0
+ version: 2.1.0
+ vite:
+ specifier: ^8.1.0
+ version: 8.1.0(@types/node@25.0.3)(@vitejs/devtools@0.3.3)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.44.1)(tsx@4.22.4)(yaml@2.9.0)
devDependencies:
+ '@types/d3-hierarchy':
+ specifier: catalog:types
+ version: 3.1.7
'@types/envinfo':
specifier: catalog:types
version: 7.8.4
+ '@types/splitpanes':
+ specifier: catalog:types
+ version: 2.2.6
'@unocss/nuxt':
specifier: catalog:build
version: 66.7.2(magicast@0.5.2)(vite@8.1.0)(webpack@5.104.1(esbuild@0.28.1))
+ '@vueuse/components':
+ specifier: catalog:frontend
+ version: 14.3.0(vue@3.5.38(typescript@6.0.3))
'@vueuse/core':
specifier: catalog:frontend
version: 14.3.0(vue@3.5.38(typescript@6.0.3))
'@vueuse/nuxt':
specifier: catalog:build
version: 14.3.0(magicast@0.5.2)(nuxt@4.4.8(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0))(@parcel/watcher@2.5.6)(@types/node@25.0.3)(@vitejs/devtools@0.3.3)(@vue/compiler-sfc@3.5.38)(cac@7.0.0)(db0@0.3.4)(esbuild@0.28.1)(eslint@10.5.0(jiti@2.7.0))(idb-keyval@6.2.5)(ioredis@5.10.1)(magicast@0.5.2)(optionator@0.9.4)(rolldown@1.1.3)(rollup-plugin-visualizer@7.0.1(rolldown@1.1.3)(rollup@4.60.2))(rollup@4.60.2)(terser@5.44.1)(tsx@4.22.4)(typescript@6.0.3)(vite@8.1.0)(vue-tsc@3.3.5(typescript@6.0.3))(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3))
+ comlink:
+ specifier: catalog:frontend
+ version: 4.4.2
+ d3-hierarchy:
+ specifier: catalog:frontend
+ version: 3.1.2
+ diff-match-patch-es:
+ specifier: catalog:frontend
+ version: 1.0.1
floating-vue:
specifier: catalog:frontend
version: 5.2.2(@nuxt/kit@4.4.8(magicast@0.5.2))(vue@3.5.38(typescript@6.0.3))
+ fuse.js:
+ specifier: catalog:frontend
+ version: 7.4.2
+ modern-monaco:
+ specifier: catalog:frontend
+ version: 0.4.2(typescript@6.0.3)
+ nanovis:
+ specifier: catalog:frontend
+ version: 1.0.0
+ splitpanes:
+ specifier: catalog:frontend
+ version: 4.1.2(vue@3.5.38(typescript@6.0.3))
tsdown:
specifier: catalog:build
version: 0.22.3(@vitejs/devtools@0.3.3)(publint@0.3.21)(tsx@4.22.4)(typescript@6.0.3)(unrun@0.3.1(synckit@0.11.12))(vue-tsc@3.3.5(typescript@6.0.3))
unocss:
specifier: catalog:build
version: 66.7.2(@unocss/webpack@66.7.2(webpack@5.104.1(esbuild@0.28.1)))(vite@8.1.0)
+ vue-virtual-scroller:
+ specifier: catalog:frontend
+ version: 3.0.4(vue@3.5.38(typescript@6.0.3))
packages/webext:
dependencies:
@@ -1211,10 +1256,6 @@ packages:
resolution: {integrity: sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==}
engines: {node: ^22.18.0 || >=24.11.0}
- '@babel/generator@8.0.0-rc.6':
- resolution: {integrity: sha512-6mIzgVK8DgEzvIapoQwhXTMnnkuE4STQmVv9H03i/tZ2ml8oev3TRvZJgTenK2Bsq0YWNtzOrFdTyNzCMFtjJQ==}
- engines: {node: ^22.18.0 || >=24.11.0}
-
'@babel/helper-annotate-as-pure@7.27.3':
resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==}
engines: {node: '>=6.9.0'}
@@ -1277,18 +1318,10 @@ packages:
resolution: {integrity: sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==}
engines: {node: ^22.18.0 || >=24.11.0}
- '@babel/helper-string-parser@8.0.0-rc.6':
- resolution: {integrity: sha512-BCkFy+zN6kXQed3YOT7aJl93NfDSzQc3pBfsvTVPs9gU9X3V0aefEF5kwBT0E+mDWH9QgKaZstYUQN9VdQZT4g==}
- engines: {node: ^22.18.0 || >=24.11.0}
-
'@babel/helper-validator-identifier@7.29.7':
resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==}
engines: {node: '>=6.9.0'}
- '@babel/helper-validator-identifier@8.0.0-rc.6':
- resolution: {integrity: sha512-nVJ+1JcCgntv8d78rRo++o2wuODT0Irknx2BF8Np4Ft2CRgjLqIs4qzSZ8b66yGbBdMWGmZBO9WEZv1hhNiSpg==}
- engines: {node: ^22.18.0 || >=24.11.0}
-
'@babel/helper-validator-identifier@8.0.2':
resolution: {integrity: sha512-9Fr9QeyCAyi1BR1jKZ6uYQ24EIhQUx5ReHfQU7drOE+TPOb+w11/dsqLkMOT2U29OdCT71XajrOT8xDc1C7orA==}
engines: {node: ^22.18.0 || >=24.11.0}
@@ -1311,11 +1344,6 @@ packages:
engines: {node: ^22.18.0 || >=24.11.0}
hasBin: true
- '@babel/parser@8.0.0-rc.6':
- resolution: {integrity: sha512-rOS8IpdO7mQELkTPlCsTgPejO0bFuZdEDCGQJouYbYf9e1FLTym7Fei2pEjq8q7MWbX0ravcd7QQYKs1TxOuog==}
- engines: {node: ^22.18.0 || >=24.11.0}
- hasBin: true
-
'@babel/plugin-syntax-jsx@7.28.6':
resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==}
engines: {node: '>=6.9.0'}
@@ -1350,10 +1378,6 @@ packages:
resolution: {integrity: sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw==}
engines: {node: ^22.18.0 || >=24.11.0}
- '@babel/types@8.0.0-rc.6':
- resolution: {integrity: sha512-p7/ABylAYlexb31wtRdIfH9L9A0Z2T/9H6zAqzqndkY2PLkvNNc580wGhp/gGKN4Sp9sQvSkhc6Oga8/O+wTyw==}
- engines: {node: ^22.18.0 || >=24.11.0}
-
'@bomb.sh/tab@0.0.15':
resolution: {integrity: sha512-Y90ub44TAvbdO9P8mcD/XPyQjFhiR5xmd4Fk7JErmWmEWEUimNnjWiBrVZ16Tj3GA1rLZ+uvCN2V/pzLawv31g==}
hasBin: true
@@ -1378,18 +1402,10 @@ packages:
'@chevrotain/types@11.1.2':
resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==}
- '@clack/core@1.4.1':
- resolution: {integrity: sha512-FILJa1gGKEFTGZAJE9RpVhrjKz3c3h4ar60dSv6cGuDqufQ84YEIS3GAGvZiN+H6yaLbbvTFNejjCC4tXpZEuw==}
- engines: {node: '>= 20.12.0'}
-
'@clack/core@1.4.2':
resolution: {integrity: sha512-0Ty/1Gfm+Kb07sXcuESjyKfwEhSy4Ns1AgeEisHb/bDY5fWme0tTeTkU14T1Gmcs17YIjB/teiDe4uaCghbYqQ==}
engines: {node: '>= 20.12.0'}
- '@clack/prompts@1.5.1':
- resolution: {integrity: sha512-zccHj2z2oCCO4yrDiRSlFOxWerGqRiysP7a5jPK6uoI9URKAquwY42Dd/iUP8JWHxEzdRe4TlbvZCo8z1/mhrw==}
- engines: {node: '>= 20.12.0'}
-
'@clack/prompts@1.6.0':
resolution: {integrity: sha512-EYlRokl8szrP9Z25qT5aepMdBjzBvHF9ZEhzIiUBc9guz/T31EqRgvD0QSgZcpE93xiwrr+OkB4nz0BZyF6fSA==}
engines: {node: '>= 20.12.0'}
@@ -1440,18 +1456,12 @@ packages:
'@emnapi/core@1.10.0':
resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==}
- '@emnapi/core@1.11.0':
- resolution: {integrity: sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==}
-
'@emnapi/core@1.11.1':
resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==}
'@emnapi/runtime@1.10.0':
resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==}
- '@emnapi/runtime@1.11.0':
- resolution: {integrity: sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==}
-
'@emnapi/runtime@1.11.1':
resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==}
@@ -1759,9 +1769,6 @@ packages:
'@iconify-json/ri@1.2.10':
resolution: {integrity: sha512-WWMhoncVVM+Xmu9T5fgu2lhYRrKTEWhKk3Com0KiM111EeEsRLiASjpsFKnC/SrB6covhUp95r2mH8tGxhgd5Q==}
- '@iconify-json/simple-icons@1.2.86':
- resolution: {integrity: sha512-t3jck5qPQuK1qy+bRn9eCoDQhIB7XSazKz1Fjp8hcan3XOAsTI5Mq/s3F0ekOKSvMQqkVORYK6ns6o6T9f5EMA==}
-
'@iconify-json/simple-icons@1.2.87':
resolution: {integrity: sha512-8YciStObhSji3OZFmWAWK6kBujyqO5bLCxeDwLxf3CR3F4PVelq7keC2LBvgTqviWzSTysj5/g4PCFLiAMVGsw==}
@@ -3654,14 +3661,6 @@ packages:
'@types/ws@8.18.1':
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
- '@typescript-eslint/eslint-plugin@8.61.0':
- resolution: {integrity: sha512-bFNvl9ZczlVb+wR2Akszf3gHfKVj/8WanXaGJ3UstTA7brNKg0cNdk6X1Psu5V7MZ2oQtzZKOEzIUehaoxbDGw==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- '@typescript-eslint/parser': ^8.61.0
- eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
- typescript: '>=4.8.4 <6.1.0'
-
'@typescript-eslint/eslint-plugin@8.62.0':
resolution: {integrity: sha512-o+mpz7EYiMzXoySXiKmzlabIvTVqUuK5yLrAedRPRDA0IpPFMUV1IXt6OqljIxX/kumN6EjUYp41Hqelh6p/Dw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3677,13 +3676,6 @@ packages:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.0.0'
- '@typescript-eslint/parser@8.61.0':
- resolution: {integrity: sha512-5B7PfA2e1NQGCnDHd/0lW7W3gvp3d59Ryw54FYO8Uswxo9f6ikw3AZV+Xj/TvpImmpsiYyUqAfhC6kJID1jF6w==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
- typescript: '>=4.8.4 <6.1.0'
-
'@typescript-eslint/parser@8.62.0':
resolution: {integrity: sha512-dzHeT2gySzZtLDsuqxU9AkYgIsQoHAHtRBpOqM+Ofzx1Bwrd2RcCjQJ+6iQbsHOIR6NS33bF2W1k3blN1zLDrA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3697,18 +3689,6 @@ packages:
peerDependencies:
typescript: '>=4.8.4 <6.0.0'
- '@typescript-eslint/project-service@8.61.0':
- resolution: {integrity: sha512-DV42F7MLJO6Rax7SK1yg43tcnEfGUrurSpSxKuVX+a3RCTzBlH3fuxprrOJXKCJGAaw82xXocikJ0uQaqwXgGA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- typescript: '>=4.8.4 <6.1.0'
-
- '@typescript-eslint/project-service@8.61.1':
- resolution: {integrity: sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- typescript: '>=4.8.4 <6.1.0'
-
'@typescript-eslint/project-service@8.62.0':
resolution: {integrity: sha512-wexnCqiTg7BOGtbLDftYpRWlmLq4xfoMd7BKFR6Y75sZS3QmRKLdN3yWLhmIYgqMmP/OXWpj3H8odkb5nGURCQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3725,14 +3705,6 @@ packages:
resolution: {integrity: sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/scope-manager@8.61.0':
- resolution: {integrity: sha512-IWdXFHFSb6mlC3HPc7QsLDm5zYEbUla6trDEHf32D3/dnuUyXd87plScSNXSbm0/RxMvObpI17sv/EDTGrGZkA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@typescript-eslint/scope-manager@8.61.1':
- resolution: {integrity: sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
'@typescript-eslint/scope-manager@8.62.0':
resolution: {integrity: sha512-1lX38kNxXIRb8mEc3lbq5mdHq1Pf2+U0nFU65KfT18mtPxxl0fvjuEE92mHuXPuCtElJhOrddOpyMlM3Z0umEA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3743,31 +3715,12 @@ packages:
peerDependencies:
typescript: '>=4.8.4 <6.0.0'
- '@typescript-eslint/tsconfig-utils@8.61.0':
- resolution: {integrity: sha512-O5Amvdv9ztMpxpf+vmFULGG78IE6Qwdr3bCGvqwG4nwc9H2qXkOYJJnRbRHyMkQTjv1d03olqwwwzHLMqpFePQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- typescript: '>=4.8.4 <6.1.0'
-
- '@typescript-eslint/tsconfig-utils@8.61.1':
- resolution: {integrity: sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- typescript: '>=4.8.4 <6.1.0'
-
'@typescript-eslint/tsconfig-utils@8.62.0':
resolution: {integrity: sha512-y2GAdB6ykaXUvuspbYnizQc4oDDz0Tz/Yc7iWrXf9mx8vm/L/0vLHCe0tS2boG96Zy+DivnVDQ9ZUEWoHqqx1g==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/type-utils@8.61.0':
- resolution: {integrity: sha512-TuBiQYIkd97yBfInHCTKVYMbX4kvEmpOEuixIuzCU9p8BGT1SfyyO0d0IfDMbPIHcjn/hWnusUX5e8v5Xg+X8A==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
- typescript: '>=4.8.4 <6.1.0'
-
'@typescript-eslint/type-utils@8.62.0':
resolution: {integrity: sha512-+g5O3j0w2ldzC86Pv6fvbO/xhAonbJFIdf/MKQ1d30gndlsVzUOE83ldfSE15Qrl9fhFjK6AovHs5Wpp6vx86w==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3779,14 +3732,6 @@ packages:
resolution: {integrity: sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/types@8.61.0':
- resolution: {integrity: sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@typescript-eslint/types@8.61.1':
- resolution: {integrity: sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
'@typescript-eslint/types@8.62.0':
resolution: {integrity: sha512-KvAclkktORPvM54TgLgA4z9HIV1M8zOgw9ZVNXl9f/8dLYfXYX1wkMXP7qmabpijQRV5bHJLOmoyGQbLMaUYeg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3797,18 +3742,6 @@ packages:
peerDependencies:
typescript: '>=4.8.4 <6.0.0'
- '@typescript-eslint/typescript-estree@8.61.0':
- resolution: {integrity: sha512-42zatd5qSvvcV1JdDBCLxYRznvP4eIHpPoZXdkPFnAmanA4FuZ5dibSnCBggY8hQnqajPpoGjXFdZ7fIJKQnlA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- typescript: '>=4.8.4 <6.1.0'
-
- '@typescript-eslint/typescript-estree@8.61.1':
- resolution: {integrity: sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- typescript: '>=4.8.4 <6.1.0'
-
'@typescript-eslint/typescript-estree@8.62.0':
resolution: {integrity: sha512-+hVbNxtW64pIcZWDPGbyaKF7vp2IBTVY5ma1blwwksrjdsbdqqEKvJWMGbBofei4F6Dovx1M0RJgoFeNu2279A==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3822,20 +3755,6 @@ packages:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.0.0'
- '@typescript-eslint/utils@8.61.0':
- resolution: {integrity: sha512-3bzFt7ImFMW/jVYwJamDoe/dMOdFLSC6pom6rRjdh4SZJEYupyMzem8e7vKZLclLfpHjlwSAXOUxtKxGXUiLqA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
- typescript: '>=4.8.4 <6.1.0'
-
- '@typescript-eslint/utils@8.61.1':
- resolution: {integrity: sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
- typescript: '>=4.8.4 <6.1.0'
-
'@typescript-eslint/utils@8.62.0':
resolution: {integrity: sha512-82r66fi9zYwZ+mTq3vKgwjbZ1PVk/DJzrXFLpG6RnBbdvH8TEGVHIs9H4d2drhkOzf0syZuD/OZvvlu6GDbP4g==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3847,14 +3766,6 @@ packages:
resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/visitor-keys@8.61.0':
- resolution: {integrity: sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@typescript-eslint/visitor-keys@8.61.1':
- resolution: {integrity: sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
'@typescript-eslint/visitor-keys@8.62.0':
resolution: {integrity: sha512-CY3uyFSRbcQv3nnSv8S0+lDftMVz6P963PoRlxrV7ew/Md564g9ut60PYzdLM5qW4jFn93GBF+Soi90ISAN+GQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -5209,9 +5120,6 @@ packages:
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
engines: {node: '>= 4'}
- dompurify@3.4.10:
- resolution: {integrity: sha512-0xzNv0e7oYC6yyuOGZIABPM4qtg3QxLFniDNPP4ZP90wR8Yq3zgwpRbrNiT4N3IKqDbbYFEJLV+JWEs19aZ//w==}
-
dompurify@3.4.11:
resolution: {integrity: sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==}
@@ -5394,12 +5302,6 @@ packages:
eslint-import-resolver-node:
optional: true
- eslint-plugin-jsdoc@63.0.2:
- resolution: {integrity: sha512-0TchoK1uS4VxHSo3P4CyWQ31Lm+6zsT+xkHMC5KbFKwgOf8YrXPf1Bl8EP7kpgw1wfe/Ui5jz5mSX7ou8WAVuw==}
- engines: {node: ^22.13.0 || >=24}
- peerDependencies:
- eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0
-
eslint-plugin-jsdoc@63.0.8:
resolution: {integrity: sha512-0rLBDD03N6aL+axOtkSSaUoqhHOPKe+I1gS6dLEwm9zjqAd1e78Z7cilTnkNRQ8wOiEvzR6mvl6IDu7jiTR8UA==}
engines: {node: ^22.13.0 || >=24}
@@ -5791,10 +5693,6 @@ packages:
resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==}
engines: {node: '>=18'}
- globals@17.6.0:
- resolution: {integrity: sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==}
- engines: {node: '>=18'}
-
globals@17.7.0:
resolution: {integrity: sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==}
engines: {node: '>=18'}
@@ -7318,10 +7216,6 @@ packages:
resolution: {integrity: sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==}
engines: {node: '>=0.10.0'}
- regjsparser@0.13.0:
- resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==}
- hasBin: true
-
regjsparser@0.13.2:
resolution: {integrity: sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==}
hasBin: true
@@ -8594,7 +8488,7 @@ snapshots:
'@eslint-community/eslint-plugin-eslint-comments': 4.7.2(eslint@10.5.0(jiti@2.7.0))
'@eslint/markdown': 8.0.2
'@stylistic/eslint-plugin': 5.10.0(eslint@10.5.0(jiti@2.7.0))
- '@typescript-eslint/eslint-plugin': 8.62.0(@typescript-eslint/parser@8.61.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
+ '@typescript-eslint/eslint-plugin': 8.62.0(@typescript-eslint/parser@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
'@typescript-eslint/parser': 8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
'@vitest/eslint-plugin': 1.6.20(@typescript-eslint/eslint-plugin@8.62.0(@typescript-eslint/parser@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.9(@types/node@25.0.3)(vite@8.1.0))
ansis: 4.3.1
@@ -8703,15 +8597,6 @@ snapshots:
'@types/jsesc': 2.5.1
jsesc: 3.1.0
- '@babel/generator@8.0.0-rc.6':
- dependencies:
- '@babel/parser': 8.0.0-rc.6
- '@babel/types': 8.0.0-rc.6
- '@jridgewell/gen-mapping': 0.3.13
- '@jridgewell/trace-mapping': 0.3.31
- '@types/jsesc': 2.5.1
- jsesc: 3.1.0
-
'@babel/helper-annotate-as-pure@7.27.3':
dependencies:
'@babel/types': 7.29.7
@@ -8792,12 +8677,8 @@ snapshots:
'@babel/helper-string-parser@8.0.0': {}
- '@babel/helper-string-parser@8.0.0-rc.6': {}
-
'@babel/helper-validator-identifier@7.29.7': {}
- '@babel/helper-validator-identifier@8.0.0-rc.6': {}
-
'@babel/helper-validator-identifier@8.0.2': {}
'@babel/helper-validator-option@7.27.1': {}
@@ -8815,10 +8696,6 @@ snapshots:
dependencies:
'@babel/types': 8.0.0
- '@babel/parser@8.0.0-rc.6':
- dependencies:
- '@babel/types': 8.0.0-rc.6
-
'@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
@@ -8868,11 +8745,6 @@ snapshots:
'@babel/helper-string-parser': 8.0.0
'@babel/helper-validator-identifier': 8.0.2
- '@babel/types@8.0.0-rc.6':
- dependencies:
- '@babel/helper-string-parser': 8.0.0-rc.6
- '@babel/helper-validator-identifier': 8.0.0-rc.6
-
'@bomb.sh/tab@0.0.15(cac@7.0.0)(citty@0.2.2)':
optionalDependencies:
cac: 7.0.0
@@ -8885,23 +8757,11 @@ snapshots:
'@chevrotain/types@11.1.2': {}
- '@clack/core@1.4.1':
- dependencies:
- fast-wrap-ansi: 0.2.0
- sisteransi: 1.0.5
-
'@clack/core@1.4.2':
dependencies:
fast-wrap-ansi: 0.2.0
sisteransi: 1.0.5
- '@clack/prompts@1.5.1':
- dependencies:
- '@clack/core': 1.4.1
- fast-string-width: 3.0.2
- fast-wrap-ansi: 0.2.0
- sisteransi: 1.0.5
-
'@clack/prompts@1.6.0':
dependencies:
'@clack/core': 1.4.2
@@ -8956,12 +8816,6 @@ snapshots:
tslib: 2.8.1
optional: true
- '@emnapi/core@1.11.0':
- dependencies:
- '@emnapi/wasi-threads': 1.2.2
- tslib: 2.8.1
- optional: true
-
'@emnapi/core@1.11.1':
dependencies:
'@emnapi/wasi-threads': 1.2.2
@@ -8973,11 +8827,6 @@ snapshots:
tslib: 2.8.1
optional: true
- '@emnapi/runtime@1.11.0':
- dependencies:
- tslib: 2.8.1
- optional: true
-
'@emnapi/runtime@1.11.1':
dependencies:
tslib: 2.8.1
@@ -8996,7 +8845,7 @@ snapshots:
'@es-joy/jsdoccomment@0.84.0':
dependencies:
'@types/estree': 1.0.9
- '@typescript-eslint/types': 8.61.1
+ '@typescript-eslint/types': 8.62.0
comment-parser: 1.4.5
esquery: 1.7.0
jsdoc-type-pratt-parser: 7.1.1
@@ -9004,7 +8853,7 @@ snapshots:
'@es-joy/jsdoccomment@0.87.0':
dependencies:
'@types/estree': 1.0.9
- '@typescript-eslint/types': 8.61.1
+ '@typescript-eslint/types': 8.62.0
comment-parser: 1.4.7
esquery: 1.7.0
jsdoc-type-pratt-parser: 7.2.0
@@ -9246,10 +9095,6 @@ snapshots:
dependencies:
'@iconify/types': 2.0.0
- '@iconify-json/simple-icons@1.2.86':
- dependencies:
- '@iconify/types': 2.0.0
-
'@iconify-json/simple-icons@1.2.87':
dependencies:
'@iconify/types': 2.0.0
@@ -9378,9 +9223,9 @@ snapshots:
'@napi-rs/wasm-runtime@0.2.12':
dependencies:
- '@emnapi/core': 1.11.0
- '@emnapi/runtime': 1.11.0
- '@tybys/wasm-util': 0.10.2
+ '@emnapi/core': 1.11.1
+ '@emnapi/runtime': 1.11.1
+ '@tybys/wasm-util': 0.10.3
optional: true
'@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)':
@@ -9390,6 +9235,13 @@ snapshots:
'@tybys/wasm-util': 0.10.2
optional: true
+ '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)':
+ dependencies:
+ '@emnapi/core': 1.10.0
+ '@emnapi/runtime': 1.10.0
+ '@tybys/wasm-util': 0.10.3
+ optional: true
+
'@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)':
dependencies:
'@emnapi/core': 1.11.1
@@ -9412,7 +9264,7 @@ snapshots:
'@nuxt/cli@3.35.2(@nuxt/schema@4.4.8)(cac@7.0.0)(magicast@0.5.2)':
dependencies:
'@bomb.sh/tab': 0.0.15(cac@7.0.0)(citty@0.2.2)
- '@clack/prompts': 1.5.1
+ '@clack/prompts': 1.6.0
c12: 3.3.4(magicast@0.5.2)
citty: 0.2.2
confbox: 0.2.4
@@ -9467,7 +9319,7 @@ snapshots:
'@nuxt/devtools-wizard@3.2.4':
dependencies:
- '@clack/prompts': 1.5.1
+ '@clack/prompts': 1.6.0
consola: 3.4.2
diff: 8.0.4
execa: 8.0.1
@@ -9565,24 +9417,24 @@ snapshots:
'@nuxt/eslint-config@1.16.0(@typescript-eslint/utils@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(@vue/compiler-sfc@3.5.38)(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)':
dependencies:
'@antfu/install-pkg': 1.1.0
- '@clack/prompts': 1.5.1
+ '@clack/prompts': 1.6.0
'@eslint/js': 10.0.1(eslint@10.5.0(jiti@2.7.0))
'@nuxt/eslint-plugin': 1.16.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
'@stylistic/eslint-plugin': 5.10.0(eslint@10.5.0(jiti@2.7.0))
- '@typescript-eslint/eslint-plugin': 8.61.0(@typescript-eslint/parser@8.61.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
- '@typescript-eslint/parser': 8.61.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
+ '@typescript-eslint/eslint-plugin': 8.62.0(@typescript-eslint/parser@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
+ '@typescript-eslint/parser': 8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
eslint: 10.5.0(jiti@2.7.0)
eslint-config-flat-gitignore: 2.3.0(eslint@10.5.0(jiti@2.7.0))
eslint-flat-config-utils: 3.2.0
eslint-merge-processors: 2.0.0(eslint@10.5.0(jiti@2.7.0))
eslint-plugin-import-lite: 0.6.0(eslint@10.5.0(jiti@2.7.0))
eslint-plugin-import-x: 4.16.2(@typescript-eslint/utils@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))
- eslint-plugin-jsdoc: 63.0.2(eslint@10.5.0(jiti@2.7.0))
+ eslint-plugin-jsdoc: 63.0.8(eslint@10.5.0(jiti@2.7.0))
eslint-plugin-regexp: 3.1.0(eslint@10.5.0(jiti@2.7.0))
eslint-plugin-unicorn: 65.0.1(eslint@10.5.0(jiti@2.7.0))
- eslint-plugin-vue: 10.9.2(@stylistic/eslint-plugin@5.10.0(eslint@10.5.0(jiti@2.7.0)))(@typescript-eslint/parser@8.61.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(vue-eslint-parser@10.4.1(eslint@10.5.0(jiti@2.7.0)))
+ eslint-plugin-vue: 10.9.2(@stylistic/eslint-plugin@5.10.0(eslint@10.5.0(jiti@2.7.0)))(@typescript-eslint/parser@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(vue-eslint-parser@10.4.1(eslint@10.5.0(jiti@2.7.0)))
eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.38)(eslint@10.5.0(jiti@2.7.0))
- globals: 17.6.0
+ globals: 17.7.0
local-pkg: 1.2.1
pathe: 2.0.3
vue-eslint-parser: 10.4.1(eslint@10.5.0(jiti@2.7.0))
@@ -9595,7 +9447,7 @@ snapshots:
'@nuxt/eslint-plugin@1.16.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)':
dependencies:
- '@typescript-eslint/types': 8.61.1
+ '@typescript-eslint/types': 8.62.0
'@typescript-eslint/utils': 8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
eslint: 10.5.0(jiti@2.7.0)
transitivePeerDependencies:
@@ -9986,7 +9838,7 @@ snapshots:
dependencies:
'@emnapi/core': 1.10.0
'@emnapi/runtime': 1.10.0
- '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
+ '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
optional: true
'@oxc-minify/binding-win32-arm64-msvc@0.133.0':
@@ -10194,28 +10046,28 @@ snapshots:
dependencies:
'@emnapi/core': 1.10.0
'@emnapi/runtime': 1.10.0
- '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
+ '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
optional: true
'@oxc-parser/binding-wasm32-wasi@0.131.0':
dependencies:
'@emnapi/core': 1.10.0
'@emnapi/runtime': 1.10.0
- '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
+ '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
optional: true
'@oxc-parser/binding-wasm32-wasi@0.132.0':
dependencies:
'@emnapi/core': 1.10.0
'@emnapi/runtime': 1.10.0
- '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
+ '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
optional: true
'@oxc-parser/binding-wasm32-wasi@0.133.0':
dependencies:
'@emnapi/core': 1.10.0
'@emnapi/runtime': 1.10.0
- '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
+ '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
optional: true
'@oxc-parser/binding-win32-arm64-msvc@0.129.0':
@@ -10743,7 +10595,7 @@ snapshots:
'@stylistic/eslint-plugin@5.10.0(eslint@10.5.0(jiti@2.7.0))':
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0(jiti@2.7.0))
- '@typescript-eslint/types': 8.61.1
+ '@typescript-eslint/types': 8.62.0
eslint: 10.5.0(jiti@2.7.0)
eslint-visitor-keys: 4.2.1
espree: 10.4.0
@@ -11104,26 +10956,10 @@ snapshots:
dependencies:
'@types/node': 25.0.3
- '@typescript-eslint/eslint-plugin@8.61.0(@typescript-eslint/parser@8.61.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)':
- dependencies:
- '@eslint-community/regexpp': 4.12.2
- '@typescript-eslint/parser': 8.61.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
- '@typescript-eslint/scope-manager': 8.61.0
- '@typescript-eslint/type-utils': 8.61.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
- '@typescript-eslint/utils': 8.61.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
- '@typescript-eslint/visitor-keys': 8.61.0
- eslint: 10.5.0(jiti@2.7.0)
- ignore: 7.0.5
- natural-compare: 1.4.0
- ts-api-utils: 2.5.0(typescript@6.0.3)
- typescript: 6.0.3
- transitivePeerDependencies:
- - supports-color
-
- '@typescript-eslint/eslint-plugin@8.62.0(@typescript-eslint/parser@8.61.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)':
+ '@typescript-eslint/eslint-plugin@8.62.0(@typescript-eslint/parser@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
- '@typescript-eslint/parser': 8.61.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
+ '@typescript-eslint/parser': 8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
'@typescript-eslint/scope-manager': 8.62.0
'@typescript-eslint/type-utils': 8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
'@typescript-eslint/utils': 8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
@@ -11148,18 +10984,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@8.61.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)':
- dependencies:
- '@typescript-eslint/scope-manager': 8.61.0
- '@typescript-eslint/types': 8.61.0
- '@typescript-eslint/typescript-estree': 8.61.0(typescript@6.0.3)
- '@typescript-eslint/visitor-keys': 8.61.0
- debug: 4.4.3
- eslint: 10.5.0(jiti@2.7.0)
- typescript: 6.0.3
- transitivePeerDependencies:
- - supports-color
-
'@typescript-eslint/parser@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.62.0
@@ -11174,26 +10998,8 @@ snapshots:
'@typescript-eslint/project-service@8.56.1(typescript@6.0.3)':
dependencies:
- '@typescript-eslint/tsconfig-utils': 8.61.1(typescript@6.0.3)
- '@typescript-eslint/types': 8.61.1
- debug: 4.4.3
- typescript: 6.0.3
- transitivePeerDependencies:
- - supports-color
-
- '@typescript-eslint/project-service@8.61.0(typescript@6.0.3)':
- dependencies:
- '@typescript-eslint/tsconfig-utils': 8.61.1(typescript@6.0.3)
- '@typescript-eslint/types': 8.61.1
- debug: 4.4.3
- typescript: 6.0.3
- transitivePeerDependencies:
- - supports-color
-
- '@typescript-eslint/project-service@8.61.1(typescript@6.0.3)':
- dependencies:
- '@typescript-eslint/tsconfig-utils': 8.61.1(typescript@6.0.3)
- '@typescript-eslint/types': 8.61.1
+ '@typescript-eslint/tsconfig-utils': 8.62.0(typescript@6.0.3)
+ '@typescript-eslint/types': 8.62.0
debug: 4.4.3
typescript: 6.0.3
transitivePeerDependencies:
@@ -11227,16 +11033,6 @@ snapshots:
'@typescript-eslint/types': 8.56.1
'@typescript-eslint/visitor-keys': 8.56.1
- '@typescript-eslint/scope-manager@8.61.0':
- dependencies:
- '@typescript-eslint/types': 8.61.0
- '@typescript-eslint/visitor-keys': 8.61.0
-
- '@typescript-eslint/scope-manager@8.61.1':
- dependencies:
- '@typescript-eslint/types': 8.61.1
- '@typescript-eslint/visitor-keys': 8.61.1
-
'@typescript-eslint/scope-manager@8.62.0':
dependencies:
'@typescript-eslint/types': 8.62.0
@@ -11246,30 +11042,10 @@ snapshots:
dependencies:
typescript: 6.0.3
- '@typescript-eslint/tsconfig-utils@8.61.0(typescript@6.0.3)':
- dependencies:
- typescript: 6.0.3
-
- '@typescript-eslint/tsconfig-utils@8.61.1(typescript@6.0.3)':
- dependencies:
- typescript: 6.0.3
-
'@typescript-eslint/tsconfig-utils@8.62.0(typescript@6.0.3)':
dependencies:
typescript: 6.0.3
- '@typescript-eslint/type-utils@8.61.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)':
- dependencies:
- '@typescript-eslint/types': 8.61.0
- '@typescript-eslint/typescript-estree': 8.61.0(typescript@6.0.3)
- '@typescript-eslint/utils': 8.61.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
- debug: 4.4.3
- eslint: 10.5.0(jiti@2.7.0)
- ts-api-utils: 2.5.0(typescript@6.0.3)
- typescript: 6.0.3
- transitivePeerDependencies:
- - supports-color
-
'@typescript-eslint/type-utils@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)':
dependencies:
'@typescript-eslint/types': 8.62.0
@@ -11284,10 +11060,6 @@ snapshots:
'@typescript-eslint/types@8.56.1': {}
- '@typescript-eslint/types@8.61.0': {}
-
- '@typescript-eslint/types@8.61.1': {}
-
'@typescript-eslint/types@8.62.0': {}
'@typescript-eslint/typescript-estree@8.56.1(typescript@6.0.3)':
@@ -11305,36 +11077,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/typescript-estree@8.61.0(typescript@6.0.3)':
- dependencies:
- '@typescript-eslint/project-service': 8.61.0(typescript@6.0.3)
- '@typescript-eslint/tsconfig-utils': 8.61.0(typescript@6.0.3)
- '@typescript-eslint/types': 8.61.0
- '@typescript-eslint/visitor-keys': 8.61.0
- debug: 4.4.3
- minimatch: 10.2.4
- semver: 7.8.5
- tinyglobby: 0.2.17
- ts-api-utils: 2.5.0(typescript@6.0.3)
- typescript: 6.0.3
- transitivePeerDependencies:
- - supports-color
-
- '@typescript-eslint/typescript-estree@8.61.1(typescript@6.0.3)':
- dependencies:
- '@typescript-eslint/project-service': 8.61.1(typescript@6.0.3)
- '@typescript-eslint/tsconfig-utils': 8.61.1(typescript@6.0.3)
- '@typescript-eslint/types': 8.61.1
- '@typescript-eslint/visitor-keys': 8.61.1
- debug: 4.4.3
- minimatch: 10.2.4
- semver: 7.8.5
- tinyglobby: 0.2.17
- ts-api-utils: 2.5.0(typescript@6.0.3)
- typescript: 6.0.3
- transitivePeerDependencies:
- - supports-color
-
'@typescript-eslint/typescript-estree@8.62.0(typescript@6.0.3)':
dependencies:
'@typescript-eslint/project-service': 8.62.0(typescript@6.0.3)
@@ -11361,28 +11103,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@8.61.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)':
- dependencies:
- '@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0(jiti@2.7.0))
- '@typescript-eslint/scope-manager': 8.61.0
- '@typescript-eslint/types': 8.61.0
- '@typescript-eslint/typescript-estree': 8.61.0(typescript@6.0.3)
- eslint: 10.5.0(jiti@2.7.0)
- typescript: 6.0.3
- transitivePeerDependencies:
- - supports-color
-
- '@typescript-eslint/utils@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)':
- dependencies:
- '@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0(jiti@2.7.0))
- '@typescript-eslint/scope-manager': 8.61.1
- '@typescript-eslint/types': 8.61.1
- '@typescript-eslint/typescript-estree': 8.61.1(typescript@6.0.3)
- eslint: 10.5.0(jiti@2.7.0)
- typescript: 6.0.3
- transitivePeerDependencies:
- - supports-color
-
'@typescript-eslint/utils@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)':
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0(jiti@2.7.0))
@@ -11399,16 +11119,6 @@ snapshots:
'@typescript-eslint/types': 8.56.1
eslint-visitor-keys: 5.0.1
- '@typescript-eslint/visitor-keys@8.61.0':
- dependencies:
- '@typescript-eslint/types': 8.61.0
- eslint-visitor-keys: 5.0.1
-
- '@typescript-eslint/visitor-keys@8.61.1':
- dependencies:
- '@typescript-eslint/types': 8.61.1
- eslint-visitor-keys: 5.0.1
-
'@typescript-eslint/visitor-keys@8.62.0':
dependencies:
'@typescript-eslint/types': 8.62.0
@@ -11466,7 +11176,7 @@ snapshots:
'@unocss/eslint-plugin@66.7.2(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)':
dependencies:
- '@typescript-eslint/utils': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
+ '@typescript-eslint/utils': 8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
'@unocss/config': 66.7.2
'@unocss/core': 66.7.2
'@unocss/rule-utils': 66.7.2
@@ -11901,11 +11611,11 @@ snapshots:
'@vitest/eslint-plugin@1.6.20(@typescript-eslint/eslint-plugin@8.62.0(@typescript-eslint/parser@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)(vitest@4.1.9(@types/node@25.0.3)(vite@8.1.0))':
dependencies:
- '@typescript-eslint/scope-manager': 8.61.1
- '@typescript-eslint/utils': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
+ '@typescript-eslint/scope-manager': 8.62.0
+ '@typescript-eslint/utils': 8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
eslint: 10.5.0(jiti@2.7.0)
optionalDependencies:
- '@typescript-eslint/eslint-plugin': 8.62.0(@typescript-eslint/parser@8.61.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
+ '@typescript-eslint/eslint-plugin': 8.62.0(@typescript-eslint/parser@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
typescript: 6.0.3
vitest: 4.1.9(@types/node@25.0.3)(vite@8.1.0)
transitivePeerDependencies:
@@ -13104,10 +12814,6 @@ snapshots:
dependencies:
domelementtype: 2.3.0
- dompurify@3.4.10:
- optionalDependencies:
- '@types/trusted-types': 2.0.7
-
dompurify@3.4.11:
optionalDependencies:
'@types/trusted-types': 2.0.7
@@ -13260,7 +12966,7 @@ snapshots:
eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0)):
dependencies:
'@package-json/types': 0.0.12
- '@typescript-eslint/types': 8.61.1
+ '@typescript-eslint/types': 8.62.0
comment-parser: 1.4.7
debug: 4.4.3
eslint: 10.5.0(jiti@2.7.0)
@@ -13275,26 +12981,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-plugin-jsdoc@63.0.2(eslint@10.5.0(jiti@2.7.0)):
- dependencies:
- '@es-joy/jsdoccomment': 0.87.0
- '@es-joy/resolve.exports': 1.2.0
- are-docs-informative: 0.0.2
- comment-parser: 1.4.7
- debug: 4.4.3
- escape-string-regexp: 4.0.0
- eslint: 10.5.0(jiti@2.7.0)
- espree: 11.2.0
- esquery: 1.7.0
- html-entities: 2.6.0
- object-deep-merge: 2.0.1
- parse-imports-exports: 0.2.4
- semver: 7.8.5
- spdx-expression-parse: 4.0.0
- to-valid-identifier: 1.0.0
- transitivePeerDependencies:
- - supports-color
-
eslint-plugin-jsdoc@63.0.8(eslint@10.5.0(jiti@2.7.0)):
dependencies:
'@es-joy/jsdoccomment': 0.87.0
@@ -13348,7 +13034,7 @@ snapshots:
eslint-plugin-perfectionist@5.9.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3):
dependencies:
- '@typescript-eslint/utils': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
+ '@typescript-eslint/utils': 8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
eslint: 10.5.0(jiti@2.7.0)
natural-orderby: 5.0.0
transitivePeerDependencies:
@@ -13398,12 +13084,12 @@ snapshots:
detect-indent: 7.0.2
eslint: 10.5.0(jiti@2.7.0)
find-up-simple: 1.0.1
- globals: 17.6.0
+ globals: 17.7.0
indent-string: 5.0.0
is-builtin-module: 5.0.0
jsesc: 3.1.0
pluralize: 8.0.0
- regjsparser: 0.13.0
+ regjsparser: 0.13.2
semver: 7.8.5
strip-indent: 4.1.1
@@ -13418,7 +13104,7 @@ snapshots:
detect-indent: 7.0.2
eslint: 10.5.0(jiti@2.7.0)
find-up-simple: 1.0.1
- globals: 17.6.0
+ globals: 17.7.0
indent-string: 5.0.0
is-builtin-module: 5.0.0
jsesc: 3.1.0
@@ -13431,21 +13117,7 @@ snapshots:
dependencies:
eslint: 10.5.0(jiti@2.7.0)
optionalDependencies:
- '@typescript-eslint/eslint-plugin': 8.62.0(@typescript-eslint/parser@8.61.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
-
- eslint-plugin-vue@10.9.2(@stylistic/eslint-plugin@5.10.0(eslint@10.5.0(jiti@2.7.0)))(@typescript-eslint/parser@8.61.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(vue-eslint-parser@10.4.1(eslint@10.5.0(jiti@2.7.0))):
- dependencies:
- '@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0(jiti@2.7.0))
- eslint: 10.5.0(jiti@2.7.0)
- natural-compare: 1.4.0
- nth-check: 2.1.1
- postcss-selector-parser: 7.1.1
- semver: 7.8.5
- vue-eslint-parser: 10.4.1(eslint@10.5.0(jiti@2.7.0))
- xml-name-validator: 4.0.0
- optionalDependencies:
- '@stylistic/eslint-plugin': 5.10.0(eslint@10.5.0(jiti@2.7.0))
- '@typescript-eslint/parser': 8.61.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
+ '@typescript-eslint/eslint-plugin': 8.62.0(@typescript-eslint/parser@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3)
eslint-plugin-vue@10.9.2(@stylistic/eslint-plugin@5.10.0(eslint@10.5.0(jiti@2.7.0)))(@typescript-eslint/parser@8.62.0(eslint@10.5.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.5.0(jiti@2.7.0))(vue-eslint-parser@10.4.1(eslint@10.5.0(jiti@2.7.0))):
dependencies:
@@ -13774,8 +13446,6 @@ snapshots:
globals@15.15.0: {}
- globals@17.6.0: {}
-
globals@17.7.0: {}
globby@16.2.0:
@@ -14477,7 +14147,7 @@ snapshots:
d3-sankey: 0.12.3
dagre-d3-es: 7.0.14
dayjs: 1.11.19
- dompurify: 3.4.10
+ dompurify: 3.4.11
es-toolkit: 1.46.1
katex: 0.16.27
khroma: 2.1.0
@@ -15834,10 +15504,6 @@ snapshots:
dependencies:
rc: 1.2.8
- regjsparser@0.13.0:
- dependencies:
- jsesc: 3.1.0
-
regjsparser@0.13.2:
dependencies:
jsesc: 3.1.0
@@ -16140,7 +15806,7 @@ snapshots:
skills-npm@1.2.0:
dependencies:
- '@clack/prompts': 1.5.1
+ '@clack/prompts': 1.6.0
cac: 7.0.0
gray-matter: 4.0.3
picocolors: 1.1.1
@@ -17054,7 +16720,7 @@ snapshots:
'@docsearch/css': 4.5.4
'@docsearch/js': 4.5.4
'@docsearch/sidepanel-js': 4.5.4
- '@iconify-json/simple-icons': 1.2.86
+ '@iconify-json/simple-icons': 1.2.87
'@shikijs/core': 3.23.0
'@shikijs/transformers': 3.23.0
'@shikijs/types': 3.23.0
@@ -17183,7 +16849,7 @@ snapshots:
vue-router@5.1.0(@vue/compiler-sfc@3.5.38)(vite@8.1.0(@types/node@25.0.3)(@vitejs/devtools@packages+core)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.44.1)(tsx@4.22.4)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)):
dependencies:
- '@babel/generator': 8.0.0-rc.6
+ '@babel/generator': 8.0.0
'@vue-macros/common': 3.1.2(vue@3.5.38(typescript@6.0.3))
'@vue/devtools-api': 8.1.2
ast-walker-scope: 0.9.0
@@ -17207,7 +16873,7 @@ snapshots:
vue-router@5.1.0(@vue/compiler-sfc@3.5.38)(vite@8.1.0)(vue@3.5.38(typescript@6.0.3)):
dependencies:
- '@babel/generator': 8.0.0-rc.6
+ '@babel/generator': 8.0.0
'@vue-macros/common': 3.1.2(vue@3.5.38(typescript@6.0.3))
'@vue/devtools-api': 8.1.2
ast-walker-scope: 0.9.0