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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/devtools-kit/src/_types/server-ctx.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { DevToolsNodeContext } from '@vitejs/devtools-kit'
import type { BirpcGroup } from 'birpc'
import type { Nuxt, NuxtDebugModuleMutationRecord } from 'nuxt/schema'
import type { ModuleOptions } from './options'
Expand Down Expand Up @@ -31,6 +32,11 @@ export interface NuxtDevtoolsServerContext {

rpc: NuxtDevtoolsRpc

/**
* The Vite DevTools Kit context, available after connection.
*/
devtoolsKit: DevToolsNodeContext | undefined

/**
* Hook to open file in editor
*/
Expand Down
44 changes: 15 additions & 29 deletions packages/devtools/src/integrations/vite-inspect.ts
Original file line number Diff line number Diff line change
@@ -1,67 +1,53 @@
import type { Plugin } from 'vite'
import type { ViteInspectAPI, ViteInspectOptions } from 'vite-plugin-inspect'
import type { ViteInspectOptions } from 'vite-plugin-inspect'
import type { NuxtDevtoolsServerContext } from '../types'
import { addCustomTab } from '@nuxt/devtools-kit'
import { addVitePlugin } from '@nuxt/kit'
import { addVitePlugin, logger } from '@nuxt/kit'

const DOUBLE_SLASH_RE = /\/\//g
const VERSION_QUERY_RE = /\?v=\w+$/
const VUE_EXT_RE = /\.vue($|\?v=)/

export async function createVitePluginInspect(options?: ViteInspectOptions): Promise<Plugin> {
return await import('vite-plugin-inspect').then(r => r.default(options))
}

export async function setup({ nuxt, rpc }: NuxtDevtoolsServerContext) {
export async function setup({ rpc, devtoolsKit }: NuxtDevtoolsServerContext) {
const plugin = await createVitePluginInspect()
addVitePlugin(plugin)

let api: ViteInspectAPI | undefined

nuxt.hook('vite:serverCreated', () => {
api = plugin.api
})

addCustomTab(() => ({
name: 'builtin-vite-inspect',
title: 'Inspect',
icon: 'carbon-ibm-watson-discovery',
category: 'advanced',
view: {
type: 'iframe',
src: `${nuxt.options.app.baseURL}${nuxt.options.app.buildAssetsDir}/__inspect/`.replace(DOUBLE_SLASH_RE, '/'),
},
}), nuxt)

async function getComponentsRelationships() {
const meta = await api?.rpc.getMetadata()
if (!devtoolsKit?.rpc.has('vite-plugin-inspect:get-metadata')) {
logger.warn('[nuxt-devtools] vite-plugin-inspect RPC functions not registered, component relationships unavailable')
return []
}

const meta = await devtoolsKit.rpc.invokeLocal('vite-plugin-inspect:get-metadata' as any)
const modules = (
meta && meta.instances[0]
? await api?.rpc.getModulesList({
? await devtoolsKit.rpc.invokeLocal('vite-plugin-inspect:get-modules-list' as any, {
vite: meta.instances[0].vite,
env: meta.instances[0].environments[0]!,
})
: null
) || []

const components = await rpc.functions.getComponents() || []
const vueModules = modules.filter((m) => {
const vueModules = modules.filter((m: any) => {
const plainId = m.id.replace(VERSION_QUERY_RE, '')
if (components.some(c => c.filePath === plainId))
return true
return m.id.match(VUE_EXT_RE)
})

const graph = vueModules.map((i) => {
const graph = vueModules.map((i: any) => {
function searchForVueDeps(id: string, seen = new Set<string>()): string[] {
if (seen.has(id))
return []
seen.add(id)
const module = modules.find(m => m.id === id)
const module = modules.find((m: any) => m.id === id)
if (!module)
return []
return module.deps.flatMap((i) => {
if (vueModules.some(m => m.id === i))
return module.deps.flatMap((i: string) => {
if (vueModules.some((m: any) => m.id === i))
return [i]
return searchForVueDeps(i, seen)
})
Expand Down
10 changes: 5 additions & 5 deletions packages/devtools/src/module-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export async function enableModule(options: ModuleOptions, nuxt: Nuxt) {
addVitePlugin(DevTools)

// Deferred: will be set when Vite DevTools plugin setup runs
let connectRpcHost: ((host: any) => void) | undefined
let connectDevToolsKit: ((ctx: any) => void) | undefined

addVitePlugin(defineViteDevToolsPlugin({
name: 'nuxt:devtools',
Expand All @@ -78,8 +78,8 @@ export async function enableModule(options: ModuleOptions, nuxt: Nuxt) {
url: '/__nuxt_devtools__/client/',
})

// Connect Nuxt DevTools RPC to Vite DevTools Kit's RPC host
connectRpcHost?.(ctx.rpc)
// Connect Nuxt DevTools to Vite DevTools Kit context
connectDevToolsKit?.(ctx)
},
},
}))
Expand Down Expand Up @@ -130,11 +130,11 @@ window.__NUXT_DEVTOOLS_TIME_METRIC__.appInit = Date.now()
})

const {
connectRpcHost: _connectRpcHost,
connectDevToolsKit: _connectDevToolsKit,
...ctx
} = setupRPC(nuxt, options)

connectRpcHost = _connectRpcHost
connectDevToolsKit = _connectDevToolsKit
Comment on lines 132 to +137
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -name "module-main.ts" -path "*/devtools/src/*" | head -5

Repository: nuxt/devtools

Length of output: 95


🏁 Script executed:

find . -name "*.ts" -path "*/devtools/*" | grep -E "(rpc|setup)" | head -10

Repository: nuxt/devtools

Length of output: 528


🏁 Script executed:

# Look at the module-main.ts file around lines 132-137
cat -n packages/devtools/src/module-main.ts | sed -n '120,150p'

Repository: nuxt/devtools

Length of output: 1055


🏁 Script executed:

# Find where setupRPC is defined
rg "export.*setupRPC|function setupRPC|const setupRPC" --type ts

Repository: nuxt/devtools

Length of output: 161


🏁 Script executed:

# Read the setupRPC implementation
cat -n packages/devtools/src/server-rpc/index.ts | head -100

Repository: nuxt/devtools

Length of output: 4176


🏁 Script executed:

# Check if devtoolsKit is mentioned and defined as a getter
rg "devtoolsKit" packages/devtools/src/server-rpc/index.ts -B 3 -A 3

Repository: nuxt/devtools

Length of output: 1814


🏁 Script executed:

# Look for where ctx is used after destructuring to understand impact
rg "ctx\." packages/devtools/src/module-main.ts -B 2 -A 2 | head -50

Repository: nuxt/devtools

Length of output: 182


🏁 Script executed:

# See more of setupRPC return statement
rg "return \{" packages/devtools/src/server-rpc/index.ts -A 30 | head -50

Repository: nuxt/devtools

Length of output: 911


🏁 Script executed:

# Check how ctx is used downstream - where is it passed?
rg "setupRPC\(nuxt, options\)" packages/devtools/src/module-main.ts -B 2 -A 15

Repository: nuxt/devtools

Length of output: 521


🏁 Script executed:

# Check if ctx is passed to any setup functions or integrations
rg "ctx\." packages/devtools/src/module-main.ts

Repository: nuxt/devtools

Length of output: 85


🏁 Script executed:

# Find the full return statement and see what setupRPC actually returns
rg "return \{" packages/devtools/src/server-rpc/index.ts -A 5 | tail -20

Repository: nuxt/devtools

Length of output: 249


🏁 Script executed:

# Check if there are any later uses of ctx.devtoolsKit in module-main.ts
rg "devtoolsKit" packages/devtools/src/module-main.ts

Repository: nuxt/devtools

Length of output: 39


🏁 Script executed:

# See if the destructured ctx variable is actually used after the destructuring
sed -n '132,200p' packages/devtools/src/module-main.ts | cat -n

Repository: nuxt/devtools

Length of output: 2956


Preserve the live devtoolsKit accessor.

Object rest destructuring at line 132 materializes the devtoolsKit getter into a plain undefined property before connectDevToolsKit() is called. Since ctx is then passed to integrations (e.g., at lines 64, 67), they all receive a permanently stale devtoolsKit value.

Suggested fix
-  const {
-    connectDevToolsKit: _connectDevToolsKit,
-    ...ctx
-  } = setupRPC(nuxt, options)
-
-  connectDevToolsKit = _connectDevToolsKit
+  const ctx = setupRPC(nuxt, options)
+  connectDevToolsKit = ctx.connectDevToolsKit
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const {
connectRpcHost: _connectRpcHost,
connectDevToolsKit: _connectDevToolsKit,
...ctx
} = setupRPC(nuxt, options)
connectRpcHost = _connectRpcHost
connectDevToolsKit = _connectDevToolsKit
const ctx = setupRPC(nuxt, options)
connectDevToolsKit = ctx.connectDevToolsKit
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools/src/module-main.ts` around lines 132 - 137, The object rest
(`...ctx`) from setupRPC is materializing the devtoolsKit getter into a plain
property, causing integrations that receive ctx to see a stale/undefined
devtoolsKit; instead, keep the original object returned by setupRPC so the live
getter is preserved: call const fullCtx = setupRPC(nuxt, options), set
connectDevToolsKit = fullCtx.connectDevToolsKit (or extract _connectDevToolsKit
from fullCtx), and pass fullCtx (not the spread ctx) into integrations and other
consumers so devtoolsKit remains a live accessor; reference setupRPC,
connectDevToolsKit/_connectDevToolsKit, devtoolsKit, and ctx in the change.


const clientDirExists = existsSync(clientDir)

Expand Down
30 changes: 16 additions & 14 deletions packages/devtools/src/server-rpc/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { RpcFunctionsHost } from '@vitejs/devtools-kit'
import type { DevToolsNodeContext } from '@vitejs/devtools-kit'
import type { Nuxt } from 'nuxt/schema'

import type { ModuleOptions, NuxtDevtoolsServerContext, ServerFunctions } from '../types'
Expand All @@ -21,15 +21,15 @@ import { setupTimelineRPC } from './timeline'
export function setupRPC(nuxt: Nuxt, options: ModuleOptions) {
const serverFunctions = {} as ServerFunctions
const extendedRpcMap = new Map<string, Record<string, (...args: any[]) => any>>()
let rpcHost: RpcFunctionsHost | undefined
let devtoolsKitCtx: DevToolsNodeContext | undefined
const pendingBroadcasts: { method: string, args: any[] }[] = []

function broadcast(method: string, ...args: any[]) {
if (!rpcHost) {
if (!devtoolsKitCtx) {
pendingBroadcasts.push({ method, args })
return
}
rpcHost.broadcast({
devtoolsKitCtx.rpc.broadcast({
method: method as any,
args: args as any,
event: true,
Expand Down Expand Up @@ -61,12 +61,12 @@ export function setupRPC(nuxt: Nuxt, options: ModuleOptions) {
set(target, prop, value) {
(target as any)[prop] = value
// Also update on RpcFunctionsHost if available
if (rpcHost && typeof prop === 'string') {
if (rpcHost.has(prop)) {
rpcHost.update({ name: prop, handler: value })
if (devtoolsKitCtx && typeof prop === 'string') {
if (devtoolsKitCtx.rpc.has(prop)) {
devtoolsKitCtx.rpc.update({ name: prop, handler: value })
}
else {
rpcHost.register({ name: prop, handler: value })
devtoolsKitCtx.rpc.register({ name: prop, handler: value })
}
}
return true
Expand All @@ -86,10 +86,10 @@ export function setupRPC(nuxt: Nuxt, options: ModuleOptions) {
extendedRpcMap.set(namespace, functions)

// Register on RpcFunctionsHost if already available
if (rpcHost) {
if (devtoolsKitCtx) {
for (const [fnName, handler] of Object.entries(functions)) {
if (typeof handler === 'function') {
rpcHost.register({ name: `${namespace}:${fnName}`, handler: handler as any })
devtoolsKitCtx.rpc.register({ name: `${namespace}:${fnName}`, handler: handler as any })
}
}
}
Expand All @@ -106,6 +106,7 @@ export function setupRPC(nuxt: Nuxt, options: ModuleOptions) {
nuxt,
options,
rpc: rpc as any,
get devtoolsKit() { return devtoolsKitCtx },
refresh,
extendServerRpc,
openInEditorHooks: [],
Expand Down Expand Up @@ -135,11 +136,12 @@ export function setupRPC(nuxt: Nuxt, options: ModuleOptions) {
} as ServerFunctions)

/**
* Connect to Vite DevTools Kit's RPC host.
* Connect to Vite DevTools Kit context.
* Called from the Vite DevTools plugin setup callback.
*/
function connectRpcHost(host: RpcFunctionsHost) {
rpcHost = host
function connectDevToolsKit(ctx: DevToolsNodeContext) {
devtoolsKitCtx = ctx
const host = ctx.rpc

// Flush any broadcasts that were queued before connection
for (const { method, args } of pendingBroadcasts) {
Expand Down Expand Up @@ -187,7 +189,7 @@ export function setupRPC(nuxt: Nuxt, options: ModuleOptions) {
}

return {
connectRpcHost,
connectDevToolsKit,
...ctx,
}
}
Loading
Loading