Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 74 additions & 48 deletions packages/core/inspector_modules.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,14 @@
import './globals';

import './debugger/webinspector-network';
import './debugger/webinspector-dom';
import './debugger/webinspector-css';
// require('./debugger/webinspector-network');
// require('./debugger/webinspector-dom');
// require('./debugger/webinspector-css');

/**
* Source map remapping for stack traces for the runtime in-flight error displays
* Currently this is very slow. Need to find much faster way to remap stack traces.
* 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.
*/
import { File, knownFolders } from './file-system';
// import/destructure style helps commonjs/esm build issues
import * as sourceMapJs from 'source-map-js';
const { SourceMapConsumer } = sourceMapJs;

// note: webpack config can by default use 'source-map' files with runtimes v9+
// note: bundlers can by default use 'source-map' files with runtimes v9+
// helps avoid having to decode the inline base64 source maps
// currently same performance on inline vs file source maps so file source maps may just be cleaner
const usingSourceMapFiles = true;
let loadedSourceMaps: Map<string, any>;
let consumerCache: Map<string, any>;
Expand All @@ -30,9 +19,13 @@ function getConsumer(mapPath: string, sourceMap: any) {
}
let c = consumerCache.get(mapPath);
if (!c) {
// parse once
c = new SourceMapConsumer(sourceMap);
consumerCache.set(mapPath, c);
try {
c = new SourceMapConsumer(sourceMap);
consumerCache.set(mapPath, c);
} catch (error) {
console.error(`Failed to create SourceMapConsumer for ${mapPath}:`, error);
return null;
}
}
return c;
}
Expand All @@ -43,35 +36,43 @@ function loadAndExtractMap(mapPath: string) {
loadedSourceMaps = new Map();
}
let mapText = loadedSourceMaps.get(mapPath);
// Note: not sure if separate source map files or inline is better
// need to test build times one way or other with webpack, vite and rspack
// but this handles either way
if (mapText) {
return mapText; // already loaded
} else {
if (File.exists(mapPath)) {
const contents = File.fromPath(mapPath).readTextSync();
if (usingSourceMapFiles) {
mapText = contents;
} else {
// parse out the inline base64
const match = contents.match(/\/\/[#@] sourceMappingURL=data:application\/json[^,]+,(.+)$/);
const base64 = match[1];
const binary = atob(base64);
// this is the raw text of the source map
// seems to work without doing the decodeURIComponent trick
mapText = binary;
// // escape each char code into %XX and let decodeURIComponent build the UTF-8 string
// mapText = decodeURIComponent(
// binary
// .split('')
// .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
// .join('')
// );
try {
const contents = File.fromPath(mapPath).readTextSync();

// Note: we may want to do this, keeping for reference if needed in future.
// Check size before processing (skip very large source maps)
// const maxSizeBytes = 10 * 1024 * 1024; // 10MB limit
// if (contents.length > maxSizeBytes) {
// console.warn(`Source map ${mapPath} is too large (${contents.length} bytes), skipping...`);
// return null;
// }

if (usingSourceMapFiles) {
mapText = contents;
} else {
// parse out the inline base64
const match = contents.match(/\/\/[#@] sourceMappingURL=data:application\/json[^,]+,(.+)$/);
if (!match) {
console.warn(`Invalid source map format in ${mapPath}`);
return null;
}
const base64 = match[1];
const binary = atob(base64);
// this is the raw text of the source map
// seems to work without doing decodeURIComponent tricks
mapText = binary;
}
} catch (error) {
console.error(`Failed to load source map ${mapPath}:`, error);
return null;
}
} else {
// no source maps
return { source: null, line: 0, column: 0 };
return null;
}
}
loadedSourceMaps.set(mapPath, mapText); // cache it
Expand All @@ -80,9 +81,10 @@ function loadAndExtractMap(mapPath: string) {

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

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

// 3) hand it to the consumer
const sourceMap = loadAndExtractMap(mapPath);

if (!sourceMap) {
return { source: null, line: 0, column: 0 };
}

const consumer = getConsumer(mapPath, sourceMap);
return consumer.originalPositionFor({ line, column });
if (!consumer) {
return { source: null, line: 0, column: 0 };
}

try {
return consumer.originalPositionFor({ line, column });
} catch (error) {
console.error(`Failed to get original position for ${file}:${line}:${column}:`, error);
return { source: null, line: 0, column: 0 };
}
}

function remapStack(raw: string): string {
const lines = raw.split('\n');
const out = lines.map((line) => {
const m = /\((.+):(\d+):(\d+)\)/.exec(line);
if (!m) return line;
const [_, file, l, c] = m;
const orig = remapFrame(file, +l, +c);
if (!orig.source) return line;
return line.replace(/\(.+\)/, `(${orig.source}:${orig.line}:${orig.column})`);

try {
const [_, file, l, c] = m;
const orig = remapFrame(file, +l, +c);
if (!orig.source) return line;
return line.replace(/\(.+\)/, `(${orig.source}:${orig.line}:${orig.column})`);
} catch (error) {
console.error('Failed to remap stack frame:', line, error);
return line; // return original line if remapping fails
}
});
return out.join('\n');
}

/**
* Added in 9.0 runtimes.
* Allows the runtime to remap stack traces before displaying them in the in-flight error screens.
* Added with 9.0 runtimes.
* Allows the runtime to remap stack traces before displaying them via in-flight error screens.
*/
(global as any).__ns_remapStack = (rawStack: string) => {
// console.log('Remapping stack trace...');
return remapStack(rawStack);
try {
return remapStack(rawStack);
} catch (error) {
console.error('Failed to remap stack trace, returning original:', error);
return rawStack; // fallback to original stack trace
}
};
/**
* End of source map remapping for stack traces
Expand Down
35 changes: 34 additions & 1 deletion packages/webpack5/src/configuration/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,40 @@ export default function (config: Config, env: IWebpackEnv = _env): Config {
return map as Config.DevTool;
};

config.devtool(getSourceMapType(env.sourceMap));
const sourceMapType = getSourceMapType(env.sourceMap);

// Use devtool for both CommonJS and ESM - let webpack handle source mapping properly
config.devtool(sourceMapType);

// For ESM builds, fix the sourceMappingURL to use correct paths
if (!env.commonjs && sourceMapType && sourceMapType !== 'hidden-source-map') {
class FixSourceMapUrlPlugin {
apply(compiler) {
compiler.hooks.emit.tap('FixSourceMapUrlPlugin', (compilation) => {
const leadingCharacter = process.platform === "win32" ? "/":"";
Object.keys(compilation.assets).forEach((filename) => {
if (filename.endsWith('.mjs') || filename.endsWith('.js')) {
const asset = compilation.assets[filename];
let source = asset.source();

// Replace sourceMappingURL to use file:// protocol pointing to actual location
source = source.replace(
/\/\/# sourceMappingURL=(.+\.map)/g,
`//# sourceMappingURL=file://${leadingCharacter}${outputPath}/$1`,
);

compilation.assets[filename] = {
source: () => source,
size: () => source.length,
};
}
});
});
}
}

config.plugin('FixSourceMapUrlPlugin').use(FixSourceMapUrlPlugin);
}

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