source: frontend/node_modules/webpack/lib/util/extractSourceMap.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 8.4 KB
Line 
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Natsu @xiaoxiaojx
4*/
5
6"use strict";
7
8const path = require("path");
9const urlUtils = require("url");
10const { isAbsolute, join } = require("./fs");
11
12/** @typedef {import("./fs").InputFileSystem} InputFileSystem */
13/** @typedef {string | Buffer<ArrayBufferLike>} StringOrBuffer */
14/** @typedef {(input: StringOrBuffer, resourcePath: string, fs: InputFileSystem) => Promise<{ source: StringOrBuffer, sourceMap: string | RawSourceMap | undefined, fileDependencies: string[] }>} SourceMapExtractorFunction */
15/** @typedef {import("webpack-sources").RawSourceMap} RawSourceMap */
16/** @typedef {(resourcePath: string) => Promise<StringOrBuffer>} ReadResource */
17
18/**
19 * Defines the source mapping url type used by this module.
20 * @typedef {object} SourceMappingURL
21 * @property {string} sourceMappingURL
22 * @property {string} replacementString
23 */
24
25// Matches only the last occurrence of sourceMappingURL
26const innerRegex = /\s*[#@]\s*sourceMappingURL\s*=\s*([^\s'"]*)\s*/;
27
28const validProtocolPattern = /^[a-z][a-z0-9+.-]*:/i;
29
30const sourceMappingURLRegex = new RegExp(
31 "(?:" +
32 "/\\*" +
33 "(?:\\s*\r?\n(?://)?)?" +
34 `(?:${innerRegex.source})` +
35 "\\s*" +
36 "\\*/" +
37 "|" +
38 `//(?:${innerRegex.source})` +
39 ")" +
40 "\\s*"
41);
42
43/**
44 * Extract source mapping URL from code comments
45 * @param {string} code source code content
46 * @returns {SourceMappingURL} source mapping information
47 */
48function getSourceMappingURL(code) {
49 const lines = code.split(/^/m);
50 /** @type {RegExpMatchArray | null | undefined} */
51 let match;
52
53 for (let i = lines.length - 1; i >= 0; i--) {
54 match = lines[i].match(sourceMappingURLRegex);
55 if (match) {
56 break;
57 }
58 }
59
60 const sourceMappingURL = match ? match[1] || match[2] || "" : "";
61
62 return {
63 sourceMappingURL: sourceMappingURL
64 ? decodeURI(sourceMappingURL)
65 : sourceMappingURL,
66 replacementString: match ? match[0] : ""
67 };
68}
69
70/**
71 * Get absolute path for source file
72 * @param {string} context context directory
73 * @param {string} request file request
74 * @param {string} sourceRoot source root directory
75 * @returns {string} absolute path
76 */
77function getAbsolutePath(context, request, sourceRoot) {
78 if (sourceRoot) {
79 if (isAbsolute(sourceRoot)) {
80 return join(undefined, sourceRoot, request);
81 }
82
83 return join(undefined, join(undefined, context, sourceRoot), request);
84 }
85
86 return join(undefined, context, request);
87}
88
89/**
90 * Check if value is a URL
91 * @param {string} value string to check
92 * @returns {boolean} true if value is a URL
93 */
94function isURL(value) {
95 return validProtocolPattern.test(value) && !path.win32.isAbsolute(value);
96}
97
98/**
99 * Fetch from multiple possible file paths
100 * @param {ReadResource} readResource read resource function
101 * @param {string[]} possibleRequests array of possible file paths
102 * @param {string} errorsAccumulator accumulated error messages
103 * @returns {Promise<{ path: string, data?: string }>} source content promise
104 */
105async function fetchPathsFromURL(
106 readResource,
107 possibleRequests,
108 errorsAccumulator = ""
109) {
110 /** @type {StringOrBuffer} */
111 let result;
112
113 try {
114 result = await readResource(possibleRequests[0]);
115 } catch (error) {
116 errorsAccumulator += `${/** @type {Error} */ (error).message}\n\n`;
117
118 const [, ...tailPossibleRequests] = possibleRequests;
119
120 if (tailPossibleRequests.length === 0) {
121 /** @type {Error} */ (error).message = errorsAccumulator;
122
123 throw error;
124 }
125
126 return fetchPathsFromURL(
127 readResource,
128 tailPossibleRequests,
129 errorsAccumulator
130 );
131 }
132
133 return {
134 path: possibleRequests[0],
135 data: result.toString("utf8")
136 };
137}
138
139/**
140 * Fetch source content from URL
141 * @param {ReadResource} readResource The read resource function
142 * @param {string} context context directory
143 * @param {string} url source URL
144 * @param {string=} sourceRoot source root directory
145 * @param {boolean=} skipReading whether to skip reading file content
146 * @returns {Promise<{ sourceURL: string, sourceContent?: StringOrBuffer }>} source content promise
147 */
148async function fetchFromURL(
149 readResource,
150 context,
151 url,
152 sourceRoot,
153 skipReading = false
154) {
155 // 1. It's an absolute url and it is not `windows` path like `C:\dir\file`
156 if (isURL(url)) {
157 // eslint-disable-next-line n/no-deprecated-api
158 const { protocol } = urlUtils.parse(url);
159 if (protocol === "data:") {
160 const sourceContent = skipReading ? "" : await readResource(url);
161
162 return { sourceURL: "", sourceContent };
163 }
164
165 if (protocol === "file:") {
166 const pathFromURL = urlUtils.fileURLToPath(url);
167 const sourceURL = path.normalize(pathFromURL);
168 const sourceContent = skipReading ? "" : await readResource(sourceURL);
169
170 return { sourceURL, sourceContent };
171 }
172
173 const sourceContent = skipReading ? "" : await readResource(url);
174 return { sourceURL: url, sourceContent };
175 }
176
177 // 3. Absolute path
178 if (isAbsolute(url)) {
179 let sourceURL = path.normalize(url);
180
181 /** @type {undefined | StringOrBuffer} */
182 let sourceContent;
183
184 if (!skipReading) {
185 /** @type {string[]} */
186 const possibleRequests = [sourceURL];
187
188 if (url.startsWith("/")) {
189 possibleRequests.push(
190 getAbsolutePath(context, sourceURL.slice(1), sourceRoot || "")
191 );
192 }
193
194 const result = await fetchPathsFromURL(readResource, possibleRequests);
195
196 sourceURL = result.path;
197 sourceContent = result.data;
198 }
199
200 return { sourceURL, sourceContent };
201 }
202
203 // 4. Relative path
204 const sourceURL = getAbsolutePath(context, url, sourceRoot || "");
205 /** @type {undefined | StringOrBuffer} */
206 let sourceContent;
207
208 if (!skipReading) {
209 sourceContent = await readResource(sourceURL);
210 }
211
212 return { sourceURL, sourceContent };
213}
214
215/**
216 * Extract source map from code content
217 * @param {StringOrBuffer} stringOrBuffer The input code content as string or buffer
218 * @param {string} resourcePath The path to the resource file
219 * @param {ReadResource} readResource The read resource function
220 * @returns {Promise<{ source: StringOrBuffer, sourceMap: string | RawSourceMap | undefined }>} Promise resolving to extracted source map information
221 */
222async function extractSourceMap(stringOrBuffer, resourcePath, readResource) {
223 const input =
224 typeof stringOrBuffer === "string"
225 ? stringOrBuffer
226 : stringOrBuffer.toString("utf8");
227 const inputSourceMap = undefined;
228 const output = {
229 source: stringOrBuffer,
230 sourceMap: inputSourceMap
231 };
232 const { sourceMappingURL, replacementString } = getSourceMappingURL(input);
233
234 if (!sourceMappingURL) {
235 return output;
236 }
237
238 const baseContext = path.dirname(resourcePath);
239
240 const { sourceURL, sourceContent } = await fetchFromURL(
241 readResource,
242 baseContext,
243 sourceMappingURL
244 );
245
246 if (!sourceContent) {
247 return output;
248 }
249
250 /** @type {RawSourceMap} */
251 const map = JSON.parse(
252 sourceContent.toString("utf8").replace(/^\)\]\}'/, "")
253 );
254
255 const context = sourceURL ? path.dirname(sourceURL) : baseContext;
256
257 const resolvedSources = await Promise.all(
258 map.sources.map(
259 async (/** @type {string} */ source, /** @type {number} */ i) => {
260 const originalSourceContent =
261 map.sourcesContent &&
262 typeof map.sourcesContent[i] !== "undefined" &&
263 map.sourcesContent[i] !== null
264 ? map.sourcesContent[i]
265 : undefined;
266 const skipReading = typeof originalSourceContent !== "undefined";
267 // We do not skipReading here, because we need absolute paths in sources.
268 // This is necessary so that for sourceMaps with the same file structure in sources, name collisions do not occur.
269 // https://github.com/webpack-contrib/source-map-loader/issues/51
270 let { sourceURL, sourceContent } = await fetchFromURL(
271 readResource,
272 context,
273 source,
274 map.sourceRoot,
275 skipReading
276 );
277
278 if (skipReading) {
279 sourceContent = originalSourceContent;
280 }
281
282 // Return original value of `source` when error happens
283 return { sourceURL, sourceContent };
284 }
285 )
286 );
287
288 /** @type {RawSourceMap} */
289 const newMap = { ...map };
290
291 newMap.sources = [];
292 newMap.sourcesContent = [];
293
294 delete newMap.sourceRoot;
295
296 for (const source of resolvedSources) {
297 const { sourceURL, sourceContent } = source;
298
299 newMap.sources.push(sourceURL || "");
300 newMap.sourcesContent.push(
301 sourceContent ? sourceContent.toString("utf8") : ""
302 );
303 }
304
305 const sourcesContentIsEmpty =
306 newMap.sourcesContent.filter(Boolean).length === 0;
307
308 if (sourcesContentIsEmpty) {
309 delete newMap.sourcesContent;
310 }
311
312 return {
313 source: input.replace(replacementString, ""),
314 sourceMap: /** @type {RawSourceMap} */ (newMap)
315 };
316}
317
318module.exports = extractSourceMap;
319module.exports.getSourceMappingURL = getSourceMappingURL;
Note: See TracBrowser for help on using the repository browser.