1 |
|
2 |
|
3 |
|
4 |
|
5 | "use strict";
|
6 |
|
7 | const path = require("path");
|
8 | const { ConcatSource, RawSource } = require("webpack-sources");
|
9 | const ModuleFilenameHelpers = require("./ModuleFilenameHelpers");
|
10 | const SourceMapDevToolModuleOptionsPlugin = require("./SourceMapDevToolModuleOptionsPlugin");
|
11 | const createHash = require("./util/createHash");
|
12 |
|
13 | const validateOptions = require("schema-utils");
|
14 | const schema = require("../schemas/plugins/SourceMapDevToolPlugin.json");
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 | const basename = name => {
|
40 | if (!name.includes("/")) return name;
|
41 | return name.substr(name.lastIndexOf("/") + 1);
|
42 | };
|
43 |
|
44 |
|
45 |
|
46 |
|
47 | const assetsCache = new WeakMap();
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 | const getTaskForFile = (file, asset, chunk, options, compilation) => {
|
59 | let source, sourceMap;
|
60 | |
61 |
|
62 |
|
63 | if (asset.sourceAndMap) {
|
64 | const sourceAndMap = asset.sourceAndMap(options);
|
65 | sourceMap = sourceAndMap.map;
|
66 | source = sourceAndMap.source;
|
67 | } else {
|
68 | sourceMap = asset.map(options);
|
69 | source = asset.source();
|
70 | }
|
71 | if (sourceMap) {
|
72 | return {
|
73 | chunk,
|
74 | file,
|
75 | asset,
|
76 | source,
|
77 | sourceMap,
|
78 | modules: undefined
|
79 | };
|
80 | }
|
81 | };
|
82 |
|
83 | class SourceMapDevToolPlugin {
|
84 | |
85 |
|
86 |
|
87 |
|
88 | constructor(options) {
|
89 | if (arguments.length > 1) {
|
90 | throw new Error(
|
91 | "SourceMapDevToolPlugin only takes one argument (pass an options object)"
|
92 | );
|
93 | }
|
94 |
|
95 | if (!options) options = {};
|
96 |
|
97 | validateOptions(schema, options, "SourceMap DevTool Plugin");
|
98 |
|
99 |
|
100 | this.sourceMapFilename = options.filename;
|
101 |
|
102 | this.sourceMappingURLComment =
|
103 | options.append === false
|
104 | ? false
|
105 | : options.append || "\n//# sourceMappingURL=[url]";
|
106 |
|
107 | this.moduleFilenameTemplate =
|
108 | options.moduleFilenameTemplate || "webpack://[namespace]/[resourcePath]";
|
109 |
|
110 | this.fallbackModuleFilenameTemplate =
|
111 | options.fallbackModuleFilenameTemplate ||
|
112 | "webpack://[namespace]/[resourcePath]?[hash]";
|
113 |
|
114 | this.namespace = options.namespace || "";
|
115 |
|
116 | this.options = options;
|
117 | }
|
118 |
|
119 | |
120 |
|
121 |
|
122 |
|
123 |
|
124 | apply(compiler) {
|
125 | const sourceMapFilename = this.sourceMapFilename;
|
126 | const sourceMappingURLComment = this.sourceMappingURLComment;
|
127 | const moduleFilenameTemplate = this.moduleFilenameTemplate;
|
128 | const namespace = this.namespace;
|
129 | const fallbackModuleFilenameTemplate = this.fallbackModuleFilenameTemplate;
|
130 | const requestShortener = compiler.requestShortener;
|
131 | const options = this.options;
|
132 | options.test = options.test || /\.(m?js|css)($|\?)/i;
|
133 |
|
134 | const matchObject = ModuleFilenameHelpers.matchObject.bind(
|
135 | undefined,
|
136 | options
|
137 | );
|
138 |
|
139 | compiler.hooks.compilation.tap("SourceMapDevToolPlugin", compilation => {
|
140 | new SourceMapDevToolModuleOptionsPlugin(options).apply(compilation);
|
141 |
|
142 | compilation.hooks.afterOptimizeChunkAssets.tap(
|
143 |
|
144 | ({ name: "SourceMapDevToolPlugin", context: true }),
|
145 | |
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 | (context, chunks) => {
|
152 |
|
153 | const moduleToSourceNameMapping = new Map();
|
154 | |
155 |
|
156 |
|
157 |
|
158 | const reportProgress =
|
159 | context && context.reportProgress
|
160 | ? context.reportProgress
|
161 | : () => {};
|
162 |
|
163 | const files = [];
|
164 | for (const chunk of chunks) {
|
165 | for (const file of chunk.files) {
|
166 | if (matchObject(file)) {
|
167 | files.push({
|
168 | file,
|
169 | chunk
|
170 | });
|
171 | }
|
172 | }
|
173 | }
|
174 |
|
175 | reportProgress(0.0);
|
176 | const tasks = [];
|
177 | files.forEach(({ file, chunk }, idx) => {
|
178 | const asset = compilation.getAsset(file).source;
|
179 | const cache = assetsCache.get(asset);
|
180 | |
181 |
|
182 |
|
183 | if (cache && cache.file === file) {
|
184 | for (const cachedFile in cache.assets) {
|
185 | if (cachedFile === file) {
|
186 | compilation.updateAsset(cachedFile, cache.assets[cachedFile]);
|
187 | } else {
|
188 | compilation.emitAsset(cachedFile, cache.assets[cachedFile], {
|
189 | development: true
|
190 | });
|
191 | }
|
192 | |
193 |
|
194 |
|
195 | if (cachedFile !== file) chunk.files.push(cachedFile);
|
196 | }
|
197 | return;
|
198 | }
|
199 |
|
200 | reportProgress(
|
201 | (0.5 * idx) / files.length,
|
202 | file,
|
203 | "generate SourceMap"
|
204 | );
|
205 |
|
206 | const task = getTaskForFile(
|
207 | file,
|
208 | asset,
|
209 | chunk,
|
210 | options,
|
211 | compilation
|
212 | );
|
213 |
|
214 | if (task) {
|
215 | const modules = task.sourceMap.sources.map(source => {
|
216 | const module = compilation.findModule(source);
|
217 | return module || source;
|
218 | });
|
219 |
|
220 | for (let idx = 0; idx < modules.length; idx++) {
|
221 | const module = modules[idx];
|
222 | if (!moduleToSourceNameMapping.get(module)) {
|
223 | moduleToSourceNameMapping.set(
|
224 | module,
|
225 | ModuleFilenameHelpers.createFilename(
|
226 | module,
|
227 | {
|
228 | moduleFilenameTemplate: moduleFilenameTemplate,
|
229 | namespace: namespace
|
230 | },
|
231 | requestShortener
|
232 | )
|
233 | );
|
234 | }
|
235 | }
|
236 |
|
237 | task.modules = modules;
|
238 |
|
239 | tasks.push(task);
|
240 | }
|
241 | });
|
242 |
|
243 | reportProgress(0.5, "resolve sources");
|
244 |
|
245 | const usedNamesSet = new Set(moduleToSourceNameMapping.values());
|
246 |
|
247 | const conflictDetectionSet = new Set();
|
248 |
|
249 | |
250 |
|
251 |
|
252 |
|
253 | const allModules = Array.from(moduleToSourceNameMapping.keys()).sort(
|
254 | (a, b) => {
|
255 | const ai = typeof a === "string" ? a : a.identifier();
|
256 | const bi = typeof b === "string" ? b : b.identifier();
|
257 | return ai.length - bi.length;
|
258 | }
|
259 | );
|
260 |
|
261 |
|
262 | for (let idx = 0; idx < allModules.length; idx++) {
|
263 | const module = allModules[idx];
|
264 | let sourceName = moduleToSourceNameMapping.get(module);
|
265 | let hasName = conflictDetectionSet.has(sourceName);
|
266 | if (!hasName) {
|
267 | conflictDetectionSet.add(sourceName);
|
268 | continue;
|
269 | }
|
270 |
|
271 |
|
272 | sourceName = ModuleFilenameHelpers.createFilename(
|
273 | module,
|
274 | {
|
275 | moduleFilenameTemplate: fallbackModuleFilenameTemplate,
|
276 | namespace: namespace
|
277 | },
|
278 | requestShortener
|
279 | );
|
280 | hasName = usedNamesSet.has(sourceName);
|
281 | if (!hasName) {
|
282 | moduleToSourceNameMapping.set(module, sourceName);
|
283 | usedNamesSet.add(sourceName);
|
284 | continue;
|
285 | }
|
286 |
|
287 |
|
288 | while (hasName) {
|
289 | sourceName += "*";
|
290 | hasName = usedNamesSet.has(sourceName);
|
291 | }
|
292 | moduleToSourceNameMapping.set(module, sourceName);
|
293 | usedNamesSet.add(sourceName);
|
294 | }
|
295 | tasks.forEach((task, index) => {
|
296 | reportProgress(
|
297 | 0.5 + (0.5 * index) / tasks.length,
|
298 | task.file,
|
299 | "attach SourceMap"
|
300 | );
|
301 | const assets = Object.create(null);
|
302 | const chunk = task.chunk;
|
303 | const file = task.file;
|
304 | const asset = task.asset;
|
305 | const sourceMap = task.sourceMap;
|
306 | const source = task.source;
|
307 | const modules = task.modules;
|
308 | const moduleFilenames = modules.map(m =>
|
309 | moduleToSourceNameMapping.get(m)
|
310 | );
|
311 | sourceMap.sources = moduleFilenames;
|
312 | if (options.noSources) {
|
313 | sourceMap.sourcesContent = undefined;
|
314 | }
|
315 | sourceMap.sourceRoot = options.sourceRoot || "";
|
316 | sourceMap.file = file;
|
317 | assetsCache.set(asset, { file, assets });
|
318 |
|
319 | let currentSourceMappingURLComment = sourceMappingURLComment;
|
320 | if (
|
321 | currentSourceMappingURLComment !== false &&
|
322 | /\.css($|\?)/i.test(file)
|
323 | ) {
|
324 | currentSourceMappingURLComment = currentSourceMappingURLComment.replace(
|
325 | /^\n\/\/(.*)$/,
|
326 | "\n/*$1*/"
|
327 | );
|
328 | }
|
329 | const sourceMapString = JSON.stringify(sourceMap);
|
330 | if (sourceMapFilename) {
|
331 | let filename = file;
|
332 | let query = "";
|
333 | const idx = filename.indexOf("?");
|
334 | if (idx >= 0) {
|
335 | query = filename.substr(idx);
|
336 | filename = filename.substr(0, idx);
|
337 | }
|
338 | const pathParams = {
|
339 | chunk,
|
340 | filename: options.fileContext
|
341 | ? path.relative(options.fileContext, filename)
|
342 | : filename,
|
343 | query,
|
344 | basename: basename(filename),
|
345 | contentHash: createHash("md4")
|
346 | .update(sourceMapString)
|
347 | .digest("hex")
|
348 | };
|
349 | let sourceMapFile = compilation.getPath(
|
350 | sourceMapFilename,
|
351 | pathParams
|
352 | );
|
353 | const sourceMapUrl = options.publicPath
|
354 | ? options.publicPath + sourceMapFile.replace(/\\/g, "/")
|
355 | : path
|
356 | .relative(path.dirname(file), sourceMapFile)
|
357 | .replace(/\\/g, "/");
|
358 | |
359 |
|
360 |
|
361 | if (currentSourceMappingURLComment !== false) {
|
362 | const asset = new ConcatSource(
|
363 | new RawSource(source),
|
364 | compilation.getPath(
|
365 | currentSourceMappingURLComment,
|
366 | Object.assign({ url: sourceMapUrl }, pathParams)
|
367 | )
|
368 | );
|
369 | assets[file] = asset;
|
370 | compilation.updateAsset(file, asset);
|
371 | }
|
372 | |
373 |
|
374 |
|
375 | const asset = new RawSource(sourceMapString);
|
376 | assets[sourceMapFile] = asset;
|
377 | compilation.emitAsset(sourceMapFile, asset, {
|
378 | development: true
|
379 | });
|
380 | chunk.files.push(sourceMapFile);
|
381 | } else {
|
382 | if (currentSourceMappingURLComment === false) {
|
383 | throw new Error(
|
384 | "SourceMapDevToolPlugin: append can't be false when no filename is provided"
|
385 | );
|
386 | }
|
387 | |
388 |
|
389 |
|
390 | const asset = new ConcatSource(
|
391 | new RawSource(source),
|
392 | currentSourceMappingURLComment
|
393 | .replace(/\[map\]/g, () => sourceMapString)
|
394 | .replace(
|
395 | /\[url\]/g,
|
396 | () =>
|
397 | `data:application/json;charset=utf-8;base64,${Buffer.from(
|
398 | sourceMapString,
|
399 | "utf-8"
|
400 | ).toString("base64")}`
|
401 | )
|
402 | );
|
403 | assets[file] = asset;
|
404 | compilation.updateAsset(file, asset);
|
405 | }
|
406 | });
|
407 | reportProgress(1.0);
|
408 | }
|
409 | );
|
410 | });
|
411 | }
|
412 | }
|
413 |
|
414 | module.exports = SourceMapDevToolPlugin;
|