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 .changeset/console-pipe-vite-forward-loop.md
Original file line number Diff line number Diff line change
@@ -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.
54 changes: 54 additions & 0 deletions packages/devtools-bundler-core/src/virtual-console.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
}
})
})
9 changes: 9 additions & 0 deletions packages/devtools-bundler-core/src/virtual-console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,15 @@ export function generateConsolePipeCode(
return;
}

// Vite 8 \`server.forwardConsole\` prints browser console calls in the
// terminal as "<time> [vite] (client) [console.<level>] ...". Sending
// those back to the browser as server logs makes the browser log them
// again, and Vite forwards them again, without end.
if (isServer && args.length > 0 && typeof args[0] === 'string' &&
/\\[vite\\].*\\(client\\).*\\[console\\.[a-z]+\\] /.test(args[0])) {
return;
}

Comment thread
coderabbitai[bot] marked this conversation as resolved.
var safeArgs = args.map(function(arg) {
try {
return serializeConsoleArg(arg, [], 0);
Expand Down
Loading