source: imaps-frontend/node_modules/webpack/lib/optimize/RealContentHashPlugin.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: 13.9 KB
Line 
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const { SyncBailHook } = require("tapable");
9const { RawSource, CachedSource, CompatSource } = require("webpack-sources");
10const Compilation = require("../Compilation");
11const WebpackError = require("../WebpackError");
12const { compareSelect, compareStrings } = require("../util/comparators");
13const createHash = require("../util/createHash");
14
15/** @typedef {import("webpack-sources").Source} Source */
16/** @typedef {import("../Cache").Etag} Etag */
17/** @typedef {import("../Compilation").AssetInfo} AssetInfo */
18/** @typedef {import("../Compiler")} Compiler */
19/** @typedef {typeof import("../util/Hash")} Hash */
20
21const EMPTY_SET = new Set();
22
23/**
24 * @template T
25 * @param {T | T[]} itemOrItems item or items
26 * @param {Set<T>} list list
27 */
28const addToList = (itemOrItems, list) => {
29 if (Array.isArray(itemOrItems)) {
30 for (const item of itemOrItems) {
31 list.add(item);
32 }
33 } else if (itemOrItems) {
34 list.add(itemOrItems);
35 }
36};
37
38/**
39 * @template T
40 * @param {T[]} input list
41 * @param {function(T): Buffer} fn map function
42 * @returns {Buffer[]} buffers without duplicates
43 */
44const mapAndDeduplicateBuffers = (input, fn) => {
45 // Buffer.equals compares size first so this should be efficient enough
46 // If it becomes a performance problem we can use a map and group by size
47 // instead of looping over all assets.
48 const result = [];
49 outer: for (const value of input) {
50 const buf = fn(value);
51 for (const other of result) {
52 if (buf.equals(other)) continue outer;
53 }
54 result.push(buf);
55 }
56 return result;
57};
58
59/**
60 * Escapes regular expression metacharacters
61 * @param {string} str String to quote
62 * @returns {string} Escaped string
63 */
64const quoteMeta = str => str.replace(/[-[\]\\/{}()*+?.^$|]/g, "\\$&");
65
66const cachedSourceMap = new WeakMap();
67
68/**
69 * @param {Source} source source
70 * @returns {CachedSource} cached source
71 */
72const toCachedSource = source => {
73 if (source instanceof CachedSource) {
74 return source;
75 }
76 const entry = cachedSourceMap.get(source);
77 if (entry !== undefined) return entry;
78 const newSource = new CachedSource(CompatSource.from(source));
79 cachedSourceMap.set(source, newSource);
80 return newSource;
81};
82
83/** @typedef {Set<string>} OwnHashes */
84/** @typedef {Set<string>} ReferencedHashes */
85/** @typedef {Set<string>} Hashes */
86
87/**
88 * @typedef {object} AssetInfoForRealContentHash
89 * @property {string} name
90 * @property {AssetInfo} info
91 * @property {Source} source
92 * @property {RawSource | undefined} newSource
93 * @property {RawSource | undefined} newSourceWithoutOwn
94 * @property {string} content
95 * @property {OwnHashes | undefined} ownHashes
96 * @property {Promise<void> | undefined} contentComputePromise
97 * @property {Promise<void> | undefined} contentComputeWithoutOwnPromise
98 * @property {ReferencedHashes | undefined} referencedHashes
99 * @property {Hashes} hashes
100 */
101
102/**
103 * @typedef {object} CompilationHooks
104 * @property {SyncBailHook<[Buffer[], string], string | void>} updateHash
105 */
106
107/** @type {WeakMap<Compilation, CompilationHooks>} */
108const compilationHooksMap = new WeakMap();
109
110class RealContentHashPlugin {
111 /**
112 * @param {Compilation} compilation the compilation
113 * @returns {CompilationHooks} the attached hooks
114 */
115 static getCompilationHooks(compilation) {
116 if (!(compilation instanceof Compilation)) {
117 throw new TypeError(
118 "The 'compilation' argument must be an instance of Compilation"
119 );
120 }
121 let hooks = compilationHooksMap.get(compilation);
122 if (hooks === undefined) {
123 hooks = {
124 updateHash: new SyncBailHook(["content", "oldHash"])
125 };
126 compilationHooksMap.set(compilation, hooks);
127 }
128 return hooks;
129 }
130
131 /**
132 * @param {object} options options object
133 * @param {string | Hash} options.hashFunction the hash function to use
134 * @param {string} options.hashDigest the hash digest to use
135 */
136 constructor({ hashFunction, hashDigest }) {
137 this._hashFunction = hashFunction;
138 this._hashDigest = hashDigest;
139 }
140
141 /**
142 * Apply the plugin
143 * @param {Compiler} compiler the compiler instance
144 * @returns {void}
145 */
146 apply(compiler) {
147 compiler.hooks.compilation.tap("RealContentHashPlugin", compilation => {
148 const cacheAnalyse = compilation.getCache(
149 "RealContentHashPlugin|analyse"
150 );
151 const cacheGenerate = compilation.getCache(
152 "RealContentHashPlugin|generate"
153 );
154 const hooks = RealContentHashPlugin.getCompilationHooks(compilation);
155 compilation.hooks.processAssets.tapPromise(
156 {
157 name: "RealContentHashPlugin",
158 stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_HASH
159 },
160 async () => {
161 const assets = compilation.getAssets();
162 /** @type {AssetInfoForRealContentHash[]} */
163 const assetsWithInfo = [];
164 /** @type {Map<string, [AssetInfoForRealContentHash]>} */
165 const hashToAssets = new Map();
166 for (const { source, info, name } of assets) {
167 const cachedSource = toCachedSource(source);
168 const content = /** @type {string} */ (cachedSource.source());
169 /** @type {Hashes} */
170 const hashes = new Set();
171 addToList(info.contenthash, hashes);
172 /** @type {AssetInfoForRealContentHash} */
173 const data = {
174 name,
175 info,
176 source: cachedSource,
177 newSource: undefined,
178 newSourceWithoutOwn: undefined,
179 content,
180 ownHashes: undefined,
181 contentComputePromise: undefined,
182 contentComputeWithoutOwnPromise: undefined,
183 referencedHashes: undefined,
184 hashes
185 };
186 assetsWithInfo.push(data);
187 for (const hash of hashes) {
188 const list = hashToAssets.get(hash);
189 if (list === undefined) {
190 hashToAssets.set(hash, [data]);
191 } else {
192 list.push(data);
193 }
194 }
195 }
196 if (hashToAssets.size === 0) return;
197 const hashRegExp = new RegExp(
198 Array.from(hashToAssets.keys(), quoteMeta).join("|"),
199 "g"
200 );
201 await Promise.all(
202 assetsWithInfo.map(async asset => {
203 const { name, source, content, hashes } = asset;
204 if (Buffer.isBuffer(content)) {
205 asset.referencedHashes = EMPTY_SET;
206 asset.ownHashes = EMPTY_SET;
207 return;
208 }
209 const etag = cacheAnalyse.mergeEtags(
210 cacheAnalyse.getLazyHashedEtag(source),
211 Array.from(hashes).join("|")
212 );
213 [asset.referencedHashes, asset.ownHashes] =
214 await cacheAnalyse.providePromise(name, etag, () => {
215 const referencedHashes = new Set();
216 const ownHashes = new Set();
217 const inContent = content.match(hashRegExp);
218 if (inContent) {
219 for (const hash of inContent) {
220 if (hashes.has(hash)) {
221 ownHashes.add(hash);
222 continue;
223 }
224 referencedHashes.add(hash);
225 }
226 }
227 return [referencedHashes, ownHashes];
228 });
229 })
230 );
231 /**
232 * @param {string} hash the hash
233 * @returns {undefined | ReferencedHashes} the referenced hashes
234 */
235 const getDependencies = hash => {
236 const assets = hashToAssets.get(hash);
237 if (!assets) {
238 const referencingAssets = assetsWithInfo.filter(asset =>
239 /** @type {ReferencedHashes} */ (asset.referencedHashes).has(
240 hash
241 )
242 );
243 const err = new WebpackError(`RealContentHashPlugin
244Some kind of unexpected caching problem occurred.
245An asset was cached with a reference to another asset (${hash}) that's not in the compilation anymore.
246Either the asset was incorrectly cached, or the referenced asset should also be restored from cache.
247Referenced by:
248${referencingAssets
249 .map(a => {
250 const match = new RegExp(`.{0,20}${quoteMeta(hash)}.{0,20}`).exec(
251 a.content
252 );
253 return ` - ${a.name}: ...${match ? match[0] : "???"}...`;
254 })
255 .join("\n")}`);
256 compilation.errors.push(err);
257 return;
258 }
259 const hashes = new Set();
260 for (const { referencedHashes, ownHashes } of assets) {
261 if (!(/** @type {OwnHashes} */ (ownHashes).has(hash))) {
262 for (const hash of /** @type {OwnHashes} */ (ownHashes)) {
263 hashes.add(hash);
264 }
265 }
266 for (const hash of /** @type {ReferencedHashes} */ (
267 referencedHashes
268 )) {
269 hashes.add(hash);
270 }
271 }
272 return hashes;
273 };
274 /**
275 * @param {string} hash the hash
276 * @returns {string} the hash info
277 */
278 const hashInfo = hash => {
279 const assets = hashToAssets.get(hash);
280 return `${hash} (${Array.from(
281 /** @type {AssetInfoForRealContentHash[]} */ (assets),
282 a => a.name
283 )})`;
284 };
285 const hashesInOrder = new Set();
286 for (const hash of hashToAssets.keys()) {
287 /**
288 * @param {string} hash the hash
289 * @param {Set<string>} stack stack of hashes
290 */
291 const add = (hash, stack) => {
292 const deps = getDependencies(hash);
293 if (!deps) return;
294 stack.add(hash);
295 for (const dep of deps) {
296 if (hashesInOrder.has(dep)) continue;
297 if (stack.has(dep)) {
298 throw new Error(
299 `Circular hash dependency ${Array.from(
300 stack,
301 hashInfo
302 ).join(" -> ")} -> ${hashInfo(dep)}`
303 );
304 }
305 add(dep, stack);
306 }
307 hashesInOrder.add(hash);
308 stack.delete(hash);
309 };
310 if (hashesInOrder.has(hash)) continue;
311 add(hash, new Set());
312 }
313 const hashToNewHash = new Map();
314 /**
315 * @param {AssetInfoForRealContentHash} asset asset info
316 * @returns {Etag} etag
317 */
318 const getEtag = asset =>
319 cacheGenerate.mergeEtags(
320 cacheGenerate.getLazyHashedEtag(asset.source),
321 Array.from(
322 /** @type {ReferencedHashes} */ (asset.referencedHashes),
323 hash => hashToNewHash.get(hash)
324 ).join("|")
325 );
326 /**
327 * @param {AssetInfoForRealContentHash} asset asset info
328 * @returns {Promise<void>}
329 */
330 const computeNewContent = asset => {
331 if (asset.contentComputePromise) return asset.contentComputePromise;
332 return (asset.contentComputePromise = (async () => {
333 if (
334 /** @type {OwnHashes} */ (asset.ownHashes).size > 0 ||
335 Array.from(
336 /** @type {ReferencedHashes} */
337 (asset.referencedHashes)
338 ).some(hash => hashToNewHash.get(hash) !== hash)
339 ) {
340 const identifier = asset.name;
341 const etag = getEtag(asset);
342 asset.newSource = await cacheGenerate.providePromise(
343 identifier,
344 etag,
345 () => {
346 const newContent = asset.content.replace(hashRegExp, hash =>
347 hashToNewHash.get(hash)
348 );
349 return new RawSource(newContent);
350 }
351 );
352 }
353 })());
354 };
355 /**
356 * @param {AssetInfoForRealContentHash} asset asset info
357 * @returns {Promise<void>}
358 */
359 const computeNewContentWithoutOwn = asset => {
360 if (asset.contentComputeWithoutOwnPromise)
361 return asset.contentComputeWithoutOwnPromise;
362 return (asset.contentComputeWithoutOwnPromise = (async () => {
363 if (
364 /** @type {OwnHashes} */ (asset.ownHashes).size > 0 ||
365 Array.from(
366 /** @type {ReferencedHashes} */
367 (asset.referencedHashes)
368 ).some(hash => hashToNewHash.get(hash) !== hash)
369 ) {
370 const identifier = `${asset.name}|without-own`;
371 const etag = getEtag(asset);
372 asset.newSourceWithoutOwn = await cacheGenerate.providePromise(
373 identifier,
374 etag,
375 () => {
376 const newContent = asset.content.replace(
377 hashRegExp,
378 hash => {
379 if (
380 /** @type {OwnHashes} */ (asset.ownHashes).has(hash)
381 ) {
382 return "";
383 }
384 return hashToNewHash.get(hash);
385 }
386 );
387 return new RawSource(newContent);
388 }
389 );
390 }
391 })());
392 };
393 const comparator = compareSelect(a => a.name, compareStrings);
394 for (const oldHash of hashesInOrder) {
395 const assets =
396 /** @type {AssetInfoForRealContentHash[]} */
397 (hashToAssets.get(oldHash));
398 assets.sort(comparator);
399 await Promise.all(
400 assets.map(asset =>
401 /** @type {OwnHashes} */ (asset.ownHashes).has(oldHash)
402 ? computeNewContentWithoutOwn(asset)
403 : computeNewContent(asset)
404 )
405 );
406 const assetsContent = mapAndDeduplicateBuffers(assets, asset => {
407 if (/** @type {OwnHashes} */ (asset.ownHashes).has(oldHash)) {
408 return asset.newSourceWithoutOwn
409 ? asset.newSourceWithoutOwn.buffer()
410 : asset.source.buffer();
411 }
412 return asset.newSource
413 ? asset.newSource.buffer()
414 : asset.source.buffer();
415 });
416 let newHash = hooks.updateHash.call(assetsContent, oldHash);
417 if (!newHash) {
418 const hash = createHash(this._hashFunction);
419 if (compilation.outputOptions.hashSalt) {
420 hash.update(compilation.outputOptions.hashSalt);
421 }
422 for (const content of assetsContent) {
423 hash.update(content);
424 }
425 const digest = hash.digest(this._hashDigest);
426 newHash = /** @type {string} */ (digest.slice(0, oldHash.length));
427 }
428 hashToNewHash.set(oldHash, newHash);
429 }
430 await Promise.all(
431 assetsWithInfo.map(async asset => {
432 await computeNewContent(asset);
433 const newName = asset.name.replace(hashRegExp, hash =>
434 hashToNewHash.get(hash)
435 );
436
437 const infoUpdate = {};
438 const hash = asset.info.contenthash;
439 infoUpdate.contenthash = Array.isArray(hash)
440 ? hash.map(hash => hashToNewHash.get(hash))
441 : hashToNewHash.get(hash);
442
443 if (asset.newSource !== undefined) {
444 compilation.updateAsset(
445 asset.name,
446 asset.newSource,
447 infoUpdate
448 );
449 } else {
450 compilation.updateAsset(asset.name, asset.source, infoUpdate);
451 }
452
453 if (asset.name !== newName) {
454 compilation.renameAsset(asset.name, newName);
455 }
456 })
457 );
458 }
459 );
460 });
461 }
462}
463
464module.exports = RealContentHashPlugin;
Note: See TracBrowser for help on using the repository browser.