diff --git a/packages/devtools/src/integrations/data-inspector.ts b/packages/devtools/src/integrations/data-inspector.ts index 5f2c560d9f..0bcdc80364 100644 --- a/packages/devtools/src/integrations/data-inspector.ts +++ b/packages/devtools/src/integrations/data-inspector.ts @@ -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' @@ -19,8 +19,7 @@ import { mountDevframe } from '@vitejs/devtools-kit/node' */ interface CapturedServerData { nitro?: Nitro - viteClient?: ResolvedConfig - viteSsr?: ResolvedConfig + vite?: ViteDevServer } const captured: CapturedServerData = {} @@ -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 @@ -97,16 +92,15 @@ 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 }, ], }) @@ -114,8 +108,7 @@ export function setup(ctx: NuxtDevtoolsServerContext): void { 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 @@ -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. @@ -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 } diff --git a/packages/devtools/test/data-inspector.test.ts b/packages/devtools/test/data-inspector.test.ts index b731cd368d..71ab643d78 100644 --- a/packages/devtools/test/data-inspector.test.ts +++ b/packages/devtools/test/data-inspector.test.ts @@ -1,5 +1,5 @@ import type { Nuxt } from 'nuxt/schema' -import type { ResolvedConfig } from 'vite' +import type { ResolvedConfig, ViteDevServer } from 'vite' import { getDataSource, resetDataSources, @@ -50,12 +50,23 @@ function fakeViteConfig(overrides: Partial = {}): 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(() => { @@ -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 }, ]) }) @@ -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 () => { @@ -131,15 +143,14 @@ 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) @@ -147,12 +158,17 @@ describe('getServerData deprecated shim', () => { 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', () => {