Skip to content

Commit fb1a57e

Browse files
committed
lib: skip source maps in node_modules
Skipping source maps in `node_modules` by default improves the general performance. Add `module.setSourceMapsSupport(enabled, options)` to enable source maps in `node_modules` if it is needed. Files in `node_modules` are not authored by the user directly and the original sources are less relevant to the user.
1 parent 0e7ec5e commit fb1a57e

24 files changed

+450
-48
lines changed

benchmark/es/error-stack.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
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');
56

67
const bench = common.createBenchmark(main, {
7-
method: ['without-sourcemap', 'sourcemap'],
8+
method: ['without-sourcemap', 'sourcemap', 'node-module-sourcemap', 'node-module'],
89
n: [1e5],
910
});
1011

11-
function runN(n) {
12+
function runN(n, modPath) {
1213
delete require.cache[modPath];
1314
const mod = require(modPath);
1415
bench.start();
@@ -22,11 +23,15 @@ function main({ n, method }) {
2223
switch (method) {
2324
case 'without-sourcemap':
2425
process.setSourceMapsEnabled(false);
25-
runN(n);
26+
runN(n, modPath);
2627
break;
2728
case 'sourcemap':
2829
process.setSourceMapsEnabled(true);
29-
runN(n);
30+
runN(n, modPath);
31+
break;
32+
case 'sourcemap-with-node-modules':
33+
process.setSourceMapsEnabled(true);
34+
runN(n, nodeModulePath);
3035
break;
3136
default:
3237
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

+12-2
Original file line numberDiff line numberDiff line change
@@ -3995,9 +3995,13 @@ This feature is not available in [`Worker`][] threads.
39953995
added:
39963996
- v16.6.0
39973997
- v14.18.0
3998+
changes:
3999+
- version: REPLACEME
4000+
pr-url: https://github.com/nodejs/node/pull/56639
4001+
description: the `process.setSourceMapsEnabled` has been deprecated.
39984002
-->
39994003
4000-
> Stability: 1 - Experimental
4004+
> Stability: 0 - Deprecated: Use [`module.setSourceMapsSupport()`][] instead.
40014005
40024006
* `val` {boolean}
40034007
@@ -4042,9 +4046,13 @@ Using this function is mutually exclusive with using the deprecated
40424046
added:
40434047
- v20.7.0
40444048
- v18.19.0
4049+
changes:
4050+
- version: REPLACEME
4051+
pr-url: https://github.com/nodejs/node/pull/56639
4052+
description: the `process.sourceMapsEnabled` has been deprecated.
40454053
-->
40464054
4047-
> Stability: 1 - Experimental
4055+
> Stability: 0 - Deprecated: Use [`module.getSourceMapsSupport()`][] instead.
40484056
40494057
* {boolean}
40504058
@@ -4511,7 +4519,9 @@ cases:
45114519
[`console.error()`]: console.md#consoleerrordata-args
45124520
[`console.log()`]: console.md#consolelogdata-args
45134521
[`domain`]: domain.md
4522+
[`module.getSourceMapsSupport()`]: module.md#modulegetsourcemapssupport
45144523
[`module.isBuiltin(id)`]: module.md#moduleisbuiltinmodulename
4524+
[`module.setSourceMapsSupport()`]: module.md#modulesetsourcemapssupportenabled-options
45154525
[`net.Server`]: net.md#class-netserver
45164526
[`net.Socket`]: net.md#class-netsocket
45174527
[`os.constants.dlopen`]: os.md#dlopen-constants

lib/internal/bootstrap/node.js

+6-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,12 @@ 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+
};
388390
// The C++ land calls back to maybeCacheGeneratedSourceMap()
389391
// when code is generated by user with eval() or new Function()
390392
// 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

+2-2
Original file line numberDiff line numberDiff line change
@@ -618,9 +618,9 @@ 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+
setSourceMapsSupport(getOptionValue('--enable-source-maps'));
624624
}
625625

626626
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,

lib/module.js

+14-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
'use strict';
22

3-
const { findSourceMap } = require('internal/source_map/source_map_cache');
3+
const {
4+
findSourceMap,
5+
getSourceMapsSupport,
6+
setSourceMapsSupport,
7+
} = require('internal/source_map/source_map_cache');
48
const { Module } = require('internal/modules/cjs/loader');
59
const { register } = require('internal/modules/esm/loader');
6-
const { SourceMap } = require('internal/source_map/source_map');
10+
const {
11+
SourceMap,
12+
} = require('internal/source_map/source_map');
713
const {
814
constants,
915
enableCompileCache,
@@ -15,14 +21,18 @@ const {
1521
} = require('internal/modules/package_json_reader');
1622
const { stripTypeScriptTypes } = require('internal/modules/typescript');
1723

18-
Module.findSourceMap = findSourceMap;
1924
Module.register = register;
20-
Module.SourceMap = SourceMap;
2125
Module.constants = constants;
2226
Module.enableCompileCache = enableCompileCache;
2327
Module.findPackageJSON = findPackageJSON;
2428
Module.flushCompileCache = flushCompileCache;
2529
Module.getCompileCacheDir = getCompileCacheDir;
2630
Module.stripTypeScriptTypes = stripTypeScriptTypes;
2731

32+
// SourceMap APIs
33+
Module.findSourceMap = findSourceMap;
34+
Module.SourceMap = SourceMap;
35+
Module.getSourceMapsSupport = getSourceMapsSupport;
36+
Module.setSourceMapsSupport = setSourceMapsSupport;
37+
2838
module.exports = Module;

0 commit comments

Comments
 (0)