@tanstack/devtools-vite 0.4.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/plugin.d.ts +18 -0
- package/dist/esm/plugin.js +123 -14
- package/dist/esm/plugin.js.map +1 -1
- package/dist/esm/utils.d.ts +21 -1
- package/dist/esm/utils.js +93 -3
- package/dist/esm/utils.js.map +1 -1
- package/dist/esm/utils.test.d.ts +1 -0
- package/dist/esm/virtual-console.d.ts +19 -0
- package/dist/esm/virtual-console.js +180 -0
- package/dist/esm/virtual-console.js.map +1 -0
- package/dist/esm/virtual-console.test.d.ts +1 -0
- package/package.json +2 -2
- package/src/enhance-logs.test.ts +1 -1
- package/src/index.ts +1 -1
- package/src/plugin.ts +197 -16
- package/src/utils.test.ts +184 -0
- package/src/utils.ts +156 -3
- package/src/virtual-console.test.ts +73 -0
- package/src/virtual-console.ts +202 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
function generateConsolePipeCode(levels, viteServerUrl) {
|
|
2
|
+
const levelsArray = JSON.stringify(levels);
|
|
3
|
+
return `
|
|
4
|
+
;(function __tsdConsolePipe() {
|
|
5
|
+
// Detect environment
|
|
6
|
+
var isServer = typeof window === 'undefined';
|
|
7
|
+
var envKey = isServer ? '__TSD_SERVER_CONSOLE_PIPE_INITIALIZED__' : '__TSD_CONSOLE_PIPE_INITIALIZED__';
|
|
8
|
+
var globalObj = isServer ? globalThis : window;
|
|
9
|
+
|
|
10
|
+
// Only run once per environment
|
|
11
|
+
if (globalObj[envKey]) return;
|
|
12
|
+
globalObj[envKey] = true;
|
|
13
|
+
|
|
14
|
+
var CONSOLE_LEVELS = ${levelsArray};
|
|
15
|
+
var VITE_SERVER_URL = ${JSON.stringify(viteServerUrl)};
|
|
16
|
+
|
|
17
|
+
// Store original console methods before we override them
|
|
18
|
+
var originalConsole = {};
|
|
19
|
+
for (var i = 0; i < CONSOLE_LEVELS.length; i++) {
|
|
20
|
+
var level = CONSOLE_LEVELS[i];
|
|
21
|
+
originalConsole[level] = console[level].bind(console);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Simple inline batcher implementation
|
|
25
|
+
var batchedEntries = [];
|
|
26
|
+
var batchTimeout = null;
|
|
27
|
+
var BATCH_WAIT = isServer ? 50 : 100;
|
|
28
|
+
var BATCH_MAX_SIZE = isServer ? 20 : 50;
|
|
29
|
+
|
|
30
|
+
function flushBatch() {
|
|
31
|
+
if (batchedEntries.length === 0) return;
|
|
32
|
+
|
|
33
|
+
var entries = batchedEntries;
|
|
34
|
+
batchedEntries = [];
|
|
35
|
+
batchTimeout = null;
|
|
36
|
+
|
|
37
|
+
// Determine endpoint based on environment
|
|
38
|
+
var endpoint = isServer
|
|
39
|
+
? VITE_SERVER_URL + '/__tsd/console-pipe/server'
|
|
40
|
+
: '/__tsd/console-pipe';
|
|
41
|
+
|
|
42
|
+
// Send to Vite server via fetch
|
|
43
|
+
fetch(endpoint, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: { 'Content-Type': 'application/json' },
|
|
46
|
+
body: JSON.stringify({ entries: entries }),
|
|
47
|
+
}).catch(function( ) {
|
|
48
|
+
// Swallow errors
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function addToBatch(entry) {
|
|
53
|
+
batchedEntries.push(entry);
|
|
54
|
+
|
|
55
|
+
if (batchedEntries.length >= BATCH_MAX_SIZE) {
|
|
56
|
+
if (batchTimeout) {
|
|
57
|
+
clearTimeout(batchTimeout);
|
|
58
|
+
batchTimeout = null;
|
|
59
|
+
}
|
|
60
|
+
flushBatch();
|
|
61
|
+
} else if (!batchTimeout) {
|
|
62
|
+
batchTimeout = setTimeout(flushBatch, BATCH_WAIT);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Override global console methods
|
|
67
|
+
for (var j = 0; j < CONSOLE_LEVELS.length; j++) {
|
|
68
|
+
(function(level) {
|
|
69
|
+
var original = originalConsole[level];
|
|
70
|
+
console[level] = function() {
|
|
71
|
+
var args = Array.prototype.slice.call(arguments);
|
|
72
|
+
|
|
73
|
+
// Always call original first so logs appear normally
|
|
74
|
+
original.apply(console, args);
|
|
75
|
+
|
|
76
|
+
// Skip our own TSD Console Pipe logs to avoid recursion/noise
|
|
77
|
+
if (args.length > 0 && typeof args[0] === 'string' &&
|
|
78
|
+
(args[0].indexOf('[TSD Console Pipe]') !== -1 ||
|
|
79
|
+
args[0].indexOf('[@tanstack/devtools') !== -1)) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Serialize args safely
|
|
84
|
+
var safeArgs = args.map(function(arg) {
|
|
85
|
+
if (arg === undefined) return 'undefined';
|
|
86
|
+
if (arg === null) return null;
|
|
87
|
+
if (typeof arg === 'function') return '[Function]';
|
|
88
|
+
if (typeof arg === 'symbol') return arg.toString();
|
|
89
|
+
try {
|
|
90
|
+
JSON.stringify(arg);
|
|
91
|
+
return arg;
|
|
92
|
+
} catch (e) {
|
|
93
|
+
return String(arg);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
var entry = {
|
|
98
|
+
level: level,
|
|
99
|
+
args: safeArgs,
|
|
100
|
+
source: isServer ? 'server' : 'client',
|
|
101
|
+
timestamp: Date.now(),
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
addToBatch(entry);
|
|
105
|
+
};
|
|
106
|
+
})(CONSOLE_LEVELS[j]);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// CLIENT ONLY: Listen for server console logs via SSE
|
|
110
|
+
if (!isServer) {
|
|
111
|
+
// Transform server log args - strip ANSI codes and convert source paths to clickable URLs
|
|
112
|
+
function transformServerLogArgs(args) {
|
|
113
|
+
var escChar = String.fromCharCode(27);
|
|
114
|
+
var transformed = [];
|
|
115
|
+
|
|
116
|
+
for (var k = 0; k < args.length; k++) {
|
|
117
|
+
var arg = args[k];
|
|
118
|
+
if (typeof arg === 'string') {
|
|
119
|
+
// Strip ANSI escape sequences (ESC[...m patterns)
|
|
120
|
+
var cleaned = arg;
|
|
121
|
+
// Remove ESC character followed by [...m] - need to build regex dynamically
|
|
122
|
+
while (cleaned.indexOf(escChar) !== -1) {
|
|
123
|
+
cleaned = cleaned.split(escChar).join('');
|
|
124
|
+
}
|
|
125
|
+
// Also remove any leftover bracket codes like [35m
|
|
126
|
+
cleaned = cleaned.replace(/\\[[0-9;]*m/g, '');
|
|
127
|
+
|
|
128
|
+
// Transform source paths to clickable URLs
|
|
129
|
+
// Match patterns like /src/components/Header.tsx:17:3
|
|
130
|
+
var sourceRegex = /(\\/[^\\s]+:\\d+:\\d+)/g;
|
|
131
|
+
cleaned = cleaned.replace(sourceRegex, function(match) {
|
|
132
|
+
return window.location.origin + '/__tsd/open-source?source=' + encodeURIComponent(match);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (cleaned.trim()) {
|
|
136
|
+
transformed.push(cleaned);
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
transformed.push(arg);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return transformed;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
var eventSource = new EventSource('/__tsd/console-pipe/sse');
|
|
147
|
+
|
|
148
|
+
eventSource.onmessage = function(event) {
|
|
149
|
+
try {
|
|
150
|
+
var data = JSON.parse(event.data);
|
|
151
|
+
if (data.entries) {
|
|
152
|
+
for (var m = 0; m < data.entries.length; m++) {
|
|
153
|
+
var entry = data.entries[m];
|
|
154
|
+
var transformedArgs = transformServerLogArgs(entry.args);
|
|
155
|
+
var prefix = '%c[Server]%c';
|
|
156
|
+
var prefixStyle = 'color: #9333ea; font-weight: bold;';
|
|
157
|
+
var resetStyle = 'color: inherit;';
|
|
158
|
+
var logMethod = originalConsole[entry.level] || originalConsole.log;
|
|
159
|
+
logMethod.apply(console, [prefix, prefixStyle, resetStyle].concat(transformedArgs));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} catch (err) {
|
|
163
|
+
// Swallow errors
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
eventSource.onerror = function() {
|
|
168
|
+
// Swallow errors
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Flush on page unload
|
|
172
|
+
window.addEventListener('beforeunload', flushBatch);
|
|
173
|
+
}
|
|
174
|
+
})();
|
|
175
|
+
`;
|
|
176
|
+
}
|
|
177
|
+
export {
|
|
178
|
+
generateConsolePipeCode
|
|
179
|
+
};
|
|
180
|
+
//# sourceMappingURL=virtual-console.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"virtual-console.js","sources":["../../src/virtual-console.ts"],"sourcesContent":["import type { ConsoleLevel } from './plugin'\n\n// export const VIRTUAL_MODULE_ID = 'virtual:tanstack-devtools-console'\n// export const RESOLVED_VIRTUAL_MODULE_ID = '\\0' + VIRTUAL_MODULE_ID\n\n/**\n * Generates inline code to inject into entry files (both client and server).\n * This code detects the environment at runtime and:\n *\n * CLIENT:\n * 1. Store original console methods\n * 2. Create batched wrappers that POST to server via fetch\n * 3. Override global console with the wrapped methods\n * 4. Listen for server console logs via SSE\n *\n * SERVER (Nitro/Vinxi runtime):\n * 1. Store original console methods\n * 2. Create batched wrappers that POST to Vite dev server\n * 3. Override global console - original logging still happens, just also pipes to Vite\n *\n * Returns the inline code as a string - no imports needed since we use fetch.\n */\nexport function generateConsolePipeCode(\n levels: Array<ConsoleLevel>,\n viteServerUrl: string,\n): string {\n const levelsArray = JSON.stringify(levels)\n\n return `\n;(function __tsdConsolePipe() {\n // Detect environment\n var isServer = typeof window === 'undefined';\n var envKey = isServer ? '__TSD_SERVER_CONSOLE_PIPE_INITIALIZED__' : '__TSD_CONSOLE_PIPE_INITIALIZED__';\n var globalObj = isServer ? globalThis : window;\n \n // Only run once per environment\n if (globalObj[envKey]) return;\n globalObj[envKey] = true;\n\n var CONSOLE_LEVELS = ${levelsArray};\n var VITE_SERVER_URL = ${JSON.stringify(viteServerUrl)};\n\n // Store original console methods before we override them\n var originalConsole = {};\n for (var i = 0; i < CONSOLE_LEVELS.length; i++) {\n var level = CONSOLE_LEVELS[i];\n originalConsole[level] = console[level].bind(console);\n }\n\n // Simple inline batcher implementation\n var batchedEntries = [];\n var batchTimeout = null;\n var BATCH_WAIT = isServer ? 50 : 100;\n var BATCH_MAX_SIZE = isServer ? 20 : 50;\n\n function flushBatch() {\n if (batchedEntries.length === 0) return;\n \n var entries = batchedEntries;\n batchedEntries = [];\n batchTimeout = null;\n \n // Determine endpoint based on environment\n var endpoint = isServer \n ? VITE_SERVER_URL + '/__tsd/console-pipe/server'\n : '/__tsd/console-pipe';\n \n // Send to Vite server via fetch\n fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ entries: entries }),\n }).catch(function( ) {\n // Swallow errors \n });\n }\n\n function addToBatch(entry) {\n batchedEntries.push(entry);\n \n if (batchedEntries.length >= BATCH_MAX_SIZE) {\n if (batchTimeout) {\n clearTimeout(batchTimeout);\n batchTimeout = null;\n }\n flushBatch();\n } else if (!batchTimeout) {\n batchTimeout = setTimeout(flushBatch, BATCH_WAIT);\n }\n }\n\n // Override global console methods\n for (var j = 0; j < CONSOLE_LEVELS.length; j++) {\n (function(level) {\n var original = originalConsole[level];\n console[level] = function() {\n var args = Array.prototype.slice.call(arguments);\n \n // Always call original first so logs appear normally\n original.apply(console, args);\n \n // Skip our own TSD Console Pipe logs to avoid recursion/noise\n if (args.length > 0 && typeof args[0] === 'string' && \n (args[0].indexOf('[TSD Console Pipe]') !== -1 || \n args[0].indexOf('[@tanstack/devtools') !== -1)) {\n return;\n }\n\n // Serialize args safely\n var safeArgs = args.map(function(arg) {\n if (arg === undefined) return 'undefined';\n if (arg === null) return null;\n if (typeof arg === 'function') return '[Function]';\n if (typeof arg === 'symbol') return arg.toString();\n try {\n JSON.stringify(arg);\n return arg;\n } catch (e) {\n return String(arg);\n }\n });\n\n var entry = {\n level: level,\n args: safeArgs,\n source: isServer ? 'server' : 'client',\n timestamp: Date.now(),\n };\n \n addToBatch(entry);\n };\n })(CONSOLE_LEVELS[j]);\n }\n\n // CLIENT ONLY: Listen for server console logs via SSE\n if (!isServer) {\n // Transform server log args - strip ANSI codes and convert source paths to clickable URLs\n function transformServerLogArgs(args) {\n var escChar = String.fromCharCode(27);\n var transformed = [];\n \n for (var k = 0; k < args.length; k++) {\n var arg = args[k];\n if (typeof arg === 'string') {\n // Strip ANSI escape sequences (ESC[...m patterns)\n var cleaned = arg;\n // Remove ESC character followed by [...m] - need to build regex dynamically\n while (cleaned.indexOf(escChar) !== -1) {\n cleaned = cleaned.split(escChar).join('');\n }\n // Also remove any leftover bracket codes like [35m\n cleaned = cleaned.replace(/\\\\[[0-9;]*m/g, '');\n \n // Transform source paths to clickable URLs\n // Match patterns like /src/components/Header.tsx:17:3\n var sourceRegex = /(\\\\/[^\\\\s]+:\\\\d+:\\\\d+)/g;\n cleaned = cleaned.replace(sourceRegex, function(match) {\n return window.location.origin + '/__tsd/open-source?source=' + encodeURIComponent(match);\n });\n \n if (cleaned.trim()) {\n transformed.push(cleaned);\n }\n } else {\n transformed.push(arg);\n }\n }\n \n return transformed;\n }\n\n var eventSource = new EventSource('/__tsd/console-pipe/sse');\n \n eventSource.onmessage = function(event) {\n try {\n var data = JSON.parse(event.data);\n if (data.entries) {\n for (var m = 0; m < data.entries.length; m++) {\n var entry = data.entries[m];\n var transformedArgs = transformServerLogArgs(entry.args);\n var prefix = '%c[Server]%c';\n var prefixStyle = 'color: #9333ea; font-weight: bold;';\n var resetStyle = 'color: inherit;';\n var logMethod = originalConsole[entry.level] || originalConsole.log;\n logMethod.apply(console, [prefix, prefixStyle, resetStyle].concat(transformedArgs));\n }\n }\n } catch (err) {\n // Swallow errors \n }\n };\n\n eventSource.onerror = function() {\n // Swallow errors \n };\n\n // Flush on page unload\n window.addEventListener('beforeunload', flushBatch);\n }\n})();\n`\n}\n"],"names":[],"mappings":"AAsBO,SAAS,wBACd,QACA,eACQ;AACR,QAAM,cAAc,KAAK,UAAU,MAAM;AAEzC,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAWgB,WAAW;AAAA,0BACV,KAAK,UAAU,aAAa,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiKvD;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/devtools-vite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "TanStack Vite plugin used to enhance the core devtools with additional functionalities",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"launch-editor": "^2.11.1",
|
|
51
51
|
"picomatch": "^4.0.3",
|
|
52
52
|
"@tanstack/devtools-client": "0.0.5",
|
|
53
|
-
"@tanstack/devtools-event-bus": "0.4.
|
|
53
|
+
"@tanstack/devtools-event-bus": "0.4.1"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/babel__core": "^7.20.5",
|
package/src/enhance-logs.test.ts
CHANGED
|
@@ -5,7 +5,7 @@ const removeEmptySpace = (str: string) => {
|
|
|
5
5
|
return str.replace(/\s/g, '').trim()
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
describe('
|
|
8
|
+
describe('enhance-logs', () => {
|
|
9
9
|
test('it adds enhanced console.logs to console.log()', () => {
|
|
10
10
|
const output = removeEmptySpace(
|
|
11
11
|
enhanceConsoleLog(
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { devtools, defineDevtoolsConfig } from './plugin'
|
|
2
|
-
export type { TanStackDevtoolsViteConfig } from './plugin'
|
|
2
|
+
export type { TanStackDevtoolsViteConfig, ConsoleLevel } from './plugin'
|
package/src/plugin.ts
CHANGED
|
@@ -2,7 +2,11 @@ import { devtoolsEventClient } from '@tanstack/devtools-client'
|
|
|
2
2
|
import { ServerEventBus } from '@tanstack/devtools-event-bus/server'
|
|
3
3
|
import { normalizePath } from 'vite'
|
|
4
4
|
import chalk from 'chalk'
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
handleDevToolsViteRequest,
|
|
7
|
+
readPackageJson,
|
|
8
|
+
stripEnhancedLogPrefix,
|
|
9
|
+
} from './utils'
|
|
6
10
|
import { DEFAULT_EDITOR_CONFIG, handleOpenSource } from './editor'
|
|
7
11
|
import { removeDevtools } from './remove-devtools'
|
|
8
12
|
import { addSourceToJsx } from './inject-source'
|
|
@@ -13,9 +17,16 @@ import {
|
|
|
13
17
|
emitOutdatedDeps,
|
|
14
18
|
installPackage,
|
|
15
19
|
} from './package-manager'
|
|
20
|
+
import { generateConsolePipeCode } from './virtual-console'
|
|
21
|
+
import type { ServerResponse } from 'node:http'
|
|
16
22
|
import type { Plugin } from 'vite'
|
|
17
23
|
import type { EditorConfig } from './editor'
|
|
18
|
-
import type {
|
|
24
|
+
import type {
|
|
25
|
+
HttpServerLike,
|
|
26
|
+
ServerEventBusConfig,
|
|
27
|
+
} from '@tanstack/devtools-event-bus/server'
|
|
28
|
+
|
|
29
|
+
export type ConsoleLevel = 'log' | 'warn' | 'error' | 'info' | 'debug'
|
|
19
30
|
|
|
20
31
|
export type TanStackDevtoolsViteConfig = {
|
|
21
32
|
/**
|
|
@@ -70,6 +81,23 @@ export type TanStackDevtoolsViteConfig = {
|
|
|
70
81
|
components?: Array<string | RegExp>
|
|
71
82
|
}
|
|
72
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Configuration for console piping between client and server.
|
|
86
|
+
* When enabled, console logs from the client will appear in the terminal,
|
|
87
|
+
* and server logs will appear in the browser console.
|
|
88
|
+
*/
|
|
89
|
+
consolePiping?: {
|
|
90
|
+
/**
|
|
91
|
+
* Whether to enable console piping.
|
|
92
|
+
* @default true
|
|
93
|
+
*/
|
|
94
|
+
enabled?: boolean
|
|
95
|
+
/**
|
|
96
|
+
* Which console methods to pipe.
|
|
97
|
+
* @default ['log', 'warn', 'error', 'info', 'debug']
|
|
98
|
+
*/
|
|
99
|
+
levels?: Array<ConsoleLevel>
|
|
100
|
+
}
|
|
73
101
|
}
|
|
74
102
|
|
|
75
103
|
export const defineDevtoolsConfig = (config: TanStackDevtoolsViteConfig) =>
|
|
@@ -82,9 +110,14 @@ export const devtools = (args?: TanStackDevtoolsViteConfig): Array<Plugin> => {
|
|
|
82
110
|
const injectSourceConfig = args?.injectSource ?? { enabled: true }
|
|
83
111
|
const removeDevtoolsOnBuild = args?.removeDevtoolsOnBuild ?? true
|
|
84
112
|
const serverBusEnabled = args?.eventBusConfig?.enabled ?? true
|
|
113
|
+
const consolePipingConfig = args?.consolePiping ?? { enabled: true }
|
|
114
|
+
const consolePipingLevels: Array<ConsoleLevel> =
|
|
115
|
+
consolePipingConfig.levels ?? ['log', 'warn', 'error', 'info', 'debug']
|
|
85
116
|
|
|
86
117
|
let devtoolsFileId: string | null = null
|
|
87
118
|
let devtoolsPort: number | null = null
|
|
119
|
+
let devtoolsHost: string | null = null
|
|
120
|
+
let devtoolsProtocol: 'http' | 'https' | null = null
|
|
88
121
|
|
|
89
122
|
return [
|
|
90
123
|
{
|
|
@@ -142,9 +175,24 @@ export const devtools = (args?: TanStackDevtoolsViteConfig): Array<Plugin> => {
|
|
|
142
175
|
async configureServer(server) {
|
|
143
176
|
if (serverBusEnabled) {
|
|
144
177
|
const preferredPort = args?.eventBusConfig?.port ?? 4206
|
|
178
|
+
const isHttps = !!server.config.server.https
|
|
179
|
+
const serverHost =
|
|
180
|
+
typeof server.config.server.host === 'string'
|
|
181
|
+
? server.config.server.host
|
|
182
|
+
: 'localhost'
|
|
183
|
+
|
|
184
|
+
devtoolsProtocol = isHttps ? 'https' : 'http'
|
|
185
|
+
devtoolsHost = serverHost
|
|
186
|
+
|
|
145
187
|
const bus = new ServerEventBus({
|
|
146
188
|
...args?.eventBusConfig,
|
|
147
189
|
port: preferredPort,
|
|
190
|
+
host: serverHost,
|
|
191
|
+
// When HTTPS is enabled, piggyback on Vite's server
|
|
192
|
+
// so WebSocket/SSE connections share the same TLS certificate
|
|
193
|
+
...(isHttps && server.httpServer
|
|
194
|
+
? { httpServer: server.httpServer as HttpServerLike }
|
|
195
|
+
: {}),
|
|
148
196
|
})
|
|
149
197
|
// start() now handles EADDRINUSE and returns the actual port
|
|
150
198
|
devtoolsPort = await bus.start()
|
|
@@ -175,16 +223,77 @@ export const devtools = (args?: TanStackDevtoolsViteConfig): Array<Plugin> => {
|
|
|
175
223
|
}
|
|
176
224
|
await editor.open(path, lineNum, columnNum)
|
|
177
225
|
}
|
|
226
|
+
|
|
227
|
+
// SSE clients for broadcasting server logs to browser
|
|
228
|
+
const sseClients: Array<{
|
|
229
|
+
res: ServerResponse
|
|
230
|
+
id: number
|
|
231
|
+
}> = []
|
|
232
|
+
let sseClientId = 0
|
|
233
|
+
const consolePipingEnabled = consolePipingConfig.enabled ?? true
|
|
234
|
+
|
|
178
235
|
server.middlewares.use((req, res, next) =>
|
|
179
|
-
handleDevToolsViteRequest(req, res, next,
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
236
|
+
handleDevToolsViteRequest(req, res, next, {
|
|
237
|
+
onOpenSource: (parsedData) => {
|
|
238
|
+
const { data, routine } = parsedData
|
|
239
|
+
if (routine === 'open-source') {
|
|
240
|
+
return handleOpenSource({
|
|
241
|
+
data: { type: data.type, data },
|
|
242
|
+
openInEditor,
|
|
243
|
+
})
|
|
244
|
+
}
|
|
245
|
+
return
|
|
246
|
+
},
|
|
247
|
+
...(consolePipingEnabled
|
|
248
|
+
? {
|
|
249
|
+
onConsolePipe: (entries) => {
|
|
250
|
+
for (const entry of entries) {
|
|
251
|
+
const prefix = chalk.cyan('[Client]')
|
|
252
|
+
const logMethod = console[entry.level as ConsoleLevel]
|
|
253
|
+
const cleanedArgs = stripEnhancedLogPrefix(
|
|
254
|
+
entry.args,
|
|
255
|
+
(loc) => chalk.gray(loc),
|
|
256
|
+
)
|
|
257
|
+
logMethod(prefix, ...cleanedArgs)
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
onConsolePipeSSE: (res, req) => {
|
|
261
|
+
res.setHeader('Content-Type', 'text/event-stream')
|
|
262
|
+
res.setHeader('Cache-Control', 'no-cache')
|
|
263
|
+
res.setHeader('Connection', 'keep-alive')
|
|
264
|
+
res.setHeader('Access-Control-Allow-Origin', '*')
|
|
265
|
+
res.flushHeaders()
|
|
266
|
+
|
|
267
|
+
const clientId = ++sseClientId
|
|
268
|
+
sseClients.push({ res, id: clientId })
|
|
269
|
+
|
|
270
|
+
req.on('close', () => {
|
|
271
|
+
const index = sseClients.findIndex(
|
|
272
|
+
(c) => c.id === clientId,
|
|
273
|
+
)
|
|
274
|
+
if (index !== -1) {
|
|
275
|
+
sseClients.splice(index, 1)
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
},
|
|
279
|
+
onServerConsolePipe: (entries) => {
|
|
280
|
+
try {
|
|
281
|
+
const data = JSON.stringify({
|
|
282
|
+
entries: entries.map((e) => ({
|
|
283
|
+
level: e.level,
|
|
284
|
+
args: e.args,
|
|
285
|
+
source: 'server',
|
|
286
|
+
timestamp: e.timestamp || Date.now(),
|
|
287
|
+
})),
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
for (const client of sseClients) {
|
|
291
|
+
client.res.write(`data: ${data}\n\n`)
|
|
292
|
+
}
|
|
293
|
+
} catch {}
|
|
294
|
+
},
|
|
295
|
+
}
|
|
296
|
+
: {}),
|
|
188
297
|
}),
|
|
189
298
|
)
|
|
190
299
|
},
|
|
@@ -417,6 +526,8 @@ export const devtools = (args?: TanStackDevtoolsViteConfig): Array<Plugin> => {
|
|
|
417
526
|
packageJson,
|
|
418
527
|
})
|
|
419
528
|
})
|
|
529
|
+
|
|
530
|
+
// Console piping is now handled via HTTP endpoints in the custom-server plugin
|
|
420
531
|
},
|
|
421
532
|
async handleHotUpdate({ file }) {
|
|
422
533
|
if (file.endsWith('package.json')) {
|
|
@@ -428,6 +539,56 @@ export const devtools = (args?: TanStackDevtoolsViteConfig): Array<Plugin> => {
|
|
|
428
539
|
}
|
|
429
540
|
},
|
|
430
541
|
},
|
|
542
|
+
// Inject console piping code into entry files (both client and server)
|
|
543
|
+
{
|
|
544
|
+
name: '@tanstack/devtools:console-pipe-transform',
|
|
545
|
+
enforce: 'pre',
|
|
546
|
+
apply(config, { command }) {
|
|
547
|
+
return (
|
|
548
|
+
config.mode === 'development' &&
|
|
549
|
+
command === 'serve' &&
|
|
550
|
+
(consolePipingConfig.enabled ?? true)
|
|
551
|
+
)
|
|
552
|
+
},
|
|
553
|
+
transform(code, id) {
|
|
554
|
+
// Inject the console pipe code into entry files
|
|
555
|
+
if (
|
|
556
|
+
id.includes('node_modules') ||
|
|
557
|
+
id.includes('dist') ||
|
|
558
|
+
id.includes('?') ||
|
|
559
|
+
!id.match(/\.(tsx?|jsx?)$/)
|
|
560
|
+
) {
|
|
561
|
+
return
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Only inject once - check if already injected
|
|
565
|
+
if (code.includes('__tsdConsolePipe')) {
|
|
566
|
+
return
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Check if this is a root entry file (with <html> JSX or client entry points)
|
|
570
|
+
// In SSR frameworks, this file runs on BOTH server (SSR) and client (hydration)
|
|
571
|
+
// so our runtime check (typeof window === 'undefined') handles both environments
|
|
572
|
+
const isRootEntry =
|
|
573
|
+
/<html[\s>]/i.test(code) ||
|
|
574
|
+
code.includes('StartClient') ||
|
|
575
|
+
code.includes('hydrateRoot') ||
|
|
576
|
+
code.includes('createRoot') ||
|
|
577
|
+
(code.includes('solid-js/web') && code.includes('render('))
|
|
578
|
+
|
|
579
|
+
if (isRootEntry) {
|
|
580
|
+
const viteServerUrl = `http://localhost:${port}`
|
|
581
|
+
const inlineCode = generateConsolePipeCode(
|
|
582
|
+
consolePipingLevels,
|
|
583
|
+
viteServerUrl,
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
return `${inlineCode}\n${code}`
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
return undefined
|
|
590
|
+
},
|
|
591
|
+
},
|
|
431
592
|
{
|
|
432
593
|
name: '@tanstack/devtools:better-console-logs',
|
|
433
594
|
enforce: 'pre',
|
|
@@ -467,22 +628,42 @@ export const devtools = (args?: TanStackDevtoolsViteConfig): Array<Plugin> => {
|
|
|
467
628
|
},
|
|
468
629
|
},
|
|
469
630
|
{
|
|
470
|
-
name: '@tanstack/devtools:
|
|
631
|
+
name: '@tanstack/devtools:connection-injection',
|
|
471
632
|
apply(config, { command }) {
|
|
472
633
|
return config.mode === 'development' && command === 'serve'
|
|
473
634
|
},
|
|
474
635
|
transform(code, id) {
|
|
475
|
-
// Only transform @tanstack packages that contain the
|
|
476
|
-
|
|
636
|
+
// Only transform @tanstack packages that contain the connection placeholders
|
|
637
|
+
const hasPlaceholder =
|
|
638
|
+
code.includes('__TANSTACK_DEVTOOLS_PORT__') ||
|
|
639
|
+
code.includes('__TANSTACK_DEVTOOLS_HOST__') ||
|
|
640
|
+
code.includes('__TANSTACK_DEVTOOLS_PROTOCOL__')
|
|
641
|
+
if (!hasPlaceholder) return
|
|
477
642
|
if (
|
|
478
643
|
!id.includes('@tanstack/devtools') &&
|
|
479
644
|
!id.includes('@tanstack/event-bus')
|
|
480
645
|
)
|
|
481
646
|
return
|
|
482
647
|
|
|
483
|
-
// Replace
|
|
648
|
+
// Replace placeholders with actual values (or fallback defaults)
|
|
484
649
|
const portValue = devtoolsPort ?? 4206
|
|
485
|
-
|
|
650
|
+
const hostValue = devtoolsHost ?? 'localhost'
|
|
651
|
+
const protocolValue = devtoolsProtocol ?? 'http'
|
|
652
|
+
|
|
653
|
+
let result = code
|
|
654
|
+
result = result.replace(
|
|
655
|
+
/__TANSTACK_DEVTOOLS_PORT__/g,
|
|
656
|
+
String(portValue),
|
|
657
|
+
)
|
|
658
|
+
result = result.replace(
|
|
659
|
+
/__TANSTACK_DEVTOOLS_HOST__/g,
|
|
660
|
+
JSON.stringify(hostValue),
|
|
661
|
+
)
|
|
662
|
+
result = result.replace(
|
|
663
|
+
/__TANSTACK_DEVTOOLS_PROTOCOL__/g,
|
|
664
|
+
JSON.stringify(protocolValue),
|
|
665
|
+
)
|
|
666
|
+
return result
|
|
486
667
|
},
|
|
487
668
|
},
|
|
488
669
|
]
|