trickle-observe 0.2.71 → 0.2.73
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/vite-plugin.js +52 -27
- package/dist-esm/vite-plugin.js +52 -27
- package/package.json +1 -1
- package/src/vite-plugin.ts +79 -28
package/dist/vite-plugin.js
CHANGED
|
@@ -594,6 +594,28 @@ function findReassignments(source) {
|
|
|
594
594
|
* for (let i = 0; i < n; i++) { ... } → trace i
|
|
595
595
|
* Inserts trace calls at the start of the loop body.
|
|
596
596
|
*/
|
|
597
|
+
/**
|
|
598
|
+
* Find catch clause variables and return insertions for tracing.
|
|
599
|
+
* Handles: catch (err) { ... } → trace err at start of catch body.
|
|
600
|
+
*/
|
|
601
|
+
function findCatchVars(source) {
|
|
602
|
+
const results = [];
|
|
603
|
+
const catchRegex = /\bcatch\s*\(\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?::\s*[^)]+?)?\s*\)\s*\{/g;
|
|
604
|
+
let match;
|
|
605
|
+
while ((match = catchRegex.exec(source)) !== null) {
|
|
606
|
+
const varName = match[1];
|
|
607
|
+
if (varName.startsWith('__trickle'))
|
|
608
|
+
continue;
|
|
609
|
+
const bodyBrace = match.index + match[0].length - 1;
|
|
610
|
+
let lineNo = 1;
|
|
611
|
+
for (let i = 0; i < match.index; i++) {
|
|
612
|
+
if (source[i] === '\n')
|
|
613
|
+
lineNo++;
|
|
614
|
+
}
|
|
615
|
+
results.push({ bodyStart: bodyBrace + 1, varNames: [varName], lineNo });
|
|
616
|
+
}
|
|
617
|
+
return results;
|
|
618
|
+
}
|
|
597
619
|
function findForLoopVars(source) {
|
|
598
620
|
const results = [];
|
|
599
621
|
// Match: for (const/let/var ...
|
|
@@ -1219,9 +1241,11 @@ function transformEsmSource(source, filename, moduleName, backendUrl, debug, tra
|
|
|
1219
1241
|
const reassignInsertions = traceVars ? findReassignments(source) : [];
|
|
1220
1242
|
// Find for-loop variable declarations for tracing
|
|
1221
1243
|
const forLoopInsertions = traceVars ? findForLoopVars(source) : [];
|
|
1244
|
+
// Find catch clause variables for tracing
|
|
1245
|
+
const catchInsertions = traceVars ? findCatchVars(source) : [];
|
|
1222
1246
|
// Find function parameter names for tracing
|
|
1223
1247
|
const funcParamInsertions = traceVars ? findFunctionParams(source, isReactFile) : [];
|
|
1224
|
-
if (funcInsertions.length === 0 && varInsertions.length === 0 && destructInsertions.length === 0 && reassignInsertions.length === 0 && forLoopInsertions.length === 0 && funcParamInsertions.length === 0 && bodyInsertions.length === 0 && hookInsertions.length === 0 && stateInsertions.length === 0 && conciseBodyInsertions.length === 0)
|
|
1248
|
+
if (funcInsertions.length === 0 && varInsertions.length === 0 && destructInsertions.length === 0 && reassignInsertions.length === 0 && forLoopInsertions.length === 0 && catchInsertions.length === 0 && funcParamInsertions.length === 0 && bodyInsertions.length === 0 && hookInsertions.length === 0 && stateInsertions.length === 0 && conciseBodyInsertions.length === 0)
|
|
1225
1249
|
return source;
|
|
1226
1250
|
// Fix line numbers: Vite transforms (TypeScript stripping) may change line numbers.
|
|
1227
1251
|
// Map transformed line numbers to original source line numbers.
|
|
@@ -1290,32 +1314,25 @@ function transformEsmSource(source, filename, moduleName, backendUrl, debug, tra
|
|
|
1290
1314
|
}
|
|
1291
1315
|
}
|
|
1292
1316
|
// Build prefix — ALL imports first (ESM requires imports before any statements)
|
|
1293
|
-
const needsTracing = varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || funcParamInsertions.length > 0 || bodyInsertions.length > 0 || hookInsertions.length > 0 || stateInsertions.length > 0 || conciseBodyInsertions.length > 0;
|
|
1294
|
-
const importLines = [
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1317
|
+
const needsTracing = varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || catchInsertions.length > 0 || funcParamInsertions.length > 0 || bodyInsertions.length > 0 || hookInsertions.length > 0 || stateInsertions.length > 0 || conciseBodyInsertions.length > 0;
|
|
1318
|
+
const importLines = [];
|
|
1319
|
+
if (isSSR) {
|
|
1320
|
+
// SSR/Node.js — import trickle-observe for function wrapping + file system for writing
|
|
1321
|
+
importLines.push(`import { wrapFunction as __trickle_wrapFn, configure as __trickle_configure } from 'trickle-observe';`);
|
|
1322
|
+
if (needsTracing) {
|
|
1323
|
+
importLines.push(`import { mkdirSync as __trickle_mkdirSync, appendFileSync as __trickle_appendFileSync } from 'node:fs';`, `import { join as __trickle_join } from 'node:path';`);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
// Browser mode: no imports needed — variable tracers are self-contained,
|
|
1327
|
+
// function wrapping is a no-op, and transport uses import.meta.hot
|
|
1328
|
+
const prefixLines = [...importLines];
|
|
1329
|
+
if (isSSR) {
|
|
1330
|
+
prefixLines.push(`__trickle_configure({ backendUrl: ${JSON.stringify(backendUrl)}, batchIntervalMs: 2000, debug: ${debug}, enabled: true, environment: 'node' });`, `function __trickle_wrap(fn, name, paramNames) {`, ` const opts = {`, ` functionName: name,`, ` module: ${JSON.stringify(moduleName)},`, ` trackArgs: true,`, ` trackReturn: true,`, ` sampleRate: 1,`, ` maxDepth: 5,`, ` environment: 'node',`, ` enabled: true,`, ` };`, ` if (paramNames && paramNames.length) opts.paramNames = paramNames;`, ` return __trickle_wrapFn(fn, opts);`, `}`);
|
|
1331
|
+
}
|
|
1332
|
+
else {
|
|
1333
|
+
// Browser mode: __trickle_wrap is a no-op (function wrapping uses Node.js APIs)
|
|
1334
|
+
prefixLines.push(`function __trickle_wrap(fn) { return fn; }`);
|
|
1300
1335
|
}
|
|
1301
|
-
const prefixLines = [
|
|
1302
|
-
...importLines,
|
|
1303
|
-
`__trickle_configure({ backendUrl: ${JSON.stringify(backendUrl)}, batchIntervalMs: 2000, debug: ${debug}, enabled: true, environment: 'node' });`,
|
|
1304
|
-
`function __trickle_wrap(fn, name, paramNames) {`,
|
|
1305
|
-
` const opts = {`,
|
|
1306
|
-
` functionName: name,`,
|
|
1307
|
-
` module: ${JSON.stringify(moduleName)},`,
|
|
1308
|
-
` trackArgs: true,`,
|
|
1309
|
-
` trackReturn: true,`,
|
|
1310
|
-
` sampleRate: 1,`,
|
|
1311
|
-
` maxDepth: 5,`,
|
|
1312
|
-
` environment: 'node',`,
|
|
1313
|
-
` enabled: true,`,
|
|
1314
|
-
` };`,
|
|
1315
|
-
` if (paramNames && paramNames.length) opts.paramNames = paramNames;`,
|
|
1316
|
-
` return __trickle_wrapFn(fn, opts);`,
|
|
1317
|
-
`}`,
|
|
1318
|
-
];
|
|
1319
1336
|
// Add unified __trickle_send() transport — browser uses HMR WebSocket, SSR uses fs
|
|
1320
1337
|
if (needsTracing) {
|
|
1321
1338
|
if (isSSR) {
|
|
@@ -1328,7 +1345,7 @@ function transformEsmSource(source, filename, moduleName, backendUrl, debug, tra
|
|
|
1328
1345
|
}
|
|
1329
1346
|
}
|
|
1330
1347
|
// Add variable tracing if needed — inlined to avoid import resolution issues in Vite SSR.
|
|
1331
|
-
if (varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || funcParamInsertions.length > 0) {
|
|
1348
|
+
if (varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || catchInsertions.length > 0 || funcParamInsertions.length > 0) {
|
|
1332
1349
|
prefixLines.push(`if (!globalThis.__trickle_var_tracer) {`, ` const _cache = new Set();`, ` function _inferType(v, d) {`, ` if (d <= 0) return { kind: 'primitive', name: 'unknown' };`, ` if (v === null) return { kind: 'primitive', name: 'null' };`, ` if (v === undefined) return { kind: 'primitive', name: 'undefined' };`, ` const t = typeof v;`, ` if (t === 'string' || t === 'number' || t === 'boolean' || t === 'bigint' || t === 'symbol') return { kind: 'primitive', name: t };`, ` if (t === 'function') return { kind: 'function' };`, ` if (Array.isArray(v)) { return v.length === 0 ? { kind: 'array', element: { kind: 'primitive', name: 'unknown' } } : { kind: 'array', element: _inferType(v[0], d-1) }; }`, ` if (t === 'object') {`, ` if (v instanceof Date) return { kind: 'object', properties: { __date: { kind: 'primitive', name: 'string' } } };`, ` if (v instanceof RegExp) return { kind: 'object', properties: { __regexp: { kind: 'primitive', name: 'string' } } };`, ` if (v instanceof Error) return { kind: 'object', properties: { __error: { kind: 'primitive', name: 'string' } } };`, ` if (v instanceof Promise) return { kind: 'promise', resolved: { kind: 'primitive', name: 'unknown' } };`, ` const props = {}; const keys = Object.keys(v).slice(0, 20);`, ` for (const k of keys) { try { props[k] = _inferType(v[k], d-1); } catch(e) { props[k] = { kind: 'primitive', name: 'unknown' }; } }`, ` return { kind: 'object', properties: props };`, ` }`, ` return { kind: 'primitive', name: 'unknown' };`, ` }`, ` function _sanitize(v, d) {`, ` if (d <= 0) return '[truncated]'; if (v === null || v === undefined) return v; const t = typeof v;`, ` if (t === 'string') return v.length > 100 ? v.substring(0, 100) + '...' : v;`, ` if (t === 'number' || t === 'boolean') return v; if (t === 'bigint') return String(v);`, ` if (t === 'function') return '[Function: ' + (v.name || 'anonymous') + ']';`, ` if (Array.isArray(v)) return v.slice(0, 3).map(i => _sanitize(i, d-1));`, ` if (t === 'object') { if (v instanceof Date) return v.toISOString(); if (v instanceof RegExp) return String(v); if (v instanceof Error) return { error: v.message }; if (v instanceof Promise) return '[Promise]';`, ` const r = {}; const keys = Object.keys(v).slice(0, 10); for (const k of keys) { try { r[k] = _sanitize(v[k], d-1); } catch(e) { r[k] = '[unreadable]'; } } return r; }`, ` return String(v);`, ` }`, ` globalThis.__trickle_var_tracer = function(v, n, l, mod, file) {`, ` try {`, ` const type = _inferType(v, 3);`, ` const th = JSON.stringify(type).substring(0, 32);`, ` const ck = file + ':' + l + ':' + n + ':' + th;`, ` if (_cache.has(ck)) return;`, ` _cache.add(ck);`, ` __trickle_send(JSON.stringify({ kind: 'variable', varName: n, line: l, module: mod, file: file, type: type, typeHash: th, sample: _sanitize(v, 2) }));`, ` } catch(e) {}`, ` };`, `}`, `function __trickle_tv(v, n, l) { try { globalThis.__trickle_var_tracer(v, n, l, ${JSON.stringify(moduleName)}, ${JSON.stringify(filename)}); } catch(e) {} }`);
|
|
1333
1350
|
}
|
|
1334
1351
|
// Add React component render tracker if needed
|
|
@@ -1373,6 +1390,14 @@ function transformEsmSource(source, filename, moduleName, backendUrl, debug, tra
|
|
|
1373
1390
|
code: `\n;try{__trickle_tv(${varName},${JSON.stringify(varName)},${lineNo})}catch(__e){}\n`,
|
|
1374
1391
|
});
|
|
1375
1392
|
}
|
|
1393
|
+
// Catch clause insertions: insert trace at start of catch body
|
|
1394
|
+
for (const { bodyStart, varNames, lineNo } of catchInsertions) {
|
|
1395
|
+
const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
|
|
1396
|
+
allInsertions.push({
|
|
1397
|
+
position: bodyStart,
|
|
1398
|
+
code: `\ntry{${calls}}catch(__e2){}\n`,
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1376
1401
|
// For-loop variable insertions: insert trace at start of loop body
|
|
1377
1402
|
for (const { bodyStart, varNames, lineNo } of forLoopInsertions) {
|
|
1378
1403
|
const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
|
package/dist-esm/vite-plugin.js
CHANGED
|
@@ -587,6 +587,28 @@ function findReassignments(source) {
|
|
|
587
587
|
* for (let i = 0; i < n; i++) { ... } → trace i
|
|
588
588
|
* Inserts trace calls at the start of the loop body.
|
|
589
589
|
*/
|
|
590
|
+
/**
|
|
591
|
+
* Find catch clause variables and return insertions for tracing.
|
|
592
|
+
* Handles: catch (err) { ... } → trace err at start of catch body.
|
|
593
|
+
*/
|
|
594
|
+
function findCatchVars(source) {
|
|
595
|
+
const results = [];
|
|
596
|
+
const catchRegex = /\bcatch\s*\(\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?::\s*[^)]+?)?\s*\)\s*\{/g;
|
|
597
|
+
let match;
|
|
598
|
+
while ((match = catchRegex.exec(source)) !== null) {
|
|
599
|
+
const varName = match[1];
|
|
600
|
+
if (varName.startsWith('__trickle'))
|
|
601
|
+
continue;
|
|
602
|
+
const bodyBrace = match.index + match[0].length - 1;
|
|
603
|
+
let lineNo = 1;
|
|
604
|
+
for (let i = 0; i < match.index; i++) {
|
|
605
|
+
if (source[i] === '\n')
|
|
606
|
+
lineNo++;
|
|
607
|
+
}
|
|
608
|
+
results.push({ bodyStart: bodyBrace + 1, varNames: [varName], lineNo });
|
|
609
|
+
}
|
|
610
|
+
return results;
|
|
611
|
+
}
|
|
590
612
|
function findForLoopVars(source) {
|
|
591
613
|
const results = [];
|
|
592
614
|
// Match: for (const/let/var ...
|
|
@@ -1212,9 +1234,11 @@ export function transformEsmSource(source, filename, moduleName, backendUrl, deb
|
|
|
1212
1234
|
const reassignInsertions = traceVars ? findReassignments(source) : [];
|
|
1213
1235
|
// Find for-loop variable declarations for tracing
|
|
1214
1236
|
const forLoopInsertions = traceVars ? findForLoopVars(source) : [];
|
|
1237
|
+
// Find catch clause variables for tracing
|
|
1238
|
+
const catchInsertions = traceVars ? findCatchVars(source) : [];
|
|
1215
1239
|
// Find function parameter names for tracing
|
|
1216
1240
|
const funcParamInsertions = traceVars ? findFunctionParams(source, isReactFile) : [];
|
|
1217
|
-
if (funcInsertions.length === 0 && varInsertions.length === 0 && destructInsertions.length === 0 && reassignInsertions.length === 0 && forLoopInsertions.length === 0 && funcParamInsertions.length === 0 && bodyInsertions.length === 0 && hookInsertions.length === 0 && stateInsertions.length === 0 && conciseBodyInsertions.length === 0)
|
|
1241
|
+
if (funcInsertions.length === 0 && varInsertions.length === 0 && destructInsertions.length === 0 && reassignInsertions.length === 0 && forLoopInsertions.length === 0 && catchInsertions.length === 0 && funcParamInsertions.length === 0 && bodyInsertions.length === 0 && hookInsertions.length === 0 && stateInsertions.length === 0 && conciseBodyInsertions.length === 0)
|
|
1218
1242
|
return source;
|
|
1219
1243
|
// Fix line numbers: Vite transforms (TypeScript stripping) may change line numbers.
|
|
1220
1244
|
// Map transformed line numbers to original source line numbers.
|
|
@@ -1283,32 +1307,25 @@ export function transformEsmSource(source, filename, moduleName, backendUrl, deb
|
|
|
1283
1307
|
}
|
|
1284
1308
|
}
|
|
1285
1309
|
// Build prefix — ALL imports first (ESM requires imports before any statements)
|
|
1286
|
-
const needsTracing = varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || funcParamInsertions.length > 0 || bodyInsertions.length > 0 || hookInsertions.length > 0 || stateInsertions.length > 0 || conciseBodyInsertions.length > 0;
|
|
1287
|
-
const importLines = [
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1310
|
+
const needsTracing = varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || catchInsertions.length > 0 || funcParamInsertions.length > 0 || bodyInsertions.length > 0 || hookInsertions.length > 0 || stateInsertions.length > 0 || conciseBodyInsertions.length > 0;
|
|
1311
|
+
const importLines = [];
|
|
1312
|
+
if (isSSR) {
|
|
1313
|
+
// SSR/Node.js — import trickle-observe for function wrapping + file system for writing
|
|
1314
|
+
importLines.push(`import { wrapFunction as __trickle_wrapFn, configure as __trickle_configure } from 'trickle-observe';`);
|
|
1315
|
+
if (needsTracing) {
|
|
1316
|
+
importLines.push(`import { mkdirSync as __trickle_mkdirSync, appendFileSync as __trickle_appendFileSync } from 'node:fs';`, `import { join as __trickle_join } from 'node:path';`);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
// Browser mode: no imports needed — variable tracers are self-contained,
|
|
1320
|
+
// function wrapping is a no-op, and transport uses import.meta.hot
|
|
1321
|
+
const prefixLines = [...importLines];
|
|
1322
|
+
if (isSSR) {
|
|
1323
|
+
prefixLines.push(`__trickle_configure({ backendUrl: ${JSON.stringify(backendUrl)}, batchIntervalMs: 2000, debug: ${debug}, enabled: true, environment: 'node' });`, `function __trickle_wrap(fn, name, paramNames) {`, ` const opts = {`, ` functionName: name,`, ` module: ${JSON.stringify(moduleName)},`, ` trackArgs: true,`, ` trackReturn: true,`, ` sampleRate: 1,`, ` maxDepth: 5,`, ` environment: 'node',`, ` enabled: true,`, ` };`, ` if (paramNames && paramNames.length) opts.paramNames = paramNames;`, ` return __trickle_wrapFn(fn, opts);`, `}`);
|
|
1324
|
+
}
|
|
1325
|
+
else {
|
|
1326
|
+
// Browser mode: __trickle_wrap is a no-op (function wrapping uses Node.js APIs)
|
|
1327
|
+
prefixLines.push(`function __trickle_wrap(fn) { return fn; }`);
|
|
1293
1328
|
}
|
|
1294
|
-
const prefixLines = [
|
|
1295
|
-
...importLines,
|
|
1296
|
-
`__trickle_configure({ backendUrl: ${JSON.stringify(backendUrl)}, batchIntervalMs: 2000, debug: ${debug}, enabled: true, environment: 'node' });`,
|
|
1297
|
-
`function __trickle_wrap(fn, name, paramNames) {`,
|
|
1298
|
-
` const opts = {`,
|
|
1299
|
-
` functionName: name,`,
|
|
1300
|
-
` module: ${JSON.stringify(moduleName)},`,
|
|
1301
|
-
` trackArgs: true,`,
|
|
1302
|
-
` trackReturn: true,`,
|
|
1303
|
-
` sampleRate: 1,`,
|
|
1304
|
-
` maxDepth: 5,`,
|
|
1305
|
-
` environment: 'node',`,
|
|
1306
|
-
` enabled: true,`,
|
|
1307
|
-
` };`,
|
|
1308
|
-
` if (paramNames && paramNames.length) opts.paramNames = paramNames;`,
|
|
1309
|
-
` return __trickle_wrapFn(fn, opts);`,
|
|
1310
|
-
`}`,
|
|
1311
|
-
];
|
|
1312
1329
|
// Add unified __trickle_send() transport — browser uses HMR WebSocket, SSR uses fs
|
|
1313
1330
|
if (needsTracing) {
|
|
1314
1331
|
if (isSSR) {
|
|
@@ -1321,7 +1338,7 @@ export function transformEsmSource(source, filename, moduleName, backendUrl, deb
|
|
|
1321
1338
|
}
|
|
1322
1339
|
}
|
|
1323
1340
|
// Add variable tracing if needed — inlined to avoid import resolution issues in Vite SSR.
|
|
1324
|
-
if (varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || funcParamInsertions.length > 0) {
|
|
1341
|
+
if (varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || catchInsertions.length > 0 || funcParamInsertions.length > 0) {
|
|
1325
1342
|
prefixLines.push(`if (!globalThis.__trickle_var_tracer) {`, ` const _cache = new Set();`, ` function _inferType(v, d) {`, ` if (d <= 0) return { kind: 'primitive', name: 'unknown' };`, ` if (v === null) return { kind: 'primitive', name: 'null' };`, ` if (v === undefined) return { kind: 'primitive', name: 'undefined' };`, ` const t = typeof v;`, ` if (t === 'string' || t === 'number' || t === 'boolean' || t === 'bigint' || t === 'symbol') return { kind: 'primitive', name: t };`, ` if (t === 'function') return { kind: 'function' };`, ` if (Array.isArray(v)) { return v.length === 0 ? { kind: 'array', element: { kind: 'primitive', name: 'unknown' } } : { kind: 'array', element: _inferType(v[0], d-1) }; }`, ` if (t === 'object') {`, ` if (v instanceof Date) return { kind: 'object', properties: { __date: { kind: 'primitive', name: 'string' } } };`, ` if (v instanceof RegExp) return { kind: 'object', properties: { __regexp: { kind: 'primitive', name: 'string' } } };`, ` if (v instanceof Error) return { kind: 'object', properties: { __error: { kind: 'primitive', name: 'string' } } };`, ` if (v instanceof Promise) return { kind: 'promise', resolved: { kind: 'primitive', name: 'unknown' } };`, ` const props = {}; const keys = Object.keys(v).slice(0, 20);`, ` for (const k of keys) { try { props[k] = _inferType(v[k], d-1); } catch(e) { props[k] = { kind: 'primitive', name: 'unknown' }; } }`, ` return { kind: 'object', properties: props };`, ` }`, ` return { kind: 'primitive', name: 'unknown' };`, ` }`, ` function _sanitize(v, d) {`, ` if (d <= 0) return '[truncated]'; if (v === null || v === undefined) return v; const t = typeof v;`, ` if (t === 'string') return v.length > 100 ? v.substring(0, 100) + '...' : v;`, ` if (t === 'number' || t === 'boolean') return v; if (t === 'bigint') return String(v);`, ` if (t === 'function') return '[Function: ' + (v.name || 'anonymous') + ']';`, ` if (Array.isArray(v)) return v.slice(0, 3).map(i => _sanitize(i, d-1));`, ` if (t === 'object') { if (v instanceof Date) return v.toISOString(); if (v instanceof RegExp) return String(v); if (v instanceof Error) return { error: v.message }; if (v instanceof Promise) return '[Promise]';`, ` const r = {}; const keys = Object.keys(v).slice(0, 10); for (const k of keys) { try { r[k] = _sanitize(v[k], d-1); } catch(e) { r[k] = '[unreadable]'; } } return r; }`, ` return String(v);`, ` }`, ` globalThis.__trickle_var_tracer = function(v, n, l, mod, file) {`, ` try {`, ` const type = _inferType(v, 3);`, ` const th = JSON.stringify(type).substring(0, 32);`, ` const ck = file + ':' + l + ':' + n + ':' + th;`, ` if (_cache.has(ck)) return;`, ` _cache.add(ck);`, ` __trickle_send(JSON.stringify({ kind: 'variable', varName: n, line: l, module: mod, file: file, type: type, typeHash: th, sample: _sanitize(v, 2) }));`, ` } catch(e) {}`, ` };`, `}`, `function __trickle_tv(v, n, l) { try { globalThis.__trickle_var_tracer(v, n, l, ${JSON.stringify(moduleName)}, ${JSON.stringify(filename)}); } catch(e) {} }`);
|
|
1326
1343
|
}
|
|
1327
1344
|
// Add React component render tracker if needed
|
|
@@ -1366,6 +1383,14 @@ export function transformEsmSource(source, filename, moduleName, backendUrl, deb
|
|
|
1366
1383
|
code: `\n;try{__trickle_tv(${varName},${JSON.stringify(varName)},${lineNo})}catch(__e){}\n`,
|
|
1367
1384
|
});
|
|
1368
1385
|
}
|
|
1386
|
+
// Catch clause insertions: insert trace at start of catch body
|
|
1387
|
+
for (const { bodyStart, varNames, lineNo } of catchInsertions) {
|
|
1388
|
+
const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
|
|
1389
|
+
allInsertions.push({
|
|
1390
|
+
position: bodyStart,
|
|
1391
|
+
code: `\ntry{${calls}}catch(__e2){}\n`,
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1369
1394
|
// For-loop variable insertions: insert trace at start of loop body
|
|
1370
1395
|
for (const { bodyStart, varNames, lineNo } of forLoopInsertions) {
|
|
1371
1396
|
const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
|
package/package.json
CHANGED
package/src/vite-plugin.ts
CHANGED
|
@@ -566,6 +566,31 @@ function findReassignments(source: string): Array<{ lineEnd: number; varName: st
|
|
|
566
566
|
* for (let i = 0; i < n; i++) { ... } → trace i
|
|
567
567
|
* Inserts trace calls at the start of the loop body.
|
|
568
568
|
*/
|
|
569
|
+
/**
|
|
570
|
+
* Find catch clause variables and return insertions for tracing.
|
|
571
|
+
* Handles: catch (err) { ... } → trace err at start of catch body.
|
|
572
|
+
*/
|
|
573
|
+
function findCatchVars(source: string): Array<{ bodyStart: number; varNames: string[]; lineNo: number }> {
|
|
574
|
+
const results: Array<{ bodyStart: number; varNames: string[]; lineNo: number }> = [];
|
|
575
|
+
const catchRegex = /\bcatch\s*\(\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?::\s*[^)]+?)?\s*\)\s*\{/g;
|
|
576
|
+
let match;
|
|
577
|
+
|
|
578
|
+
while ((match = catchRegex.exec(source)) !== null) {
|
|
579
|
+
const varName = match[1];
|
|
580
|
+
if (varName.startsWith('__trickle')) continue;
|
|
581
|
+
|
|
582
|
+
const bodyBrace = match.index + match[0].length - 1;
|
|
583
|
+
let lineNo = 1;
|
|
584
|
+
for (let i = 0; i < match.index; i++) {
|
|
585
|
+
if (source[i] === '\n') lineNo++;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
results.push({ bodyStart: bodyBrace + 1, varNames: [varName], lineNo });
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
return results;
|
|
592
|
+
}
|
|
593
|
+
|
|
569
594
|
function findForLoopVars(source: string): Array<{ bodyStart: number; varNames: string[]; lineNo: number }> {
|
|
570
595
|
const results: Array<{ bodyStart: number; varNames: string[]; lineNo: number }> = [];
|
|
571
596
|
|
|
@@ -1209,10 +1234,13 @@ export function transformEsmSource(
|
|
|
1209
1234
|
// Find for-loop variable declarations for tracing
|
|
1210
1235
|
const forLoopInsertions = traceVars ? findForLoopVars(source) : [];
|
|
1211
1236
|
|
|
1237
|
+
// Find catch clause variables for tracing
|
|
1238
|
+
const catchInsertions = traceVars ? findCatchVars(source) : [];
|
|
1239
|
+
|
|
1212
1240
|
// Find function parameter names for tracing
|
|
1213
1241
|
const funcParamInsertions = traceVars ? findFunctionParams(source, isReactFile) : [];
|
|
1214
1242
|
|
|
1215
|
-
if (funcInsertions.length === 0 && varInsertions.length === 0 && destructInsertions.length === 0 && reassignInsertions.length === 0 && forLoopInsertions.length === 0 && funcParamInsertions.length === 0 && bodyInsertions.length === 0 && hookInsertions.length === 0 && stateInsertions.length === 0 && conciseBodyInsertions.length === 0) return source;
|
|
1243
|
+
if (funcInsertions.length === 0 && varInsertions.length === 0 && destructInsertions.length === 0 && reassignInsertions.length === 0 && forLoopInsertions.length === 0 && catchInsertions.length === 0 && funcParamInsertions.length === 0 && bodyInsertions.length === 0 && hookInsertions.length === 0 && stateInsertions.length === 0 && conciseBodyInsertions.length === 0) return source;
|
|
1216
1244
|
|
|
1217
1245
|
// Fix line numbers: Vite transforms (TypeScript stripping) may change line numbers.
|
|
1218
1246
|
// Map transformed line numbers to original source line numbers.
|
|
@@ -1280,36 +1308,50 @@ export function transformEsmSource(
|
|
|
1280
1308
|
}
|
|
1281
1309
|
|
|
1282
1310
|
// Build prefix — ALL imports first (ESM requires imports before any statements)
|
|
1283
|
-
const needsTracing = varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || funcParamInsertions.length > 0 || bodyInsertions.length > 0 || hookInsertions.length > 0 || stateInsertions.length > 0 || conciseBodyInsertions.length > 0;
|
|
1284
|
-
const importLines: string[] = [
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
// SSR/Node.js — use file system for writing
|
|
1311
|
+
const needsTracing = varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || catchInsertions.length > 0 || funcParamInsertions.length > 0 || bodyInsertions.length > 0 || hookInsertions.length > 0 || stateInsertions.length > 0 || conciseBodyInsertions.length > 0;
|
|
1312
|
+
const importLines: string[] = [];
|
|
1313
|
+
|
|
1314
|
+
if (isSSR) {
|
|
1315
|
+
// SSR/Node.js — import trickle-observe for function wrapping + file system for writing
|
|
1289
1316
|
importLines.push(
|
|
1290
|
-
`import {
|
|
1291
|
-
`import { join as __trickle_join } from 'node:path';`,
|
|
1317
|
+
`import { wrapFunction as __trickle_wrapFn, configure as __trickle_configure } from 'trickle-observe';`,
|
|
1292
1318
|
);
|
|
1319
|
+
if (needsTracing) {
|
|
1320
|
+
importLines.push(
|
|
1321
|
+
`import { mkdirSync as __trickle_mkdirSync, appendFileSync as __trickle_appendFileSync } from 'node:fs';`,
|
|
1322
|
+
`import { join as __trickle_join } from 'node:path';`,
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1293
1325
|
}
|
|
1326
|
+
// Browser mode: no imports needed — variable tracers are self-contained,
|
|
1327
|
+
// function wrapping is a no-op, and transport uses import.meta.hot
|
|
1294
1328
|
|
|
1295
|
-
const prefixLines = [
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1329
|
+
const prefixLines = [...importLines];
|
|
1330
|
+
|
|
1331
|
+
if (isSSR) {
|
|
1332
|
+
prefixLines.push(
|
|
1333
|
+
`__trickle_configure({ backendUrl: ${JSON.stringify(backendUrl)}, batchIntervalMs: 2000, debug: ${debug}, enabled: true, environment: 'node' });`,
|
|
1334
|
+
`function __trickle_wrap(fn, name, paramNames) {`,
|
|
1335
|
+
` const opts = {`,
|
|
1336
|
+
` functionName: name,`,
|
|
1337
|
+
` module: ${JSON.stringify(moduleName)},`,
|
|
1338
|
+
` trackArgs: true,`,
|
|
1339
|
+
` trackReturn: true,`,
|
|
1340
|
+
` sampleRate: 1,`,
|
|
1341
|
+
` maxDepth: 5,`,
|
|
1342
|
+
` environment: 'node',`,
|
|
1343
|
+
` enabled: true,`,
|
|
1344
|
+
` };`,
|
|
1345
|
+
` if (paramNames && paramNames.length) opts.paramNames = paramNames;`,
|
|
1346
|
+
` return __trickle_wrapFn(fn, opts);`,
|
|
1347
|
+
`}`,
|
|
1348
|
+
);
|
|
1349
|
+
} else {
|
|
1350
|
+
// Browser mode: __trickle_wrap is a no-op (function wrapping uses Node.js APIs)
|
|
1351
|
+
prefixLines.push(
|
|
1352
|
+
`function __trickle_wrap(fn) { return fn; }`,
|
|
1353
|
+
);
|
|
1354
|
+
}
|
|
1313
1355
|
|
|
1314
1356
|
// Add unified __trickle_send() transport — browser uses HMR WebSocket, SSR uses fs
|
|
1315
1357
|
if (needsTracing) {
|
|
@@ -1350,7 +1392,7 @@ export function transformEsmSource(
|
|
|
1350
1392
|
}
|
|
1351
1393
|
|
|
1352
1394
|
// Add variable tracing if needed — inlined to avoid import resolution issues in Vite SSR.
|
|
1353
|
-
if (varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || funcParamInsertions.length > 0) {
|
|
1395
|
+
if (varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || catchInsertions.length > 0 || funcParamInsertions.length > 0) {
|
|
1354
1396
|
prefixLines.push(
|
|
1355
1397
|
`if (!globalThis.__trickle_var_tracer) {`,
|
|
1356
1398
|
` const _cache = new Set();`,
|
|
@@ -1529,6 +1571,15 @@ export function transformEsmSource(
|
|
|
1529
1571
|
});
|
|
1530
1572
|
}
|
|
1531
1573
|
|
|
1574
|
+
// Catch clause insertions: insert trace at start of catch body
|
|
1575
|
+
for (const { bodyStart, varNames, lineNo } of catchInsertions) {
|
|
1576
|
+
const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
|
|
1577
|
+
allInsertions.push({
|
|
1578
|
+
position: bodyStart,
|
|
1579
|
+
code: `\ntry{${calls}}catch(__e2){}\n`,
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1532
1583
|
// For-loop variable insertions: insert trace at start of loop body
|
|
1533
1584
|
for (const { bodyStart, varNames, lineNo } of forLoopInsertions) {
|
|
1534
1585
|
const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
|