trickle-observe 0.2.32 → 0.2.34
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/auto-esm.mjs +1 -0
- package/observe-esm-hooks.mjs +127 -2
- package/package.json +4 -2
package/auto-esm.mjs
CHANGED
|
@@ -39,6 +39,7 @@ register(pathToFileURL(hooksPath).href, {
|
|
|
39
39
|
wrapperPath: join(__dirname, 'dist', 'wrap.js'),
|
|
40
40
|
transportPath: join(__dirname, 'dist', 'transport.js'),
|
|
41
41
|
envDetectPath: join(__dirname, 'dist', 'env-detect.js'),
|
|
42
|
+
traceVarPath: join(__dirname, 'dist', 'trace-var.js'),
|
|
42
43
|
backendUrl: 'http://localhost:4888', // unused in local mode but configure() needs it
|
|
43
44
|
debug,
|
|
44
45
|
includePatterns: process.env.TRICKLE_OBSERVE_INCLUDE
|
package/observe-esm-hooks.mjs
CHANGED
|
@@ -11,7 +11,50 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { createRequire } from 'node:module';
|
|
13
13
|
import { fileURLToPath } from 'node:url';
|
|
14
|
-
import { basename, sep } from 'node:path';
|
|
14
|
+
import { basename, sep, extname } from 'node:path';
|
|
15
|
+
import { readFileSync } from 'node:fs';
|
|
16
|
+
|
|
17
|
+
// Lazy esbuild loader for JSX/TSX stripping
|
|
18
|
+
let _esbuild = null;
|
|
19
|
+
async function getEsbuild() {
|
|
20
|
+
if (_esbuild !== null) return _esbuild;
|
|
21
|
+
try {
|
|
22
|
+
_esbuild = await import('esbuild');
|
|
23
|
+
return _esbuild;
|
|
24
|
+
} catch {
|
|
25
|
+
_esbuild = false;
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Strip JSX/TSX syntax using esbuild, returning plain ESM JavaScript.
|
|
32
|
+
* Preserves line count as much as possible so line numbers stay accurate.
|
|
33
|
+
*/
|
|
34
|
+
async function stripJsx(source, filePath) {
|
|
35
|
+
const esbuild = await getEsbuild();
|
|
36
|
+
if (!esbuild) return null; // esbuild not available
|
|
37
|
+
|
|
38
|
+
const ext = extname(filePath).slice(1); // 'jsx' | 'tsx'
|
|
39
|
+
const loader = ext === 'tsx' ? 'tsx' : 'jsx';
|
|
40
|
+
try {
|
|
41
|
+
const result = await esbuild.transform(source, {
|
|
42
|
+
loader,
|
|
43
|
+
format: 'esm',
|
|
44
|
+
jsx: 'automatic',
|
|
45
|
+
target: 'esnext',
|
|
46
|
+
sourcemap: false,
|
|
47
|
+
treeShaking: false,
|
|
48
|
+
minify: false,
|
|
49
|
+
minifyWhitespace: false,
|
|
50
|
+
minifySyntax: false,
|
|
51
|
+
});
|
|
52
|
+
return result.code;
|
|
53
|
+
} catch (err) {
|
|
54
|
+
if (config.debug) console.error('[trickle/esm] esbuild JSX transform failed:', err.message);
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
15
58
|
|
|
16
59
|
let config = {
|
|
17
60
|
wrapperPath: '',
|
|
@@ -352,7 +395,46 @@ function extractDestructuredNamesESM(pattern) {
|
|
|
352
395
|
return names;
|
|
353
396
|
}
|
|
354
397
|
|
|
355
|
-
|
|
398
|
+
/**
|
|
399
|
+
* Map a transformed line number to the original source file line.
|
|
400
|
+
* Searches within ±80 lines for a `const/let/var <varName>` pattern.
|
|
401
|
+
*/
|
|
402
|
+
function findOriginalLineESM(origLines, varName, transformedLine) {
|
|
403
|
+
const pattern = new RegExp(`\\b(const|let|var)\\s+${varName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`);
|
|
404
|
+
for (let delta = 0; delta <= 80; delta++) {
|
|
405
|
+
const fwd = transformedLine - 1 + delta;
|
|
406
|
+
if (fwd >= 0 && fwd < origLines.length && pattern.test(origLines[fwd])) return fwd + 1;
|
|
407
|
+
if (delta > 0 && delta <= 10) {
|
|
408
|
+
const bwd = transformedLine - 1 - delta;
|
|
409
|
+
if (bwd >= 0 && bwd < origLines.length && pattern.test(origLines[bwd])) return bwd + 1;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return -1;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Map a transformed destructured declaration line to the original source.
|
|
417
|
+
*/
|
|
418
|
+
function findOriginalLineDestructuredESM(origLines, varNames, transformedLine) {
|
|
419
|
+
const namePatterns = varNames.map(n => new RegExp(`\\b${n.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b(?!\\s*:)`));
|
|
420
|
+
for (let delta = 0; delta <= 80; delta++) {
|
|
421
|
+
const fwd = transformedLine - 1 + delta;
|
|
422
|
+
if (fwd >= 0 && fwd < origLines.length) {
|
|
423
|
+
const line = origLines[fwd];
|
|
424
|
+
if (/\b(const|let|var)\s+[\[{]/.test(line) && namePatterns.some(p => p.test(line))) return fwd + 1;
|
|
425
|
+
}
|
|
426
|
+
if (delta > 0 && delta <= 10) {
|
|
427
|
+
const bwd = transformedLine - 1 - delta;
|
|
428
|
+
if (bwd >= 0 && bwd < origLines.length) {
|
|
429
|
+
const line = origLines[bwd];
|
|
430
|
+
if (/\b(const|let|var)\s+[\[{]/.test(line) && namePatterns.some(p => p.test(line))) return bwd + 1;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return -1;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function transformSource(source, url, originalSource) {
|
|
356
438
|
const moduleName = moduleNameFromUrl(url);
|
|
357
439
|
let filePath = url;
|
|
358
440
|
try { filePath = fileURLToPath(url); } catch {}
|
|
@@ -368,6 +450,21 @@ function transformSource(source, url) {
|
|
|
368
450
|
const varDecls = varTraceEnabled ? findVarDeclarationsESM(source) : [];
|
|
369
451
|
const destructDecls = varTraceEnabled ? findDestructuredDeclarationsESM(source) : [];
|
|
370
452
|
|
|
453
|
+
// Map transformed line numbers to original source line numbers (if original source differs)
|
|
454
|
+
if (originalSource && originalSource !== source) {
|
|
455
|
+
const origLines = originalSource.split('\n');
|
|
456
|
+
for (const vi of varDecls) {
|
|
457
|
+
const orig = findOriginalLineESM(origLines, vi.varName, vi.lineNo);
|
|
458
|
+
if (orig !== -1) vi.lineNo = orig;
|
|
459
|
+
}
|
|
460
|
+
for (const di of destructDecls) {
|
|
461
|
+
if (di.varNames.length > 0) {
|
|
462
|
+
const orig = findOriginalLineDestructuredESM(origLines, di.varNames, di.lineNo);
|
|
463
|
+
if (orig !== -1) di.lineNo = orig;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
371
468
|
// Build a map: line number → trace calls to insert AFTER that line
|
|
372
469
|
const traceAfterLine = new Map();
|
|
373
470
|
for (const { varName, lineNo, insertAfterLine } of varDecls) {
|
|
@@ -563,6 +660,34 @@ function transformSource(source, url) {
|
|
|
563
660
|
* ESM load hook — intercepts module loading to transform user modules.
|
|
564
661
|
*/
|
|
565
662
|
export async function load(url, context, nextLoad) {
|
|
663
|
+
// Handle JSX/TSX files ourselves — Node.js cannot parse JSX natively.
|
|
664
|
+
// We read the file, strip JSX with esbuild, apply trickle instrumentation,
|
|
665
|
+
// and return the result without calling nextLoad (which would fail for JSX).
|
|
666
|
+
if (shouldObserve(url) && /\.(jsx|tsx)$/.test(url)) {
|
|
667
|
+
let filePath;
|
|
668
|
+
try { filePath = fileURLToPath(url); } catch { filePath = null; }
|
|
669
|
+
|
|
670
|
+
if (filePath) {
|
|
671
|
+
try {
|
|
672
|
+
const rawSource = readFileSync(filePath, 'utf-8');
|
|
673
|
+
const jsSource = await stripJsx(rawSource, filePath);
|
|
674
|
+
if (jsSource !== null) {
|
|
675
|
+
const varTraceEnabled = process.env.TRICKLE_TRACE_VARS !== '0' && config.traceVarPath;
|
|
676
|
+
const hasVarDecls = varTraceEnabled && /^[ \t]*(?:export\s+)?(?:const|let|var)\s+[a-zA-Z_$]/m.test(jsSource);
|
|
677
|
+
if (jsSource.includes('export ') || hasVarDecls) {
|
|
678
|
+
// Pass rawSource as originalSource so line numbers map to the .jsx/.tsx file
|
|
679
|
+
const transformed = transformSource(jsSource, url, rawSource);
|
|
680
|
+
return { source: transformed, format: 'module', shortCircuit: true };
|
|
681
|
+
}
|
|
682
|
+
// No observable exports/vars — return stripped JS so Node can execute it
|
|
683
|
+
return { source: jsSource, format: 'module', shortCircuit: true };
|
|
684
|
+
}
|
|
685
|
+
} catch (err) {
|
|
686
|
+
if (config.debug) console.error(`[trickle/esm] JSX load failed for ${url}:`, err.message);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
566
691
|
const result = await nextLoad(url, context);
|
|
567
692
|
|
|
568
693
|
// Only transform ESM modules we should observe
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trickle-observe",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.34",
|
|
4
4
|
"description": "Runtime type observability for JavaScript applications",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
"build": "tsc",
|
|
20
20
|
"prepublishOnly": "npm run build"
|
|
21
21
|
},
|
|
22
|
-
"
|
|
22
|
+
"optionalDependencies": {
|
|
23
|
+
"esbuild": "^0.27.4"
|
|
24
|
+
},
|
|
23
25
|
"devDependencies": {
|
|
24
26
|
"typescript": "^5.4.0"
|
|
25
27
|
},
|