33const {
44 ArrayPrototypePush,
55 JSONParse,
6- ObjectKeys,
76 RegExpPrototypeExec,
87 SafeMap,
98 StringPrototypeCodePointAt,
@@ -25,24 +24,22 @@ const {
2524} = require ( 'internal/errors' ) ;
2625const { getLazy } = require ( 'internal/util' ) ;
2726
28- // Since the CJS module cache is mutable, which leads to memory leaks when
29- // modules are deleted, we use a WeakMap so that the source map cache will
30- // be purged automatically:
31- const getCjsSourceMapCache = getLazy ( ( ) => {
32- const { IterableWeakMap } = require ( 'internal/util/iterable_weak_map' ) ;
33- return new IterableWeakMap ( ) ;
27+ const getModuleSourceMapCache = getLazy ( ( ) => {
28+ const { SourceMapCacheMap } = require ( 'internal/source_map/source_map_cache_map' ) ;
29+ return new SourceMapCacheMap ( ) ;
3430} ) ;
3531
36- // The esm cache is not mutable, so we can use a Map without memory concerns:
37- const esmSourceMapCache = new SafeMap ( ) ;
38- // The generated sources is not mutable, so we can use a Map without memory concerns:
32+ // The generated source module/script instance is not accessible, so we can use
33+ // a Map without memory concerns. Separate generated source entries with the module
34+ // source entries to avoid overriding the module source entries with arbitrary
35+ // source url magic comments.
3936const generatedSourceMapCache = new SafeMap ( ) ;
4037const kLeadingProtocol = / ^ \w + : \/ \/ / ;
4138const kSourceMappingURLMagicComment = / \/ [ * / ] # \s + s o u r c e M a p p i n g U R L = (?< sourceMappingURL > [ ^ \s ] + ) / g;
4239const kSourceURLMagicComment = / \/ [ * / ] # \s + s o u r c e U R L = (?< sourceURL > [ ^ \s ] + ) / g;
4340
4441const { isAbsolute } = require ( 'path' ) ;
45- const { fileURLToPath, pathToFileURL, URL } = require ( 'internal/url' ) ;
42+ const { fileURLToPath, pathToFileURL, URL , URLParse } = require ( 'internal/url' ) ;
4643
4744let SourceMap ;
4845
@@ -52,6 +49,10 @@ function getSourceMapsEnabled() {
5249 return sourceMapsEnabled ;
5350}
5451
52+ /**
53+ * Enables or disables source maps programmatically.
54+ * @param {boolean } val
55+ */
5556function setSourceMapsEnabled ( val ) {
5657 validateBoolean ( val , 'val' ) ;
5758
@@ -72,6 +73,14 @@ function setSourceMapsEnabled(val) {
7273 sourceMapsEnabled = val ;
7374}
7475
76+ /**
77+ * Extracts the source url from the content if present. For example
78+ * //# sourceURL=file:///path/to/file
79+ *
80+ * Read more at: https://tc39.es/source-map-spec/#linking-evald-code-to-named-generated-code
81+ * @param {string } content - source content
82+ * @returns {string | null } source url or null if not present
83+ */
7584function extractSourceURLMagicComment ( content ) {
7685 let match ;
7786 let matchSourceURL ;
@@ -90,6 +99,14 @@ function extractSourceURLMagicComment(content) {
9099 return sourceURL ;
91100}
92101
102+ /**
103+ * Extracts the source map url from the content if present. For example
104+ * //# sourceMappingURL=file:///path/to/file
105+ *
106+ * Read more at: https://tc39.es/source-map-spec/#linking-generated-code
107+ * @param {string } content - source content
108+ * @returns {string | null } source map url or null if not present
109+ */
93110function extractSourceMapURLMagicComment ( content ) {
94111 let match ;
95112 let lastMatch ;
@@ -104,7 +121,17 @@ function extractSourceMapURLMagicComment(content) {
104121 return lastMatch . groups . sourceMappingURL ;
105122}
106123
107- function maybeCacheSourceMap ( filename , content , cjsModuleInstance , isGeneratedSource , sourceURL , sourceMapURL ) {
124+ /**
125+ * Caches the source map if it is present in the content, with the given filename, moduleInstance, and sourceURL.
126+ * @param {string } filename - the actual filename
127+ * @param {string } content - the actual source content
128+ * @param {import('internal/modules/cjs/loader').Module | ModuleWrap } moduleInstance - a module instance that
129+ * associated with the source, once this is reclaimed, the source map entry will be removed from the cache
130+ * @param {boolean } isGeneratedSource - if the source was generated and evaluated with the global eval
131+ * @param {string | undefined } sourceURL - the source url
132+ * @param {string | undefined } sourceMapURL - the source map url
133+ */
134+ function maybeCacheSourceMap ( filename , content , moduleInstance , isGeneratedSource , sourceURL , sourceMapURL ) {
108135 const sourceMapsEnabled = getSourceMapsEnabled ( ) ;
109136 if ( ! ( process . env . NODE_V8_COVERAGE || sourceMapsEnabled ) ) return ;
110137 const { normalizeReferrerURL } = require ( 'internal/modules/helpers' ) ;
@@ -130,45 +157,32 @@ function maybeCacheSourceMap(filename, content, cjsModuleInstance, isGeneratedSo
130157 }
131158
132159 const data = dataFromUrl ( filename , sourceMapURL ) ;
133- const url = data ? null : sourceMapURL ;
134- if ( cjsModuleInstance ) {
135- getCjsSourceMapCache ( ) . set ( cjsModuleInstance , {
136- __proto__ : null ,
137- filename,
138- lineLengths : lineLengths ( content ) ,
139- data,
140- url,
141- sourceURL,
142- } ) ;
143- } else if ( isGeneratedSource ) {
144- const entry = {
145- __proto__ : null ,
146- lineLengths : lineLengths ( content ) ,
147- data,
148- url,
149- sourceURL,
150- } ;
160+ const entry = {
161+ __proto__ : null ,
162+ lineLengths : lineLengths ( content ) ,
163+ data,
164+ // Save the source map url if it is not a data url.
165+ sourceMapURL : data ? null : sourceMapURL ,
166+ sourceURL,
167+ } ;
168+
169+ if ( isGeneratedSource ) {
151170 generatedSourceMapCache . set ( filename , entry ) ;
152171 if ( sourceURL ) {
153172 generatedSourceMapCache . set ( sourceURL , entry ) ;
154173 }
155- } else {
156- // If there is no cjsModuleInstance and is not generated source assume we are in a
157- // "modules/esm" context.
158- const entry = {
159- __proto__ : null ,
160- lineLengths : lineLengths ( content ) ,
161- data,
162- url,
163- sourceURL,
164- } ;
165- esmSourceMapCache . set ( filename , entry ) ;
166- if ( sourceURL ) {
167- esmSourceMapCache . set ( sourceURL , entry ) ;
168- }
174+ return ;
169175 }
176+ // If it is not a generated source, we assume we are in a "cjs/esm"
177+ // context.
178+ const keys = sourceURL ? [ filename , sourceURL ] : [ filename ] ;
179+ getModuleSourceMapCache ( ) . set ( keys , entry , moduleInstance ) ;
170180}
171181
182+ /**
183+ * Caches the source map if it is present in the eval'd source.
184+ * @param {string } content - the eval'd source code
185+ */
172186function maybeCacheGeneratedSourceMap ( content ) {
173187 const sourceMapsEnabled = getSourceMapsEnabled ( ) ;
174188 if ( ! ( process . env . NODE_V8_COVERAGE || sourceMapsEnabled ) ) return ;
@@ -186,22 +200,29 @@ function maybeCacheGeneratedSourceMap(content) {
186200 }
187201}
188202
203+ /**
204+ * Resolves source map payload data from the source url and source map url.
205+ * If the source map url is a data url, the data is returned.
206+ * Otherwise the source map url is resolved to a file path and the file is read.
207+ * @param {string } sourceURL - url of the source file
208+ * @param {string } sourceMappingURL - url of the source map
209+ * @returns {object } deserialized source map JSON object
210+ */
189211function dataFromUrl ( sourceURL , sourceMappingURL ) {
190- try {
191- const url = new URL ( sourceMappingURL ) ;
212+ const url = URLParse ( sourceMappingURL ) ;
213+
214+ if ( url != null ) {
192215 switch ( url . protocol ) {
193216 case 'data:' :
194217 return sourceMapFromDataUrl ( sourceURL , url . pathname ) ;
195218 default :
196219 debug ( `unknown protocol ${ url . protocol } ` ) ;
197220 return null ;
198221 }
199- } catch ( err ) {
200- debug ( err ) ;
201- // If no scheme is present, we assume we are dealing with a file path.
202- const mapURL = new URL ( sourceMappingURL , sourceURL ) . href ;
203- return sourceMapFromFile ( mapURL ) ;
204222 }
223+
224+ const mapURL = new URL ( sourceMappingURL , sourceURL ) . href ;
225+ return sourceMapFromFile ( mapURL ) ;
205226}
206227
207228// Cache the length of each line in the file that a source map was extracted
@@ -227,7 +248,11 @@ function lineLengths(content) {
227248 return output ;
228249}
229250
230-
251+ /**
252+ * Read source map from file.
253+ * @param {string } mapURL - file url of the source map
254+ * @returns {object } deserialized source map JSON object
255+ */
231256function sourceMapFromFile ( mapURL ) {
232257 try {
233258 const fs = require ( 'fs' ) ;
@@ -281,56 +306,44 @@ function sourcesToAbsolute(baseURL, data) {
281306 return data ;
282307}
283308
284- // WARNING: The `sourceMapCacheToObject` and `appendCJSCache` run during
285- // shutdown. In particular, they also run when Workers are terminated, making
286- // it important that they do not call out to any user-provided code, including
287- // built-in prototypes that might have been tampered with.
309+ // WARNING: The `sourceMapCacheToObject` runs during shutdown. In particular,
310+ // it also runs when Workers are terminated, making it important that it does
311+ // not call out to any user-provided code, including built-in prototypes that
312+ // might have been tampered with.
288313
289314// Get serialized representation of source-map cache, this is used
290315// to persist a cache of source-maps to disk when NODE_V8_COVERAGE is enabled.
291316function sourceMapCacheToObject ( ) {
292- const obj = { __proto__ : null } ;
293-
294- for ( const { 0 : k , 1 : v } of esmSourceMapCache ) {
295- obj [ k ] = v ;
296- }
297-
298- appendCJSCache ( obj ) ;
299-
300- if ( ObjectKeys ( obj ) . length === 0 ) {
317+ const moduleSourceMapCache = getModuleSourceMapCache ( ) ;
318+ if ( moduleSourceMapCache . size === 0 ) {
301319 return undefined ;
302320 }
303- return obj ;
304- }
305321
306- function appendCJSCache ( obj ) {
307- for ( const value of getCjsSourceMapCache ( ) ) {
308- obj [ value . filename ] = {
322+ const obj = { __proto__ : null } ;
323+ for ( const { 0 : k , 1 : v } of moduleSourceMapCache ) {
324+ obj [ k ] = {
309325 __proto__ : null ,
310- lineLengths : value . lineLengths ,
311- data : value . data ,
312- url : value . url ,
326+ lineLengths : v . lineLengths ,
327+ data : v . data ,
328+ url : v . sourceMapURL ,
313329 } ;
314330 }
331+ return obj ;
315332}
316333
334+ /**
335+ * Find a source map for a given actual source URL or path.
336+ * @param {string } sourceURL - actual source URL or path
337+ * @returns {import('internal/source_map/source_map').SourceMap | undefined } a source map or undefined if not found
338+ */
317339function findSourceMap ( sourceURL ) {
318340 if ( RegExpPrototypeExec ( kLeadingProtocol , sourceURL ) === null ) {
319341 sourceURL = pathToFileURL ( sourceURL ) . href ;
320342 }
321343 if ( ! SourceMap ) {
322344 SourceMap = require ( 'internal/source_map/source_map' ) . SourceMap ;
323345 }
324- let entry = esmSourceMapCache . get ( sourceURL ) ?? generatedSourceMapCache . get ( sourceURL ) ;
325- if ( entry === undefined ) {
326- for ( const value of getCjsSourceMapCache ( ) ) {
327- const filename = value . filename ;
328- const cachedSourceURL = value . sourceURL ;
329- if ( sourceURL === filename || sourceURL === cachedSourceURL ) {
330- entry = value ;
331- }
332- }
333- }
346+ const entry = getModuleSourceMapCache ( ) . get ( sourceURL ) ?? generatedSourceMapCache . get ( sourceURL ) ;
334347 if ( entry === undefined ) {
335348 return undefined ;
336349 }
0 commit comments