source: imaps-frontend/node_modules/webpack/lib/SourceMapDevToolPlugin.js

main
Last change on this file was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 4 days ago

F4 Finalna Verzija

  • Property mode set to 100644
File size: 18.8 KB
RevLine 
[79a0317]1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const asyncLib = require("neo-async");
9const { ConcatSource, RawSource } = require("webpack-sources");
10const Compilation = require("./Compilation");
11const ModuleFilenameHelpers = require("./ModuleFilenameHelpers");
12const ProgressPlugin = require("./ProgressPlugin");
13const SourceMapDevToolModuleOptionsPlugin = require("./SourceMapDevToolModuleOptionsPlugin");
14const createSchemaValidation = require("./util/create-schema-validation");
15const createHash = require("./util/createHash");
16const { relative, dirname } = require("./util/fs");
17const generateDebugId = require("./util/generateDebugId");
18const { makePathsAbsolute } = require("./util/identifier");
19
20/** @typedef {import("webpack-sources").MapOptions} MapOptions */
21/** @typedef {import("webpack-sources").Source} Source */
22/** @typedef {import("../declarations/plugins/SourceMapDevToolPlugin").SourceMapDevToolPluginOptions} SourceMapDevToolPluginOptions */
23/** @typedef {import("./Cache").Etag} Etag */
24/** @typedef {import("./CacheFacade").ItemCacheFacade} ItemCacheFacade */
25/** @typedef {import("./Chunk")} Chunk */
26/** @typedef {import("./Compilation").Asset} Asset */
27/** @typedef {import("./Compilation").AssetInfo} AssetInfo */
28/** @typedef {import("./Compiler")} Compiler */
29/** @typedef {import("./Module")} Module */
30/** @typedef {import("./NormalModule").SourceMap} SourceMap */
31/** @typedef {import("./TemplatedPathPlugin").TemplatePath} TemplatePath */
32/** @typedef {import("./util/Hash")} Hash */
33/** @typedef {import("./util/createHash").Algorithm} Algorithm */
34/** @typedef {import("./util/fs").OutputFileSystem} OutputFileSystem */
35
36const validate = createSchemaValidation(
37 require("../schemas/plugins/SourceMapDevToolPlugin.check.js"),
38 () => require("../schemas/plugins/SourceMapDevToolPlugin.json"),
39 {
40 name: "SourceMap DevTool Plugin",
41 baseDataPath: "options"
42 }
43);
44/**
45 * @typedef {object} SourceMapTask
46 * @property {Source} asset
47 * @property {AssetInfo} assetInfo
48 * @property {(string | Module)[]} modules
49 * @property {string} source
50 * @property {string} file
51 * @property {SourceMap} sourceMap
52 * @property {ItemCacheFacade} cacheItem cache item
53 */
54
55const METACHARACTERS_REGEXP = /[-[\]\\/{}()*+?.^$|]/g;
56const CONTENT_HASH_DETECT_REGEXP = /\[contenthash(:\w+)?\]/;
57const CSS_AND_JS_MODULE_EXTENSIONS_REGEXP = /\.((c|m)?js|css)($|\?)/i;
58const CSS_EXTENSION_DETECT_REGEXP = /\.css($|\?)/i;
59const MAP_URL_COMMENT_REGEXP = /\[map\]/g;
60const URL_COMMENT_REGEXP = /\[url\]/g;
61const URL_FORMATTING_REGEXP = /^\n\/\/(.*)$/;
62
63/**
64 * Reset's .lastIndex of stateful Regular Expressions
65 * For when `test` or `exec` is called on them
66 * @param {RegExp} regexp Stateful Regular Expression to be reset
67 * @returns {void}
68 */
69const resetRegexpState = regexp => {
70 regexp.lastIndex = -1;
71};
72
73/**
74 * Escapes regular expression metacharacters
75 * @param {string} str String to quote
76 * @returns {string} Escaped string
77 */
78const quoteMeta = str => str.replace(METACHARACTERS_REGEXP, "\\$&");
79
80/**
81 * Creating {@link SourceMapTask} for given file
82 * @param {string} file current compiled file
83 * @param {Source} asset the asset
84 * @param {AssetInfo} assetInfo the asset info
85 * @param {MapOptions} options source map options
86 * @param {Compilation} compilation compilation instance
87 * @param {ItemCacheFacade} cacheItem cache item
88 * @returns {SourceMapTask | undefined} created task instance or `undefined`
89 */
90const getTaskForFile = (
91 file,
92 asset,
93 assetInfo,
94 options,
95 compilation,
96 cacheItem
97) => {
98 let source;
99 /** @type {SourceMap} */
100 let sourceMap;
101 /**
102 * Check if asset can build source map
103 */
104 if (asset.sourceAndMap) {
105 const sourceAndMap = asset.sourceAndMap(options);
106 sourceMap = /** @type {SourceMap} */ (sourceAndMap.map);
107 source = sourceAndMap.source;
108 } else {
109 sourceMap = /** @type {SourceMap} */ (asset.map(options));
110 source = asset.source();
111 }
112 if (!sourceMap || typeof source !== "string") return;
113 const context = /** @type {string} */ (compilation.options.context);
114 const root = compilation.compiler.root;
115 const cachedAbsolutify = makePathsAbsolute.bindContextCache(context, root);
116 const modules = sourceMap.sources.map(source => {
117 if (!source.startsWith("webpack://")) return source;
118 source = cachedAbsolutify(source.slice(10));
119 const module = compilation.findModule(source);
120 return module || source;
121 });
122
123 return {
124 file,
125 asset,
126 source,
127 assetInfo,
128 sourceMap,
129 modules,
130 cacheItem
131 };
132};
133
134class SourceMapDevToolPlugin {
135 /**
136 * @param {SourceMapDevToolPluginOptions} [options] options object
137 * @throws {Error} throws error, if got more than 1 arguments
138 */
139 constructor(options = {}) {
140 validate(options);
141
142 this.sourceMapFilename = /** @type {string | false} */ (options.filename);
143 /** @type {false | TemplatePath}} */
144 this.sourceMappingURLComment =
145 options.append === false
146 ? false
147 : // eslint-disable-next-line no-useless-concat
148 options.append || "\n//# source" + "MappingURL=[url]";
149 /** @type {string | Function} */
150 this.moduleFilenameTemplate =
151 options.moduleFilenameTemplate || "webpack://[namespace]/[resourcePath]";
152 /** @type {string | Function} */
153 this.fallbackModuleFilenameTemplate =
154 options.fallbackModuleFilenameTemplate ||
155 "webpack://[namespace]/[resourcePath]?[hash]";
156 /** @type {string} */
157 this.namespace = options.namespace || "";
158 /** @type {SourceMapDevToolPluginOptions} */
159 this.options = options;
160 }
161
162 /**
163 * Apply the plugin
164 * @param {Compiler} compiler compiler instance
165 * @returns {void}
166 */
167 apply(compiler) {
168 const outputFs = /** @type {OutputFileSystem} */ (
169 compiler.outputFileSystem
170 );
171 const sourceMapFilename = this.sourceMapFilename;
172 const sourceMappingURLComment = this.sourceMappingURLComment;
173 const moduleFilenameTemplate = this.moduleFilenameTemplate;
174 const namespace = this.namespace;
175 const fallbackModuleFilenameTemplate = this.fallbackModuleFilenameTemplate;
176 const requestShortener = compiler.requestShortener;
177 const options = this.options;
178 options.test = options.test || CSS_AND_JS_MODULE_EXTENSIONS_REGEXP;
179
180 const matchObject = ModuleFilenameHelpers.matchObject.bind(
181 undefined,
182 options
183 );
184
185 compiler.hooks.compilation.tap("SourceMapDevToolPlugin", compilation => {
186 new SourceMapDevToolModuleOptionsPlugin(options).apply(compilation);
187
188 compilation.hooks.processAssets.tapAsync(
189 {
190 name: "SourceMapDevToolPlugin",
191 stage: Compilation.PROCESS_ASSETS_STAGE_DEV_TOOLING,
192 additionalAssets: true
193 },
194 (assets, callback) => {
195 const chunkGraph = compilation.chunkGraph;
196 const cache = compilation.getCache("SourceMapDevToolPlugin");
197 /** @type {Map<string | Module, string>} */
198 const moduleToSourceNameMapping = new Map();
199 /**
200 * @type {Function}
201 * @returns {void}
202 */
203 const reportProgress =
204 ProgressPlugin.getReporter(compilation.compiler) || (() => {});
205
206 /** @type {Map<string, Chunk>} */
207 const fileToChunk = new Map();
208 for (const chunk of compilation.chunks) {
209 for (const file of chunk.files) {
210 fileToChunk.set(file, chunk);
211 }
212 for (const file of chunk.auxiliaryFiles) {
213 fileToChunk.set(file, chunk);
214 }
215 }
216
217 /** @type {string[]} */
218 const files = [];
219 for (const file of Object.keys(assets)) {
220 if (matchObject(file)) {
221 files.push(file);
222 }
223 }
224
225 reportProgress(0);
226 /** @type {SourceMapTask[]} */
227 const tasks = [];
228 let fileIndex = 0;
229
230 asyncLib.each(
231 files,
232 (file, callback) => {
233 const asset =
234 /** @type {Readonly<Asset>} */
235 (compilation.getAsset(file));
236 if (asset.info.related && asset.info.related.sourceMap) {
237 fileIndex++;
238 return callback();
239 }
240
241 const chunk = fileToChunk.get(file);
242 const sourceMapNamespace = compilation.getPath(this.namespace, {
243 chunk
244 });
245
246 const cacheItem = cache.getItemCache(
247 file,
248 cache.mergeEtags(
249 cache.getLazyHashedEtag(asset.source),
250 sourceMapNamespace
251 )
252 );
253
254 cacheItem.get((err, cacheEntry) => {
255 if (err) {
256 return callback(err);
257 }
258 /**
259 * If presented in cache, reassigns assets. Cache assets already have source maps.
260 */
261 if (cacheEntry) {
262 const { assets, assetsInfo } = cacheEntry;
263 for (const cachedFile of Object.keys(assets)) {
264 if (cachedFile === file) {
265 compilation.updateAsset(
266 cachedFile,
267 assets[cachedFile],
268 assetsInfo[cachedFile]
269 );
270 } else {
271 compilation.emitAsset(
272 cachedFile,
273 assets[cachedFile],
274 assetsInfo[cachedFile]
275 );
276 }
277 /**
278 * Add file to chunk, if not presented there
279 */
280 if (cachedFile !== file && chunk !== undefined)
281 chunk.auxiliaryFiles.add(cachedFile);
282 }
283
284 reportProgress(
285 (0.5 * ++fileIndex) / files.length,
286 file,
287 "restored cached SourceMap"
288 );
289
290 return callback();
291 }
292
293 reportProgress(
294 (0.5 * fileIndex) / files.length,
295 file,
296 "generate SourceMap"
297 );
298
299 /** @type {SourceMapTask | undefined} */
300 const task = getTaskForFile(
301 file,
302 asset.source,
303 asset.info,
304 {
305 module: options.module,
306 columns: options.columns
307 },
308 compilation,
309 cacheItem
310 );
311
312 if (task) {
313 const modules = task.modules;
314
315 for (let idx = 0; idx < modules.length; idx++) {
316 const module = modules[idx];
317
318 if (
319 typeof module === "string" &&
320 /^(data|https?):/.test(module)
321 ) {
322 moduleToSourceNameMapping.set(module, module);
323 continue;
324 }
325
326 if (!moduleToSourceNameMapping.get(module)) {
327 moduleToSourceNameMapping.set(
328 module,
329 ModuleFilenameHelpers.createFilename(
330 module,
331 {
332 moduleFilenameTemplate,
333 namespace: sourceMapNamespace
334 },
335 {
336 requestShortener,
337 chunkGraph,
338 hashFunction: compilation.outputOptions.hashFunction
339 }
340 )
341 );
342 }
343 }
344
345 tasks.push(task);
346 }
347
348 reportProgress(
349 (0.5 * ++fileIndex) / files.length,
350 file,
351 "generated SourceMap"
352 );
353
354 callback();
355 });
356 },
357 err => {
358 if (err) {
359 return callback(err);
360 }
361
362 reportProgress(0.5, "resolve sources");
363 /** @type {Set<string>} */
364 const usedNamesSet = new Set(moduleToSourceNameMapping.values());
365 /** @type {Set<string>} */
366 const conflictDetectionSet = new Set();
367
368 /**
369 * all modules in defined order (longest identifier first)
370 * @type {Array<string | Module>}
371 */
372 const allModules = Array.from(
373 moduleToSourceNameMapping.keys()
374 ).sort((a, b) => {
375 const ai = typeof a === "string" ? a : a.identifier();
376 const bi = typeof b === "string" ? b : b.identifier();
377 return ai.length - bi.length;
378 });
379
380 // find modules with conflicting source names
381 for (let idx = 0; idx < allModules.length; idx++) {
382 const module = allModules[idx];
383 let sourceName =
384 /** @type {string} */
385 (moduleToSourceNameMapping.get(module));
386 let hasName = conflictDetectionSet.has(sourceName);
387 if (!hasName) {
388 conflictDetectionSet.add(sourceName);
389 continue;
390 }
391
392 // try the fallback name first
393 sourceName = ModuleFilenameHelpers.createFilename(
394 module,
395 {
396 moduleFilenameTemplate: fallbackModuleFilenameTemplate,
397 namespace
398 },
399 {
400 requestShortener,
401 chunkGraph,
402 hashFunction: compilation.outputOptions.hashFunction
403 }
404 );
405 hasName = usedNamesSet.has(sourceName);
406 if (!hasName) {
407 moduleToSourceNameMapping.set(module, sourceName);
408 usedNamesSet.add(sourceName);
409 continue;
410 }
411
412 // otherwise just append stars until we have a valid name
413 while (hasName) {
414 sourceName += "*";
415 hasName = usedNamesSet.has(sourceName);
416 }
417 moduleToSourceNameMapping.set(module, sourceName);
418 usedNamesSet.add(sourceName);
419 }
420
421 let taskIndex = 0;
422
423 asyncLib.each(
424 tasks,
425 (task, callback) => {
426 const assets = Object.create(null);
427 const assetsInfo = Object.create(null);
428 const file = task.file;
429 const chunk = fileToChunk.get(file);
430 const sourceMap = task.sourceMap;
431 const source = task.source;
432 const modules = task.modules;
433
434 reportProgress(
435 0.5 + (0.5 * taskIndex) / tasks.length,
436 file,
437 "attach SourceMap"
438 );
439
440 const moduleFilenames = modules.map(m =>
441 moduleToSourceNameMapping.get(m)
442 );
443 sourceMap.sources = /** @type {string[]} */ (moduleFilenames);
444 if (options.noSources) {
445 sourceMap.sourcesContent = undefined;
446 }
447 sourceMap.sourceRoot = options.sourceRoot || "";
448 sourceMap.file = file;
449 const usesContentHash =
450 sourceMapFilename &&
451 CONTENT_HASH_DETECT_REGEXP.test(sourceMapFilename);
452
453 resetRegexpState(CONTENT_HASH_DETECT_REGEXP);
454
455 // If SourceMap and asset uses contenthash, avoid a circular dependency by hiding hash in `file`
456 if (usesContentHash && task.assetInfo.contenthash) {
457 const contenthash = task.assetInfo.contenthash;
458 const pattern = Array.isArray(contenthash)
459 ? contenthash.map(quoteMeta).join("|")
460 : quoteMeta(contenthash);
461 sourceMap.file = sourceMap.file.replace(
462 new RegExp(pattern, "g"),
463 m => "x".repeat(m.length)
464 );
465 }
466
467 /** @type {false | TemplatePath} */
468 let currentSourceMappingURLComment = sourceMappingURLComment;
469 const cssExtensionDetected =
470 CSS_EXTENSION_DETECT_REGEXP.test(file);
471 resetRegexpState(CSS_EXTENSION_DETECT_REGEXP);
472 if (
473 currentSourceMappingURLComment !== false &&
474 typeof currentSourceMappingURLComment !== "function" &&
475 cssExtensionDetected
476 ) {
477 currentSourceMappingURLComment =
478 currentSourceMappingURLComment.replace(
479 URL_FORMATTING_REGEXP,
480 "\n/*$1*/"
481 );
482 }
483
484 if (options.debugIds) {
485 const debugId = generateDebugId(source, sourceMap.file);
486 sourceMap.debugId = debugId;
487 currentSourceMappingURLComment = `\n//# debugId=${debugId}${currentSourceMappingURLComment}`;
488 }
489
490 const sourceMapString = JSON.stringify(sourceMap);
491 if (sourceMapFilename) {
492 const filename = file;
493 const sourceMapContentHash =
494 /** @type {string} */
495 (
496 usesContentHash &&
497 createHash(
498 /** @type {Algorithm} */
499 (compilation.outputOptions.hashFunction)
500 )
501 .update(sourceMapString)
502 .digest("hex")
503 );
504 const pathParams = {
505 chunk,
506 filename: options.fileContext
507 ? relative(
508 outputFs,
509 `/${options.fileContext}`,
510 `/${filename}`
511 )
512 : filename,
513 contentHash: sourceMapContentHash
514 };
515 const { path: sourceMapFile, info: sourceMapInfo } =
516 compilation.getPathWithInfo(
517 sourceMapFilename,
518 pathParams
519 );
520 const sourceMapUrl = options.publicPath
521 ? options.publicPath + sourceMapFile
522 : relative(
523 outputFs,
524 dirname(outputFs, `/${file}`),
525 `/${sourceMapFile}`
526 );
527 /** @type {Source} */
528 let asset = new RawSource(source);
529 if (currentSourceMappingURLComment !== false) {
530 // Add source map url to compilation asset, if currentSourceMappingURLComment is set
531 asset = new ConcatSource(
532 asset,
533 compilation.getPath(currentSourceMappingURLComment, {
534 url: sourceMapUrl,
535 ...pathParams
536 })
537 );
538 }
539 const assetInfo = {
540 related: { sourceMap: sourceMapFile }
541 };
542 assets[file] = asset;
543 assetsInfo[file] = assetInfo;
544 compilation.updateAsset(file, asset, assetInfo);
545 // Add source map file to compilation assets and chunk files
546 const sourceMapAsset = new RawSource(sourceMapString);
547 const sourceMapAssetInfo = {
548 ...sourceMapInfo,
549 development: true
550 };
551 assets[sourceMapFile] = sourceMapAsset;
552 assetsInfo[sourceMapFile] = sourceMapAssetInfo;
553 compilation.emitAsset(
554 sourceMapFile,
555 sourceMapAsset,
556 sourceMapAssetInfo
557 );
558 if (chunk !== undefined)
559 chunk.auxiliaryFiles.add(sourceMapFile);
560 } else {
561 if (currentSourceMappingURLComment === false) {
562 throw new Error(
563 "SourceMapDevToolPlugin: append can't be false when no filename is provided"
564 );
565 }
566 if (typeof currentSourceMappingURLComment === "function") {
567 throw new Error(
568 "SourceMapDevToolPlugin: append can't be a function when no filename is provided"
569 );
570 }
571 /**
572 * Add source map as data url to asset
573 */
574 const asset = new ConcatSource(
575 new RawSource(source),
576 currentSourceMappingURLComment
577 .replace(MAP_URL_COMMENT_REGEXP, () => sourceMapString)
578 .replace(
579 URL_COMMENT_REGEXP,
580 () =>
581 `data:application/json;charset=utf-8;base64,${Buffer.from(
582 sourceMapString,
583 "utf-8"
584 ).toString("base64")}`
585 )
586 );
587 assets[file] = asset;
588 assetsInfo[file] = undefined;
589 compilation.updateAsset(file, asset);
590 }
591
592 task.cacheItem.store({ assets, assetsInfo }, err => {
593 reportProgress(
594 0.5 + (0.5 * ++taskIndex) / tasks.length,
595 task.file,
596 "attached SourceMap"
597 );
598
599 if (err) {
600 return callback(err);
601 }
602 callback();
603 });
604 },
605 err => {
606 reportProgress(1);
607 callback(err);
608 }
609 );
610 }
611 );
612 }
613 );
614 });
615 }
616}
617
618module.exports = SourceMapDevToolPlugin;
Note: See TracBrowser for help on using the repository browser.