| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129 | /*	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 {	constructor({ maxGenerations }) {		this._maxGenerations = maxGenerations;	}	/**	 * Apply the plugin	 * @param {Compiler} compiler the compiler instance	 * @returns {void}	 */	apply(compiler) {		const maxGenerations = this._maxGenerations;		/** @type {Map<string, { etag: Etag | null, data: any }>} */		const cache = new Map();		/** @type {Map<string, { entry: { etag: Etag | null, data: any }, until: number }>} */		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;			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;					} else {						if (cacheEntry.etag !== etag) return null;						oldCache.delete(identifier);						cache.set(identifier, cacheEntry);						return cacheEntry.data;					}				}				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;
 |