GeezerVison is showing github.com — lib: allow skipping source maps in node_modules · nodejs/node@2fb007f
File treeExpand file treeCollapse file tree Open diff view settings fixtures/node_modules/error-stack
Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: benchmark/es/error-stack.js +22-4Lines changed: 22 additions & 4 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 2 | 2 |
| 3 | 3 | const common = require('../common.js'); | 4 | 4 | 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'); | 5 | 7 |
| 6 | 8 | 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'], | 8 | 14 | n: [1e5], | 9 | 15 | }); | 10 | 16 |
| 11 | | -function runN(n) { | | 17 | +function runN(n, modPath) { | 12 | 18 | delete require.cache[modPath]; | 13 | 19 | const mod = require(modPath); | 14 | 20 | bench.start(); | @@ -22,11 +28,23 @@ function main({ n, method }) { | 22 | 28 | switch (method) { | 23 | 29 | case 'without-sourcemap': | 24 | 30 | process.setSourceMapsEnabled(false); | 25 | | - runN(n); | | 31 | + runN(n, modPath); | 26 | 32 | break; | 27 | 33 | case 'sourcemap': | 28 | 34 | 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); | 30 | 48 | break; | 31 | 49 | default: | 32 | 50 | throw new Error(`Unexpected method "${method}"`); | |
Collapse file Copy file name to clipboardExpand all lines: benchmark/fixtures/node_modules/error-stack/simple-error-stack.js +16Lines changed: 16 additions & 0 deletions Collapse file Copy file name to clipboardExpand all lines: benchmark/fixtures/node_modules/error-stack/simple-error-stack.ts +19Lines changed: 19 additions & 0 deletions Collapse file Copy file name to clipboardExpand all lines: doc/api/module.md +40Lines changed: 40 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -1590,6 +1590,20 @@ import { findSourceMap, SourceMap } from 'node:module'; | 1590 | 1590 | const { findSourceMap, SourceMap } = require('node:module'); | 1591 | 1591 | ``` | 1592 | 1592 |
| | 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 | +
| 1593 | 1607 | <!-- Anchors to make sure old links find a target --> | 1594 | 1608 |
| 1595 | 1609 | <a id="module_module_findsourcemap_path_error"></a> | @@ -1609,6 +1623,31 @@ added: | 1609 | 1623 | `path` is the resolved path for the file for which a corresponding source map | 1610 | 1624 | should be fetched. | 1611 | 1625 |
| | 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 | +
| 1612 | 1651 | ### Class: `module.SourceMap` | 1613 | 1652 |
| 1614 | 1653 | <!-- YAML | @@ -1709,6 +1748,7 @@ returned object contains the following keys: | 1709 | 1748 | [Customization hooks]: #customization-hooks | 1710 | 1749 | [ES Modules]: esm.md | 1711 | 1750 | [Permission Model]: permissions.md#permission-model | | 1751 | +[Source Map]: https://sourcemaps.info/spec.html | 1712 | 1752 | [Source map v3 format]: https://sourcemaps.info/spec.html#h.mofvlxcwqzej | 1713 | 1753 | [V8 JavaScript code coverage]: https://v8project.blogspot.com/2017/12/javascript-code-coverage.html | 1714 | 1754 | [V8 code cache]: https://v8.dev/blog/code-caching-for-devs | |
Collapse file Copy file name to clipboardExpand all lines: doc/api/process.md +7-2Lines changed: 7 additions & 2 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -3987,7 +3987,7 @@ added: | 3987 | 3987 | - v14.18.0 | 3988 | 3988 | --> | 3989 | 3989 |
| 3990 | | -> Stability: 1 - Experimental | | 3990 | +> Stability: 1 - Experimental: Use [`module.setSourceMapsSupport()`][] instead. | 3991 | 3991 |
| 3992 | 3992 | * `val` {boolean} | 3993 | 3993 |
| @@ -4000,6 +4000,9 @@ It provides same features as launching Node.js process with commandline options | 4000 | 4000 | Only source maps in JavaScript files that are loaded after source maps has been | 4001 | 4001 | enabled will be parsed and loaded. | 4002 | 4002 |
| | 4003 | +This implies calling `module.setSourceMapsSupport()` with an option | | 4004 | +`{ nodeModules: true, generatedCode: true }`. | | 4005 | +
| 4003 | 4006 | ## `process.setUncaughtExceptionCaptureCallback(fn)` | 4004 | 4007 |
| 4005 | 4008 | <!-- YAML | @@ -4034,7 +4037,7 @@ added: | 4034 | 4037 | - v18.19.0 | 4035 | 4038 | --> | 4036 | 4039 |
| 4037 | | -> Stability: 1 - Experimental | | 4040 | +> Stability: 1 - Experimental: Use [`module.getSourceMapsSupport()`][] instead. | 4038 | 4041 |
| 4039 | 4042 | * {boolean} | 4040 | 4043 |
| @@ -4501,7 +4504,9 @@ cases: | 4501 | 4504 | [`console.error()`]: console.md#consoleerrordata-args | 4502 | 4505 | [`console.log()`]: console.md#consolelogdata-args | 4503 | 4506 | [`domain`]: domain.md | | 4507 | +[`module.getSourceMapsSupport()`]: module.md#modulegetsourcemapssupport | 4504 | 4508 | [`module.isBuiltin(id)`]: module.md#moduleisbuiltinmodulename | | 4509 | +[`module.setSourceMapsSupport()`]: module.md#modulesetsourcemapssupportenabled-options | 4505 | 4510 | [`net.Server`]: net.md#class-netserver | 4506 | 4511 | [`net.Socket`]: net.md#class-netsocket | 4507 | 4512 | [`os.constants.dlopen`]: os.md#dlopen-constants | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/bootstrap/node.js +13-4Lines changed: 13 additions & 4 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -368,8 +368,8 @@ internalBinding('process_methods').setEmitWarningSync(emitWarningSync); | 368 | 368 |
| 369 | 369 | { | 370 | 370 | const { | 371 | | - getSourceMapsEnabled, | 372 | | - setSourceMapsEnabled, | | 371 | + getSourceMapsSupport, | | 372 | + setSourceMapsSupport, | 373 | 373 | maybeCacheGeneratedSourceMap, | 374 | 374 | } = require('internal/source_map/source_map_cache'); | 375 | 375 | const { | @@ -381,10 +381,19 @@ internalBinding('process_methods').setEmitWarningSync(emitWarningSync); | 381 | 381 | enumerable: true, | 382 | 382 | configurable: true, | 383 | 383 | get() { | 384 | | - return getSourceMapsEnabled(); | | 384 | + return getSourceMapsSupport().enabled; | 385 | 385 | }, | 386 | 386 | }); | 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 | + }; | 388 | 397 | // The C++ land calls back to maybeCacheGeneratedSourceMap() | 389 | 398 | // when code is generated by user with eval() or new Function() | 390 | 399 | // to cache the source maps from the evaluated code, if any. | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/modules/esm/module_job.js +2-2Lines changed: 2 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -30,7 +30,7 @@ const { | 30 | 30 | } = internalBinding('util'); | 31 | 31 | const { decorateErrorStack, kEmptyObject } = require('internal/util'); | 32 | 32 | const { | 33 | | - getSourceMapsEnabled, | | 33 | + getSourceMapsSupport, | 34 | 34 | } = require('internal/source_map/source_map_cache'); | 35 | 35 | const assert = require('internal/assert'); | 36 | 36 | const resolvedPromise = PromiseResolve(); | @@ -186,7 +186,7 @@ class ModuleJob extends ModuleJobBase { | 186 | 186 | // of missing named export. This is currently not possible because | 187 | 187 | // stack trace originates in module_job, not the file itself. A hidden | 188 | 188 | // symbol with filename could be set in node_errors.cc to facilitate this. | 189 | | - if (!getSourceMapsEnabled() && | | 189 | + if (!getSourceMapsSupport().enabled && | 190 | 190 | StringPrototypeIncludes(e.message, | 191 | 191 | ' does not provide an export named')) { | 192 | 192 | const splitStack = StringPrototypeSplit(e.stack, '\n'); | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/process/pre_execution.js +10-2Lines changed: 10 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -608,9 +608,17 @@ function initializeESMLoader(forceDefaultLoader) { | 608 | 608 |
| 609 | 609 | function initializeSourceMapsHandlers() { | 610 | 610 | const { | 611 | | - setSourceMapsEnabled, | | 611 | + setSourceMapsSupport, | 612 | 612 | } = 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 | + }); | 614 | 622 | } | 615 | 623 |
| 616 | 624 | function initializeFrozenIntrinsics() { | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/source_map/source_map_cache.js +46-19Lines changed: 46 additions & 19 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 3 | 3 | const { | 4 | 4 | ArrayPrototypePush, | 5 | 5 | JSONParse, | | 6 | + ObjectFreeze, | 6 | 7 | RegExpPrototypeExec, | 7 | 8 | SafeMap, | 8 | 9 | StringPrototypeCodePointAt, | @@ -15,15 +16,15 @@ let debug = require('internal/util/debuglog').debuglog('source_map', (fn) => { | 15 | 16 | debug = fn; | 16 | 17 | }); | 17 | 18 |
| 18 | | -const { validateBoolean } = require('internal/validators'); | | 19 | +const { validateBoolean, validateObject } = require('internal/validators'); | 19 | 20 | const { | 20 | 21 | setSourceMapsEnabled: setSourceMapsNative, | 21 | 22 | } = internalBinding('errors'); | 22 | 23 | const { | 23 | 24 | defaultPrepareStackTrace, | 24 | 25 | setInternalPrepareStackTrace, | 25 | 26 | } = require('internal/errors'); | 26 | | -const { getLazy } = require('internal/util'); | | 27 | +const { getLazy, isUnderNodeModules, kEmptyObject } = require('internal/util'); | 27 | 28 |
| 28 | 29 | const getModuleSourceMapCache = getLazy(() => { | 29 | 30 | const { SourceMapCacheMap } = require('internal/source_map/source_map_cache_map'); | @@ -45,30 +46,48 @@ const { fileURLToPath, pathToFileURL, URL, URLParse } = require('internal/url'); | 45 | 46 | let SourceMap; | 46 | 47 |
| 47 | 48 | // 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; | 51 | 58 | } | 52 | 59 |
| 53 | 60 | /** | 54 | 61 | * 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] | 56 | 66 | */ | 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'); | 59 | 74 |
| 60 | | - setSourceMapsNative(val); | 61 | | - if (val) { | | 75 | + setSourceMapsNative(enabled); | | 76 | + if (enabled) { | 62 | 77 | const { | 63 | 78 | prepareStackTraceWithSourceMaps, | 64 | 79 | } = require('internal/source_map/prepare_stack_trace'); | 65 | 80 | setInternalPrepareStackTrace(prepareStackTraceWithSourceMaps); | 66 | | - } else if (sourceMapsEnabled !== undefined) { | 67 | | - // Reset prepare stack trace callback only when disabling source maps. | | 81 | + } else { | 68 | 82 | setInternalPrepareStackTrace(defaultPrepareStackTrace); | 69 | 83 | } | 70 | 84 |
| 71 | | - sourceMapsEnabled = val; | | 85 | + sourceMapsSupport = ObjectFreeze({ | | 86 | + __proto__: null, | | 87 | + enabled, | | 88 | + nodeModules: nodeModules, | | 89 | + generatedCode: generatedCode, | | 90 | + }); | 72 | 91 | } | 73 | 92 |
| 74 | 93 | /** | @@ -130,14 +149,18 @@ function extractSourceMapURLMagicComment(content) { | 130 | 149 | * @param {string | undefined} sourceMapURL - the source map url | 131 | 150 | */ | 132 | 151 | 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; | 135 | 154 | const { normalizeReferrerURL } = require('internal/modules/helpers'); | 136 | 155 | filename = normalizeReferrerURL(filename); | 137 | 156 | if (filename === undefined) { | 138 | 157 | // This is most likely an invalid filename in sourceURL of [eval]-wrapper. | 139 | 158 | return; | 140 | 159 | } | | 160 | + if (!support.nodeModules && isUnderNodeModules(filename)) { | | 161 | + // Skip file under node_modules if not enabled. | | 162 | + return; | | 163 | + } | 141 | 164 |
| 142 | 165 | if (sourceMapURL === undefined) { | 143 | 166 | sourceMapURL = extractSourceMapURLMagicComment(content); | @@ -185,8 +208,8 @@ function maybeCacheSourceMap(filename, content, moduleInstance, isGeneratedSourc | 185 | 208 | * @param {string} content - the eval'd source code | 186 | 209 | */ | 187 | 210 | 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; | 190 | 213 |
| 191 | 214 | const sourceURL = extractSourceURLMagicComment(content); | 192 | 215 | if (sourceURL === null) { | @@ -352,6 +375,10 @@ function findSourceMap(sourceURL) { | 352 | 375 | return undefined; | 353 | 376 | } | 354 | 377 |
| | 378 | + if (!getSourceMapsSupport().nodeModules && isUnderNodeModules(sourceURL)) { | | 379 | + return undefined; | | 380 | + } | | 381 | + | 355 | 382 | SourceMap ??= require('internal/source_map/source_map').SourceMap; | 356 | 383 | try { | 357 | 384 | if (RegExpPrototypeExec(kLeadingProtocol, sourceURL) === null) { | @@ -377,8 +404,8 @@ function findSourceMap(sourceURL) { | 377 | 404 |
| 378 | 405 | module.exports = { | 379 | 406 | findSourceMap, | 380 | | - getSourceMapsEnabled, | 381 | | - setSourceMapsEnabled, | | 407 | + getSourceMapsSupport, | | 408 | + setSourceMapsSupport, | 382 | 409 | maybeCacheSourceMap, | 383 | 410 | maybeCacheGeneratedSourceMap, | 384 | 411 | sourceMapCacheToObject, | |
|