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 | const basename = name => {
|
19 | if (!name.includes("/")) return name;
|
20 | return name.substr(name.lastIndexOf("/") + 1);
|
21 | };
|
22 |
|
23 | const assetsCache = new WeakMap();
|
24 |
|
25 | const getTaskForFile = (file, chunk, options, compilation) => {
|
26 | const asset = compilation.assets[file];
|
27 | const cache = assetsCache.get(asset);
|
28 | if (cache && cache.file === file) {
|
29 | for (const cachedFile in cache.assets) {
|
30 | compilation.assets[cachedFile] = cache.assets[cachedFile];
|
31 | if (cachedFile !== file) chunk.files.push(cachedFile);
|
32 | }
|
33 | return;
|
34 | }
|
35 | let source, sourceMap;
|
36 | if (asset.sourceAndMap) {
|
37 | const sourceAndMap = asset.sourceAndMap(options);
|
38 | sourceMap = sourceAndMap.map;
|
39 | source = sourceAndMap.source;
|
40 | } else {
|
41 | sourceMap = asset.map(options);
|
42 | source = asset.source();
|
43 | }
|
44 | if (sourceMap) {
|
45 | return {
|
46 | chunk,
|
47 | file,
|
48 | asset,
|
49 | source,
|
50 | sourceMap,
|
51 | modules: undefined
|
52 | };
|
53 | }
|
54 | };
|
55 |
|
56 | class SourceMapDevToolPlugin {
|
57 | |
58 |
|
59 |
|
60 | constructor(options) {
|
61 | if (arguments.length > 1) {
|
62 | throw new Error(
|
63 | "SourceMapDevToolPlugin only takes one argument (pass an options object)"
|
64 | );
|
65 | }
|
66 |
|
67 | if (!options) options = {};
|
68 |
|
69 | validateOptions(schema, options, "SourceMap DevTool Plugin");
|
70 |
|
71 | this.sourceMapFilename = options.filename;
|
72 |
|
73 | this.sourceMappingURLComment =
|
74 | options.append === false
|
75 | ? false
|
76 | : options.append || "\n//# sourceMappingURL=[url]";
|
77 | this.moduleFilenameTemplate =
|
78 | options.moduleFilenameTemplate || "webpack://[namespace]/[resourcePath]";
|
79 | this.fallbackModuleFilenameTemplate =
|
80 | options.fallbackModuleFilenameTemplate ||
|
81 | "webpack://[namespace]/[resourcePath]?[hash]";
|
82 | this.namespace = options.namespace || "";
|
83 | this.options = options;
|
84 | }
|
85 |
|
86 | apply(compiler) {
|
87 | const sourceMapFilename = this.sourceMapFilename;
|
88 | const sourceMappingURLComment = this.sourceMappingURLComment;
|
89 | const moduleFilenameTemplate = this.moduleFilenameTemplate;
|
90 | const namespace = this.namespace;
|
91 | const fallbackModuleFilenameTemplate = this.fallbackModuleFilenameTemplate;
|
92 | const requestShortener = compiler.requestShortener;
|
93 | const options = this.options;
|
94 | options.test = options.test || /\.(m?js|css)($|\?)/i;
|
95 |
|
96 | const matchObject = ModuleFilenameHelpers.matchObject.bind(
|
97 | undefined,
|
98 | options
|
99 | );
|
100 |
|
101 | compiler.hooks.compilation.tap("SourceMapDevToolPlugin", compilation => {
|
102 | new SourceMapDevToolModuleOptionsPlugin(options).apply(compilation);
|
103 |
|
104 | compilation.hooks.afterOptimizeChunkAssets.tap(
|
105 | {
|
106 | name: "SourceMapDevToolPlugin",
|
107 | context: true
|
108 | },
|
109 | (context, chunks) => {
|
110 | const moduleToSourceNameMapping = new Map();
|
111 | const reportProgress =
|
112 | context && context.reportProgress
|
113 | ? context.reportProgress
|
114 | : () => {};
|
115 |
|
116 | const files = [];
|
117 | for (const chunk of chunks) {
|
118 | for (const file of chunk.files) {
|
119 | if (matchObject(file)) {
|
120 | files.push({
|
121 | file,
|
122 | chunk
|
123 | });
|
124 | }
|
125 | }
|
126 | }
|
127 |
|
128 | reportProgress(0.0);
|
129 | const tasks = [];
|
130 | files.forEach(({ file, chunk }, idx) => {
|
131 | reportProgress(
|
132 | (0.5 * idx) / files.length,
|
133 | file,
|
134 | "generate SourceMap"
|
135 | );
|
136 | const task = getTaskForFile(file, chunk, options, compilation);
|
137 |
|
138 | if (task) {
|
139 | const modules = task.sourceMap.sources.map(source => {
|
140 | const module = compilation.findModule(source);
|
141 | return module || source;
|
142 | });
|
143 |
|
144 | for (let idx = 0; idx < modules.length; idx++) {
|
145 | const module = modules[idx];
|
146 | if (!moduleToSourceNameMapping.get(module)) {
|
147 | moduleToSourceNameMapping.set(
|
148 | module,
|
149 | ModuleFilenameHelpers.createFilename(
|
150 | module,
|
151 | {
|
152 | moduleFilenameTemplate: moduleFilenameTemplate,
|
153 | namespace: namespace
|
154 | },
|
155 | requestShortener
|
156 | )
|
157 | );
|
158 | }
|
159 | }
|
160 |
|
161 | task.modules = modules;
|
162 |
|
163 | tasks.push(task);
|
164 | }
|
165 | });
|
166 |
|
167 | reportProgress(0.5, "resolve sources");
|
168 | const usedNamesSet = new Set(moduleToSourceNameMapping.values());
|
169 | const conflictDetectionSet = new Set();
|
170 |
|
171 |
|
172 | const allModules = Array.from(moduleToSourceNameMapping.keys()).sort(
|
173 | (a, b) => {
|
174 | const ai = typeof a === "string" ? a : a.identifier();
|
175 | const bi = typeof b === "string" ? b : b.identifier();
|
176 | return ai.length - bi.length;
|
177 | }
|
178 | );
|
179 |
|
180 |
|
181 | for (let idx = 0; idx < allModules.length; idx++) {
|
182 | const module = allModules[idx];
|
183 | let sourceName = moduleToSourceNameMapping.get(module);
|
184 | let hasName = conflictDetectionSet.has(sourceName);
|
185 | if (!hasName) {
|
186 | conflictDetectionSet.add(sourceName);
|
187 | continue;
|
188 | }
|
189 |
|
190 |
|
191 | sourceName = ModuleFilenameHelpers.createFilename(
|
192 | module,
|
193 | {
|
194 | moduleFilenameTemplate: fallbackModuleFilenameTemplate,
|
195 | namespace: namespace
|
196 | },
|
197 | requestShortener
|
198 | );
|
199 | hasName = usedNamesSet.has(sourceName);
|
200 | if (!hasName) {
|
201 | moduleToSourceNameMapping.set(module, sourceName);
|
202 | usedNamesSet.add(sourceName);
|
203 | continue;
|
204 | }
|
205 |
|
206 |
|
207 | while (hasName) {
|
208 | sourceName += "*";
|
209 | hasName = usedNamesSet.has(sourceName);
|
210 | }
|
211 | moduleToSourceNameMapping.set(module, sourceName);
|
212 | usedNamesSet.add(sourceName);
|
213 | }
|
214 | tasks.forEach((task, index) => {
|
215 | reportProgress(
|
216 | 0.5 + (0.5 * index) / tasks.length,
|
217 | task.file,
|
218 | "attach SourceMap"
|
219 | );
|
220 | const assets = Object.create(null);
|
221 | const chunk = task.chunk;
|
222 | const file = task.file;
|
223 | const asset = task.asset;
|
224 | const sourceMap = task.sourceMap;
|
225 | const source = task.source;
|
226 | const modules = task.modules;
|
227 | const moduleFilenames = modules.map(m =>
|
228 | moduleToSourceNameMapping.get(m)
|
229 | );
|
230 | sourceMap.sources = moduleFilenames;
|
231 | if (options.noSources) {
|
232 | sourceMap.sourcesContent = undefined;
|
233 | }
|
234 | sourceMap.sourceRoot = options.sourceRoot || "";
|
235 | sourceMap.file = file;
|
236 | assetsCache.set(asset, { file, assets });
|
237 |
|
238 | let currentSourceMappingURLComment = sourceMappingURLComment;
|
239 | if (
|
240 | currentSourceMappingURLComment !== false &&
|
241 | /\.css($|\?)/i.test(file)
|
242 | ) {
|
243 | currentSourceMappingURLComment = currentSourceMappingURLComment.replace(
|
244 | /^\n\/\/(.*)$/,
|
245 | "\n/*$1*/"
|
246 | );
|
247 | }
|
248 | const sourceMapString = JSON.stringify(sourceMap);
|
249 | if (sourceMapFilename) {
|
250 | let filename = file;
|
251 | let query = "";
|
252 | const idx = filename.indexOf("?");
|
253 | if (idx >= 0) {
|
254 | query = filename.substr(idx);
|
255 | filename = filename.substr(0, idx);
|
256 | }
|
257 | let sourceMapFile = compilation.getPath(sourceMapFilename, {
|
258 | chunk,
|
259 | filename: options.fileContext
|
260 | ? path.relative(options.fileContext, filename)
|
261 | : filename,
|
262 | query,
|
263 | basename: basename(filename),
|
264 | contentHash: createHash("md4")
|
265 | .update(sourceMapString)
|
266 | .digest("hex")
|
267 | });
|
268 | const sourceMapUrl = options.publicPath
|
269 | ? options.publicPath + sourceMapFile.replace(/\\/g, "/")
|
270 | : path
|
271 | .relative(path.dirname(file), sourceMapFile)
|
272 | .replace(/\\/g, "/");
|
273 | if (currentSourceMappingURLComment !== false) {
|
274 | assets[file] = compilation.assets[file] = new ConcatSource(
|
275 | new RawSource(source),
|
276 | currentSourceMappingURLComment.replace(
|
277 | /\[url\]/g,
|
278 | sourceMapUrl
|
279 | )
|
280 | );
|
281 | }
|
282 | assets[sourceMapFile] = compilation.assets[
|
283 | sourceMapFile
|
284 | ] = new RawSource(sourceMapString);
|
285 | chunk.files.push(sourceMapFile);
|
286 | } else {
|
287 | if (currentSourceMappingURLComment === false) {
|
288 | throw new Error(
|
289 | "SourceMapDevToolPlugin: append can't be false when no filename is provided"
|
290 | );
|
291 | }
|
292 | assets[file] = compilation.assets[file] = new ConcatSource(
|
293 | new RawSource(source),
|
294 | currentSourceMappingURLComment
|
295 | .replace(/\[map\]/g, () => sourceMapString)
|
296 | .replace(
|
297 | /\[url\]/g,
|
298 | () =>
|
299 | `data:application/json;charset=utf-8;base64,${Buffer.from(
|
300 | sourceMapString,
|
301 | "utf-8"
|
302 | ).toString("base64")}`
|
303 | )
|
304 | );
|
305 | }
|
306 | });
|
307 | reportProgress(1.0);
|
308 | }
|
309 | );
|
310 | });
|
311 | }
|
312 | }
|
313 |
|
314 | module.exports = SourceMapDevToolPlugin;
|