Skip to content

Commit 593b4ff

Browse files
committed
lib: allow skipping source maps in node_modules
Files in `node_modules` are not authored by the user directly and the original sources are less relevant to the user. Skipping source maps in `node_modules` improves the general performance. Add `module.setSourceMapsSupport(enabled, options)` to skip source maps in `node_modules` if it is needed. This moves all source maps related API to `node:module` and this a step to promote the source maps API to stable.
1 parent bf59539 commit 593b4ff

24 files changed

+494
-48
lines changed

benchmark/es/error-stack.js

+22-4
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@
22

33
const common = require('../common.js');
44
const modPath = require.resolve('../fixtures/simple-error-stack.js');
5+
const nodeModulePath = require.resolve('../fixtures/node_modules/error-stack/simple-error-stack.js');
6+
const Module = require('node:module');
57

68
const bench = common.createBenchmark(main, {
7-
method: ['without-sourcemap', 'sourcemap'],
9+
method: [
10+
'without-sourcemap',
11+
'sourcemap',
12+
'node-modules-without-sourcemap',
13+
'node-module-sourcemap'],
814
n: [1e5],
915
});
1016

11-
function runN(n) {
17+
function runN(n, modPath) {
1218
delete require.cache[modPath];
1319
const mod = require(modPath);
1420
bench.start();
@@ -22,11 +28,23 @@ function main({ n, method }) {
2228
switch (method) {
2329
case 'without-sourcemap':
2430
process.setSourceMapsEnabled(false);
25-
runN(n);
31+
runN(n, modPath);
2632
break;
2733
case 'sourcemap':
2834
process.setSourceMapsEnabled(true);
29-
runN(n);
35+
runN(n, modPath);
36+
break;
37+
case 'node-modules-without-sourcemap':
38+
Module.setSourceMapsSupport(true, {
39+
nodeModules: false,
40+
});
41+
runN(n, nodeModulePath);
42+
break;
43+
case 'node-modules-sourcemap':
44+
Module.setSourceMapsSupport(true, {
45+
nodeModules: true,
46+
});
47+
runN(n, nodeModulePath);
3048
break;
3149
default:
3250
throw new Error(`Unexpected method "${method}"`);

benchmark/fixtures/node_modules/error-stack/simple-error-stack.js

+16
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

benchmark/fixtures/node_modules/error-stack/simple-error-stack.ts

+19
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

doc/api/module.md

+38
Original file line numberDiff line numberDiff line change
@@ -1587,6 +1587,43 @@ import { findSourceMap, SourceMap } from 'node:module';
15871587
const { findSourceMap, SourceMap } = require('node:module');
15881588
```
15891589
1590+
### `module.setSourceMapsSupport(enabled[, options])`
1591+
1592+
<!-- YAML
1593+
added: REPLACEME
1594+
-->
1595+
1596+
* `enabled` {boolean} Enable the source map support.
1597+
* `options` {Object} Optional
1598+
* `nodeModules` {boolean} If enabling the support for files in `node_modules`.
1599+
* `generatedCode` {boolean} If enabling the support for generated code from `eval` or `new Function`.
1600+
1601+
This function enables or disables the [Source Map v3][Source Map] support for
1602+
stack traces.
1603+
1604+
It provides same features as launching Node.js process with commandline options
1605+
`--enable-source-maps`, with additional options to alter the support for files
1606+
in `node_modules` or generated codes.
1607+
1608+
Only source maps in JavaScript files that are loaded after source maps has been
1609+
enabled will be parsed and loaded. Preferably, use the commandline options
1610+
`--enable-source-maps` to avoid losing track of source maps of modules loaded
1611+
before this API call.
1612+
1613+
### `module.getSourceMapsSupport()`
1614+
1615+
<!-- YAML
1616+
added: REPLACEME
1617+
-->
1618+
1619+
* Returns: {Object}
1620+
* `enabled` {boolean} If the source maps support is enabled
1621+
* `nodeModules` {boolean} If the support is enabled for files in `node_modules`.
1622+
* `generatedCode` {boolean} If the support is enabled for generated code from `eval` or `new Function`.
1623+
1624+
This method returns whether the [Source Map v3][Source Map] support for stack
1625+
traces is enabled.
1626+
15901627
<!-- Anchors to make sure old links find a target -->
15911628
15921629
<a id="module_module_findsourcemap_path_error"></a>
@@ -1705,6 +1742,7 @@ returned object contains the following keys:
17051742
[Conditional exports]: packages.md#conditional-exports
17061743
[Customization hooks]: #customization-hooks
17071744
[ES Modules]: esm.md
1745+
[Source Map]: https://sourcemaps.info/spec.html
17081746
[Source map v3 format]: https://sourcemaps.info/spec.html#h.mofvlxcwqzej
17091747
[V8 JavaScript code coverage]: https://v8project.blogspot.com/2017/12/javascript-code-coverage.html
17101748
[V8 code cache]: https://v8.dev/blog/code-caching-for-devs

doc/api/process.md

+15-2
Original file line numberDiff line numberDiff line change
@@ -4001,9 +4001,13 @@ This feature is not available in [`Worker`][] threads.
40014001
added:
40024002
- v16.6.0
40034003
- v14.18.0
4004+
changes:
4005+
- version: REPLACEME
4006+
pr-url: https://github.com/nodejs/node/pull/56639
4007+
description: the `process.setSourceMapsEnabled` has been deprecated.
40044008
-->
40054009
4006-
> Stability: 1 - Experimental
4010+
> Stability: 0 - Deprecated: Use [`module.setSourceMapsSupport()`][] instead.
40074011
40084012
* `val` {boolean}
40094013
@@ -4016,6 +4020,9 @@ It provides same features as launching Node.js process with commandline options
40164020
Only source maps in JavaScript files that are loaded after source maps has been
40174021
enabled will be parsed and loaded.
40184022
4023+
This implies calling `module.setSourceMapsSupport()` with an option
4024+
`{ nodeModule: true, generatedCode: true }`.
4025+
40194026
## `process.setUncaughtExceptionCaptureCallback(fn)`
40204027
40214028
<!-- YAML
@@ -4048,9 +4055,13 @@ Using this function is mutually exclusive with using the deprecated
40484055
added:
40494056
- v20.7.0
40504057
- v18.19.0
4058+
changes:
4059+
- version: REPLACEME
4060+
pr-url: https://github.com/nodejs/node/pull/56639
4061+
description: the `process.sourceMapsEnabled` has been deprecated.
40514062
-->
40524063
4053-
> Stability: 1 - Experimental
4064+
> Stability: 0 - Deprecated: Use [`module.getSourceMapsSupport()`][] instead.
40544065
40554066
* {boolean}
40564067
@@ -4517,7 +4528,9 @@ cases:
45174528
[`console.error()`]: console.md#consoleerrordata-args
45184529
[`console.log()`]: console.md#consolelogdata-args
45194530
[`domain`]: domain.md
4531+
[`module.getSourceMapsSupport()`]: module.md#modulegetsourcemapssupport
45204532
[`module.isBuiltin(id)`]: module.md#moduleisbuiltinmodulename
4533+
[`module.setSourceMapsSupport()`]: module.md#modulesetsourcemapssupportenabled-options
45214534
[`net.Server`]: net.md#class-netserver
45224535
[`net.Socket`]: net.md#class-netsocket
45234536
[`os.constants.dlopen`]: os.md#dlopen-constants

lib/internal/bootstrap/node.js

+13-4
Original file line numberDiff line numberDiff line change
@@ -368,8 +368,8 @@ internalBinding('process_methods').setEmitWarningSync(emitWarningSync);
368368

369369
{
370370
const {
371-
getSourceMapsEnabled,
372-
setSourceMapsEnabled,
371+
getSourceMapsSupport,
372+
setSourceMapsSupport,
373373
maybeCacheGeneratedSourceMap,
374374
} = require('internal/source_map/source_map_cache');
375375
const {
@@ -381,10 +381,19 @@ internalBinding('process_methods').setEmitWarningSync(emitWarningSync);
381381
enumerable: true,
382382
configurable: true,
383383
get() {
384-
return getSourceMapsEnabled();
384+
return getSourceMapsSupport().enabled;
385385
},
386386
});
387-
process.setSourceMapsEnabled = setSourceMapsEnabled;
387+
process.setSourceMapsEnabled = function setSourceMapsEnabled(val) {
388+
setSourceMapsSupport(val, {
389+
__proto__: null,
390+
// TODO(legendecas): In order to smoothly improve the source map support,
391+
// skip source maps in node_modules and generated code with
392+
// `process.setSourceMapsEnabled(true)` in a semver major version.
393+
nodeModules: val,
394+
generatedCode: val,
395+
});
396+
};
388397
// The C++ land calls back to maybeCacheGeneratedSourceMap()
389398
// when code is generated by user with eval() or new Function()
390399
// to cache the source maps from the evaluated code, if any.

lib/internal/modules/esm/module_job.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const {
3030
} = internalBinding('util');
3131
const { decorateErrorStack, kEmptyObject } = require('internal/util');
3232
const {
33-
getSourceMapsEnabled,
33+
getSourceMapsSupport,
3434
} = require('internal/source_map/source_map_cache');
3535
const assert = require('internal/assert');
3636
const resolvedPromise = PromiseResolve();
@@ -186,7 +186,7 @@ class ModuleJob extends ModuleJobBase {
186186
// of missing named export. This is currently not possible because
187187
// stack trace originates in module_job, not the file itself. A hidden
188188
// symbol with filename could be set in node_errors.cc to facilitate this.
189-
if (!getSourceMapsEnabled() &&
189+
if (!getSourceMapsSupport().enabled &&
190190
StringPrototypeIncludes(e.message,
191191
' does not provide an export named')) {
192192
const splitStack = StringPrototypeSplit(e.stack, '\n');

lib/internal/process/pre_execution.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -618,9 +618,17 @@ function initializeESMLoader(forceDefaultLoader) {
618618

619619
function initializeSourceMapsHandlers() {
620620
const {
621-
setSourceMapsEnabled,
621+
setSourceMapsSupport,
622622
} = require('internal/source_map/source_map_cache');
623-
setSourceMapsEnabled(getOptionValue('--enable-source-maps'));
623+
const enabled = getOptionValue('--enable-source-maps');
624+
setSourceMapsSupport(enabled, {
625+
__proto__: null,
626+
// TODO(legendecas): In order to smoothly improve the source map support,
627+
// skip source maps in node_modules and generated code with
628+
// `--enable-source-maps` in a semver major version.
629+
nodeModules: enabled,
630+
generatedCode: enabled,
631+
});
624632
}
625633

626634
function initializeFrozenIntrinsics() {

lib/internal/source_map/source_map_cache.js

+46-19
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const {
44
ArrayPrototypePush,
55
JSONParse,
6+
ObjectFreeze,
67
RegExpPrototypeExec,
78
SafeMap,
89
StringPrototypeCodePointAt,
@@ -15,15 +16,15 @@ let debug = require('internal/util/debuglog').debuglog('source_map', (fn) => {
1516
debug = fn;
1617
});
1718

18-
const { validateBoolean } = require('internal/validators');
19+
const { validateBoolean, validateObject } = require('internal/validators');
1920
const {
2021
setSourceMapsEnabled: setSourceMapsNative,
2122
} = internalBinding('errors');
2223
const {
2324
defaultPrepareStackTrace,
2425
setInternalPrepareStackTrace,
2526
} = require('internal/errors');
26-
const { getLazy } = require('internal/util');
27+
const { getLazy, isUnderNodeModules, kEmptyObject } = require('internal/util');
2728

2829
const getModuleSourceMapCache = getLazy(() => {
2930
const { SourceMapCacheMap } = require('internal/source_map/source_map_cache_map');
@@ -45,30 +46,48 @@ const { fileURLToPath, pathToFileURL, URL, URLParse } = require('internal/url');
4546
let SourceMap;
4647

4748
// This is configured with --enable-source-maps during pre-execution.
48-
let sourceMapsEnabled = false;
49-
function getSourceMapsEnabled() {
50-
return sourceMapsEnabled;
49+
let sourceMapsSupport = ObjectFreeze({
50+
__proto__: null,
51+
enabled: false,
52+
nodeModules: false,
53+
generatedCode: false,
54+
});
55+
function getSourceMapsSupport() {
56+
// Return a read-only object.
57+
return sourceMapsSupport;
5158
}
5259

5360
/**
5461
* Enables or disables source maps programmatically.
55-
* @param {boolean} val
62+
* @param {boolean} enabled
63+
* @param {object} options
64+
* @param {boolean} [options.nodeModules]
65+
* @param {boolean} [options.generatedCode]
5666
*/
57-
function setSourceMapsEnabled(val) {
58-
validateBoolean(val, 'val');
67+
function setSourceMapsSupport(enabled, options = kEmptyObject) {
68+
validateBoolean(enabled, 'enabled');
69+
validateObject(options, 'options');
70+
71+
const { nodeModules = false, generatedCode = false } = options;
72+
validateBoolean(nodeModules, 'options.nodeModules');
73+
validateBoolean(generatedCode, 'options.generatedCode');
5974

60-
setSourceMapsNative(val);
61-
if (val) {
75+
setSourceMapsNative(enabled);
76+
if (enabled) {
6277
const {
6378
prepareStackTraceWithSourceMaps,
6479
} = require('internal/source_map/prepare_stack_trace');
6580
setInternalPrepareStackTrace(prepareStackTraceWithSourceMaps);
66-
} else if (sourceMapsEnabled !== undefined) {
67-
// Reset prepare stack trace callback only when disabling source maps.
81+
} else {
6882
setInternalPrepareStackTrace(defaultPrepareStackTrace);
6983
}
7084

71-
sourceMapsEnabled = val;
85+
sourceMapsSupport = ObjectFreeze({
86+
__proto__: null,
87+
enabled,
88+
nodeModules: nodeModules,
89+
generatedCode: generatedCode,
90+
});
7291
}
7392

7493
/**
@@ -130,14 +149,18 @@ function extractSourceMapURLMagicComment(content) {
130149
* @param {string | undefined} sourceMapURL - the source map url
131150
*/
132151
function maybeCacheSourceMap(filename, content, moduleInstance, isGeneratedSource, sourceURL, sourceMapURL) {
133-
const sourceMapsEnabled = getSourceMapsEnabled();
134-
if (!(process.env.NODE_V8_COVERAGE || sourceMapsEnabled)) return;
152+
const support = getSourceMapsSupport();
153+
if (!(process.env.NODE_V8_COVERAGE || support.enabled)) return;
135154
const { normalizeReferrerURL } = require('internal/modules/helpers');
136155
filename = normalizeReferrerURL(filename);
137156
if (filename === undefined) {
138157
// This is most likely an invalid filename in sourceURL of [eval]-wrapper.
139158
return;
140159
}
160+
if (!support.nodeModules && isUnderNodeModules(filename)) {
161+
// Skip file under node_modules if not enabled.
162+
return;
163+
}
141164

142165
if (sourceMapURL === undefined) {
143166
sourceMapURL = extractSourceMapURLMagicComment(content);
@@ -185,8 +208,8 @@ function maybeCacheSourceMap(filename, content, moduleInstance, isGeneratedSourc
185208
* @param {string} content - the eval'd source code
186209
*/
187210
function maybeCacheGeneratedSourceMap(content) {
188-
const sourceMapsEnabled = getSourceMapsEnabled();
189-
if (!(process.env.NODE_V8_COVERAGE || sourceMapsEnabled)) return;
211+
const support = getSourceMapsSupport();
212+
if (!(process.env.NODE_V8_COVERAGE || support.enabled || support.generated)) return;
190213

191214
const sourceURL = extractSourceURLMagicComment(content);
192215
if (sourceURL === null) {
@@ -352,6 +375,10 @@ function findSourceMap(sourceURL) {
352375
return undefined;
353376
}
354377

378+
if (!getSourceMapsSupport().nodeModules && isUnderNodeModules(sourceURL)) {
379+
return undefined;
380+
}
381+
355382
SourceMap ??= require('internal/source_map/source_map').SourceMap;
356383
try {
357384
if (RegExpPrototypeExec(kLeadingProtocol, sourceURL) === null) {
@@ -377,8 +404,8 @@ function findSourceMap(sourceURL) {
377404

378405
module.exports = {
379406
findSourceMap,
380-
getSourceMapsEnabled,
381-
setSourceMapsEnabled,
407+
getSourceMapsSupport,
408+
setSourceMapsSupport,
382409
maybeCacheSourceMap,
383410
maybeCacheGeneratedSourceMap,
384411
sourceMapCacheToObject,

0 commit comments

Comments
 (0)