trickle-observe 0.2.85 → 0.2.86

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.
@@ -70,25 +70,75 @@ function decodeVLQ(encoded) {
70
70
  }
71
71
  return values;
72
72
  }
73
+ /**
74
+ * Resolve a source map source path to a real filesystem path.
75
+ * Handles webpack:// URLs, file:// URLs, and relative paths.
76
+ */
77
+ function resolveSourcePath(source, mapDir, sourceRoot, jsFilePath) {
78
+ // Handle webpack:// URLs: webpack://package-name/./src/file.ts
79
+ if (source.startsWith('webpack://')) {
80
+ // Strip webpack://package-name/ prefix
81
+ const withoutProtocol = source.replace(/^webpack:\/\/[^/]*\//, '');
82
+ // Resolve relative to the project root (parent of dist/ or the JS file's directory)
83
+ const projectRoot = findProjectRoot(jsFilePath);
84
+ return path_1.default.resolve(projectRoot, withoutProtocol);
85
+ }
86
+ // Handle file:// URLs
87
+ if (source.startsWith('file://')) {
88
+ return source.replace(/^file:\/\//, '');
89
+ }
90
+ // Regular relative path
91
+ return path_1.default.resolve(mapDir, sourceRoot, source);
92
+ }
93
+ /**
94
+ * Find the project root by looking for package.json or tsconfig.json
95
+ * starting from the JS file's directory and walking up.
96
+ */
97
+ function findProjectRoot(jsFilePath) {
98
+ let dir = path_1.default.dirname(jsFilePath);
99
+ for (let i = 0; i < 10; i++) {
100
+ if (fs_1.default.existsSync(path_1.default.join(dir, 'package.json')) || fs_1.default.existsSync(path_1.default.join(dir, 'tsconfig.json'))) {
101
+ return dir;
102
+ }
103
+ const parent = path_1.default.dirname(dir);
104
+ if (parent === dir)
105
+ break;
106
+ dir = parent;
107
+ }
108
+ return path_1.default.dirname(jsFilePath);
109
+ }
73
110
  /**
74
111
  * Parse a source map JSON and build a compiled→original line mapping.
75
- * Only handles single-source maps (most common for tsc output).
112
+ * Supports both single-source (tsc) and multi-source (webpack/rollup) maps.
76
113
  */
77
- function parseSourceMap(mapJson, mapFilePath) {
114
+ function parseSourceMap(mapJson, mapFilePath, jsFilePath) {
78
115
  try {
79
116
  const map = JSON.parse(mapJson);
80
117
  if (!map.mappings || !map.sources || map.sources.length === 0)
81
118
  return null;
82
- // Resolve original source path relative to the .map file location
83
119
  const mapDir = path_1.default.dirname(mapFilePath);
84
120
  const sourceRoot = map.sourceRoot || '';
85
- const originalFile = path_1.default.resolve(mapDir, sourceRoot, map.sources[0]);
121
+ // Resolve all source paths, filtering out non-user sources (webpack internals)
122
+ const resolvedSources = map.sources.map((s) => resolveSourcePath(s, mapDir, sourceRoot, jsFilePath));
123
+ // Find the first user source file (skip webpack bootstrap/runtime)
124
+ let primarySourceIdx = 0;
125
+ for (let i = 0; i < resolvedSources.length; i++) {
126
+ const s = map.sources[i];
127
+ if (!s.includes('webpack/bootstrap') && !s.includes('webpack/runtime') && fs_1.default.existsSync(resolvedSources[i])) {
128
+ primarySourceIdx = i;
129
+ break;
130
+ }
131
+ }
132
+ const originalFile = resolvedSources[primarySourceIdx];
86
133
  // Decode mappings: semicolons separate lines, commas separate segments
87
- // VLQ values are cumulative across ALL segments (not just first per line),
88
- // so we must process every segment to maintain correct state.
134
+ // VLQ values are cumulative across ALL segments (not just first per line).
135
+ // For multi-source maps, also track the source index to map lines to different files.
89
136
  const lineMap = new Map();
137
+ /** Map from generated line → resolved source file path */
138
+ const lineSourceMap = new Map();
90
139
  const lines = map.mappings.split(';');
91
- let sourceLine = 0; // Cumulative source line (0-based, relative)
140
+ let sourceLine = 0;
141
+ let sourceIdx = 0;
92
142
  for (let genLine = 0; genLine < lines.length; genLine++) {
93
143
  const line = lines[genLine];
94
144
  if (!line)
@@ -99,18 +149,22 @@ function parseSourceMap(mapJson, mapFilePath) {
99
149
  if (!seg)
100
150
  continue;
101
151
  const decoded = decodeVLQ(seg);
102
- // decoded: [genCol, sourceIdx, sourceLine, sourceCol, ...]
103
152
  if (decoded.length >= 3) {
104
- sourceLine += decoded[2]; // Update cumulative source line
105
- // Only record the first segment's mapping for this generated line
153
+ if (decoded.length >= 2)
154
+ sourceIdx += decoded[1]; // Update source index
155
+ sourceLine += decoded[2];
106
156
  if (!firstSegmentMapped) {
107
- lineMap.set(genLine + 1, sourceLine + 1); // Convert to 1-based
157
+ lineMap.set(genLine + 1, sourceLine + 1);
158
+ // Track which source file this line belongs to
159
+ if (sourceIdx >= 0 && sourceIdx < resolvedSources.length) {
160
+ lineSourceMap.set(genLine + 1, resolvedSources[sourceIdx]);
161
+ }
108
162
  firstSegmentMapped = true;
109
163
  }
110
164
  }
111
165
  }
112
166
  }
113
- return { lineMap, originalFile, sources: map.sources };
167
+ return { lineMap, originalFile, sources: map.sources, lineSourceMap };
114
168
  }
115
169
  catch {
116
170
  return null;
@@ -133,14 +187,14 @@ function loadSourceMap(jsFilePath, source) {
133
187
  const mapPath = path_1.default.resolve(path_1.default.dirname(jsFilePath), url);
134
188
  if (fs_1.default.existsSync(mapPath)) {
135
189
  const mapJson = fs_1.default.readFileSync(mapPath, 'utf8');
136
- return parseSourceMap(mapJson, mapPath);
190
+ return parseSourceMap(mapJson, mapPath, jsFilePath);
137
191
  }
138
192
  }
139
193
  // Fallback: check for .map file alongside .js
140
194
  const mapPath = jsFilePath + '.map';
141
195
  if (fs_1.default.existsSync(mapPath)) {
142
196
  const mapJson = fs_1.default.readFileSync(mapPath, 'utf8');
143
- return parseSourceMap(mapJson, mapPath);
197
+ return parseSourceMap(mapJson, mapPath, jsFilePath);
144
198
  }
145
199
  return null;
146
200
  }
@@ -322,6 +376,11 @@ function findVarDeclarations(source, lineOffset = 0) {
322
376
  continue;
323
377
  if (varName === '__commonJS' || varName === '__toCommonJS' || varName === '__export' || varName === '__copyProps')
324
378
  continue;
379
+ // Skip webpack internals
380
+ if (varName.startsWith('__webpack_'))
381
+ continue;
382
+ if (varName === '__unused_webpack_module')
383
+ continue;
325
384
  // Skip React Refresh / HMR internals
326
385
  if (varName === 'prevRefreshReg' || varName === 'prevRefreshSig' || varName === 'inWebWorker' || varName === 'invalidateMessage')
327
386
  continue;
@@ -619,6 +678,14 @@ function transformCjsSource(source, filename, moduleName, env, sourceMap) {
619
678
  const remapLine = sourceMap
620
679
  ? (line) => mapLineToOriginal(sourceMap, line)
621
680
  : (line) => line;
681
+ // Helper: get source file for a compiled line (for multi-source bundles)
682
+ const hasMultiSource = sourceMap?.lineSourceMap && sourceMap.lineSourceMap.size > 0;
683
+ const getSourceFile = hasMultiSource
684
+ ? (compiledLine) => {
685
+ const file = sourceMap.lineSourceMap.get(compiledLine);
686
+ return file && !file.includes('webpack/bootstrap') && !file.includes('webpack/runtime') ? file : undefined;
687
+ }
688
+ : (_) => undefined;
622
689
  if (varTraceEnabled) {
623
690
  const isTsFile = /\.[mc]?tsx?$/.test(filename);
624
691
  if (isTsFile && !sourceMap) {
@@ -682,10 +749,12 @@ function transformCjsSource(source, filename, moduleName, env, sourceMap) {
682
749
  // Plain JS or source-map-assisted: parse compiled source, then remap lines
683
750
  varInsertions = findVarDeclarations(source).map(ins => ({
684
751
  ...ins,
752
+ sourceFile: getSourceFile(ins.lineNo),
685
753
  lineNo: remapLine(ins.lineNo),
686
754
  }));
687
755
  destructInsertions = findDestructuredDeclarations(source).map(ins => ({
688
756
  ...ins,
757
+ sourceFile: getSourceFile(ins.lineNo),
689
758
  lineNo: remapLine(ins.lineNo),
690
759
  }));
691
760
  }
@@ -696,14 +765,17 @@ function transformCjsSource(source, filename, moduleName, env, sourceMap) {
696
765
  // Apply source map remapping to these too.
697
766
  const reassignInsertions = (0, vite_plugin_1.findReassignments)(source).map(ins => ({
698
767
  ...ins,
768
+ sourceFile: getSourceFile(ins.lineNo),
699
769
  lineNo: remapLine(ins.lineNo),
700
770
  }));
701
771
  const forLoopInsertions = (0, vite_plugin_1.findForLoopVars)(source).map(ins => ({
702
772
  ...ins,
773
+ sourceFile: getSourceFile(ins.lineNo),
703
774
  lineNo: remapLine(ins.lineNo),
704
775
  }));
705
776
  const catchInsertions = (0, vite_plugin_1.findCatchVars)(source).map(ins => ({
706
777
  ...ins,
778
+ sourceFile: getSourceFile(ins.lineNo),
707
779
  lineNo: remapLine(ins.lineNo),
708
780
  }));
709
781
  if (insertions.length === 0 && varInsertions.length === 0 && destructInsertions.length === 0 && reassignInsertions.length === 0 && forLoopInsertions.length === 0 && catchInsertions.length === 0 && classInsertions.length === 0)
@@ -740,7 +812,7 @@ function transformCjsSource(source, filename, moduleName, env, sourceMap) {
740
812
  const traceModuleName = sourceMap
741
813
  ? path_1.default.basename(sourceMap.originalFile).replace(/\.[jt]sx?$/, '')
742
814
  : moduleName;
743
- prefixLines.push(`var __trickle_tv_mod = require(${JSON.stringify(traceVarPath)});`, `var __trickle_tv = function(v, n, l) { try { __trickle_tv_mod.traceVar(v, n, l, ${JSON.stringify(traceModuleName)}, ${JSON.stringify(traceFilePath)}); } catch(e){} };`);
815
+ prefixLines.push(`var __trickle_tv_mod = require(${JSON.stringify(traceVarPath)});`, `var __trickle_tv = function(v, n, l, m, f) { try { __trickle_tv_mod.traceVar(v, n, l, m || ${JSON.stringify(traceModuleName)}, f || ${JSON.stringify(traceFilePath)}); } catch(e){} };`);
744
816
  }
745
817
  prefixLines.push('');
746
818
  const prefix = prefixLines.join('\n');
@@ -752,37 +824,47 @@ function transformCjsSource(source, filename, moduleName, env, sourceMap) {
752
824
  code: `\ntry{${name}=__trickle_wrap(${name},'${name}',${paramNamesArg})}catch(__e){}\n`,
753
825
  });
754
826
  }
755
- for (const { lineEnd, varName, lineNo } of varInsertions) {
827
+ // Helper to generate source file args for __trickle_tv calls (for multi-source bundles)
828
+ const sfArgs = (sourceFile) => {
829
+ if (!sourceFile)
830
+ return '';
831
+ const mod = path_1.default.basename(sourceFile).replace(/\.[jt]sx?$/, '');
832
+ return `,${JSON.stringify(mod)},${JSON.stringify(sourceFile)}`;
833
+ };
834
+ for (const { lineEnd, varName, lineNo, sourceFile } of varInsertions) {
756
835
  allInsertions.push({
757
836
  position: lineEnd,
758
- code: `\ntry{__trickle_tv(${varName},${JSON.stringify(varName)},${lineNo})}catch(__e){}\n`,
837
+ code: `\ntry{__trickle_tv(${varName},${JSON.stringify(varName)},${lineNo}${sfArgs(sourceFile)})}catch(__e){}\n`,
759
838
  });
760
839
  }
761
- for (const { lineEnd, varNames, lineNo } of destructInsertions) {
762
- const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
840
+ for (const { lineEnd, varNames, lineNo, sourceFile } of destructInsertions) {
841
+ const sf = sfArgs(sourceFile);
842
+ const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo}${sf})`).join(';');
763
843
  allInsertions.push({
764
844
  position: lineEnd,
765
845
  code: `\n;try{${calls}}catch(__e){}\n`,
766
846
  });
767
847
  }
768
848
  // Reassignment insertions
769
- for (const { lineEnd, varName, lineNo } of reassignInsertions) {
849
+ for (const { lineEnd, varName, lineNo, sourceFile } of reassignInsertions) {
770
850
  allInsertions.push({
771
851
  position: lineEnd,
772
- code: `\n;try{__trickle_tv(${varName},${JSON.stringify(varName)},${lineNo})}catch(__e){}\n`,
852
+ code: `\n;try{__trickle_tv(${varName},${JSON.stringify(varName)},${lineNo}${sfArgs(sourceFile)})}catch(__e){}\n`,
773
853
  });
774
854
  }
775
855
  // For-loop variable insertions
776
- for (const { bodyStart, varNames, lineNo } of forLoopInsertions) {
777
- const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
856
+ for (const { bodyStart, varNames, lineNo, sourceFile } of forLoopInsertions) {
857
+ const sf = sfArgs(sourceFile);
858
+ const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo}${sf})`).join(';');
778
859
  allInsertions.push({
779
860
  position: bodyStart,
780
861
  code: `\ntry{${calls}}catch(__e){}\n`,
781
862
  });
782
863
  }
783
864
  // Catch clause insertions
784
- for (const { bodyStart, varNames, lineNo } of catchInsertions) {
785
- const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
865
+ for (const { bodyStart, varNames, lineNo, sourceFile } of catchInsertions) {
866
+ const sf = sfArgs(sourceFile);
867
+ const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo}${sf})`).join(';');
786
868
  allInsertions.push({
787
869
  position: bodyStart,
788
870
  code: `\ntry{${calls}}catch(__e2){}\n`,
@@ -278,6 +278,11 @@ function findVarDeclarations(source) {
278
278
  continue;
279
279
  if (varName === '__commonJS' || varName === '__toCommonJS' || varName === '__export' || varName === '__copyProps')
280
280
  continue;
281
+ // Skip webpack internals
282
+ if (varName.startsWith('__webpack_'))
283
+ continue;
284
+ if (varName === '__unused_webpack_module')
285
+ continue;
281
286
  // Skip React Refresh / HMR internals (Vite, webpack, Next.js inject these)
282
287
  if (varName === 'prevRefreshReg' || varName === 'prevRefreshSig' || varName === 'inWebWorker' || varName === 'invalidateMessage')
283
288
  continue;
@@ -265,6 +265,11 @@ function findVarDeclarations(source) {
265
265
  continue;
266
266
  if (varName === '__commonJS' || varName === '__toCommonJS' || varName === '__export' || varName === '__copyProps')
267
267
  continue;
268
+ // Skip webpack internals
269
+ if (varName.startsWith('__webpack_'))
270
+ continue;
271
+ if (varName === '__unused_webpack_module')
272
+ continue;
268
273
  // Skip React Refresh / HMR internals (Vite, webpack, Next.js inject these)
269
274
  if (varName === 'prevRefreshReg' || varName === 'prevRefreshSig' || varName === 'inWebWorker' || varName === 'invalidateMessage')
270
275
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trickle-observe",
3
- "version": "0.2.85",
3
+ "version": "0.2.86",
4
4
  "description": "Runtime type observability for JavaScript applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -76,32 +76,87 @@ function decodeVLQ(encoded: string): number[] {
76
76
  interface SourceMapData {
77
77
  /** Map from compiled 1-based line → original 1-based line */
78
78
  lineMap: Map<number, number>;
79
- /** Original source file path (resolved to absolute) */
79
+ /** Original source file path (resolved to absolute) — primary/first user source */
80
80
  originalFile: string;
81
81
  /** All source files listed in the source map */
82
82
  sources: string[];
83
+ /** Map from compiled line → resolved source file path (for multi-source bundles) */
84
+ lineSourceMap?: Map<number, string>;
85
+ }
86
+
87
+ /**
88
+ * Resolve a source map source path to a real filesystem path.
89
+ * Handles webpack:// URLs, file:// URLs, and relative paths.
90
+ */
91
+ function resolveSourcePath(source: string, mapDir: string, sourceRoot: string, jsFilePath: string): string {
92
+ // Handle webpack:// URLs: webpack://package-name/./src/file.ts
93
+ if (source.startsWith('webpack://')) {
94
+ // Strip webpack://package-name/ prefix
95
+ const withoutProtocol = source.replace(/^webpack:\/\/[^/]*\//, '');
96
+ // Resolve relative to the project root (parent of dist/ or the JS file's directory)
97
+ const projectRoot = findProjectRoot(jsFilePath);
98
+ return path.resolve(projectRoot, withoutProtocol);
99
+ }
100
+ // Handle file:// URLs
101
+ if (source.startsWith('file://')) {
102
+ return source.replace(/^file:\/\//, '');
103
+ }
104
+ // Regular relative path
105
+ return path.resolve(mapDir, sourceRoot, source);
106
+ }
107
+
108
+ /**
109
+ * Find the project root by looking for package.json or tsconfig.json
110
+ * starting from the JS file's directory and walking up.
111
+ */
112
+ function findProjectRoot(jsFilePath: string): string {
113
+ let dir = path.dirname(jsFilePath);
114
+ for (let i = 0; i < 10; i++) {
115
+ if (fs.existsSync(path.join(dir, 'package.json')) || fs.existsSync(path.join(dir, 'tsconfig.json'))) {
116
+ return dir;
117
+ }
118
+ const parent = path.dirname(dir);
119
+ if (parent === dir) break;
120
+ dir = parent;
121
+ }
122
+ return path.dirname(jsFilePath);
83
123
  }
84
124
 
85
125
  /**
86
126
  * Parse a source map JSON and build a compiled→original line mapping.
87
- * Only handles single-source maps (most common for tsc output).
127
+ * Supports both single-source (tsc) and multi-source (webpack/rollup) maps.
88
128
  */
89
- function parseSourceMap(mapJson: string, mapFilePath: string): SourceMapData | null {
129
+ function parseSourceMap(mapJson: string, mapFilePath: string, jsFilePath: string): SourceMapData | null {
90
130
  try {
91
131
  const map = JSON.parse(mapJson);
92
132
  if (!map.mappings || !map.sources || map.sources.length === 0) return null;
93
133
 
94
- // Resolve original source path relative to the .map file location
95
134
  const mapDir = path.dirname(mapFilePath);
96
135
  const sourceRoot = map.sourceRoot || '';
97
- const originalFile = path.resolve(mapDir, sourceRoot, map.sources[0]);
136
+
137
+ // Resolve all source paths, filtering out non-user sources (webpack internals)
138
+ const resolvedSources = map.sources.map((s: string) => resolveSourcePath(s, mapDir, sourceRoot, jsFilePath));
139
+
140
+ // Find the first user source file (skip webpack bootstrap/runtime)
141
+ let primarySourceIdx = 0;
142
+ for (let i = 0; i < resolvedSources.length; i++) {
143
+ const s = map.sources[i] as string;
144
+ if (!s.includes('webpack/bootstrap') && !s.includes('webpack/runtime') && fs.existsSync(resolvedSources[i])) {
145
+ primarySourceIdx = i;
146
+ break;
147
+ }
148
+ }
149
+ const originalFile = resolvedSources[primarySourceIdx];
98
150
 
99
151
  // Decode mappings: semicolons separate lines, commas separate segments
100
- // VLQ values are cumulative across ALL segments (not just first per line),
101
- // so we must process every segment to maintain correct state.
152
+ // VLQ values are cumulative across ALL segments (not just first per line).
153
+ // For multi-source maps, also track the source index to map lines to different files.
102
154
  const lineMap = new Map<number, number>();
155
+ /** Map from generated line → resolved source file path */
156
+ const lineSourceMap = new Map<number, string>();
103
157
  const lines = map.mappings.split(';');
104
- let sourceLine = 0; // Cumulative source line (0-based, relative)
158
+ let sourceLine = 0;
159
+ let sourceIdx = 0;
105
160
 
106
161
  for (let genLine = 0; genLine < lines.length; genLine++) {
107
162
  const line = lines[genLine];
@@ -112,19 +167,22 @@ function parseSourceMap(mapJson: string, mapFilePath: string): SourceMapData | n
112
167
  for (const seg of segments) {
113
168
  if (!seg) continue;
114
169
  const decoded = decodeVLQ(seg);
115
- // decoded: [genCol, sourceIdx, sourceLine, sourceCol, ...]
116
170
  if (decoded.length >= 3) {
117
- sourceLine += decoded[2]; // Update cumulative source line
118
- // Only record the first segment's mapping for this generated line
171
+ if (decoded.length >= 2) sourceIdx += decoded[1]; // Update source index
172
+ sourceLine += decoded[2];
119
173
  if (!firstSegmentMapped) {
120
- lineMap.set(genLine + 1, sourceLine + 1); // Convert to 1-based
174
+ lineMap.set(genLine + 1, sourceLine + 1);
175
+ // Track which source file this line belongs to
176
+ if (sourceIdx >= 0 && sourceIdx < resolvedSources.length) {
177
+ lineSourceMap.set(genLine + 1, resolvedSources[sourceIdx]);
178
+ }
121
179
  firstSegmentMapped = true;
122
180
  }
123
181
  }
124
182
  }
125
183
  }
126
184
 
127
- return { lineMap, originalFile, sources: map.sources };
185
+ return { lineMap, originalFile, sources: map.sources, lineSourceMap };
128
186
  } catch {
129
187
  return null;
130
188
  }
@@ -146,7 +204,7 @@ function loadSourceMap(jsFilePath: string, source: string): SourceMapData | null
146
204
  const mapPath = path.resolve(path.dirname(jsFilePath), url);
147
205
  if (fs.existsSync(mapPath)) {
148
206
  const mapJson = fs.readFileSync(mapPath, 'utf8');
149
- return parseSourceMap(mapJson, mapPath);
207
+ return parseSourceMap(mapJson, mapPath, jsFilePath);
150
208
  }
151
209
  }
152
210
 
@@ -154,7 +212,7 @@ function loadSourceMap(jsFilePath: string, source: string): SourceMapData | null
154
212
  const mapPath = jsFilePath + '.map';
155
213
  if (fs.existsSync(mapPath)) {
156
214
  const mapJson = fs.readFileSync(mapPath, 'utf8');
157
- return parseSourceMap(mapJson, mapPath);
215
+ return parseSourceMap(mapJson, mapPath, jsFilePath);
158
216
  }
159
217
 
160
218
  return null;
@@ -322,6 +380,9 @@ function findVarDeclarations(source: string, lineOffset: number = 0): Array<{ li
322
380
  // Skip esbuild helpers
323
381
  if (varName === '__defProp' || varName === '__defNormalProp' || varName === '__publicField' || varName === '__getOwnPropNames') continue;
324
382
  if (varName === '__commonJS' || varName === '__toCommonJS' || varName === '__export' || varName === '__copyProps') continue;
383
+ // Skip webpack internals
384
+ if (varName.startsWith('__webpack_')) continue;
385
+ if (varName === '__unused_webpack_module') continue;
325
386
  // Skip React Refresh / HMR internals
326
387
  if (varName === 'prevRefreshReg' || varName === 'prevRefreshSig' || varName === 'inWebWorker' || varName === 'invalidateMessage') continue;
327
388
  if (varName === '_s' || varName === '_c2' || varName === '_s2') continue;
@@ -594,14 +655,23 @@ function transformCjsSource(source: string, filename: string, moduleName: string
594
655
  // are stripped from the compiled JS, shifting line numbers. The only accurate way to get correct
595
656
  // line numbers is to read the original .ts source file and parse it directly.
596
657
  // For plain JS files, we parse the source directly.
597
- let varInsertions: Array<{ lineEnd: number; varName: string; lineNo: number }> = [];
598
- let destructInsertions: Array<{ lineEnd: number; varNames: string[]; lineNo: number }> = [];
658
+ let varInsertions: Array<{ lineEnd: number; varName: string; lineNo: number; sourceFile?: string }> = [];
659
+ let destructInsertions: Array<{ lineEnd: number; varNames: string[]; lineNo: number; sourceFile?: string }> = [];
599
660
 
600
661
  // Helper: remap line numbers using source map if available
601
662
  const remapLine = sourceMap
602
663
  ? (line: number) => mapLineToOriginal(sourceMap, line)
603
664
  : (line: number) => line;
604
665
 
666
+ // Helper: get source file for a compiled line (for multi-source bundles)
667
+ const hasMultiSource = sourceMap?.lineSourceMap && sourceMap.lineSourceMap.size > 0;
668
+ const getSourceFile = hasMultiSource
669
+ ? (compiledLine: number) => {
670
+ const file = sourceMap!.lineSourceMap!.get(compiledLine);
671
+ return file && !file.includes('webpack/bootstrap') && !file.includes('webpack/runtime') ? file : undefined;
672
+ }
673
+ : (_: number) => undefined as string | undefined;
674
+
605
675
  if (varTraceEnabled) {
606
676
  const isTsFile = /\.[mc]?tsx?$/.test(filename);
607
677
  if (isTsFile && !sourceMap) {
@@ -663,10 +733,12 @@ function transformCjsSource(source: string, filename: string, moduleName: string
663
733
  // Plain JS or source-map-assisted: parse compiled source, then remap lines
664
734
  varInsertions = findVarDeclarations(source).map(ins => ({
665
735
  ...ins,
736
+ sourceFile: getSourceFile(ins.lineNo),
666
737
  lineNo: remapLine(ins.lineNo),
667
738
  }));
668
739
  destructInsertions = findDestructuredDeclarations(source).map(ins => ({
669
740
  ...ins,
741
+ sourceFile: getSourceFile(ins.lineNo),
670
742
  lineNo: remapLine(ins.lineNo),
671
743
  }));
672
744
  }
@@ -678,14 +750,17 @@ function transformCjsSource(source: string, filename: string, moduleName: string
678
750
  // Apply source map remapping to these too.
679
751
  const reassignInsertions = findReassignments(source).map(ins => ({
680
752
  ...ins,
753
+ sourceFile: getSourceFile(ins.lineNo),
681
754
  lineNo: remapLine(ins.lineNo),
682
755
  }));
683
756
  const forLoopInsertions = findForLoopVars(source).map(ins => ({
684
757
  ...ins,
758
+ sourceFile: getSourceFile(ins.lineNo),
685
759
  lineNo: remapLine(ins.lineNo),
686
760
  }));
687
761
  const catchInsertions = findCatchVars(source).map(ins => ({
688
762
  ...ins,
763
+ sourceFile: getSourceFile(ins.lineNo),
689
764
  lineNo: remapLine(ins.lineNo),
690
765
  }));
691
766
 
@@ -726,9 +801,10 @@ function transformCjsSource(source: string, filename: string, moduleName: string
726
801
  const traceModuleName = sourceMap
727
802
  ? path.basename(sourceMap.originalFile).replace(/\.[jt]sx?$/, '')
728
803
  : moduleName;
804
+
729
805
  prefixLines.push(
730
806
  `var __trickle_tv_mod = require(${JSON.stringify(traceVarPath)});`,
731
- `var __trickle_tv = function(v, n, l) { try { __trickle_tv_mod.traceVar(v, n, l, ${JSON.stringify(traceModuleName)}, ${JSON.stringify(traceFilePath)}); } catch(e){} };`,
807
+ `var __trickle_tv = function(v, n, l, m, f) { try { __trickle_tv_mod.traceVar(v, n, l, m || ${JSON.stringify(traceModuleName)}, f || ${JSON.stringify(traceFilePath)}); } catch(e){} };`,
732
808
  );
733
809
  }
734
810
 
@@ -747,15 +823,23 @@ function transformCjsSource(source: string, filename: string, moduleName: string
747
823
  });
748
824
  }
749
825
 
750
- for (const { lineEnd, varName, lineNo } of varInsertions) {
826
+ // Helper to generate source file args for __trickle_tv calls (for multi-source bundles)
827
+ const sfArgs = (sourceFile?: string) => {
828
+ if (!sourceFile) return '';
829
+ const mod = path.basename(sourceFile).replace(/\.[jt]sx?$/, '');
830
+ return `,${JSON.stringify(mod)},${JSON.stringify(sourceFile)}`;
831
+ };
832
+
833
+ for (const { lineEnd, varName, lineNo, sourceFile } of varInsertions) {
751
834
  allInsertions.push({
752
835
  position: lineEnd,
753
- code: `\ntry{__trickle_tv(${varName},${JSON.stringify(varName)},${lineNo})}catch(__e){}\n`,
836
+ code: `\ntry{__trickle_tv(${varName},${JSON.stringify(varName)},${lineNo}${sfArgs(sourceFile)})}catch(__e){}\n`,
754
837
  });
755
838
  }
756
839
 
757
- for (const { lineEnd, varNames, lineNo } of destructInsertions) {
758
- const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
840
+ for (const { lineEnd, varNames, lineNo, sourceFile } of destructInsertions) {
841
+ const sf = sfArgs(sourceFile);
842
+ const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo}${sf})`).join(';');
759
843
  allInsertions.push({
760
844
  position: lineEnd,
761
845
  code: `\n;try{${calls}}catch(__e){}\n`,
@@ -763,16 +847,17 @@ function transformCjsSource(source: string, filename: string, moduleName: string
763
847
  }
764
848
 
765
849
  // Reassignment insertions
766
- for (const { lineEnd, varName, lineNo } of reassignInsertions) {
850
+ for (const { lineEnd, varName, lineNo, sourceFile } of reassignInsertions) {
767
851
  allInsertions.push({
768
852
  position: lineEnd,
769
- code: `\n;try{__trickle_tv(${varName},${JSON.stringify(varName)},${lineNo})}catch(__e){}\n`,
853
+ code: `\n;try{__trickle_tv(${varName},${JSON.stringify(varName)},${lineNo}${sfArgs(sourceFile)})}catch(__e){}\n`,
770
854
  });
771
855
  }
772
856
 
773
857
  // For-loop variable insertions
774
- for (const { bodyStart, varNames, lineNo } of forLoopInsertions) {
775
- const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
858
+ for (const { bodyStart, varNames, lineNo, sourceFile } of forLoopInsertions) {
859
+ const sf = sfArgs(sourceFile);
860
+ const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo}${sf})`).join(';');
776
861
  allInsertions.push({
777
862
  position: bodyStart,
778
863
  code: `\ntry{${calls}}catch(__e){}\n`,
@@ -780,8 +865,9 @@ function transformCjsSource(source: string, filename: string, moduleName: string
780
865
  }
781
866
 
782
867
  // Catch clause insertions
783
- for (const { bodyStart, varNames, lineNo } of catchInsertions) {
784
- const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
868
+ for (const { bodyStart, varNames, lineNo, sourceFile } of catchInsertions) {
869
+ const sf = sfArgs(sourceFile);
870
+ const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo}${sf})`).join(';');
785
871
  allInsertions.push({
786
872
  position: bodyStart,
787
873
  code: `\ntry{${calls}}catch(__e2){}\n`,
@@ -263,6 +263,9 @@ function findVarDeclarations(source: string): Array<{ lineEnd: number; varName:
263
263
  // Skip esbuild helpers
264
264
  if (varName === '__defProp' || varName === '__defNormalProp' || varName === '__publicField' || varName === '__getOwnPropNames') continue;
265
265
  if (varName === '__commonJS' || varName === '__toCommonJS' || varName === '__export' || varName === '__copyProps') continue;
266
+ // Skip webpack internals
267
+ if (varName.startsWith('__webpack_')) continue;
268
+ if (varName === '__unused_webpack_module') continue;
266
269
  // Skip React Refresh / HMR internals (Vite, webpack, Next.js inject these)
267
270
  if (varName === 'prevRefreshReg' || varName === 'prevRefreshSig' || varName === 'inWebWorker' || varName === 'invalidateMessage') continue;
268
271
  if (varName === '_s' || varName === '_c2' || varName === '_s2') continue;