v9.0.0-beta.0
Vue DevTools v9 is a rewrite of the Vue debugging surface on top of Vite DevTools and Devframe by @webfansplz (4df831). It keeps the familiar Components / Pages / Timeline / Pinia / Router / Graph workflow, and ships as a Vite plugin plus a Chromium extension.
See the Migration Guide for setup and plugin API changes.
🚨 Breaking Changes
- vite: Require Vite
8.3.0+and a project-level@vitejs/devtoolsinstall withdevtools: truein the Vite config - vite: Stop starting or branding the shared DevTools host from
vite-plugin-vue-devtools; registervueDevTools()as a Vue dock only - vite: Remove
componentInspectorandlaunchEditor. Component inspection lives in the Vue dock; open-in-editor is owned by@vitejs/devtoolsviavite:core:open-in-editor - vite: Move
builtinViteDevToolstodevtools.builtinDevTools, and moveembeddedVisibility/dockPreferencesunder Vite'sdevtoolsconfig.VueDevToolsDockPreferencesis no longer exported - api: Remove Vue-level
addCustomTab,addCustomCommand, andremoveCustomCommand. Use Devframectx.docks.register()andctx.commands.register()instead - Drop the overlay shell and split-screen layout from this release line
- Drop the Vite Assets tab (project file browser)
🚀 Features
- Rebuild the client as a Vite DevTools dock with Components, Pages, Timeline, Pinia, Router, Graph, Settings, and Command Palette
- client: Reactivity relationship Graph for the selected component (Vue 3.6+): refs, computed, effects, and their dependency links
- chrome: Frame-aware MV3 ports (
tabId+frameId+documentId) and per-frame runtimes, close #687, #745, #984 - client: Component search with positive terms,
-negative,tag:,file:, andis:favorite, close #304, #801, #1001, #1002 - client: Restore the selected component across reloads and keep favorites per app, close #467, #601
- client: Command Palette (
Ctrl/Cmd/Alt+K) shared across the Vite dock and Chrome panel - client: Optional render/update flash with mount/update duration (off by default), close #678, #699, #827
- kit: Typed state codec for
undefined/"undefined",null, NaN/Infinity, BigInt, Symbol, Date, RegExp, Error, functions, Map/Set, Proxy, cycles, and class instances, close #809, #906, #953, #1087, #1100 - kit: Keep the v6-style plugin setup API for custom inspectors,
_custom.actions, timeline layers, and plugin settings, close #761, #832, #918 - vite:
appendToacceptsstring | RegExp | Array<string | RegExp>; inject the client at most once per module, close #586 - vite: Open in editor only when the host exposes
capabilities.openInEditor, close #746, #774, #1093 - runtime: Highlighter uses a popover so locate still works over open
<dialog>elements, close #567, #892 - client: Recompute action on computed state (
triggerRef) for stale/lost-reactivity debugging, close #1041 - kit: Unaccessed computed values display
not accessedwithout running the getter, close #535
🐞 Bug Fixes
- chrome: Isolate iframe / micro-frontend apps and tear down ports on navigation, reload, BFCache, frame detach, and panel close, close #445, #926
- chrome: Skip non-HTML documents (XML, downloads, unsupported schemes), close #505
- kit: Isolate plugin inspector/layer IDs per plugin + app; a failing plugin no longer takes down the client, close #694
- kit: Prevent component/inspector state from leaking across selection, unmount, and reconnect, close #668, #689, #1111
- kit: Store as global variable (
$tempN) exposes the live runtime handle instead of a truncated preview - client: Do not render ordinary state strings as HTML, close #696
- kit: One owner per runtime listener so reconnect/HMR does not duplicate keyboard or component events, close #648
- kit: Isolate malformed custom timeline payloads to a single event, close #728
- client: Keep expand/collapse state when the tree filter changes; still match KeepAlive inactive children, close #787, #888, #1018
- client: Restore a horizontal scrollbar on the component tree, close #1001
- client: Tree
role=tree/treeitemwith roving focus, close #1018 - client: Use a font that distinguishes
0/O/1/I/lon component tags, close #1124 - rpc: Separate Chrome and Vite channels so a dual install no longer hijacks the other inspector, close #1004
- runtime: Remove locator overlay/listeners on cancel, route change, panel close, and dispose, close #1058
- runtime: Locator
mouseoveruses capture so nested components still highlight through@mouseover.stop, close #487 - kit: Collect setup state from
devtoolsRawSetupState/setupState/expose()whensetup()returns a render function or JSXdefineComponent, close #376, #853 - vite: Use Vite's resolved
basefor dock/client routes, close #803, #824, #914, #1013 - vite: Do not subscribe to or parse unrelated HMR payloads, close #1057
- vite: Drop
vite-plugin-inspect/vite-hot-client/vite-dev-rpc, close #1068
⚡ Performance
- perf(kit): Load component trees by depth and expand on demand instead of repeatedly refreshing the full tree; skip metadata and scheduled work for updates outside the visible tree unless their state is inspected. Related: #791
- perf(kit): Use lazy state previews and paged children to avoid serializing entire large values on first inspection. Closes #912 with lazy reads rather than streaming JSON
- perf(kit): Reduce batch mount/unmount bookkeeping with set-based child links; pair performance events by instance identity so registration cannot strand end events in a strongly held queue
- perf(kit): Yield scheduled work after a 4ms slice and apply a shared Timeline rate limit before encoding built-in or plugin payloads. Bound tree depth, update debounce, Timeline buffers, and transport message size/backpressure
- perf(client): Virtualize trees/lists, reuse the index for attribute-only tree patches, and cap pending Timeline events and history at 5,000 each. Process at most 250 entries per batch and show dropped-event markers. Closes #901
- perf(runtime): Gate collection on client attachment and allow Timeline recording/layers to be disabled. Related: #997
Remaining validation: #791 still needs representative large-app interaction checks; #997 is addressed by deferred collection and layer controls, not fully deferred runtime injection. Browser/network/GPU and long-session reports (#709, #806, #817, #835, #909) remain outside the closure claims. Manual component exclusion (#898) and Timeline JSON export (#1043) are not implemented here.
v8 vs v9 benchmark — ~10K components
Sample: 11,112 Vue components in a tree with 10 children per branch, plus 10,000 root-state records. Latest v8 main vs v9, on Apple M3 Pro / 18 GB RAM / macOS, headless Chrome, Node.js 24, and the same Vue 3.5 runtime. Timings are medians of five runs after warm-up.
| Measurement | v8 | v9 |
|---|---|---|
| Open panel → component tree visible | 540.4ms | 188.9ms |
| Batch update → inspected state painted | 810.9ms | 174.8ms |
| Main-thread CPU during batch update | 385.2ms | 111.7ms |
| Mount 11,111 components → tree visible | 694.0ms | 298.3ms |
| Select root component → state painted | 98.4ms | 87.6ms |
| Expand one branch → children painted | 66.9ms | 100.2ms |
| Stop Timeline recording during event flood | 3,577.8ms | 31.1ms |
| Initial tree payload | 2,064,622 bytes | 2,187 bytes |
| Initial state payload | 1,480,712 bytes | 10,217 bytes |
UI timings use the real iframe panels and include RPC, deferred refresh, and painting, rather than stopping at Vue nextTick. Main-thread CPU covers the same 1.1-second update window. Mount starts from an empty tree; component tests have Timeline recording off.
Timeline uses the same scheduled producer (250 emits every 50ms) and each version's default collection policy. This measures stop-button responsiveness, not equal event throughput: v9 drops excess events, while v8's producer also slows under load.
Initial payloads measure the first response, not full-tree expansion: v9 returns 12 tree nodes and lazy state previews. This reduces initial work but can make an individual branch expansion slower, as shown above.