From 460413e3c28505d97b11a957a9fae74c8d557a67 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Wed, 28 Jan 2026 11:38:53 +0100 Subject: [PATCH 01/14] feat: console piping --- packages/devtools-client/package.json | 5 +- packages/devtools-client/src/index.ts | 48 ++++ packages/devtools-vite/package.json | 3 +- packages/devtools-vite/src/console-pipe.ts | 81 ++++++ .../devtools-vite/src/enhance-logs.test.ts | 2 +- packages/devtools-vite/src/index.ts | 2 +- packages/devtools-vite/src/plugin.ts | 242 +++++++++++++++++- packages/devtools-vite/src/utils.ts | 57 ++++- .../devtools-vite/src/virtual-console.test.ts | 63 +++++ packages/devtools-vite/src/virtual-console.ts | 121 +++++++++ 10 files changed, 607 insertions(+), 17 deletions(-) create mode 100644 packages/devtools-vite/src/console-pipe.ts create mode 100644 packages/devtools-vite/src/virtual-console.test.ts create mode 100644 packages/devtools-vite/src/virtual-console.ts diff --git a/packages/devtools-client/package.json b/packages/devtools-client/package.json index 8f75af99..71e16b14 100644 --- a/packages/devtools-client/package.json +++ b/packages/devtools-client/package.json @@ -32,7 +32,8 @@ "node": ">=18" }, "dependencies": { - "@tanstack/devtools-event-client": "workspace:^" + "@tanstack/devtools-event-client": "workspace:^", + "@tanstack/pacer": "^0.17.3" }, "files": [ "dist/", @@ -48,4 +49,4 @@ "test:build": "publint --strict", "build": "vite build" } -} +} \ No newline at end of file diff --git a/packages/devtools-client/src/index.ts b/packages/devtools-client/src/index.ts index c0d33534..7ecfbd62 100644 --- a/packages/devtools-client/src/index.ts +++ b/packages/devtools-client/src/index.ts @@ -1,5 +1,14 @@ import { EventClient } from '@tanstack/devtools-event-client' +export type ConsoleLevel = 'log' | 'warn' | 'error' | 'info' | 'debug' + +export interface ConsoleLogEntry { + level: ConsoleLevel + args: Array + source: string + timestamp: number +} + export interface PackageJson { name?: string version?: string @@ -86,6 +95,12 @@ interface EventMap { 'tanstack-devtools-core:trigger-toggled': { isOpen: boolean } + 'tanstack-devtools-core:client-console': { + entries: Array + } + 'tanstack-devtools-core:server-console': { + entries: Array + } } export class DevtoolsEventClient extends EventClient { @@ -98,4 +113,37 @@ export class DevtoolsEventClient extends EventClient { const devtoolsEventClient = new DevtoolsEventClient() +// Store original console methods to avoid infinite loops +const originalConsole: Record) => void> = + { + log: console.log.bind(console), + warn: console.warn.bind(console), + error: console.error.bind(console), + info: console.info.bind(console), + debug: console.debug.bind(console), + } + +// Listen for server console logs and pipe them to the browser console +// Only set up listener on client side +if (typeof window !== 'undefined') { + devtoolsEventClient.on('server-console', (event) => { + for (const entry of event.payload.entries) { + const prefix = '%c[Server]%c' + const prefixStyle = 'color: #9333ea; font-weight: bold;' // purple + const resetStyle = 'color: inherit;' + const source = `%c${entry.source}%c` + const sourceStyle = 'color: #6b7280;' // gray + const logMethod = originalConsole[entry.level] + logMethod( + prefix + ' ' + source, + prefixStyle, + resetStyle, + sourceStyle, + resetStyle, + ...entry.args, + ) + } + }) +} + export { devtoolsEventClient } diff --git a/packages/devtools-vite/package.json b/packages/devtools-vite/package.json index 7f9d0eb8..8ea11474 100644 --- a/packages/devtools-vite/package.json +++ b/packages/devtools-vite/package.json @@ -58,6 +58,7 @@ "@babel/types": "^7.28.4", "@tanstack/devtools-client": "workspace:*", "@tanstack/devtools-event-bus": "workspace:*", + "@tanstack/pacer": "^0.17.3", "chalk": "^5.6.2", "launch-editor": "^2.11.1", "picomatch": "^4.0.3" @@ -69,4 +70,4 @@ "@types/picomatch": "^4.0.2", "happy-dom": "^18.0.1" } -} +} \ No newline at end of file diff --git a/packages/devtools-vite/src/console-pipe.ts b/packages/devtools-vite/src/console-pipe.ts new file mode 100644 index 00000000..b0d50385 --- /dev/null +++ b/packages/devtools-vite/src/console-pipe.ts @@ -0,0 +1,81 @@ +import { Batcher } from '@tanstack/pacer/batcher' +import { devtoolsEventClient } from '@tanstack/devtools-client' +import type { ConsoleLevel } from './plugin' + +export interface ConsoleLogEntry { + level: ConsoleLevel + args: Array + source: string + timestamp: number +} + +// Store original console methods +const originalConsole: Record) => void> = + { + log: console.log.bind(console), + warn: console.warn.bind(console), + error: console.error.bind(console), + info: console.info.bind(console), + debug: console.debug.bind(console), + } + +// Detect environment +const isServer = typeof window === 'undefined' + +// Create batchers for console logs (one for client, one for server) +// They batch logs for ~100ms before emitting +const consoleBatcher = new Batcher( + (entries) => { + if (isServer) { + devtoolsEventClient.emit('server-console', { entries }) + } else { + devtoolsEventClient.emit('client-console', { entries }) + } + }, + { + wait: 100, // Batch logs every 100ms + maxSize: 50, // Or when we hit 50 logs + }, +) + +/** + * Creates a wrapped console method that: + * 1. Queues the log entry to the batcher for piping + * 2. Calls the original console method + */ +function createWrappedMethod(level: ConsoleLevel) { + return function wrappedConsole(source: string, ...args: Array) { + // Queue the log entry for batching + const entry: ConsoleLogEntry = { + level, + args, + source, + timestamp: Date.now(), + } + consoleBatcher.addItem(entry) + + // Call the original console method + originalConsole[level](...args) + } +} + +/** + * Wrapped console object that pipes logs between client and server. + * Each method accepts a source string as the first argument (injected by the transform), + * followed by the original arguments. + */ +export const wrappedConsole = { + log: createWrappedMethod('log'), + warn: createWrappedMethod('warn'), + error: createWrappedMethod('error'), + info: createWrappedMethod('info'), + debug: createWrappedMethod('debug'), +} + +/** + * Flush any pending batched logs immediately. + * Useful for cleanup or ensuring logs are sent before page unload. + */ +export function flushConsoleLogs() { + consoleBatcher.flush() +} diff --git a/packages/devtools-vite/src/enhance-logs.test.ts b/packages/devtools-vite/src/enhance-logs.test.ts index 02770b08..995e4dd6 100644 --- a/packages/devtools-vite/src/enhance-logs.test.ts +++ b/packages/devtools-vite/src/enhance-logs.test.ts @@ -5,7 +5,7 @@ const removeEmptySpace = (str: string) => { return str.replace(/\s/g, '').trim() } -describe('remove-devtools', () => { +describe('enhance-logs', () => { test('it adds enhanced console.logs to console.log()', () => { const output = removeEmptySpace( enhanceConsoleLog( diff --git a/packages/devtools-vite/src/index.ts b/packages/devtools-vite/src/index.ts index 06e031f4..08e19278 100644 --- a/packages/devtools-vite/src/index.ts +++ b/packages/devtools-vite/src/index.ts @@ -1,2 +1,2 @@ export { devtools, defineDevtoolsConfig } from './plugin' -export type { TanStackDevtoolsViteConfig } from './plugin' +export type { TanStackDevtoolsViteConfig, ConsoleLevel } from './plugin' diff --git a/packages/devtools-vite/src/plugin.ts b/packages/devtools-vite/src/plugin.ts index ba91fbc0..994e0a88 100644 --- a/packages/devtools-vite/src/plugin.ts +++ b/packages/devtools-vite/src/plugin.ts @@ -13,10 +13,44 @@ import { emitOutdatedDeps, installPackage, } from './package-manager' +import { generateConsolePipeCode } from './virtual-console' +import type { ServerResponse } from 'node:http' import type { Plugin } from 'vite' import type { EditorConfig } from './editor' import type { ServerEventBusConfig } from '@tanstack/devtools-event-bus/server' +export type ConsoleLevel = 'log' | 'warn' | 'error' | 'info' | 'debug' + +/** + * Strips browser console styling (%c format specifiers and their CSS arguments) + * for clean terminal output. + */ +const stripBrowserConsoleStyles = (args: Array): Array => { + if (args.length === 0) return args + + const firstArg = args[0] + if (typeof firstArg !== 'string' || !firstArg.includes('%c')) { + return args + } + + // Count the number of %c in the format string + const styleCount = (firstArg.match(/%c/g) || []).length + + // Remove %c from the format string + const cleanedFormat = firstArg.replace(/%c/g, '') + + // Remove the CSS style arguments (one for each %c) + // The style arguments follow the format string + const remainingArgs = args.slice(1 + styleCount) + + // If the cleaned format is empty or just whitespace, skip it + if (cleanedFormat.trim() === '') { + return remainingArgs + } + + return [cleanedFormat, ...remainingArgs] +} + export type TanStackDevtoolsViteConfig = { /** * Configuration for the editor integration. Defaults to opening in VS code @@ -70,6 +104,23 @@ export type TanStackDevtoolsViteConfig = { components?: Array } } + /** + * Configuration for console piping between client and server. + * When enabled, console logs from the client will appear in the terminal, + * and server logs will appear in the browser console. + */ + consolePiping?: { + /** + * Whether to enable console piping. + * @default true + */ + enabled?: boolean + /** + * Which console methods to pipe. + * @default ['log', 'warn', 'error', 'info', 'debug'] + */ + levels?: Array + } } export const defineDevtoolsConfig = (config: TanStackDevtoolsViteConfig) => @@ -82,6 +133,21 @@ export const devtools = (args?: TanStackDevtoolsViteConfig): Array => { const injectSourceConfig = args?.injectSource ?? { enabled: true } const removeDevtoolsOnBuild = args?.removeDevtoolsOnBuild ?? true const serverBusEnabled = args?.eventBusConfig?.enabled ?? true + const consolePipingConfig = args?.consolePiping ?? { enabled: true } + const consolePipingLevels: Array = + consolePipingConfig.levels ?? ['log', 'warn', 'error', 'info', 'debug'] + + // Store original server console methods before any overrides + const originalServerConsole: Record< + ConsoleLevel, + (...args: Array) => void + > = { + log: console.log.bind(console), + warn: console.warn.bind(console), + error: console.error.bind(console), + info: console.info.bind(console), + debug: console.debug.bind(console), + } let devtoolsFileId: string | null = null let devtoolsPort: number | null = null @@ -175,16 +241,107 @@ export const devtools = (args?: TanStackDevtoolsViteConfig): Array => { } await editor.open(path, lineNum, columnNum) } - server.middlewares.use((req, res, next) => - handleDevToolsViteRequest(req, res, next, (parsedData) => { - const { data, routine } = parsedData - if (routine === 'open-source') { - return handleOpenSource({ - data: { type: data.type, data }, - openInEditor, - }) + + // SSE clients for broadcasting server logs to browser (console piping) + const sseClients: Array<{ + res: ServerResponse + id: number + }> = [] + let sseClientId = 0 + const consolePipingEnabled = consolePipingConfig.enabled ?? true + + // Override server console methods to broadcast to SSE clients + if (consolePipingEnabled) { + for (const level of consolePipingLevels) { + const original = originalServerConsole[level] + console[level] = (...args: Array) => { + // Call original console method first + original(...args) + + // Broadcast to all SSE clients + if (sseClients.length > 0) { + const data = JSON.stringify({ + entries: [ + { + level, + args, + source: 'server', + timestamp: Date.now(), + }, + ], + }) + for (const client of sseClients) { + client.res.write(`data: ${data}\n\n`) + } + } } - return + } + + originalServerConsole.log( + chalk.magenta( + '[TSD Console Pipe] Server console piping configured', + ), + ) + } + + server.middlewares.use((req, res, next) => + handleDevToolsViteRequest(req, res, next, { + onOpenSource: (parsedData) => { + const { data, routine } = parsedData + if (routine === 'open-source') { + return handleOpenSource({ + data: { type: data.type, data }, + openInEditor, + }) + } + return + }, + onConsolePipe: consolePipingEnabled + ? (entries) => { + for (const entry of entries) { + const prefix = chalk.cyan('[Client]') + const logMethod = + originalServerConsole[entry.level as ConsoleLevel] + // Strip browser console styling (%c and CSS strings) for terminal output + const cleanedArgs = stripBrowserConsoleStyles(entry.args) + logMethod(prefix, ...cleanedArgs) + } + } + : undefined, + onConsolePipeSSE: consolePipingEnabled + ? (res, req) => { + originalServerConsole.log( + chalk.magenta('[TSD Console Pipe] SSE connection request'), + ) + + res.setHeader('Content-Type', 'text/event-stream') + res.setHeader('Cache-Control', 'no-cache') + res.setHeader('Connection', 'keep-alive') + res.setHeader('Access-Control-Allow-Origin', '*') + res.flushHeaders() + + const clientId = ++sseClientId + sseClients.push({ res, id: clientId }) + + originalServerConsole.log( + chalk.magenta( + `[TSD Console Pipe] SSE client ${clientId} connected`, + ), + ) + + req.on('close', () => { + const index = sseClients.findIndex((c) => c.id === clientId) + if (index !== -1) { + sseClients.splice(index, 1) + } + originalServerConsole.log( + chalk.magenta( + `[TSD Console Pipe] SSE client ${clientId} disconnected`, + ), + ) + }) + } + : undefined, }), ) }, @@ -417,6 +574,8 @@ export const devtools = (args?: TanStackDevtoolsViteConfig): Array => { packageJson, }) }) + + // Console piping is now handled via HTTP endpoints in the custom-server plugin }, async handleHotUpdate({ file }) { if (file.endsWith('package.json')) { @@ -428,6 +587,71 @@ export const devtools = (args?: TanStackDevtoolsViteConfig): Array => { } }, }, + // Inject console piping code into client entry files + { + name: '@tanstack/devtools:console-pipe-transform', + enforce: 'pre', + apply(config, { command }) { + return ( + config.mode === 'development' && + command === 'serve' && + (consolePipingConfig.enabled ?? true) + ) + }, + transform(code, id) { + // Inject the console pipe code into client entry files + // Look for common entry patterns and inject at the top + if ( + id.includes('node_modules') || + id.includes('?') || + !id.match(/\.(tsx?|jsx?)$/) + ) { + return + } + + // Only inject into files that look like client entry points + // or files that import React/framework-specific code (indicating client code) + const isClientEntry = + id.includes('client') || + id.includes('entry') || + id.includes('main.tsx') || + id.includes('main.jsx') || + id.includes('app.tsx') || + id.includes('app.jsx') || + id.includes('App.tsx') || + id.includes('App.jsx') || + id.includes('root.tsx') || + id.includes('root.jsx') + + // Also check for StartClient or hydrateRoot which indicate client entry + const hasClientHydration = + code.includes('StartClient') || + code.includes('hydrateRoot') || + code.includes('createRoot') + + if (isClientEntry || hasClientHydration) { + // Only inject once - check if already injected + if (code.includes('__TSD_CONSOLE_PIPE_INITIALIZED__')) { + return + } + + originalServerConsole.log( + chalk.magenta( + `[TSD Console Pipe] Injecting console pipe into: ${id}`, + ), + ) + + const inlineCode = generateConsolePipeCode(consolePipingLevels) + + return { + code: `${inlineCode}\n${code}`, + map: null, + } + } + + return undefined + }, + }, { name: '@tanstack/devtools:better-console-logs', enforce: 'pre', diff --git a/packages/devtools-vite/src/utils.ts b/packages/devtools-vite/src/utils.ts index bc11747c..7036b481 100644 --- a/packages/devtools-vite/src/utils.ts +++ b/packages/devtools-vite/src/utils.ts @@ -4,12 +4,30 @@ import type { Connect } from 'vite' import type { IncomingMessage, ServerResponse } from 'node:http' import type { PackageJson } from '@tanstack/devtools-client' +export type DevToolsRequestHandler = (data: any) => void + +export type DevToolsViteRequestOptions = { + onOpenSource?: DevToolsRequestHandler + onConsolePipe?: (entries: Array) => void + onConsolePipeSSE?: ( + res: ServerResponse, + req: Connect.IncomingMessage, + ) => void +} + export const handleDevToolsViteRequest = ( req: Connect.IncomingMessage, res: ServerResponse, next: Connect.NextFunction, - cb: (data: any) => void, + cbOrOptions: DevToolsRequestHandler | DevToolsViteRequestOptions, ) => { + // Normalize to options object for backward compatibility + const options: DevToolsViteRequestOptions = + typeof cbOrOptions === 'function' + ? { onOpenSource: cbOrOptions } + : cbOrOptions + + // Handle open-source requests if (req.url?.includes('__tsd/open-source')) { const searchParams = new URLSearchParams(req.url.split('?')[1]) @@ -24,7 +42,7 @@ export const handleDevToolsViteRequest = ( } const { file, line, column } = parsed - cb({ + options.onOpenSource?.({ type: 'open-source', routine: 'open-source', data: { @@ -38,6 +56,39 @@ export const handleDevToolsViteRequest = ( res.end() return } + + // Handle console-pipe SSE endpoint + if (req.url?.includes('__tsd/console-pipe/sse') && req.method === 'GET') { + if (options.onConsolePipeSSE) { + options.onConsolePipeSSE(res, req) + return + } + return next() + } + + // Handle console-pipe POST endpoint + if (req.url?.includes('__tsd/console-pipe') && req.method === 'POST') { + if (options.onConsolePipe) { + let body = '' + req.on('data', (chunk: Buffer) => { + body += chunk.toString() + }) + req.on('end', () => { + try { + const { entries } = JSON.parse(body) + options.onConsolePipe!(entries) + res.statusCode = 200 + res.end('OK') + } catch (err) { + res.statusCode = 400 + res.end('Bad Request') + } + }) + return + } + return next() + } + if (!req.url?.includes('__tsd')) { return next() } @@ -50,7 +101,7 @@ export const handleDevToolsViteRequest = ( const dataToParse = Buffer.concat(chunks) try { const parsedData = JSON.parse(dataToParse.toString()) - cb(parsedData) + options.onOpenSource?.(parsedData) } catch (e) {} res.write('OK') }) diff --git a/packages/devtools-vite/src/virtual-console.test.ts b/packages/devtools-vite/src/virtual-console.test.ts new file mode 100644 index 00000000..a76012d6 --- /dev/null +++ b/packages/devtools-vite/src/virtual-console.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, test } from 'vitest' +import { generateConsolePipeCode } from './virtual-console' + +describe('virtual-console', () => { + test('generates inline code with specified levels', () => { + const code = generateConsolePipeCode(['log', 'error']) + + expect(code).toContain('["log","error"]') + expect(code).toContain('originalConsole') + expect(code).toContain('__TSD_CONSOLE_PIPE_INITIALIZED__') + }) + + test('uses fetch to send client logs', () => { + const code = generateConsolePipeCode(['log']) + + expect(code).toContain("fetch('/__tsd/console-pipe'") + expect(code).toContain("method: 'POST'") + }) + + test('uses SSE to receive server logs', () => { + const code = generateConsolePipeCode(['log']) + + expect(code).toContain("new EventSource('/__tsd/console-pipe/sse')") + }) + + test('includes client-only guard', () => { + const code = generateConsolePipeCode(['log']) + + expect(code).toContain("typeof window === 'undefined'") + }) + + test('includes batcher configuration', () => { + const code = generateConsolePipeCode(['log']) + + expect(code).toContain('BATCH_WAIT = 100') + expect(code).toContain('BATCH_MAX_SIZE = 50') + }) + + test('includes flush functionality', () => { + const code = generateConsolePipeCode(['log']) + + expect(code).toContain('flushBatch') + }) + + test('includes beforeunload listener for browser', () => { + const code = generateConsolePipeCode(['log']) + + expect(code).toContain('beforeunload') + }) + + test('wraps code in IIFE', () => { + const code = generateConsolePipeCode(['log']) + + expect(code).toContain('(function __tsdConsolePipe()') + expect(code).toContain('})();') + }) + + test('has no external imports', () => { + const code = generateConsolePipeCode(['log']) + + expect(code).not.toContain('import ') + }) +}) diff --git a/packages/devtools-vite/src/virtual-console.ts b/packages/devtools-vite/src/virtual-console.ts new file mode 100644 index 00000000..af80f944 --- /dev/null +++ b/packages/devtools-vite/src/virtual-console.ts @@ -0,0 +1,121 @@ +import type { ConsoleLevel } from './plugin' + +export const VIRTUAL_MODULE_ID = 'virtual:tanstack-devtools-console' +export const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID + +/** + * Generates inline code to inject into entry files. + * This code runs on the CLIENT and will: + * 1. Store original console methods + * 2. Create batched wrappers that POST to server via fetch + * 3. Override global console with the wrapped methods + * 4. Listen for server console logs via SSE + * + * Returns the inline code as a string - no imports needed since we use fetch. + */ +export function generateConsolePipeCode(levels: Array): string { + const levelsArray = JSON.stringify(levels) + + return ` +;(function __tsdConsolePipe() { + if (typeof window === 'undefined') return; // Only run on client + if (window.__TSD_CONSOLE_PIPE_INITIALIZED__) return; // Only run once + window.__TSD_CONSOLE_PIPE_INITIALIZED__ = true; + + const CONSOLE_LEVELS = ${levelsArray}; + + console.log('[TSD Console Pipe] Initializing, levels:', CONSOLE_LEVELS); + + // Store original console methods before we override them + const originalConsole = {}; + for (const level of CONSOLE_LEVELS) { + originalConsole[level] = console[level].bind(console); + } + + // Simple inline batcher implementation + let batchedEntries = []; + let batchTimeout = null; + const BATCH_WAIT = 100; + const BATCH_MAX_SIZE = 50; + + function flushBatch() { + if (batchedEntries.length === 0) return; + + const entries = batchedEntries; + batchedEntries = []; + batchTimeout = null; + + originalConsole.log('[TSD Console Pipe] Sending', entries.length, 'entries to server'); + + // Send to server via fetch + fetch('/__tsd/console-pipe', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ entries }), + }).catch((err) => { + originalConsole.error('[TSD Console Pipe] Failed to send logs:', err); + }); + } + + function addToBatch(entry) { + batchedEntries.push(entry); + + if (batchedEntries.length >= BATCH_MAX_SIZE) { + if (batchTimeout) { + clearTimeout(batchTimeout); + batchTimeout = null; + } + flushBatch(); + } else if (!batchTimeout) { + batchTimeout = setTimeout(flushBatch, BATCH_WAIT); + } + } + + // Override global console methods + for (const level of CONSOLE_LEVELS) { + const original = originalConsole[level]; + console[level] = function(...args) { + const entry = { + level, + args, + source: 'client', + timestamp: Date.now(), + }; + addToBatch(entry); + original.apply(console, args); + }; + } + + originalConsole.log('[TSD Console Pipe] Console methods overridden'); + + // Listen for server console logs via SSE + const eventSource = new EventSource('/__tsd/console-pipe/sse'); + + eventSource.onmessage = (event) => { + try { + const data = JSON.parse(event.data); + if (data.entries) { + for (const entry of data.entries) { + const prefix = '%c[Server]%c'; + const prefixStyle = 'color: #9333ea; font-weight: bold;'; + const resetStyle = 'color: inherit;'; + const logMethod = originalConsole[entry.level] || originalConsole.log; + logMethod(prefix, prefixStyle, resetStyle, ...entry.args); + } + } + } catch (err) { + originalConsole.error('[TSD Console Pipe] Failed to parse SSE data:', err); + } + }; + + eventSource.onerror = () => { + originalConsole.log('[TSD Console Pipe] SSE connection error, will retry...'); + }; + + originalConsole.log('[TSD Console Pipe] SSE listener connected'); + + // Flush on page unload + window.addEventListener('beforeunload', flushBatch); +})(); +` +} From 1aca34f3c3a60bbdd79b6f68151023becc0731e4 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Fri, 30 Jan 2026 15:08:57 +0100 Subject: [PATCH 02/14] implement the two way pipping --- .../react/start/src/components/Header.tsx | 13 +- .../src/routes/demo/start.api-request.tsx | 4 + .../src/routes/demo/start.server-funcs.tsx | 7 +- .../src/routes/demo/start.ssr.data-only.tsx | 1 + .../src/routes/demo/start.ssr.full-ssr.tsx | 1 + examples/solid/basic/src/index.tsx | 1 + examples/solid/start/src/app.tsx | 2 +- packages/devtools-vite/src/plugin.ts | 220 +++++++++++------- packages/devtools-vite/src/utils.ts | 26 ++- .../devtools-vite/src/virtual-console.test.ts | 36 +-- packages/devtools-vite/src/virtual-console.ts | 197 +++++++++++----- pnpm-lock.yaml | 21 ++ 12 files changed, 372 insertions(+), 157 deletions(-) diff --git a/examples/react/start/src/components/Header.tsx b/examples/react/start/src/components/Header.tsx index 3bc20cca..21c91ee4 100644 --- a/examples/react/start/src/components/Header.tsx +++ b/examples/react/start/src/components/Header.tsx @@ -11,6 +11,13 @@ import { StickyNote, X, } from 'lucide-react' +import { createServerFn } from '@tanstack/react-start'; + +const logger = createServerFn({ method: "POST"}).handler(async ({ data }) => { + console.log("triggered logger server function"); + console.log(data); + +}); export default function Header() { const [isOpen, setIsOpen] = useState(false) @@ -22,7 +29,11 @@ export default function Header() { <>