Skip to content
Merged
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
40 changes: 40 additions & 0 deletions docs/content/2.module/1.utils-kit.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,28 @@ export const devtoolsClient = useDevtoolsClient()

When the iframe been served with the same origin (CORS limitation), devtools will automatically inject `__NUXT_DEVTOOLS__` to the iframe's window object. You can access it as a ref using `useDevtoolsClient()` utility.

### `onDevtoolsReady()`

The client-side mirror of the server's [`onDevtoolsReady`](#ondevtoolsready). The
callback runs once the Vite DevTools client is connected and receives the
`DevToolsRpcClient` (the same object as `client.devtools.devtoolsKit`), giving
full devframe-native access. This is the recommended way to register client RPC
functions:

```ts
import { onDevtoolsReady } from '@nuxt/devtools-kit/iframe-client'

onDevtoolsReady((kit) => {
kit.client.register({
name: 'my-module:on-update',
type: 'event',
handler: (payload) => {
// ...
},
})
})
```

### `useDevtoolsClient()`

It will return a ref of `NuxtDevtoolsIframeClient` object that are intially `null` and will be updated when the connection is ready.
Expand All @@ -250,6 +272,24 @@ For example, you can get the router instance from the client app:
const router = computed(() => devtoolsClient.value?.host?.nuxt.vueApp.config.globalProperties?.$router)
```

The `devtools` object also exposes `devtoolsKit` — the connected Vite DevTools
RPC client (mirroring `nuxt.devtools.devtoolsKit` on the server) — for full
devframe-native access. Use it to register client RPC functions, call server
functions, and more:

```ts
const kit = devtoolsClient.value?.devtools.devtoolsKit
kit?.client.register({
name: 'my-module:on-update',
type: 'event',
handler: (payload) => {
// ...
},
})
```

This replaces the deprecated `devtools.extendClientRpc()`.

### `onDevtoolsClientConnected()`

Similiar to `useDevtoolsClient()` but as a callback style:
Expand Down
16 changes: 16 additions & 0 deletions packages/devtools-kit/src/_types/client-api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {} from '@nuxt/schema'
import type { DevToolsRpcClient } from '@vitejs/devtools-kit/client'
import type { Hookable } from 'hookable'
import type { NuxtApp } from 'nuxt/app'
import type { AppConfig } from 'nuxt/schema'
Expand Down Expand Up @@ -118,6 +119,21 @@ export interface NuxtDevtoolsClient {
renderMarkdown: (markdown: string) => string
colorMode: string

/**
* The connected Vite DevTools RPC client, mirroring `nuxt.devtools.devtoolsKit`
* on the server. Use it for full devframe-native access — register client RPC
* functions (`devtoolsKit.client.register(...)`), call server functions
* (`devtoolsKit.call(...)`), shared state, streaming, scoping, etc.
*
* `undefined` until the DevTools client has connected.
*/
devtoolsKit: DevToolsRpcClient | undefined

/**
* @deprecated Use `onDevtoolsReady((kit) => kit.client.register(...))` from
* `@nuxt/devtools-kit/iframe-client`, or the exposed `devtoolsKit` client
* directly (`client.devtools.devtoolsKit?.client.register(...)`).
*/
extendClientRpc: <ServerFunctions extends object = Record<string, unknown>, ClientFunctions extends object = Record<string, unknown>>(name: string, functions: ClientFunctions) => ServerFunctions
}

Expand Down
25 changes: 25 additions & 0 deletions packages/devtools-kit/src/runtime/iframe-client.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { DevToolsRpcClient } from '@vitejs/devtools-kit/client'
import type { Ref } from 'vue'
import type { NuxtDevtoolsIframeClient } from '../types'
import { shallowRef, triggerRef } from 'vue'
Expand Down Expand Up @@ -39,6 +40,30 @@ export function onDevtoolsClientConnected(fn: (client: NuxtDevtoolsIframeClient)
}
}

/**
* Run a callback once the Vite DevTools client is ready, receiving the connected
* `DevToolsRpcClient` — the client-side mirror of the server's
* `onDevtoolsReady((ctx) => …)`.
*
* This is the recommended way to do client-side DevTools integration (register
* client RPC functions, call server functions, shared state, streaming, …):
*
* ```ts
* import { onDevtoolsReady } from '@nuxt/devtools-kit/iframe-client'
*
* onDevtoolsReady((kit) => {
* kit.client.register({ name: 'my-module:on-update', type: 'event', handler })
* })
* ```
*/
export function onDevtoolsReady(fn: (kit: DevToolsRpcClient) => void) {
return onDevtoolsClientConnected((client) => {
const kit = client.devtools.devtoolsKit
if (kit)
fn(kit)
})
}

export function useDevtoolsClient() {
if (!clientRef) {
clientRef = shallowRef<NuxtDevtoolsIframeClient | undefined>()
Expand Down
18 changes: 18 additions & 0 deletions packages/devtools/client/composables/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@ import { renderMarkdown } from './client-services/markdown'
import { renderCodeHighlight } from './client-services/shiki'
import { connectPromise, rpc, rpcClient, upsertClientFunction } from './rpc'

let warnedExtendClientRpc = false
/**
* @deprecated `extendClientRpc` is deprecated. Use `onDevtoolsReady` from
* `@nuxt/devtools-kit/iframe-client`, or the exposed `devtoolsKit` client.
*/
function warnExtendClientRpcDeprecated() {
if (warnedExtendClientRpc)
return
warnedExtendClientRpc = true
console.warn(
'[nuxt-devtools] `extendClientRpc` is deprecated. Use `onDevtoolsReady((kit) => '
+ 'kit.client.register(...))` from `@nuxt/devtools-kit/iframe-client`.',
)
}

