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
58 changes: 28 additions & 30 deletions packages/devtools/src/integrations/data-inspector.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Nitro } from 'nitropack'
import type { Nuxt } from 'nuxt/schema'
import type { ResolvedConfig } from 'vite'
import type { ResolvedConfig, ViteDevServer } from 'vite'
import type { NuxtDevtoolsServerContext, NuxtServerData } from '../types'
import { createDataInspectorDevframe, registerDataSource } from '@devframes/plugin-data-inspector'
import { deprecate, NUXT_DEVTOOLS_GROUP_ID, onDevtoolsReady } from '@nuxt/devtools-kit'
Expand All @@ -19,8 +19,7 @@ import { mountDevframe } from '@vitejs/devtools-kit/node'
*/
interface CapturedServerData {
nitro?: Nitro
viteClient?: ResolvedConfig
viteSsr?: ResolvedConfig
vite?: ViteDevServer
}

const captured: CapturedServerData = {}
Expand Down Expand Up @@ -70,18 +69,14 @@ export function setup(ctx: NuxtDevtoolsServerContext): void {
captured.nitro = nitro
})

// Capture the raw resolved Vite config for each environment. Nuxt fires
// `vite:configResolved` once with `isClient` and once with `isServer`, for
// both serial Vite servers and Environment API projections, so the source
// contract stays exactly `vite: { client, ssr }`. Nuxt marks this hook
// deprecated, but it is the only current host API that reports both configs
// semantically; the returned-environment-plugin path is unreliable in Vite 8
// (Vite resolves those plugins after top-level `configResolved`).
nuxt.hook('vite:configResolved', (config, env) => {
if (env.isClient)
captured.viteClient = config as unknown as ResolvedConfig
if (env.isServer)
captured.viteSsr = config as unknown as ResolvedConfig
// Capture the live Vite dev server instance. Nuxt 5 unified the former client
// and SSR Vite servers into a single dev server, so there is exactly one
// instance to capture. The server carries far more than the resolved config
// (which lives at `server.config`): its runtime `environments` (client/ssr),
// module graph, plugin containers, watcher, and websocket. The Data Inspector
// engine handles the deep/circular/function-valued branches.
nuxt.hook('vite:serverCreated', (server) => {
captured.vite = server as unknown as ViteDevServer
})

// Register the single live source early with a non-static factory, so Nuxt
Expand All @@ -97,25 +92,23 @@ export function setup(ctx: NuxtDevtoolsServerContext): void {
data: () => ({
nuxt: nuxt.options,
nitro: captured.nitro?.options,
vite: {
client: captured.viteClient,
ssr: captured.viteSsr,
},
vite: captured.vite,
}),
queries: [
{ title: 'Overview', query: '', excludeFunctions: true },
{ title: 'Nuxt options', query: 'nuxt', excludeFunctions: true },
{ title: 'Nitro options', query: 'nitro', excludeFunctions: true },
{ title: 'Vite configs', query: 'vite', excludeFunctions: true },
{ title: 'Nuxt modules', query: 'nuxt.modules', excludeFunctions: true },
{ title: 'Vite plugins', query: 'vite.config.plugins', excludeFunctions: true },
{ title: 'Vite config', query: 'vite.config', excludeFunctions: true },
{ title: 'Vite environments', query: 'vite.environments', excludeFunctions: true },
{ title: 'Nitro routes', query: 'nitro.handlers', excludeFunctions: true },
],
})

// Avoid leaking a process-global source (e.g. across test fixtures).
nuxt.hook('close', () => {
unregister()
captured.nitro = undefined
captured.viteClient = undefined
captured.viteSsr = undefined
captured.vite = undefined
})

// Mount the Data Inspector's bundled SPA as a member of the Nuxt group. The
Expand All @@ -141,20 +134,26 @@ export function setup(ctx: NuxtDevtoolsServerContext): void {
* use; it will be removed in a future major.
*
* Unlike the live source, this returns the legacy `NuxtServerData` shape
* (`vite: { server, client }`) with the Vite configs normalized for RPC
* transport.
* (`vite: { server, client }`) with the resolved Vite config normalized for RPC
* transport. Nuxt 5 unified the former client and SSR Vite servers into a
* single dev server instance, but the legacy shape predates that; this shim
* keeps the old contract alive independently by reading the one dev server's
* resolved config (`server.config`) and projecting it onto both `server` and
* `client` (each normalized separately, so consumers that mutate one field
* don't affect the other).
*/
export function getServerData(nuxt: Nuxt): NuxtServerData {
deprecate(nuxt, 'NDT_DEP_0009', {
api: 'getServerData()',
replacement: 'the Nuxt Application source in the Data Inspector panel',
})
const config = captured.vite?.config
return {
nuxt: nuxt.options,
nitro: captured.nitro?.options,
vite: {
server: captured.viteSsr ? normalizeViteConfig(captured.viteSsr) : undefined,
client: captured.viteClient ? normalizeViteConfig(captured.viteClient) : undefined,
server: config ? normalizeViteConfig(config) : undefined,
client: config ? normalizeViteConfig(config) : undefined,
},
// `nuxt.options` is `@nuxt/schema`'s `NuxtOptions` while `NuxtServerData`
// pins the structurally-identical `nuxt/schema` one; bridge the two.
Expand All @@ -166,6 +165,5 @@ export function getServerData(nuxt: Nuxt): NuxtServerData {
*/
export function resetCapturedServerData(): void {
captured.nitro = undefined
captured.viteClient = undefined
captured.viteSsr = undefined
captured.vite = undefined
}
64 changes: 40 additions & 24 deletions packages/devtools/test/data-inspector.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Nuxt } from 'nuxt/schema'
import type { ResolvedConfig } from 'vite'
import type { ResolvedConfig, ViteDevServer } from 'vite'
import {
getDataSource,
resetDataSources,
Expand Down Expand Up @@ -50,12 +50,23 @@ function fakeViteConfig(overrides: Partial<ResolvedConfig> = {}): ResolvedConfig
} as unknown as ResolvedConfig
}

/** Drive Nuxt's `vite:configResolved` hook for one environment. */
async function captureViteEnv(fake: FakeNuxt, envName: 'client' | 'ssr', config: ResolvedConfig) {
await fake.callHook('vite:configResolved', config, {
isClient: envName === 'client',
isServer: envName === 'ssr',
})
/**
* Minimal Vite dev server stub. The resolved config lives at `.config`, and the
* runtime environments at `.environments`, mirroring `ViteDevServer`.
*/
function fakeViteServer(config: ResolvedConfig = fakeViteConfig()): ViteDevServer {
return {
config,
environments: { client: { name: 'client' }, ssr: { name: 'ssr' } },
} as unknown as ViteDevServer
}

/**
* Drive Nuxt's `vite:serverCreated` hook. Nuxt 5 unified the client and SSR
* Vite servers into a single instance, so this fires once with one dev server.
*/
async function captureVite(fake: FakeNuxt, server: ViteDevServer) {
await fake.callHook('vite:serverCreated', server)
}

beforeEach(() => {
Expand All @@ -77,16 +88,18 @@ describe('data-inspector source registration', () => {
expect(typeof entry!.data).toBe('function')
})

it('exposes the four read-only suggested queries with function exclusion', () => {
it('exposes the read-only suggested queries with function exclusion', () => {
const { nuxt } = fakeNuxt()
setup({ nuxt } as any)

const queries = getDataSource('nuxt:application')!.queries!
expect(queries).toEqual([
{ title: 'Overview', query: '', excludeFunctions: true },
{ title: 'Nuxt options', query: 'nuxt', excludeFunctions: true },
{ title: 'Nitro options', query: 'nitro', excludeFunctions: true },
{ title: 'Vite configs', query: 'vite', excludeFunctions: true },
{ title: 'Nuxt modules', query: 'nuxt.modules', excludeFunctions: true },
{ title: 'Vite plugins', query: 'vite.config.plugins', excludeFunctions: true },
{ title: 'Vite config', query: 'vite.config', excludeFunctions: true },
{ title: 'Vite environments', query: 'vite.environments', excludeFunctions: true },
{ title: 'Nitro routes', query: 'nitro.handlers', excludeFunctions: true },
])
})

Expand All @@ -98,26 +111,25 @@ describe('data-inspector source registration', () => {
const data = await resolveSourceData(getDataSource('nuxt:application')!) as any
expect(data.nuxt).toBe(options)
expect(data.nitro).toBeUndefined()
expect(data.vite).toEqual({ client: undefined, ssr: undefined })
expect(data.vite).toBeUndefined()
})

it('populates Nitro and raw Vite configs after the hooks fire', async () => {
it('populates Nitro and the raw unified Vite dev server after the hooks fire', async () => {
const fake = fakeNuxt()
setup({ nuxt: fake.nuxt } as any)

const nitro = { options: { preset: 'node' } }
await fake.callHook('nitro:build:before', nitro)

const client = fakeViteConfig({ marker: 'client' } as any)
const ssr = fakeViteConfig({ marker: 'ssr' } as any)
await captureViteEnv(fake, 'client', client)
await captureViteEnv(fake, 'ssr', ssr)
const server = fakeViteServer(fakeViteConfig({ marker: 'vite' } as any))
await captureVite(fake, server)

const data = await resolveSourceData(getDataSource('nuxt:application')!) as any
expect(data.nitro).toBe(nitro.options)
// The live source hands the RAW config objects to the Data Inspector engine.
expect(data.vite.client).toBe(client)
expect(data.vite.ssr).toBe(ssr)
// The live source hands the RAW dev server instance to the Data Inspector
// engine; the resolved config is reachable at `vite.config`.
expect(data.vite).toBe(server)
expect(data.vite.config).toBe(server.config)
})

it('unregisters the source on the Nuxt `close` hook', async () => {
Expand All @@ -131,28 +143,32 @@ describe('data-inspector source registration', () => {
})

describe('getServerData deprecated shim', () => {
it('returns the legacy `vite: { server, client }` shape with normalized configs', async () => {
it('projects the unified config onto the legacy `vite: { server, client }` shape, normalized', async () => {
const options = { appId: 'app' }
const fake = fakeNuxt(options)
setup({ nuxt: fake.nuxt } as any)

const nitro = { options: { preset: 'node' } }
await fake.callHook('nitro:build:before', nitro)
await captureViteEnv(fake, 'client', fakeViteConfig())
await captureViteEnv(fake, 'ssr', fakeViteConfig())
await captureVite(fake, fakeViteServer())

const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
const data = getServerData(fake.nuxt)
warn.mockRestore()

expect(data.nuxt).toBe(options)
expect(data.nitro).toBe(nitro.options)
// Legacy field naming: `ssr` capture maps to `vite.server`.
// Nuxt 5 has one Vite dev server; the shim reads its resolved config
// (`server.config`) and projects it onto both fields.
expect(data.vite).toHaveProperty('server')
expect(data.vite).toHaveProperty('client')
// Each field is normalized independently (separate object identities).
expect(data.vite.server).not.toBe(data.vite.client)
// Normalization strips the live Vite branches for RPC transport.
expect(data.vite.server!.inlineConfig).toBeNull()
expect((data.vite.server!.plugins[0] as any).api).toBeUndefined()
expect(data.vite.client!.inlineConfig).toBeNull()
expect((data.vite.client!.plugins[0] as any).api).toBeUndefined()
})

it('emits the NDT_DEP_0009 deprecation once per process', () => {
Expand Down
Loading