@tanstack/devtools-vite 0.4.0 → 0.5.0
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/enhance-logs.js +28 -2
- package/dist/esm/enhance-logs.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/plugin.d.ts +18 -0
- package/dist/esm/plugin.js +102 -11
- package/dist/esm/plugin.js.map +1 -1
- package/dist/esm/utils.d.ts +21 -1
- package/dist/esm/utils.js +92 -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 +1 -1
- package/src/enhance-logs.test.ts +17 -2
- package/src/enhance-logs.ts +33 -2
- package/src/index.ts +1 -1
- package/src/plugin.ts +164 -11
- package/src/utils.test.ts +184 -0
- package/src/utils.ts +155 -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
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(
|
|
@@ -94,7 +94,6 @@ describe('remove-devtools', () => {
|
|
|
94
94
|
3000,
|
|
95
95
|
)!.code,
|
|
96
96
|
)
|
|
97
|
-
console.log('output', output)
|
|
98
97
|
expect(
|
|
99
98
|
output.includes(
|
|
100
99
|
'http://localhost:3000/__tsd/open-source?source=test.jsx',
|
|
@@ -114,4 +113,20 @@ describe('remove-devtools', () => {
|
|
|
114
113
|
)
|
|
115
114
|
expect(output).toBe(undefined)
|
|
116
115
|
})
|
|
116
|
+
|
|
117
|
+
test('it adds enhanced console.log with css formatting to console.log()', () => {
|
|
118
|
+
const output = removeEmptySpace(
|
|
119
|
+
enhanceConsoleLog(
|
|
120
|
+
`
|
|
121
|
+
console.log('This is a log')
|
|
122
|
+
`,
|
|
123
|
+
'test.jsx',
|
|
124
|
+
3000,
|
|
125
|
+
)!.code,
|
|
126
|
+
)
|
|
127
|
+
expect(output.includes('color:#A0A')).toEqual(true)
|
|
128
|
+
expect(output.includes('color:#FFF')).toEqual(true)
|
|
129
|
+
expect(output.includes('color:#55F')).toEqual(true)
|
|
130
|
+
expect(output.includes('color:#FFF')).toEqual(true)
|
|
131
|
+
})
|
|
117
132
|
})
|
package/src/enhance-logs.ts
CHANGED
|
@@ -31,11 +31,42 @@ const transform = (
|
|
|
31
31
|
location.start.column,
|
|
32
32
|
]
|
|
33
33
|
const finalPath = `${filePath}:${lineNumber}:${column + 1}`
|
|
34
|
-
|
|
34
|
+
const logMessage = `${chalk.magenta('LOG')} ${chalk.blueBright(`${finalPath}`)}\n → `
|
|
35
|
+
|
|
36
|
+
const serverLogMessage = t.arrayExpression([
|
|
37
|
+
t.stringLiteral(logMessage),
|
|
38
|
+
])
|
|
39
|
+
const browserLogMessage = t.arrayExpression([
|
|
40
|
+
// LOG with css formatting specifiers: %c
|
|
35
41
|
t.stringLiteral(
|
|
36
|
-
|
|
42
|
+
`%c${'LOG'}%c %c${`Go to Source: http://localhost:${port}/__tsd/open-source?source=${encodeURIComponent(finalPath)}`}%c \n → `,
|
|
43
|
+
),
|
|
44
|
+
// magenta
|
|
45
|
+
t.stringLiteral('color:#A0A'),
|
|
46
|
+
t.stringLiteral('color:#FFF'),
|
|
47
|
+
// blueBright
|
|
48
|
+
t.stringLiteral('color:#55F'),
|
|
49
|
+
t.stringLiteral('color:#FFF'),
|
|
50
|
+
])
|
|
51
|
+
|
|
52
|
+
// typeof window === "undefined"
|
|
53
|
+
const checkServerCondition = t.binaryExpression(
|
|
54
|
+
'===',
|
|
55
|
+
t.unaryExpression('typeof', t.identifier('window')),
|
|
56
|
+
t.stringLiteral('undefined'),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
// ...(isServer ? serverLogMessage : browserLogMessage)
|
|
60
|
+
path.node.arguments.unshift(
|
|
61
|
+
t.spreadElement(
|
|
62
|
+
t.conditionalExpression(
|
|
63
|
+
checkServerCondition,
|
|
64
|
+
serverLogMessage,
|
|
65
|
+
browserLogMessage,
|
|
66
|
+
),
|
|
37
67
|
),
|
|
38
68
|
)
|
|
69
|
+
|
|
39
70
|
didTransform = true
|
|
40
71
|
}
|
|
41
72
|
},
|
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,10 +17,14 @@ 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
24
|
import type { ServerEventBusConfig } from '@tanstack/devtools-event-bus/server'
|
|
19
25
|
|
|
26
|
+
export type ConsoleLevel = 'log' | 'warn' | 'error' | 'info' | 'debug'
|
|
27
|
+
|
|
20
28
|
export type TanStackDevtoolsViteConfig = {
|
|
21
29
|
/**
|
|
22
30
|
* Configuration for the editor integration. Defaults to opening in VS code
|
|
@@ -70,6 +78,23 @@ export type TanStackDevtoolsViteConfig = {
|
|
|
70
78
|
components?: Array<string | RegExp>
|
|
71
79
|
}
|
|
72
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Configuration for console piping between client and server.
|
|
83
|
+
* When enabled, console logs from the client will appear in the terminal,
|
|
84
|
+
* and server logs will appear in the browser console.
|
|
85
|
+
*/
|
|
86
|
+
consolePiping?: {
|
|
87
|
+
/**
|
|
88
|
+
* Whether to enable console piping.
|
|
89
|
+
* @default true
|
|
90
|
+
*/
|
|
91
|
+
enabled?: boolean
|
|
92
|
+
/**
|
|
93
|
+
* Which console methods to pipe.
|
|
94
|
+
* @default ['log', 'warn', 'error', 'info', 'debug']
|
|
95
|
+
*/
|
|
96
|
+
levels?: Array<ConsoleLevel>
|
|
97
|
+
}
|
|
73
98
|
}
|
|
74
99
|
|
|
75
100
|
export const defineDevtoolsConfig = (config: TanStackDevtoolsViteConfig) =>
|
|
@@ -82,6 +107,9 @@ export const devtools = (args?: TanStackDevtoolsViteConfig): Array<Plugin> => {
|
|
|
82
107
|
const injectSourceConfig = args?.injectSource ?? { enabled: true }
|
|
83
108
|
const removeDevtoolsOnBuild = args?.removeDevtoolsOnBuild ?? true
|
|
84
109
|
const serverBusEnabled = args?.eventBusConfig?.enabled ?? true
|
|
110
|
+
const consolePipingConfig = args?.consolePiping ?? { enabled: true }
|
|
111
|
+
const consolePipingLevels: Array<ConsoleLevel> =
|
|
112
|
+
consolePipingConfig.levels ?? ['log', 'warn', 'error', 'info', 'debug']
|
|
85
113
|
|
|
86
114
|
let devtoolsFileId: string | null = null
|
|
87
115
|
let devtoolsPort: number | null = null
|
|
@@ -175,16 +203,77 @@ export const devtools = (args?: TanStackDevtoolsViteConfig): Array<Plugin> => {
|
|
|
175
203
|
}
|
|
176
204
|
await editor.open(path, lineNum, columnNum)
|
|
177
205
|
}
|
|
206
|
+
|
|
207
|
+
// SSE clients for broadcasting server logs to browser
|
|
208
|
+
const sseClients: Array<{
|
|
209
|
+
res: ServerResponse
|
|
210
|
+
id: number
|
|
211
|
+
}> = []
|
|
212
|
+
let sseClientId = 0
|
|
213
|
+
const consolePipingEnabled = consolePipingConfig.enabled ?? true
|
|
214
|
+
|
|
178
215
|
server.middlewares.use((req, res, next) =>
|
|
179
|
-
handleDevToolsViteRequest(req, res, next,
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
216
|
+
handleDevToolsViteRequest(req, res, next, {
|
|
217
|
+
onOpenSource: (parsedData) => {
|
|
218
|
+
const { data, routine } = parsedData
|
|
219
|
+
if (routine === 'open-source') {
|
|
220
|
+
return handleOpenSource({
|
|
221
|
+
data: { type: data.type, data },
|
|
222
|
+
openInEditor,
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
return
|
|
226
|
+
},
|
|
227
|
+
...(consolePipingEnabled
|
|
228
|
+
? {
|
|
229
|
+
onConsolePipe: (entries) => {
|
|
230
|
+
for (const entry of entries) {
|
|
231
|
+
const prefix = chalk.cyan('[Client]')
|
|
232
|
+
const logMethod = console[entry.level as ConsoleLevel]
|
|
233
|
+
const cleanedArgs = stripEnhancedLogPrefix(
|
|
234
|
+
entry.args,
|
|
235
|
+
(loc) => chalk.gray(loc),
|
|
236
|
+
)
|
|
237
|
+
logMethod(prefix, ...cleanedArgs)
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
onConsolePipeSSE: (res, req) => {
|
|
241
|
+
res.setHeader('Content-Type', 'text/event-stream')
|
|
242
|
+
res.setHeader('Cache-Control', 'no-cache')
|
|
243
|
+
res.setHeader('Connection', 'keep-alive')
|
|
244
|
+
res.setHeader('Access-Control-Allow-Origin', '*')
|
|
245
|
+
res.flushHeaders()
|
|
246
|
+
|
|
247
|
+
const clientId = ++sseClientId
|
|
248
|
+
sseClients.push({ res, id: clientId })
|
|
249
|
+
|
|
250
|
+
req.on('close', () => {
|
|
251
|
+
const index = sseClients.findIndex(
|
|
252
|
+
(c) => c.id === clientId,
|
|
253
|
+
)
|
|
254
|
+
if (index !== -1) {
|
|
255
|
+
sseClients.splice(index, 1)
|
|
256
|
+
}
|
|
257
|
+
})
|
|
258
|
+
},
|
|
259
|
+
onServerConsolePipe: (entries) => {
|
|
260
|
+
try {
|
|
261
|
+
const data = JSON.stringify({
|
|
262
|
+
entries: entries.map((e) => ({
|
|
263
|
+
level: e.level,
|
|
264
|
+
args: e.args,
|
|
265
|
+
source: 'server',
|
|
266
|
+
timestamp: e.timestamp || Date.now(),
|
|
267
|
+
})),
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
for (const client of sseClients) {
|
|
271
|
+
client.res.write(`data: ${data}\n\n`)
|
|
272
|
+
}
|
|
273
|
+
} catch {}
|
|
274
|
+
},
|
|
275
|
+
}
|
|
276
|
+
: {}),
|
|
188
277
|
}),
|
|
189
278
|
)
|
|
190
279
|
},
|
|
@@ -202,7 +291,19 @@ export const devtools = (args?: TanStackDevtoolsViteConfig): Array<Plugin> => {
|
|
|
202
291
|
},
|
|
203
292
|
enforce: 'pre',
|
|
204
293
|
transform(code, id) {
|
|
205
|
-
|
|
294
|
+
const devtoolPackages = [
|
|
295
|
+
'@tanstack/react-devtools',
|
|
296
|
+
'@tanstack/preact-devtools',
|
|
297
|
+
'@tanstack/solid-devtools',
|
|
298
|
+
'@tanstack/vue-devtools',
|
|
299
|
+
'@tanstack/devtools',
|
|
300
|
+
]
|
|
301
|
+
if (
|
|
302
|
+
id.includes('node_modules') ||
|
|
303
|
+
id.includes('?raw') ||
|
|
304
|
+
!devtoolPackages.some((pkg) => code.includes(pkg))
|
|
305
|
+
)
|
|
306
|
+
return
|
|
206
307
|
const transform = removeDevtools(code, id)
|
|
207
308
|
if (!transform) return
|
|
208
309
|
if (logging) {
|
|
@@ -405,6 +506,8 @@ export const devtools = (args?: TanStackDevtoolsViteConfig): Array<Plugin> => {
|
|
|
405
506
|
packageJson,
|
|
406
507
|
})
|
|
407
508
|
})
|
|
509
|
+
|
|
510
|
+
// Console piping is now handled via HTTP endpoints in the custom-server plugin
|
|
408
511
|
},
|
|
409
512
|
async handleHotUpdate({ file }) {
|
|
410
513
|
if (file.endsWith('package.json')) {
|
|
@@ -416,6 +519,56 @@ export const devtools = (args?: TanStackDevtoolsViteConfig): Array<Plugin> => {
|
|
|
416
519
|
}
|
|
417
520
|
},
|
|
418
521
|
},
|
|
522
|
+
// Inject console piping code into entry files (both client and server)
|
|
523
|
+
{
|
|
524
|
+
name: '@tanstack/devtools:console-pipe-transform',
|
|
525
|
+
enforce: 'pre',
|
|
526
|
+
apply(config, { command }) {
|
|
527
|
+
return (
|
|
528
|
+
config.mode === 'development' &&
|
|
529
|
+
command === 'serve' &&
|
|
530
|
+
(consolePipingConfig.enabled ?? true)
|
|
531
|
+
)
|
|
532
|
+
},
|
|
533
|
+
transform(code, id) {
|
|
534
|
+
// Inject the console pipe code into entry files
|
|
535
|
+
if (
|
|
536
|
+
id.includes('node_modules') ||
|
|
537
|
+
id.includes('dist') ||
|
|
538
|
+
id.includes('?') ||
|
|
539
|
+
!id.match(/\.(tsx?|jsx?)$/)
|
|
540
|
+
) {
|
|
541
|
+
return
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Only inject once - check if already injected
|
|
545
|
+
if (code.includes('__tsdConsolePipe')) {
|
|
546
|
+
return
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Check if this is a root entry file (with <html> JSX or client entry points)
|
|
550
|
+
// In SSR frameworks, this file runs on BOTH server (SSR) and client (hydration)
|
|
551
|
+
// so our runtime check (typeof window === 'undefined') handles both environments
|
|
552
|
+
const isRootEntry =
|
|
553
|
+
/<html[\s>]/i.test(code) ||
|
|
554
|
+
code.includes('StartClient') ||
|
|
555
|
+
code.includes('hydrateRoot') ||
|
|
556
|
+
code.includes('createRoot') ||
|
|
557
|
+
(code.includes('solid-js/web') && code.includes('render('))
|
|
558
|
+
|
|
559
|
+
if (isRootEntry) {
|
|
560
|
+
const viteServerUrl = `http://localhost:${port}`
|
|
561
|
+
const inlineCode = generateConsolePipeCode(
|
|
562
|
+
consolePipingLevels,
|
|
563
|
+
viteServerUrl,
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
return `${inlineCode}\n${code}`
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
return undefined
|
|
570
|
+
},
|
|
571
|
+
},
|
|
419
572
|
{
|
|
420
573
|
name: '@tanstack/devtools:better-console-logs',
|
|
421
574
|
enforce: 'pre',
|