Skip to content

Commit 2fb007f

Browse files
legendecasaduh95
authored andcommitted
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. PR-URL: #56639 Reviewed-By: Vinícius Lourenço Claro Cardoso <contact@viniciusl.com.br> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent f885496 commit 2fb007f

24 files changed

+491
-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

+40
Original file line numberDiff line numberDiff line change
@@ -1590,6 +1590,20 @@ import { findSourceMap, SourceMap } from 'node:module';
15901590
const { findSourceMap, SourceMap } = require('node:module');
15911591
```
15921592
1593+
### `module.getSourceMapsSupport()`
1594+
1595+
<!-- YAML
1596+
added: REPLACEME
1597+
-->
1598+
1599+
* Returns: {Object}
1600+
* `enabled` {boolean} If the source maps support is enabled
1601+
* `nodeModules` {boolean} If the support is enabled for files in `node_modules`.
1602+
* `generatedCode` {boolean} If the support is enabled for generated code from `eval` or `new Function`.
1603+
1604+
This method returns whether the [Source Map v3][Source Map] support for stack
1605+
traces is enabled.
1606+
15931607
<!-- Anchors to make sure old links find a target -->
15941608
15951609
<a id="module_module_findsourcemap_path_error"></a>
@@ -1609,6 +1623,31 @@ added:
16091623
`path` is the resolved path for the file for which a corresponding source map
16101624
should be fetched.
16111625
1626+
### `module.setSourceMapsSupport(enabled[, options])`
1627+
1628+
<!-- YAML
1629+
added: REPLACEME
1630+
-->
1631+
1632+
* `enabled` {boolean} Enable the source map support.
1633+
* `options` {Object} Optional
1634+
* `nodeModules` {boolean} If enabling the support for files in
1635+
`node_modules`. **Default:** `false`.
1636+
* `generatedCode` {boolean} If enabling the support for generated code from
1637+
`eval` or `new Function`. **Default:** `false`.
1638+
1639+
This function enables or disables the [Source Map v3][Source Map] support for
1640+
stack traces.
1641+
1642+
It provides same features as launching Node.js process with commandline options
1643+
`--enable-source-maps`, with additional options to alter the support for files
1644+
in `node_modules` or generated codes.
1645+
1646+
Only source maps in JavaScript files that are loaded after source maps has been
1647+
enabled will be parsed and loaded. Preferably, use the commandline options
1648+
`--enable-source-maps` to avoid losing track of source maps of modules loaded
1649+
before this API call.
1650+
16121651
### Class: `module.SourceMap`
16131652
16141653
<!-- YAML
@@ -1709,6 +1748,7 @@ returned object contains the following keys:
17091748
[Customization hooks]: #customization-hooks
17101749
[ES Modules]: esm.md
17111750
[Permission Model]: permissions.md#permission-model
1751+
[Source Map]: https://sourcemaps.info/spec.html
17121752
[Source map v3 format]: https://sourcemaps.info/spec.html#h.mofvlxcwqzej
17131753
[V8 JavaScript code coverage]: https://v8project.blogspot.com/2017/12/javascript-code-coverage.html
17141754
[V8 code cache]: https://v8.dev/blog/code-caching-for-devs

doc/api/process.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -3987,7 +3987,7 @@ added:
39873987
- v14.18.0
39883988
-->
39893989
3990-
> Stability: 1 - Experimental
3990+
> Stability: 1 - Experimental: Use [`module.setSourceMapsSupport()`][] instead.
39913991
39923992
* `val` {boolean}
39933993
@@ -4000,6 +4000,9 @@ It provides same features as launching Node.js process with commandline options
40004000
Only source maps in JavaScript files that are loaded after source maps has been
40014001
enabled will be parsed and loaded.
40024002
4003+
This implies calling `module.setSourceMapsSupport()` with an option
4004+
`{ nodeModules: true, generatedCode: true }`.
4005+
40034006
## `process.setUncaughtExceptionCaptureCallback(fn)`
40044007
40054008
<!-- YAML
@@ -4034,7 +4037,7 @@ added:
40344037
- v18.19.0
40354038
-->
40364039
4037-
> Stability: 1 - Experimental
4040+
> Stability: 1 - Experimental: Use [`module.getSourceMapsSupport()`][] instead.
40384041
40394042
* {boolean}
40404043
@@ -4501,7 +4504,9 @@ cases:
45014504
[`console.error()`]: console.md#consoleerrordata-args
45024505
[`console.log()`]: console.md#consolelogdata-args
45034506
[`domain`]: domain.md
4507+
[`module.getSourceMapsSupport()`]: module.md#modulegetsourcemapssupport
45044508
[`module.isBuiltin(id)`]: module.md#moduleisbuiltinmodulename
4509+
[`module.setSourceMapsSupport()`]: module.md#modulesetsourcemapssupportenabled-options
45054510
[`net.Server`]: net.md#class-netserver
45064511
[`net.Socket`]: net.md#class-netsocket
45074512
[`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
@@ -608,9 +608,17 @@ function initializeESMLoader(forceDefaultLoader) {
608608

609609
function initializeSourceMapsHandlers() {
610610
const {
611-
setSourceMapsEnabled,
611+
setSourceMapsSupport,
612612
} = require('internal/source_map/source_map_cache');
613-
setSourceMapsEnabled(getOptionValue('--enable-source-maps'));
613+
const enabled = getOptionValue('--enable-source-maps');
614+
setSourceMapsSupport(enabled, {
615+
__proto__: null,
616+
// TODO(legendecas): In order to smoothly improve the source map support,
617+
// skip source maps in node_modules and generated code with
618+
// `--enable-source-maps` in a semver major version.
619+
nodeModules: enabled,
620+
generatedCode: enabled,
621+
});
614622
}
615623

616624
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)