Skip to content

Commit ce589a8

Browse files
authored
fix(webpack): es module source map resolution (NativeScript#10860)
Including source file remapping for newer runtimes.
1 parent 54c069f commit ce589a8

File tree

2 files changed

+108
-49
lines changed

2 files changed

+108
-49
lines changed

packages/core/inspector_modules.ts

Lines changed: 74 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,14 @@
11
import './globals';
2-
32
import './debugger/webinspector-network';
43
import './debugger/webinspector-dom';
54
import './debugger/webinspector-css';
6-
// require('./debugger/webinspector-network');
7-
// require('./debugger/webinspector-dom');
8-
// require('./debugger/webinspector-css');
9-
10-
/**
11-
* Source map remapping for stack traces for the runtime in-flight error displays
12-
* Currently this is very slow. Need to find much faster way to remap stack traces.
13-
* NOTE: This likely should not be in core because errors can happen on boot before core is fully loaded. Ideally the runtime should provide this in full but unsure.
14-
*/
155
import { File, knownFolders } from './file-system';
166
// import/destructure style helps commonjs/esm build issues
177
import * as sourceMapJs from 'source-map-js';
188
const { SourceMapConsumer } = sourceMapJs;
199

20-
// note: webpack config can by default use 'source-map' files with runtimes v9+
10+
// note: bundlers can by default use 'source-map' files with runtimes v9+
2111
// helps avoid having to decode the inline base64 source maps
22-
// currently same performance on inline vs file source maps so file source maps may just be cleaner
2312
const usingSourceMapFiles = true;
2413
let loadedSourceMaps: Map<string, any>;
2514
let consumerCache: Map<string, any>;
@@ -30,9 +19,13 @@ function getConsumer(mapPath: string, sourceMap: any) {
3019
}
3120
let c = consumerCache.get(mapPath);
3221
if (!c) {
33-
// parse once
34-
c = new SourceMapConsumer(sourceMap);
35-
consumerCache.set(mapPath, c);
22+
try {
23+
c = new SourceMapConsumer(sourceMap);
24+
consumerCache.set(mapPath, c);
25+
} catch (error) {
26+
console.error(`Failed to create SourceMapConsumer for ${mapPath}:`, error);
27+
return null;
28+
}
3629
}
3730
return c;
3831
}
@@ -43,35 +36,43 @@ function loadAndExtractMap(mapPath: string) {
4336
loadedSourceMaps = new Map();
4437
}
4538
let mapText = loadedSourceMaps.get(mapPath);
46-
// Note: not sure if separate source map files or inline is better
47-
// need to test build times one way or other with webpack, vite and rspack
48-
// but this handles either way
4939
if (mapText) {
5040
return mapText; // already loaded
5141
} else {
5242
if (File.exists(mapPath)) {
53-
const contents = File.fromPath(mapPath).readTextSync();
54-
if (usingSourceMapFiles) {
55-
mapText = contents;
56-
} else {
57-
// parse out the inline base64
58-
const match = contents.match(/\/\/[#@] sourceMappingURL=data:application\/json[^,]+,(.+)$/);
59-
const base64 = match[1];
60-
const binary = atob(base64);
61-
// this is the raw text of the source map
62-
// seems to work without doing the decodeURIComponent trick
63-
mapText = binary;
64-
// // escape each char code into %XX and let decodeURIComponent build the UTF-8 string
65-
// mapText = decodeURIComponent(
66-
// binary
67-
// .split('')
68-
// .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
69-
// .join('')
70-
// );
43+
try {
44+
const contents = File.fromPath(mapPath).readTextSync();
45+
46+
// Note: we may want to do this, keeping for reference if needed in future.
47+
// Check size before processing (skip very large source maps)
48+
// const maxSizeBytes = 10 * 1024 * 1024; // 10MB limit
49+
// if (contents.length > maxSizeBytes) {
50+
// console.warn(`Source map ${mapPath} is too large (${contents.length} bytes), skipping...`);
51+
// return null;
52+
// }
53+
54+
if (usingSourceMapFiles) {
55+
mapText = contents;
56+
} else {
57+
// parse out the inline base64
58+
const match = contents.match(/\/\/[#@] sourceMappingURL=data:application\/json[^,]+,(.+)$/);
59+
if (!match) {
60+
console.warn(`Invalid source map format in ${mapPath}`);
61+
return null;
62+
}
63+
const base64 = match[1];
64+
const binary = atob(base64);
65+
// this is the raw text of the source map
66+
// seems to work without doing decodeURIComponent tricks
67+
mapText = binary;
68+
}
69+
} catch (error) {
70+
console.error(`Failed to load source map ${mapPath}:`, error);
71+
return null;
7172
}
7273
} else {
7374
// no source maps
74-
return { source: null, line: 0, column: 0 };
75+
return null;
7576
}
7677
}
7778
loadedSourceMaps.set(mapPath, mapText); // cache it
@@ -80,9 +81,10 @@ function loadAndExtractMap(mapPath: string) {
8081

8182
function remapFrame(file: string, line: number, column: number) {
8283
/**
83-
* webpack config can use source map files or inline.
84+
* bundlers can use source map files or inline.
8485
* To use source map files, run with `--env.sourceMap=source-map`.
85-
* @nativescript/webpack 5.1 enables `source-map` files by default when using runtimes v9+.
86+
* Notes:
87+
* Starting with @nativescript/webpack 5.0.25, `source-map` files are used by default when using runtimes v9+.
8688
*/
8789

8890
const appPath = knownFolders.currentApp().path;
@@ -92,32 +94,56 @@ function remapFrame(file: string, line: number, column: number) {
9294
}
9395
const mapPath = `${appPath}/${file.replace('file:///app/', '')}${sourceMapFileExt}`;
9496

95-
// 3) hand it to the consumer
9697
const sourceMap = loadAndExtractMap(mapPath);
98+
99+
if (!sourceMap) {
100+
return { source: null, line: 0, column: 0 };
101+
}
102+
97103
const consumer = getConsumer(mapPath, sourceMap);
98-
return consumer.originalPositionFor({ line, column });
104+
if (!consumer) {
105+
return { source: null, line: 0, column: 0 };
106+
}
107+
108+
try {
109+
return consumer.originalPositionFor({ line, column });
110+
} catch (error) {
111+
console.error(`Failed to get original position for ${file}:${line}:${column}:`, error);
112+
return { source: null, line: 0, column: 0 };
113+
}
99114
}
100115

101116
function remapStack(raw: string): string {
102117
const lines = raw.split('\n');
103118
const out = lines.map((line) => {
104119
const m = /\((.+):(\d+):(\d+)\)/.exec(line);
105120
if (!m) return line;
106-
const [_, file, l, c] = m;
107-
const orig = remapFrame(file, +l, +c);
108-
if (!orig.source) return line;
109-
return line.replace(/\(.+\)/, `(${orig.source}:${orig.line}:${orig.column})`);
121+
122+
try {
123+
const [_, file, l, c] = m;
124+
const orig = remapFrame(file, +l, +c);
125+
if (!orig.source) return line;
126+
return line.replace(/\(.+\)/, `(${orig.source}:${orig.line}:${orig.column})`);
127+
} catch (error) {
128+
console.error('Failed to remap stack frame:', line, error);
129+
return line; // return original line if remapping fails
130+
}
110131
});
111132
return out.join('\n');
112133
}
113134

114135
/**
115-
* Added in 9.0 runtimes.
116-
* Allows the runtime to remap stack traces before displaying them in the in-flight error screens.
136+
* Added with 9.0 runtimes.
137+
* Allows the runtime to remap stack traces before displaying them via in-flight error screens.
117138
*/
118139
(global as any).__ns_remapStack = (rawStack: string) => {
119140
// console.log('Remapping stack trace...');
120-
return remapStack(rawStack);
141+
try {
142+
return remapStack(rawStack);
143+
} catch (error) {
144+
console.error('Failed to remap stack trace, returning original:', error);
145+
return rawStack; // fallback to original stack trace
146+
}
121147
};
122148
/**
123149
* End of source map remapping for stack traces

packages/webpack5/src/configuration/base.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,40 @@ export default function (config: Config, env: IWebpackEnv = _env): Config {
171171
return map as Config.DevTool;
172172
};
173173

174-
config.devtool(getSourceMapType(env.sourceMap));
174+
const sourceMapType = getSourceMapType(env.sourceMap);
175+
176+
// Use devtool for both CommonJS and ESM - let webpack handle source mapping properly
177+
config.devtool(sourceMapType);
178+
179+
// For ESM builds, fix the sourceMappingURL to use correct paths
180+
if (!env.commonjs && sourceMapType && sourceMapType !== 'hidden-source-map') {
181+
class FixSourceMapUrlPlugin {
182+
apply(compiler) {
183+
compiler.hooks.emit.tap('FixSourceMapUrlPlugin', (compilation) => {
184+
const leadingCharacter = process.platform === "win32" ? "/":"";
185+
Object.keys(compilation.assets).forEach((filename) => {
186+
if (filename.endsWith('.mjs') || filename.endsWith('.js')) {
187+
const asset = compilation.assets[filename];
188+
let source = asset.source();
189+
190+
// Replace sourceMappingURL to use file:// protocol pointing to actual location
191+
source = source.replace(
192+
/\/\/# sourceMappingURL=(.+\.map)/g,
193+
`//# sourceMappingURL=file://${leadingCharacter}${outputPath}/$1`,
194+
);
195+
196+
compilation.assets[filename] = {
197+
source: () => source,
198+
size: () => source.length,
199+
};
200+
}
201+
});
202+
});
203+
}
204+
}
205+
206+
config.plugin('FixSourceMapUrlPlugin').use(FixSourceMapUrlPlugin);
207+
}
175208

176209
// when using hidden-source-map, output source maps to the `platforms/{platformName}-sourceMaps` folder
177210
if (env.sourceMap === 'hidden-source-map') {

0 commit comments

Comments
 (0)