trickle-observe 0.2.70 → 0.2.72
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 +154 -3
- package/dist-esm/vite-plugin.js +154 -3
- package/package.json +1 -1
- package/src/vite-plugin.ts +151 -3
package/dist/vite-plugin.js
CHANGED
|
@@ -480,6 +480,110 @@ function extractDestructuredNames(pattern) {
|
|
|
480
480
|
}
|
|
481
481
|
return names;
|
|
482
482
|
}
|
|
483
|
+
/**
|
|
484
|
+
* Find variable reassignments (not declarations) and return insertions for tracing.
|
|
485
|
+
* Handles: x = newValue; x += 1; x ||= fallback; etc.
|
|
486
|
+
* Only matches standalone reassignment statements at the start of a line.
|
|
487
|
+
* Skips: property assignments (obj.x = ...), indexed (arr[i] = ...),
|
|
488
|
+
* comparisons (===, !==), arrow functions (=>), declarations (const/let/var).
|
|
489
|
+
*/
|
|
490
|
+
function findReassignments(source) {
|
|
491
|
+
const results = [];
|
|
492
|
+
// Match: <identifier> <assignOp>= <value> at the start of a line
|
|
493
|
+
// Compound operators: +=, -=, *=, /=, %=, **=, &&=, ||=, ??=, <<=, >>=, >>>=, &=, |=, ^=
|
|
494
|
+
// Plain: = (but not ==, ===, =>, !=)
|
|
495
|
+
const reassignRegex = /^([ \t]*)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?:\+|-|\*\*?|\/|%|&&|\|\||<<|>>>?|&|\||\^|\?\?)?=[^=>]/gm;
|
|
496
|
+
let match;
|
|
497
|
+
while ((match = reassignRegex.exec(source)) !== null) {
|
|
498
|
+
const varName = match[2];
|
|
499
|
+
// Skip trickle internals
|
|
500
|
+
if (varName.startsWith('__trickle') || varName.startsWith('_$'))
|
|
501
|
+
continue;
|
|
502
|
+
// Skip common non-variable patterns
|
|
503
|
+
if (varName === '_a' || varName === '_b' || varName === '_c')
|
|
504
|
+
continue;
|
|
505
|
+
// Skip 'this', 'self', 'super' (not reassignable in practice)
|
|
506
|
+
if (varName === 'this' || varName === 'super')
|
|
507
|
+
continue;
|
|
508
|
+
// Skip keywords that could look like identifiers
|
|
509
|
+
if (['if', 'else', 'while', 'for', 'do', 'switch', 'case', 'default', 'return', 'throw',
|
|
510
|
+
'break', 'continue', 'try', 'catch', 'finally', 'new', 'delete', 'typeof', 'void',
|
|
511
|
+
'yield', 'await', 'class', 'extends', 'import', 'export', 'from', 'as', 'of', 'in',
|
|
512
|
+
'const', 'let', 'var', 'function', 'true', 'false', 'null', 'undefined'].includes(varName))
|
|
513
|
+
continue;
|
|
514
|
+
// Check that this line doesn't start with const/let/var (would be a declaration, already handled)
|
|
515
|
+
const lineStart = source.lastIndexOf('\n', match.index) + 1;
|
|
516
|
+
const linePrefix = source.slice(lineStart, match.index + match[1].length).trim();
|
|
517
|
+
if (/^(export\s+)?(const|let|var)\s/.test(source.slice(lineStart).trimStart()))
|
|
518
|
+
continue;
|
|
519
|
+
// Skip if this looks like a property in an object literal (preceded by a key: pattern on same line)
|
|
520
|
+
// or if it's a label (label: ...)
|
|
521
|
+
const beforeOnLine = source.slice(lineStart, match.index).trim();
|
|
522
|
+
if (beforeOnLine.endsWith(':') || beforeOnLine.endsWith(','))
|
|
523
|
+
continue;
|
|
524
|
+
// Calculate line number
|
|
525
|
+
let lineNo = 1;
|
|
526
|
+
for (let i = 0; i < match.index; i++) {
|
|
527
|
+
if (source[i] === '\n')
|
|
528
|
+
lineNo++;
|
|
529
|
+
}
|
|
530
|
+
// Find end of statement (same logic as findVarDeclarations)
|
|
531
|
+
const startPos = match.index + match[0].length - 1;
|
|
532
|
+
let pos = startPos;
|
|
533
|
+
let depth = 0;
|
|
534
|
+
let foundEnd = -1;
|
|
535
|
+
while (pos < source.length) {
|
|
536
|
+
const ch = source[pos];
|
|
537
|
+
if (ch === '(' || ch === '[' || ch === '{') {
|
|
538
|
+
depth++;
|
|
539
|
+
}
|
|
540
|
+
else if (ch === ')' || ch === ']' || ch === '}') {
|
|
541
|
+
depth--;
|
|
542
|
+
if (depth < 0)
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
else if (ch === ';' && depth === 0) {
|
|
546
|
+
foundEnd = pos;
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
else if (ch === '\n' && depth === 0) {
|
|
550
|
+
const nextNonWs = source.slice(pos + 1).match(/^\s*(\S)/);
|
|
551
|
+
if (nextNonWs && !'.+=-|&?:,'.includes(nextNonWs[1])) {
|
|
552
|
+
foundEnd = pos;
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
else if (ch === '"' || ch === "'" || ch === '`') {
|
|
557
|
+
const quote = ch;
|
|
558
|
+
pos++;
|
|
559
|
+
while (pos < source.length) {
|
|
560
|
+
if (source[pos] === '\\') {
|
|
561
|
+
pos++;
|
|
562
|
+
}
|
|
563
|
+
else if (source[pos] === quote)
|
|
564
|
+
break;
|
|
565
|
+
pos++;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
else if (ch === '/' && pos + 1 < source.length && source[pos + 1] === '/') {
|
|
569
|
+
while (pos < source.length && source[pos] !== '\n')
|
|
570
|
+
pos++;
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
else if (ch === '/' && pos + 1 < source.length && source[pos + 1] === '*') {
|
|
574
|
+
pos += 2;
|
|
575
|
+
while (pos < source.length - 1 && !(source[pos] === '*' && source[pos + 1] === '/'))
|
|
576
|
+
pos++;
|
|
577
|
+
pos++;
|
|
578
|
+
}
|
|
579
|
+
pos++;
|
|
580
|
+
}
|
|
581
|
+
if (foundEnd === -1)
|
|
582
|
+
continue;
|
|
583
|
+
results.push({ lineEnd: foundEnd + 1, varName, lineNo });
|
|
584
|
+
}
|
|
585
|
+
return results;
|
|
586
|
+
}
|
|
483
587
|
/**
|
|
484
588
|
* Find for-loop variable declarations and return insertions for tracing.
|
|
485
589
|
* Handles:
|
|
@@ -490,6 +594,28 @@ function extractDestructuredNames(pattern) {
|
|
|
490
594
|
* for (let i = 0; i < n; i++) { ... } → trace i
|
|
491
595
|
* Inserts trace calls at the start of the loop body.
|
|
492
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
|
+
}
|
|
493
619
|
function findForLoopVars(source) {
|
|
494
620
|
const results = [];
|
|
495
621
|
// Match: for (const/let/var ...
|
|
@@ -1111,11 +1237,15 @@ function transformEsmSource(source, filename, moduleName, backendUrl, debug, tra
|
|
|
1111
1237
|
const varInsertions = traceVars ? findVarDeclarations(source) : [];
|
|
1112
1238
|
// Find destructured variable declarations for tracing
|
|
1113
1239
|
const destructInsertions = traceVars ? findDestructuredDeclarations(source) : [];
|
|
1240
|
+
// Find variable reassignments for tracing
|
|
1241
|
+
const reassignInsertions = traceVars ? findReassignments(source) : [];
|
|
1114
1242
|
// Find for-loop variable declarations for tracing
|
|
1115
1243
|
const forLoopInsertions = traceVars ? findForLoopVars(source) : [];
|
|
1244
|
+
// Find catch clause variables for tracing
|
|
1245
|
+
const catchInsertions = traceVars ? findCatchVars(source) : [];
|
|
1116
1246
|
// Find function parameter names for tracing
|
|
1117
1247
|
const funcParamInsertions = traceVars ? findFunctionParams(source, isReactFile) : [];
|
|
1118
|
-
if (funcInsertions.length === 0 && varInsertions.length === 0 && destructInsertions.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)
|
|
1119
1249
|
return source;
|
|
1120
1250
|
// Fix line numbers: Vite transforms (TypeScript stripping) may change line numbers.
|
|
1121
1251
|
// Map transformed line numbers to original source line numbers.
|
|
@@ -1135,6 +1265,12 @@ function transformEsmSource(source, filename, moduleName, backendUrl, debug, tra
|
|
|
1135
1265
|
di.lineNo = origLine;
|
|
1136
1266
|
}
|
|
1137
1267
|
}
|
|
1268
|
+
// Fix reassignment line numbers
|
|
1269
|
+
for (const ri of reassignInsertions) {
|
|
1270
|
+
const origLine = findOriginalLine(origLines, ri.varName, ri.lineNo);
|
|
1271
|
+
if (origLine !== -1)
|
|
1272
|
+
ri.lineNo = origLine;
|
|
1273
|
+
}
|
|
1138
1274
|
// Fix for-loop var line numbers
|
|
1139
1275
|
for (const fi of forLoopInsertions) {
|
|
1140
1276
|
if (fi.varNames.length > 0) {
|
|
@@ -1178,7 +1314,7 @@ function transformEsmSource(source, filename, moduleName, backendUrl, debug, tra
|
|
|
1178
1314
|
}
|
|
1179
1315
|
}
|
|
1180
1316
|
// Build prefix — ALL imports first (ESM requires imports before any statements)
|
|
1181
|
-
const needsTracing = varInsertions.length > 0 || destructInsertions.length > 0 || forLoopInsertions.length > 0 || funcParamInsertions.length > 0 || bodyInsertions.length > 0 || hookInsertions.length > 0 || stateInsertions.length > 0 || conciseBodyInsertions.length > 0;
|
|
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;
|
|
1182
1318
|
const importLines = [
|
|
1183
1319
|
`import { wrapFunction as __trickle_wrapFn, configure as __trickle_configure } from 'trickle-observe';`,
|
|
1184
1320
|
];
|
|
@@ -1216,7 +1352,7 @@ function transformEsmSource(source, filename, moduleName, backendUrl, debug, tra
|
|
|
1216
1352
|
}
|
|
1217
1353
|
}
|
|
1218
1354
|
// Add variable tracing if needed — inlined to avoid import resolution issues in Vite SSR.
|
|
1219
|
-
if (varInsertions.length > 0 || destructInsertions.length > 0 || forLoopInsertions.length > 0 || funcParamInsertions.length > 0) {
|
|
1355
|
+
if (varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || catchInsertions.length > 0 || funcParamInsertions.length > 0) {
|
|
1220
1356
|
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) {} }`);
|
|
1221
1357
|
}
|
|
1222
1358
|
// Add React component render tracker if needed
|
|
@@ -1254,6 +1390,21 @@ function transformEsmSource(source, filename, moduleName, backendUrl, debug, tra
|
|
|
1254
1390
|
code: `\n;try{${calls}}catch(__e){}\n`,
|
|
1255
1391
|
});
|
|
1256
1392
|
}
|
|
1393
|
+
// Reassignment insertions: trace after the reassignment statement
|
|
1394
|
+
for (const { lineEnd, varName, lineNo } of reassignInsertions) {
|
|
1395
|
+
allInsertions.push({
|
|
1396
|
+
position: lineEnd,
|
|
1397
|
+
code: `\n;try{__trickle_tv(${varName},${JSON.stringify(varName)},${lineNo})}catch(__e){}\n`,
|
|
1398
|
+
});
|
|
1399
|
+
}
|
|
1400
|
+
// Catch clause insertions: insert trace at start of catch body
|
|
1401
|
+
for (const { bodyStart, varNames, lineNo } of catchInsertions) {
|
|
1402
|
+
const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
|
|
1403
|
+
allInsertions.push({
|
|
1404
|
+
position: bodyStart,
|
|
1405
|
+
code: `\ntry{${calls}}catch(__e2){}\n`,
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1257
1408
|
// For-loop variable insertions: insert trace at start of loop body
|
|
1258
1409
|
for (const { bodyStart, varNames, lineNo } of forLoopInsertions) {
|
|
1259
1410
|
const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
|
package/dist-esm/vite-plugin.js
CHANGED
|
@@ -473,6 +473,110 @@ function extractDestructuredNames(pattern) {
|
|
|
473
473
|
}
|
|
474
474
|
return names;
|
|
475
475
|
}
|
|
476
|
+
/**
|
|
477
|
+
* Find variable reassignments (not declarations) and return insertions for tracing.
|
|
478
|
+
* Handles: x = newValue; x += 1; x ||= fallback; etc.
|
|
479
|
+
* Only matches standalone reassignment statements at the start of a line.
|
|
480
|
+
* Skips: property assignments (obj.x = ...), indexed (arr[i] = ...),
|
|
481
|
+
* comparisons (===, !==), arrow functions (=>), declarations (const/let/var).
|
|
482
|
+
*/
|
|
483
|
+
function findReassignments(source) {
|
|
484
|
+
const results = [];
|
|
485
|
+
// Match: <identifier> <assignOp>= <value> at the start of a line
|
|
486
|
+
// Compound operators: +=, -=, *=, /=, %=, **=, &&=, ||=, ??=, <<=, >>=, >>>=, &=, |=, ^=
|
|
487
|
+
// Plain: = (but not ==, ===, =>, !=)
|
|
488
|
+
const reassignRegex = /^([ \t]*)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?:\+|-|\*\*?|\/|%|&&|\|\||<<|>>>?|&|\||\^|\?\?)?=[^=>]/gm;
|
|
489
|
+
let match;
|
|
490
|
+
while ((match = reassignRegex.exec(source)) !== null) {
|
|
491
|
+
const varName = match[2];
|
|
492
|
+
// Skip trickle internals
|
|
493
|
+
if (varName.startsWith('__trickle') || varName.startsWith('_$'))
|
|
494
|
+
continue;
|
|
495
|
+
// Skip common non-variable patterns
|
|
496
|
+
if (varName === '_a' || varName === '_b' || varName === '_c')
|
|
497
|
+
continue;
|
|
498
|
+
// Skip 'this', 'self', 'super' (not reassignable in practice)
|
|
499
|
+
if (varName === 'this' || varName === 'super')
|
|
500
|
+
continue;
|
|
501
|
+
// Skip keywords that could look like identifiers
|
|
502
|
+
if (['if', 'else', 'while', 'for', 'do', 'switch', 'case', 'default', 'return', 'throw',
|
|
503
|
+
'break', 'continue', 'try', 'catch', 'finally', 'new', 'delete', 'typeof', 'void',
|
|
504
|
+
'yield', 'await', 'class', 'extends', 'import', 'export', 'from', 'as', 'of', 'in',
|
|
505
|
+
'const', 'let', 'var', 'function', 'true', 'false', 'null', 'undefined'].includes(varName))
|
|
506
|
+
continue;
|
|
507
|
+
// Check that this line doesn't start with const/let/var (would be a declaration, already handled)
|
|
508
|
+
const lineStart = source.lastIndexOf('\n', match.index) + 1;
|
|
509
|
+
const linePrefix = source.slice(lineStart, match.index + match[1].length).trim();
|
|
510
|
+
if (/^(export\s+)?(const|let|var)\s/.test(source.slice(lineStart).trimStart()))
|
|
511
|
+
continue;
|
|
512
|
+
// Skip if this looks like a property in an object literal (preceded by a key: pattern on same line)
|
|
513
|
+
// or if it's a label (label: ...)
|
|
514
|
+
const beforeOnLine = source.slice(lineStart, match.index).trim();
|
|
515
|
+
if (beforeOnLine.endsWith(':') || beforeOnLine.endsWith(','))
|
|
516
|
+
continue;
|
|
517
|
+
// Calculate line number
|
|
518
|
+
let lineNo = 1;
|
|
519
|
+
for (let i = 0; i < match.index; i++) {
|
|
520
|
+
if (source[i] === '\n')
|
|
521
|
+
lineNo++;
|
|
522
|
+
}
|
|
523
|
+
// Find end of statement (same logic as findVarDeclarations)
|
|
524
|
+
const startPos = match.index + match[0].length - 1;
|
|
525
|
+
let pos = startPos;
|
|
526
|
+
let depth = 0;
|
|
527
|
+
let foundEnd = -1;
|
|
528
|
+
while (pos < source.length) {
|
|
529
|
+
const ch = source[pos];
|
|
530
|
+
if (ch === '(' || ch === '[' || ch === '{') {
|
|
531
|
+
depth++;
|
|
532
|
+
}
|
|
533
|
+
else if (ch === ')' || ch === ']' || ch === '}') {
|
|
534
|
+
depth--;
|
|
535
|
+
if (depth < 0)
|
|
536
|
+
break;
|
|
537
|
+
}
|
|
538
|
+
else if (ch === ';' && depth === 0) {
|
|
539
|
+
foundEnd = pos;
|
|
540
|
+
break;
|
|
541
|
+
}
|
|
542
|
+
else if (ch === '\n' && depth === 0) {
|
|
543
|
+
const nextNonWs = source.slice(pos + 1).match(/^\s*(\S)/);
|
|
544
|
+
if (nextNonWs && !'.+=-|&?:,'.includes(nextNonWs[1])) {
|
|
545
|
+
foundEnd = pos;
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
else if (ch === '"' || ch === "'" || ch === '`') {
|
|
550
|
+
const quote = ch;
|
|
551
|
+
pos++;
|
|
552
|
+
while (pos < source.length) {
|
|
553
|
+
if (source[pos] === '\\') {
|
|
554
|
+
pos++;
|
|
555
|
+
}
|
|
556
|
+
else if (source[pos] === quote)
|
|
557
|
+
break;
|
|
558
|
+
pos++;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
else if (ch === '/' && pos + 1 < source.length && source[pos + 1] === '/') {
|
|
562
|
+
while (pos < source.length && source[pos] !== '\n')
|
|
563
|
+
pos++;
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
else if (ch === '/' && pos + 1 < source.length && source[pos + 1] === '*') {
|
|
567
|
+
pos += 2;
|
|
568
|
+
while (pos < source.length - 1 && !(source[pos] === '*' && source[pos + 1] === '/'))
|
|
569
|
+
pos++;
|
|
570
|
+
pos++;
|
|
571
|
+
}
|
|
572
|
+
pos++;
|
|
573
|
+
}
|
|
574
|
+
if (foundEnd === -1)
|
|
575
|
+
continue;
|
|
576
|
+
results.push({ lineEnd: foundEnd + 1, varName, lineNo });
|
|
577
|
+
}
|
|
578
|
+
return results;
|
|
579
|
+
}
|
|
476
580
|
/**
|
|
477
581
|
* Find for-loop variable declarations and return insertions for tracing.
|
|
478
582
|
* Handles:
|
|
@@ -483,6 +587,28 @@ function extractDestructuredNames(pattern) {
|
|
|
483
587
|
* for (let i = 0; i < n; i++) { ... } → trace i
|
|
484
588
|
* Inserts trace calls at the start of the loop body.
|
|
485
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
|
+
}
|
|
486
612
|
function findForLoopVars(source) {
|
|
487
613
|
const results = [];
|
|
488
614
|
// Match: for (const/let/var ...
|
|
@@ -1104,11 +1230,15 @@ export function transformEsmSource(source, filename, moduleName, backendUrl, deb
|
|
|
1104
1230
|
const varInsertions = traceVars ? findVarDeclarations(source) : [];
|
|
1105
1231
|
// Find destructured variable declarations for tracing
|
|
1106
1232
|
const destructInsertions = traceVars ? findDestructuredDeclarations(source) : [];
|
|
1233
|
+
// Find variable reassignments for tracing
|
|
1234
|
+
const reassignInsertions = traceVars ? findReassignments(source) : [];
|
|
1107
1235
|
// Find for-loop variable declarations for tracing
|
|
1108
1236
|
const forLoopInsertions = traceVars ? findForLoopVars(source) : [];
|
|
1237
|
+
// Find catch clause variables for tracing
|
|
1238
|
+
const catchInsertions = traceVars ? findCatchVars(source) : [];
|
|
1109
1239
|
// Find function parameter names for tracing
|
|
1110
1240
|
const funcParamInsertions = traceVars ? findFunctionParams(source, isReactFile) : [];
|
|
1111
|
-
if (funcInsertions.length === 0 && varInsertions.length === 0 && destructInsertions.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)
|
|
1112
1242
|
return source;
|
|
1113
1243
|
// Fix line numbers: Vite transforms (TypeScript stripping) may change line numbers.
|
|
1114
1244
|
// Map transformed line numbers to original source line numbers.
|
|
@@ -1128,6 +1258,12 @@ export function transformEsmSource(source, filename, moduleName, backendUrl, deb
|
|
|
1128
1258
|
di.lineNo = origLine;
|
|
1129
1259
|
}
|
|
1130
1260
|
}
|
|
1261
|
+
// Fix reassignment line numbers
|
|
1262
|
+
for (const ri of reassignInsertions) {
|
|
1263
|
+
const origLine = findOriginalLine(origLines, ri.varName, ri.lineNo);
|
|
1264
|
+
if (origLine !== -1)
|
|
1265
|
+
ri.lineNo = origLine;
|
|
1266
|
+
}
|
|
1131
1267
|
// Fix for-loop var line numbers
|
|
1132
1268
|
for (const fi of forLoopInsertions) {
|
|
1133
1269
|
if (fi.varNames.length > 0) {
|
|
@@ -1171,7 +1307,7 @@ export function transformEsmSource(source, filename, moduleName, backendUrl, deb
|
|
|
1171
1307
|
}
|
|
1172
1308
|
}
|
|
1173
1309
|
// Build prefix — ALL imports first (ESM requires imports before any statements)
|
|
1174
|
-
const needsTracing = varInsertions.length > 0 || destructInsertions.length > 0 || forLoopInsertions.length > 0 || funcParamInsertions.length > 0 || bodyInsertions.length > 0 || hookInsertions.length > 0 || stateInsertions.length > 0 || conciseBodyInsertions.length > 0;
|
|
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;
|
|
1175
1311
|
const importLines = [
|
|
1176
1312
|
`import { wrapFunction as __trickle_wrapFn, configure as __trickle_configure } from 'trickle-observe';`,
|
|
1177
1313
|
];
|
|
@@ -1209,7 +1345,7 @@ export function transformEsmSource(source, filename, moduleName, backendUrl, deb
|
|
|
1209
1345
|
}
|
|
1210
1346
|
}
|
|
1211
1347
|
// Add variable tracing if needed — inlined to avoid import resolution issues in Vite SSR.
|
|
1212
|
-
if (varInsertions.length > 0 || destructInsertions.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) {
|
|
1213
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) {} }`);
|
|
1214
1350
|
}
|
|
1215
1351
|
// Add React component render tracker if needed
|
|
@@ -1247,6 +1383,21 @@ export function transformEsmSource(source, filename, moduleName, backendUrl, deb
|
|
|
1247
1383
|
code: `\n;try{${calls}}catch(__e){}\n`,
|
|
1248
1384
|
});
|
|
1249
1385
|
}
|
|
1386
|
+
// Reassignment insertions: trace after the reassignment statement
|
|
1387
|
+
for (const { lineEnd, varName, lineNo } of reassignInsertions) {
|
|
1388
|
+
allInsertions.push({
|
|
1389
|
+
position: lineEnd,
|
|
1390
|
+
code: `\n;try{__trickle_tv(${varName},${JSON.stringify(varName)},${lineNo})}catch(__e){}\n`,
|
|
1391
|
+
});
|
|
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
|
+
}
|
|
1250
1401
|
// For-loop variable insertions: insert trace at start of loop body
|
|
1251
1402
|
for (const { bodyStart, varNames, lineNo } of forLoopInsertions) {
|
|
1252
1403
|
const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
|
package/package.json
CHANGED
package/src/vite-plugin.ts
CHANGED
|
@@ -461,6 +461,101 @@ function extractDestructuredNames(pattern: string): string[] {
|
|
|
461
461
|
return names;
|
|
462
462
|
}
|
|
463
463
|
|
|
464
|
+
/**
|
|
465
|
+
* Find variable reassignments (not declarations) and return insertions for tracing.
|
|
466
|
+
* Handles: x = newValue; x += 1; x ||= fallback; etc.
|
|
467
|
+
* Only matches standalone reassignment statements at the start of a line.
|
|
468
|
+
* Skips: property assignments (obj.x = ...), indexed (arr[i] = ...),
|
|
469
|
+
* comparisons (===, !==), arrow functions (=>), declarations (const/let/var).
|
|
470
|
+
*/
|
|
471
|
+
function findReassignments(source: string): Array<{ lineEnd: number; varName: string; lineNo: number }> {
|
|
472
|
+
const results: Array<{ lineEnd: number; varName: string; lineNo: number }> = [];
|
|
473
|
+
|
|
474
|
+
// Match: <identifier> <assignOp>= <value> at the start of a line
|
|
475
|
+
// Compound operators: +=, -=, *=, /=, %=, **=, &&=, ||=, ??=, <<=, >>=, >>>=, &=, |=, ^=
|
|
476
|
+
// Plain: = (but not ==, ===, =>, !=)
|
|
477
|
+
const reassignRegex = /^([ \t]*)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?:\+|-|\*\*?|\/|%|&&|\|\||<<|>>>?|&|\||\^|\?\?)?=[^=>]/gm;
|
|
478
|
+
let match;
|
|
479
|
+
|
|
480
|
+
while ((match = reassignRegex.exec(source)) !== null) {
|
|
481
|
+
const varName = match[2];
|
|
482
|
+
|
|
483
|
+
// Skip trickle internals
|
|
484
|
+
if (varName.startsWith('__trickle') || varName.startsWith('_$')) continue;
|
|
485
|
+
// Skip common non-variable patterns
|
|
486
|
+
if (varName === '_a' || varName === '_b' || varName === '_c') continue;
|
|
487
|
+
// Skip 'this', 'self', 'super' (not reassignable in practice)
|
|
488
|
+
if (varName === 'this' || varName === 'super') continue;
|
|
489
|
+
// Skip keywords that could look like identifiers
|
|
490
|
+
if (['if', 'else', 'while', 'for', 'do', 'switch', 'case', 'default', 'return', 'throw',
|
|
491
|
+
'break', 'continue', 'try', 'catch', 'finally', 'new', 'delete', 'typeof', 'void',
|
|
492
|
+
'yield', 'await', 'class', 'extends', 'import', 'export', 'from', 'as', 'of', 'in',
|
|
493
|
+
'const', 'let', 'var', 'function', 'true', 'false', 'null', 'undefined'].includes(varName)) continue;
|
|
494
|
+
|
|
495
|
+
// Check that this line doesn't start with const/let/var (would be a declaration, already handled)
|
|
496
|
+
const lineStart = source.lastIndexOf('\n', match.index) + 1;
|
|
497
|
+
const linePrefix = source.slice(lineStart, match.index + match[1].length).trim();
|
|
498
|
+
if (/^(export\s+)?(const|let|var)\s/.test(source.slice(lineStart).trimStart())) continue;
|
|
499
|
+
|
|
500
|
+
// Skip if this looks like a property in an object literal (preceded by a key: pattern on same line)
|
|
501
|
+
// or if it's a label (label: ...)
|
|
502
|
+
const beforeOnLine = source.slice(lineStart, match.index).trim();
|
|
503
|
+
if (beforeOnLine.endsWith(':') || beforeOnLine.endsWith(',')) continue;
|
|
504
|
+
|
|
505
|
+
// Calculate line number
|
|
506
|
+
let lineNo = 1;
|
|
507
|
+
for (let i = 0; i < match.index; i++) {
|
|
508
|
+
if (source[i] === '\n') lineNo++;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Find end of statement (same logic as findVarDeclarations)
|
|
512
|
+
const startPos = match.index + match[0].length - 1;
|
|
513
|
+
let pos = startPos;
|
|
514
|
+
let depth = 0;
|
|
515
|
+
let foundEnd = -1;
|
|
516
|
+
|
|
517
|
+
while (pos < source.length) {
|
|
518
|
+
const ch = source[pos];
|
|
519
|
+
if (ch === '(' || ch === '[' || ch === '{') {
|
|
520
|
+
depth++;
|
|
521
|
+
} else if (ch === ')' || ch === ']' || ch === '}') {
|
|
522
|
+
depth--;
|
|
523
|
+
if (depth < 0) break;
|
|
524
|
+
} else if (ch === ';' && depth === 0) {
|
|
525
|
+
foundEnd = pos;
|
|
526
|
+
break;
|
|
527
|
+
} else if (ch === '\n' && depth === 0) {
|
|
528
|
+
const nextNonWs = source.slice(pos + 1).match(/^\s*(\S)/);
|
|
529
|
+
if (nextNonWs && !'.+=-|&?:,'.includes(nextNonWs[1])) {
|
|
530
|
+
foundEnd = pos;
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
533
|
+
} else if (ch === '"' || ch === "'" || ch === '`') {
|
|
534
|
+
const quote = ch;
|
|
535
|
+
pos++;
|
|
536
|
+
while (pos < source.length) {
|
|
537
|
+
if (source[pos] === '\\') { pos++; }
|
|
538
|
+
else if (source[pos] === quote) break;
|
|
539
|
+
pos++;
|
|
540
|
+
}
|
|
541
|
+
} else if (ch === '/' && pos + 1 < source.length && source[pos + 1] === '/') {
|
|
542
|
+
while (pos < source.length && source[pos] !== '\n') pos++;
|
|
543
|
+
continue;
|
|
544
|
+
} else if (ch === '/' && pos + 1 < source.length && source[pos + 1] === '*') {
|
|
545
|
+
pos += 2;
|
|
546
|
+
while (pos < source.length - 1 && !(source[pos] === '*' && source[pos + 1] === '/')) pos++;
|
|
547
|
+
pos++;
|
|
548
|
+
}
|
|
549
|
+
pos++;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (foundEnd === -1) continue;
|
|
553
|
+
results.push({ lineEnd: foundEnd + 1, varName, lineNo });
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return results;
|
|
557
|
+
}
|
|
558
|
+
|
|
464
559
|
/**
|
|
465
560
|
* Find for-loop variable declarations and return insertions for tracing.
|
|
466
561
|
* Handles:
|
|
@@ -471,6 +566,31 @@ function extractDestructuredNames(pattern: string): string[] {
|
|
|
471
566
|
* for (let i = 0; i < n; i++) { ... } → trace i
|
|
472
567
|
* Inserts trace calls at the start of the loop body.
|
|
473
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
|
+
|
|
474
594
|
function findForLoopVars(source: string): Array<{ bodyStart: number; varNames: string[]; lineNo: number }> {
|
|
475
595
|
const results: Array<{ bodyStart: number; varNames: string[]; lineNo: number }> = [];
|
|
476
596
|
|
|
@@ -1108,13 +1228,19 @@ export function transformEsmSource(
|
|
|
1108
1228
|
// Find destructured variable declarations for tracing
|
|
1109
1229
|
const destructInsertions = traceVars ? findDestructuredDeclarations(source) : [];
|
|
1110
1230
|
|
|
1231
|
+
// Find variable reassignments for tracing
|
|
1232
|
+
const reassignInsertions = traceVars ? findReassignments(source) : [];
|
|
1233
|
+
|
|
1111
1234
|
// Find for-loop variable declarations for tracing
|
|
1112
1235
|
const forLoopInsertions = traceVars ? findForLoopVars(source) : [];
|
|
1113
1236
|
|
|
1237
|
+
// Find catch clause variables for tracing
|
|
1238
|
+
const catchInsertions = traceVars ? findCatchVars(source) : [];
|
|
1239
|
+
|
|
1114
1240
|
// Find function parameter names for tracing
|
|
1115
1241
|
const funcParamInsertions = traceVars ? findFunctionParams(source, isReactFile) : [];
|
|
1116
1242
|
|
|
1117
|
-
if (funcInsertions.length === 0 && varInsertions.length === 0 && destructInsertions.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;
|
|
1118
1244
|
|
|
1119
1245
|
// Fix line numbers: Vite transforms (TypeScript stripping) may change line numbers.
|
|
1120
1246
|
// Map transformed line numbers to original source line numbers.
|
|
@@ -1133,6 +1259,11 @@ export function transformEsmSource(
|
|
|
1133
1259
|
if (origLine !== -1) di.lineNo = origLine;
|
|
1134
1260
|
}
|
|
1135
1261
|
}
|
|
1262
|
+
// Fix reassignment line numbers
|
|
1263
|
+
for (const ri of reassignInsertions) {
|
|
1264
|
+
const origLine = findOriginalLine(origLines, ri.varName, ri.lineNo);
|
|
1265
|
+
if (origLine !== -1) ri.lineNo = origLine;
|
|
1266
|
+
}
|
|
1136
1267
|
// Fix for-loop var line numbers
|
|
1137
1268
|
for (const fi of forLoopInsertions) {
|
|
1138
1269
|
if (fi.varNames.length > 0) {
|
|
@@ -1177,7 +1308,7 @@ export function transformEsmSource(
|
|
|
1177
1308
|
}
|
|
1178
1309
|
|
|
1179
1310
|
// Build prefix — ALL imports first (ESM requires imports before any statements)
|
|
1180
|
-
const needsTracing = varInsertions.length > 0 || destructInsertions.length > 0 || forLoopInsertions.length > 0 || funcParamInsertions.length > 0 || bodyInsertions.length > 0 || hookInsertions.length > 0 || stateInsertions.length > 0 || conciseBodyInsertions.length > 0;
|
|
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;
|
|
1181
1312
|
const importLines: string[] = [
|
|
1182
1313
|
`import { wrapFunction as __trickle_wrapFn, configure as __trickle_configure } from 'trickle-observe';`,
|
|
1183
1314
|
];
|
|
@@ -1247,7 +1378,7 @@ export function transformEsmSource(
|
|
|
1247
1378
|
}
|
|
1248
1379
|
|
|
1249
1380
|
// Add variable tracing if needed — inlined to avoid import resolution issues in Vite SSR.
|
|
1250
|
-
if (varInsertions.length > 0 || destructInsertions.length > 0 || forLoopInsertions.length > 0 || funcParamInsertions.length > 0) {
|
|
1381
|
+
if (varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || catchInsertions.length > 0 || funcParamInsertions.length > 0) {
|
|
1251
1382
|
prefixLines.push(
|
|
1252
1383
|
`if (!globalThis.__trickle_var_tracer) {`,
|
|
1253
1384
|
` const _cache = new Set();`,
|
|
@@ -1418,6 +1549,23 @@ export function transformEsmSource(
|
|
|
1418
1549
|
});
|
|
1419
1550
|
}
|
|
1420
1551
|
|
|
1552
|
+
// Reassignment insertions: trace after the reassignment statement
|
|
1553
|
+
for (const { lineEnd, varName, lineNo } of reassignInsertions) {
|
|
1554
|
+
allInsertions.push({
|
|
1555
|
+
position: lineEnd,
|
|
1556
|
+
code: `\n;try{__trickle_tv(${varName},${JSON.stringify(varName)},${lineNo})}catch(__e){}\n`,
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
// Catch clause insertions: insert trace at start of catch body
|
|
1561
|
+
for (const { bodyStart, varNames, lineNo } of catchInsertions) {
|
|
1562
|
+
const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
|
|
1563
|
+
allInsertions.push({
|
|
1564
|
+
position: bodyStart,
|
|
1565
|
+
code: `\ntry{${calls}}catch(__e2){}\n`,
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1421
1569
|
// For-loop variable insertions: insert trace at start of loop body
|
|
1422
1570
|
for (const { bodyStart, varNames, lineNo } of forLoopInsertions) {
|
|
1423
1571
|
const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
|