Skip to content

Commit 7a31ae8

Browse files
legendecasRafaelGSS
authored andcommitted
src,lib: retrieve parsed source map url from v8
V8 already parses the source map magic comments. Currently, only scripts and functions expose the parsed source map URLs. It is unnecessary to parse the source map magic comments again when the parsed information is available. PR-URL: #44798 Reviewed-By: Joyee Cheung <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: Jan Krems <[email protected]>
1 parent c23e023 commit 7a31ae8

File tree

10 files changed

+282
-143
lines changed

10 files changed

+282
-143
lines changed

doc/api/vm.md

+35
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,41 @@ console.log(globalVar);
344344
// 1000
345345
```
346346

347+
### `script.sourceMapURL`
348+
349+
<!-- YAML
350+
added: REPLACEME
351+
-->
352+
353+
* {string|undefined}
354+
355+
When the script is compiled from a source that contains a source map magic
356+
comment, this property will be set to the URL of the source map.
357+
358+
```mjs
359+
import vm from 'node:vm';
360+
361+
const script = new vm.Script(`
362+
function myFunc() {}
363+
//# sourceMappingURL=sourcemap.json
364+
`);
365+
366+
console.log(script.sourceMapURL);
367+
// Prints: sourcemap.json
368+
```
369+
370+
```cjs
371+
const vm = require('node:vm');
372+
373+
const script = new vm.Script(`
374+
function myFunc() {}
375+
//# sourceMappingURL=sourcemap.json
376+
`);
377+
378+
console.log(script.sourceMapURL);
379+
// Prints: sourcemap.json
380+
```
381+
347382
## Class: `vm.Module`
348383

349384
<!-- YAML

lib/internal/modules/cjs/loader.js

+21-5
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ const {
8686
filterOwnProperties,
8787
setOwnProperty,
8888
} = require('internal/util');
89-
const vm = require('vm');
89+
const { Script } = require('vm');
90+
const { internalCompileFunction } = require('internal/vm');
9091
const assert = require('internal/assert');
9192
const fs = require('fs');
9293
const internalFS = require('internal/fs/utils');
@@ -1073,19 +1074,28 @@ let hasPausedEntry = false;
10731074
function wrapSafe(filename, content, cjsModuleInstance) {
10741075
if (patched) {
10751076
const wrapper = Module.wrap(content);
1076-
return vm.runInThisContext(wrapper, {
1077+
const script = new Script(wrapper, {
10771078
filename,
10781079
lineOffset: 0,
1079-
displayErrors: true,
10801080
importModuleDynamically: async (specifier, _, importAssertions) => {
10811081
const loader = asyncESM.esmLoader;
10821082
return loader.import(specifier, normalizeReferrerURL(filename),
10831083
importAssertions);
10841084
},
10851085
});
1086+
1087+
// Cache the source map for the module if present.
1088+
if (script.sourceMapURL) {
1089+
maybeCacheSourceMap(filename, content, this, false, undefined, script.sourceMapURL);
1090+
}
1091+
1092+
return script.runInThisContext({
1093+
displayErrors: true,
1094+
});
10861095
}
1096+
10871097
try {
1088-
return vm.compileFunction(content, [
1098+
const result = internalCompileFunction(content, [
10891099
'exports',
10901100
'require',
10911101
'module',
@@ -1099,6 +1109,13 @@ function wrapSafe(filename, content, cjsModuleInstance) {
10991109
importAssertions);
11001110
},
11011111
});
1112+
1113+
// Cache the source map for the module if present.
1114+
if (result.sourceMapURL) {
1115+
maybeCacheSourceMap(filename, content, this, false, undefined, result.sourceMapURL);
1116+
}
1117+
1118+
return result.function;
11021119
} catch (err) {
11031120
if (process.mainModule === cjsModuleInstance)
11041121
enrichCJSError(err, content);
@@ -1119,7 +1136,6 @@ Module.prototype._compile = function(content, filename) {
11191136
policy.manifest.assertIntegrity(moduleURL, content);
11201137
}
11211138

1122-
maybeCacheSourceMap(filename, content, this);
11231139
const compiledWrapper = wrapSafe(filename, content, this);
11241140

11251141
let inspectorWrapper = null;

lib/internal/process/pre_execution.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -602,9 +602,11 @@ function initializeESMLoader() {
602602
}
603603

604604
function initializeSourceMapsHandlers() {
605-
const { setSourceMapsEnabled } =
605+
const { setSourceMapsEnabled, getSourceMapsEnabled } =
606606
require('internal/source_map/source_map_cache');
607607
process.setSourceMapsEnabled = setSourceMapsEnabled;
608+
// Initialize the environment flag of source maps.
609+
getSourceMapsEnabled();
608610
}
609611

610612
function initializeFrozenIntrinsics() {

lib/internal/source_map/source_map_cache.js

+56-42
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,21 @@ function extractSourceURLMagicComment(content) {
9797
return sourceURL;
9898
}
9999

100-
function maybeCacheSourceMap(filename, content, cjsModuleInstance, isGeneratedSource, sourceURL) {
100+
function extractSourceMapURLMagicComment(content) {
101+
let match;
102+
let lastMatch;
103+
// A while loop is used here to get the last occurrence of sourceMappingURL.
104+
// This is needed so that we don't match sourceMappingURL in string literals.
105+
while ((match = RegExpPrototypeExec(kSourceMappingURLMagicComment, content))) {
106+
lastMatch = match;
107+
}
108+
if (lastMatch == null) {
109+
return null;
110+
}
111+
return lastMatch.groups.sourceMappingURL;
112+
}
113+
114+
function maybeCacheSourceMap(filename, content, cjsModuleInstance, isGeneratedSource, sourceURL, sourceMapURL) {
101115
const sourceMapsEnabled = getSourceMapsEnabled();
102116
if (!(process.env.NODE_V8_COVERAGE || sourceMapsEnabled)) return;
103117
try {
@@ -108,52 +122,52 @@ function maybeCacheSourceMap(filename, content, cjsModuleInstance, isGeneratedSo
108122
return;
109123
}
110124

111-
let match;
112-
let lastMatch;
113-
// A while loop is used here to get the last occurrence of sourceMappingURL.
114-
// This is needed so that we don't match sourceMappingURL in string literals.
115-
while ((match = RegExpPrototypeExec(kSourceMappingURLMagicComment, content))) {
116-
lastMatch = match;
125+
if (sourceMapURL === undefined) {
126+
sourceMapURL = extractSourceMapURLMagicComment(content);
127+
}
128+
129+
// Bail out when there is no source map url.
130+
if (typeof sourceMapURL !== 'string') {
131+
return;
117132
}
118133

119134
if (sourceURL === undefined) {
120135
sourceURL = extractSourceURLMagicComment(content);
121136
}
122-
if (lastMatch) {
123-
const data = dataFromUrl(filename, lastMatch.groups.sourceMappingURL);
124-
const url = data ? null : lastMatch.groups.sourceMappingURL;
125-
if (cjsModuleInstance) {
126-
cjsSourceMapCache.set(cjsModuleInstance, {
127-
filename,
128-
lineLengths: lineLengths(content),
129-
data,
130-
url,
131-
sourceURL,
132-
});
133-
} else if (isGeneratedSource) {
134-
const entry = {
135-
lineLengths: lineLengths(content),
136-
data,
137-
url,
138-
sourceURL
139-
};
140-
generatedSourceMapCache.set(filename, entry);
141-
if (sourceURL) {
142-
generatedSourceMapCache.set(sourceURL, entry);
143-
}
144-
} else {
145-
// If there is no cjsModuleInstance and is not generated source assume we are in a
146-
// "modules/esm" context.
147-
const entry = {
148-
lineLengths: lineLengths(content),
149-
data,
150-
url,
151-
sourceURL,
152-
};
153-
esmSourceMapCache.set(filename, entry);
154-
if (sourceURL) {
155-
esmSourceMapCache.set(sourceURL, entry);
156-
}
137+
138+
const data = dataFromUrl(filename, sourceMapURL);
139+
const url = data ? null : sourceMapURL;
140+
if (cjsModuleInstance) {
141+
cjsSourceMapCache.set(cjsModuleInstance, {
142+
filename,
143+
lineLengths: lineLengths(content),
144+
data,
145+
url,
146+
sourceURL,
147+
});
148+
} else if (isGeneratedSource) {
149+
const entry = {
150+
lineLengths: lineLengths(content),
151+
data,
152+
url,
153+
sourceURL
154+
};
155+
generatedSourceMapCache.set(filename, entry);
156+
if (sourceURL) {
157+
generatedSourceMapCache.set(sourceURL, entry);
158+
}
159+
} else {
160+
// If there is no cjsModuleInstance and is not generated source assume we are in a
161+
// "modules/esm" context.
162+
const entry = {
163+
lineLengths: lineLengths(content),
164+
data,
165+
url,
166+
sourceURL,
167+
};
168+
esmSourceMapCache.set(filename, entry);
169+
if (sourceURL) {
170+
esmSourceMapCache.set(sourceURL, entry);
157171
}
158172
}
159173
}

lib/internal/vm.js

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
'use strict';
2+
3+
const {
4+
ArrayPrototypeForEach,
5+
} = primordials;
6+
7+
const {
8+
compileFunction,
9+
isContext: _isContext,
10+
} = internalBinding('contextify');
11+
const {
12+
validateArray,
13+
validateBoolean,
14+
validateBuffer,
15+
validateFunction,
16+
validateObject,
17+
validateString,
18+
validateUint32,
19+
} = require('internal/validators');
20+
const {
21+
ERR_INVALID_ARG_TYPE,
22+
} = require('internal/errors').codes;
23+
24+
function isContext(object) {
25+
validateObject(object, 'object', { __proto__: null, allowArray: true });
26+
27+
return _isContext(object);
28+
}
29+
30+
function internalCompileFunction(code, params, options) {
31+
validateString(code, 'code');
32+
if (params !== undefined) {
33+
validateArray(params, 'params');
34+
ArrayPrototypeForEach(params,
35+
(param, i) => validateString(param, `params[${i}]`));
36+
}
37+
38+
const {
39+
filename = '',
40+
columnOffset = 0,
41+
lineOffset = 0,
42+
cachedData = undefined,
43+
produceCachedData = false,
44+
parsingContext = undefined,
45+
contextExtensions = [],
46+
importModuleDynamically,
47+
} = options;
48+
49+
validateString(filename, 'options.filename');
50+
validateUint32(columnOffset, 'options.columnOffset');
51+
validateUint32(lineOffset, 'options.lineOffset');
52+
if (cachedData !== undefined)
53+
validateBuffer(cachedData, 'options.cachedData');
54+
validateBoolean(produceCachedData, 'options.produceCachedData');
55+
if (parsingContext !== undefined) {
56+
if (
57+
typeof parsingContext !== 'object' ||
58+
parsingContext === null ||
59+
!isContext(parsingContext)
60+
) {
61+
throw new ERR_INVALID_ARG_TYPE(
62+
'options.parsingContext',
63+
'Context',
64+
parsingContext
65+
);
66+
}
67+
}
68+
validateArray(contextExtensions, 'options.contextExtensions');
69+
ArrayPrototypeForEach(contextExtensions, (extension, i) => {
70+
const name = `options.contextExtensions[${i}]`;
71+
validateObject(extension, name, { __proto__: null, nullable: true });
72+
});
73+
74+
const result = compileFunction(
75+
code,
76+
filename,
77+
lineOffset,
78+
columnOffset,
79+
cachedData,
80+
produceCachedData,
81+
parsingContext,
82+
contextExtensions,
83+
params
84+
);
85+
86+
if (produceCachedData) {
87+
result.function.cachedDataProduced = result.cachedDataProduced;
88+
}
89+
90+
if (result.cachedData) {
91+
result.function.cachedData = result.cachedData;
92+
}
93+
94+
if (importModuleDynamically !== undefined) {
95+
validateFunction(importModuleDynamically,
96+
'options.importModuleDynamically');
97+
const { importModuleDynamicallyWrap } =
98+
require('internal/vm/module');
99+
const { callbackMap } = internalBinding('module_wrap');
100+
const wrapped = importModuleDynamicallyWrap(importModuleDynamically);
101+
const func = result.function;
102+
callbackMap.set(result.cacheKey, {
103+
importModuleDynamically: (s, _k, i) => wrapped(s, func, i),
104+
});
105+
}
106+
107+
return result;
108+
}
109+
110+
module.exports = {
111+
internalCompileFunction,
112+
isContext,
113+
};

0 commit comments

Comments
 (0)