Skip to content

Commit df4e7df

Browse files
committed
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.
1 parent e6018e2 commit df4e7df

File tree

8 files changed

+199
-110
lines changed

8 files changed

+199
-110
lines changed

doc/api/vm.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,29 @@ 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+
```js
359+
const vm = require('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+
347370
## Class: `vm.Module`
348371

349372
<!-- YAML

lib/internal/modules/cjs/loader.js

Lines changed: 15 additions & 5 deletions
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,25 @@ 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+
maybeCacheSourceMap(filename, content, this, false, undefined, script.sourceMapURL);
1088+
1089+
return script.runInThisContext({
1090+
displayErrors: true,
1091+
});
10861092
}
1093+
10871094
try {
1088-
return vm.compileFunction(content, [
1095+
const result = internalCompileFunction(content, [
10891096
'exports',
10901097
'require',
10911098
'module',
@@ -1099,6 +1106,10 @@ function wrapSafe(filename, content, cjsModuleInstance) {
10991106
importAssertions);
11001107
},
11011108
});
1109+
1110+
maybeCacheSourceMap(filename, content, this, false, undefined, result.sourceMapURL);
1111+
1112+
return result.function;
11021113
} catch (err) {
11031114
if (process.mainModule === cjsModuleInstance)
11041115
enrichCJSError(err, content);
@@ -1119,7 +1130,6 @@ Module.prototype._compile = function(content, filename) {
11191130
policy.manifest.assertIntegrity(moduleURL, content);
11201131
}
11211132

1122-
maybeCacheSourceMap(filename, content, this);
11231133
const compiledWrapper = wrapSafe(filename, content, this);
11241134

11251135
let inspectorWrapper = null;

lib/internal/source_map/source_map_cache.js

Lines changed: 21 additions & 10 deletions
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,20 +122,17 @@ 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);
117127
}
118128

119129
if (sourceURL === undefined) {
120130
sourceURL = extractSourceURLMagicComment(content);
121131
}
122-
if (lastMatch) {
123-
const data = dataFromUrl(filename, lastMatch.groups.sourceMappingURL);
124-
const url = data ? null : lastMatch.groups.sourceMappingURL;
132+
133+
if (sourceMapURL) {
134+
const data = dataFromUrl(filename, sourceMapURL);
135+
const url = data ? null : sourceMapURL;
125136
if (cjsModuleInstance) {
126137
cjsSourceMapCache.set(cjsModuleInstance, {
127138
filename,

lib/internal/vm.js

Lines changed: 113 additions & 0 deletions
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', { 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, { 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+
};

lib/vm.js

Lines changed: 5 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ const {
3232
ContextifyScript,
3333
MicrotaskQueue,
3434
makeContext,
35-
isContext: _isContext,
3635
constants,
37-
compileFunction: _compileFunction,
3836
measureMemory: _measureMemory,
3937
} = internalBinding('contextify');
4038
const {
@@ -45,9 +43,7 @@ const {
4543
isArrayBufferView,
4644
} = require('internal/util/types');
4745
const {
48-
validateArray,
4946
validateBoolean,
50-
validateBuffer,
5147
validateFunction,
5248
validateInt32,
5349
validateObject,
@@ -60,6 +56,10 @@ const {
6056
kEmptyObject,
6157
kVmBreakFirstLineSymbol,
6258
} = require('internal/util');
59+
const {
60+
internalCompileFunction,
61+
isContext,
62+
} = require('internal/vm');
6363
const kParsingContext = Symbol('script parsing context');
6464

6565
class Script extends ContextifyScript {
@@ -213,12 +213,6 @@ function getContextOptions(options) {
213213
return contextOptions;
214214
}
215215

216-
function isContext(object) {
217-
validateObject(object, 'object', { allowArray: true });
218-
219-
return _isContext(object);
220-
}
221-
222216
let defaultContextNameIndex = 1;
223217
function createContext(contextObject = {}, options = kEmptyObject) {
224218
if (isContext(contextObject)) {
@@ -314,83 +308,7 @@ function runInThisContext(code, options) {
314308
}
315309

316310
function compileFunction(code, params, options = kEmptyObject) {
317-
validateString(code, 'code');
318-
if (params !== undefined) {
319-
validateArray(params, 'params');
320-
ArrayPrototypeForEach(params,
321-
(param, i) => validateString(param, `params[${i}]`));
322-
}
323-
324-
const {
325-
filename = '',
326-
columnOffset = 0,
327-
lineOffset = 0,
328-
cachedData = undefined,
329-
produceCachedData = false,
330-
parsingContext = undefined,
331-
contextExtensions = [],
332-
importModuleDynamically,
333-
} = options;
334-
335-
validateString(filename, 'options.filename');
336-
validateUint32(columnOffset, 'options.columnOffset');
337-
validateUint32(lineOffset, 'options.lineOffset');
338-
if (cachedData !== undefined)
339-
validateBuffer(cachedData, 'options.cachedData');
340-
validateBoolean(produceCachedData, 'options.produceCachedData');
341-
if (parsingContext !== undefined) {
342-
if (
343-
typeof parsingContext !== 'object' ||
344-
parsingContext === null ||
345-
!isContext(parsingContext)
346-
) {
347-
throw new ERR_INVALID_ARG_TYPE(
348-
'options.parsingContext',
349-
'Context',
350-
parsingContext
351-
);
352-
}
353-
}
354-
validateArray(contextExtensions, 'options.contextExtensions');
355-
ArrayPrototypeForEach(contextExtensions, (extension, i) => {
356-
const name = `options.contextExtensions[${i}]`;
357-
validateObject(extension, name, { nullable: true });
358-
});
359-
360-
const result = _compileFunction(
361-
code,
362-
filename,
363-
lineOffset,
364-
columnOffset,
365-
cachedData,
366-
produceCachedData,
367-
parsingContext,
368-
contextExtensions,
369-
params
370-
);
371-
372-
if (produceCachedData) {
373-
result.function.cachedDataProduced = result.cachedDataProduced;
374-
}
375-
376-
if (result.cachedData) {
377-
result.function.cachedData = result.cachedData;
378-
}
379-
380-
if (importModuleDynamically !== undefined) {
381-
validateFunction(importModuleDynamically,
382-
'options.importModuleDynamically');
383-
const { importModuleDynamicallyWrap } =
384-
require('internal/vm/module');
385-
const { callbackMap } = internalBinding('module_wrap');
386-
const wrapped = importModuleDynamicallyWrap(importModuleDynamically);
387-
const func = result.function;
388-
callbackMap.set(result.cacheKey, {
389-
importModuleDynamically: (s, _k, i) => wrapped(s, func, i),
390-
});
391-
}
392-
393-
return result.function;
311+
return internalCompileFunction(code, params, options).function;
394312
}
395313

396314
const measureMemoryModes = {

src/env_properties.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@
276276
V(sni_context_err_string, "Invalid SNI context") \
277277
V(sni_context_string, "sni_context") \
278278
V(source_string, "source") \
279+
V(source_map_url_string, "sourceMapURL") \
279280
V(stack_string, "stack") \
280281
V(standard_name_string, "standardName") \
281282
V(start_time_string, "startTime") \

0 commit comments

Comments
 (0)