diff --git a/.changeset/console-pipe-vite-forward-loop.md b/.changeset/console-pipe-vite-forward-loop.md new file mode 100644 index 000000000..306bddb99 --- /dev/null +++ b/.changeset/console-pipe-vite-forward-loop.md @@ -0,0 +1,6 @@ +--- +'@tanstack/devtools-bundler-core': patch +'@tanstack/devtools-vite': patch +--- + +Stop the console pipe feedback loop with Vite 8 `server.forwardConsole`. The server side of the pipe no longer sends browser logs that Vite printed in the terminal back to the browser. diff --git a/packages/devtools-bundler-core/src/virtual-console.test.ts b/packages/devtools-bundler-core/src/virtual-console.test.ts index 31788c278..ec5ff9001 100644 --- a/packages/devtools-bundler-core/src/virtual-console.test.ts +++ b/packages/devtools-bundler-core/src/virtual-console.test.ts @@ -7,8 +7,30 @@ afterEach(() => { vi.useRealTimers() vi.unstubAllGlobals() delete (window as any).__TSD_CONSOLE_PIPE_INITIALIZED__ + delete (globalThis as any).__TSD_SERVER_CONSOLE_PIPE_INITIALIZED__ }) +function setupServerErrorConsolePipe() { + const originalError = console.error + const originalErrorMock = vi.fn() + const fetchMock = vi.fn().mockResolvedValue(undefined) + + console.error = originalErrorMock + vi.stubGlobal('fetch', fetchMock) + + // Shadow `window` so the generated code takes its server branch. + const code = generateConsolePipeCode(['error'], TEST_VITE_URL) + new Function('window', code)(undefined) + + return { + fetchMock, + originalErrorMock, + restore: () => { + console.error = originalError + }, + } +} + function setupWarnConsolePipe() { const originalWarn = console.warn const originalWarnMock = vi.fn() @@ -241,4 +263,36 @@ describe('virtual-console', () => { restore() } }) + + test('does not send browser logs that Vite forwarded to the terminal back to the browser', async () => { + vi.useFakeTimers() + + const { fetchMock, originalErrorMock, restore } = + setupServerErrorConsolePipe() + + try { + // The shape of a Vite 8 `server.forwardConsole` line. + const forwarded = + '\x1b[2m9:27:59 PM\x1b[22m \x1b[36m\x1b[1m[vite]\x1b[22m\x1b[39m \x1b[2m(client)\x1b[22m \x1b[2m[console.error] \x1b[22m[Server] seed' + + console.error(forwarded) + await vi.advanceTimersByTimeAsync(100) + + expect(originalErrorMock).toHaveBeenCalledWith(forwarded) + expect(fetchMock).not.toHaveBeenCalled() + + // A server log that only mentions the marker is still sent. + console.error('request hit [console.error] detail') + await vi.advanceTimersByTimeAsync(100) + + expect(fetchMock).toHaveBeenCalledTimes(1) + expect(getFirstFetchBody(fetchMock).entries[0]).toMatchObject({ + level: 'error', + source: 'server', + args: ['request hit [console.error] detail'], + }) + } finally { + restore() + } + }) }) diff --git a/packages/devtools-bundler-core/src/virtual-console.ts b/packages/devtools-bundler-core/src/virtual-console.ts index 04aa02527..a221006e6 100644 --- a/packages/devtools-bundler-core/src/virtual-console.ts +++ b/packages/devtools-bundler-core/src/virtual-console.ts @@ -251,6 +251,15 @@ export function generateConsolePipeCode( return; } + // Vite 8 \`server.forwardConsole\` prints browser console calls in the + // terminal as "