export function useClient() {
return useState<NuxtDevtoolsHostClient>('devtools-client')
}
Expand Down Expand Up @@ -52,6 +67,7 @@ export function useInjectionClient(): ComputedRef<NuxtDevtoolsIframeClient> {
host: client.value,
devtools: <NuxtDevtoolsClient>{
rpc,
devtoolsKit: rpcClient.value,
colorMode: mode.value,
renderCodeHighlight(code, lang) {
return renderCodeHighlight(code, lang as any)
Expand All @@ -60,9 +76,11 @@ export function useInjectionClient(): ComputedRef<NuxtDevtoolsIframeClient> {
return renderMarkdown(code)
},
extendClientRpc(namespace, functions) {
warnExtendClientRpcDeprecated()
const register = (client: DevToolsRpcClient) => {
for (const [name, handler] of Object.entries(functions)) {
if (typeof handler === 'function')
// force-registers (override by default) via upsertClientFunction
upsertClientFunction(client, `${namespace}:${name}`, handler as any)
}
}
Expand Down
15 changes: 9 additions & 6 deletions packages/devtools/client/composables/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,16 @@ export async function registerClientFunctions() {
* Register (or replace) a client-side event function on the devframe RPC host.
*
* devframe 0.6 split registration into `register()` (throws DF0021 if the name
* already exists) and `update()` (throws DF0022 if it does not), so pick the
* right one based on the current definitions to keep this an idempotent upsert.
* already exists) and `update()` (throws DF0022 if it does not). We always
* force-register so this stays an idempotent override regardless of the current
* state — the client re-runs registration on (re)connect and integrations may
* override a function, so neither error should ever surface.
*/
export function upsertClientFunction(client: DevToolsRpcClient, name: string, handler: (...args: any[]) => any) {
const definition = { name, type: 'event', handler } as const
if (client.client.definitions.has(name))
client.client.update(definition as any)
else
client.client.register(definition as any)
// The cast is a workaround for a devframe type gap: its `RpcFunctionsCollector`
// interface (used for the client host) omits the `force` parameter that the
// concrete `RpcFunctionsCollectorBase` and the runtime actually accept. Drop
// the cast once devframe types `register(fn, force?)` on the interface.
;(client.client.register as (fn: any, force?: boolean) => void)(definition, true)
}
Loading