| 
									
										
										
										
											2021-04-01 22:59:00 +08:00
										 |  |  | /* | 
					
						
							|  |  |  | 	MIT License http://www.opensource.org/licenses/mit-license.php
 | 
					
						
							|  |  |  | 	Author Tobias Koppers @sokra | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | "use strict"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const Cache = require("../Cache"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** @typedef {import("webpack-sources").Source} Source */ | 
					
						
							|  |  |  | /** @typedef {import("../Cache").Etag} Etag */ | 
					
						
							|  |  |  | /** @typedef {import("../Compiler")} Compiler */ | 
					
						
							|  |  |  | /** @typedef {import("../Module")} Module */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class MemoryWithGcCachePlugin { | 
					
						
							| 
									
										
										
										
											2024-03-18 01:15:44 +08:00
										 |  |  | 	/** | 
					
						
							| 
									
										
										
										
											2024-06-11 21:09:50 +08:00
										 |  |  | 	 * @param {object} options Options | 
					
						
							| 
									
										
										
										
											2024-03-18 01:15:44 +08:00
										 |  |  | 	 * @param {number} options.maxGenerations max generations | 
					
						
							|  |  |  | 	 */ | 
					
						
							| 
									
										
										
										
											2021-04-01 22:59:00 +08:00
										 |  |  | 	constructor({ maxGenerations }) { | 
					
						
							|  |  |  | 		this._maxGenerations = maxGenerations; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-07-31 12:23:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-01 22:59:00 +08:00
										 |  |  | 	/** | 
					
						
							|  |  |  | 	 * Apply the plugin | 
					
						
							|  |  |  | 	 * @param {Compiler} compiler the compiler instance | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	apply(compiler) { | 
					
						
							|  |  |  | 		const maxGenerations = this._maxGenerations; | 
					
						
							| 
									
										
										
										
											2024-03-18 01:15:44 +08:00
										 |  |  | 		/** @type {Map<string, { etag: Etag | null, data: any } | undefined | null>} */ | 
					
						
							| 
									
										
										
										
											2021-04-01 22:59:00 +08:00
										 |  |  | 		const cache = new Map(); | 
					
						
							| 
									
										
										
										
											2024-03-18 01:15:44 +08:00
										 |  |  | 		/** @type {Map<string, { entry: { etag: Etag | null, data: any } | null, until: number }>} */ | 
					
						
							| 
									
										
										
										
											2021-04-01 22:59:00 +08:00
										 |  |  | 		const oldCache = new Map(); | 
					
						
							|  |  |  | 		let generation = 0; | 
					
						
							|  |  |  | 		let cachePosition = 0; | 
					
						
							|  |  |  | 		const logger = compiler.getInfrastructureLogger("MemoryWithGcCachePlugin"); | 
					
						
							|  |  |  | 		compiler.hooks.afterDone.tap("MemoryWithGcCachePlugin", () => { | 
					
						
							|  |  |  | 			generation++; | 
					
						
							|  |  |  | 			let clearedEntries = 0; | 
					
						
							|  |  |  | 			let lastClearedIdentifier; | 
					
						
							| 
									
										
										
										
											2023-05-01 04:06:02 +08:00
										 |  |  | 			// Avoid coverage problems due indirect changes
 | 
					
						
							|  |  |  | 			/* istanbul ignore next */ | 
					
						
							| 
									
										
										
										
											2021-04-01 22:59:00 +08:00
										 |  |  | 			for (const [identifier, entry] of oldCache) { | 
					
						
							|  |  |  | 				if (entry.until > generation) break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				oldCache.delete(identifier); | 
					
						
							|  |  |  | 				if (cache.get(identifier) === undefined) { | 
					
						
							|  |  |  | 					cache.delete(identifier); | 
					
						
							|  |  |  | 					clearedEntries++; | 
					
						
							|  |  |  | 					lastClearedIdentifier = identifier; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if (clearedEntries > 0 || oldCache.size > 0) { | 
					
						
							|  |  |  | 				logger.log( | 
					
						
							|  |  |  | 					`${cache.size - oldCache.size} active entries, ${ | 
					
						
							|  |  |  | 						oldCache.size | 
					
						
							|  |  |  | 					} recently unused cached entries${ | 
					
						
							|  |  |  | 						clearedEntries > 0 | 
					
						
							|  |  |  | 							? `, ${clearedEntries} old unused cache entries removed e. g. ${lastClearedIdentifier}` | 
					
						
							|  |  |  | 							: "" | 
					
						
							|  |  |  | 					}`
 | 
					
						
							|  |  |  | 				); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			let i = (cache.size / maxGenerations) | 0; | 
					
						
							|  |  |  | 			let j = cachePosition >= cache.size ? 0 : cachePosition; | 
					
						
							|  |  |  | 			cachePosition = j + i; | 
					
						
							|  |  |  | 			for (const [identifier, entry] of cache) { | 
					
						
							|  |  |  | 				if (j !== 0) { | 
					
						
							|  |  |  | 					j--; | 
					
						
							|  |  |  | 					continue; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				if (entry !== undefined) { | 
					
						
							|  |  |  | 					// We don't delete the cache entry, but set it to undefined instead
 | 
					
						
							|  |  |  | 					// This reserves the location in the data table and avoids rehashing
 | 
					
						
							|  |  |  | 					// when constantly adding and removing entries.
 | 
					
						
							|  |  |  | 					// It will be deleted when removed from oldCache.
 | 
					
						
							|  |  |  | 					cache.set(identifier, undefined); | 
					
						
							|  |  |  | 					oldCache.delete(identifier); | 
					
						
							|  |  |  | 					oldCache.set(identifier, { | 
					
						
							|  |  |  | 						entry, | 
					
						
							|  |  |  | 						until: generation + maxGenerations | 
					
						
							|  |  |  | 					}); | 
					
						
							|  |  |  | 					if (i-- === 0) break; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 		compiler.cache.hooks.store.tap( | 
					
						
							|  |  |  | 			{ name: "MemoryWithGcCachePlugin", stage: Cache.STAGE_MEMORY }, | 
					
						
							|  |  |  | 			(identifier, etag, data) => { | 
					
						
							|  |  |  | 				cache.set(identifier, { etag, data }); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 		compiler.cache.hooks.get.tap( | 
					
						
							|  |  |  | 			{ name: "MemoryWithGcCachePlugin", stage: Cache.STAGE_MEMORY }, | 
					
						
							|  |  |  | 			(identifier, etag, gotHandlers) => { | 
					
						
							|  |  |  | 				const cacheEntry = cache.get(identifier); | 
					
						
							|  |  |  | 				if (cacheEntry === null) { | 
					
						
							|  |  |  | 					return null; | 
					
						
							|  |  |  | 				} else if (cacheEntry !== undefined) { | 
					
						
							|  |  |  | 					return cacheEntry.etag === etag ? cacheEntry.data : null; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				const oldCacheEntry = oldCache.get(identifier); | 
					
						
							|  |  |  | 				if (oldCacheEntry !== undefined) { | 
					
						
							|  |  |  | 					const cacheEntry = oldCacheEntry.entry; | 
					
						
							|  |  |  | 					if (cacheEntry === null) { | 
					
						
							|  |  |  | 						oldCache.delete(identifier); | 
					
						
							|  |  |  | 						cache.set(identifier, cacheEntry); | 
					
						
							|  |  |  | 						return null; | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2024-07-31 04:21:27 +08:00
										 |  |  | 					if (cacheEntry.etag !== etag) return null; | 
					
						
							|  |  |  | 					oldCache.delete(identifier); | 
					
						
							|  |  |  | 					cache.set(identifier, cacheEntry); | 
					
						
							|  |  |  | 					return cacheEntry.data; | 
					
						
							| 
									
										
										
										
											2021-04-01 22:59:00 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 				gotHandlers.push((result, callback) => { | 
					
						
							|  |  |  | 					if (result === undefined) { | 
					
						
							|  |  |  | 						cache.set(identifier, null); | 
					
						
							|  |  |  | 					} else { | 
					
						
							|  |  |  | 						cache.set(identifier, { etag, data: result }); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					return callback(); | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 		compiler.cache.hooks.shutdown.tap( | 
					
						
							|  |  |  | 			{ name: "MemoryWithGcCachePlugin", stage: Cache.STAGE_MEMORY }, | 
					
						
							|  |  |  | 			() => { | 
					
						
							|  |  |  | 				cache.clear(); | 
					
						
							|  |  |  | 				oldCache.clear(); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | module.exports = MemoryWithGcCachePlugin; |