| 
									
										
										
										
											2019-01-26 02:21:45 +08:00
										 |  |  | /* | 
					
						
							|  |  |  | 	MIT License http://www.opensource.org/licenses/mit-license.php
 | 
					
						
							|  |  |  | 	Author Tobias Koppers @sokra | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | "use strict"; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-13 17:03:10 +08:00
										 |  |  | const Cache = require("../Cache"); | 
					
						
							| 
									
										
										
										
											2020-08-14 13:27:30 +08:00
										 |  |  | const ProgressPlugin = require("../ProgressPlugin"); | 
					
						
							| 
									
										
										
										
											2019-05-13 17:03:10 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-26 02:21:45 +08:00
										 |  |  | /** @typedef {import("../Compiler")} Compiler */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-13 04:59:09 +08:00
										 |  |  | const BUILD_DEPENDENCIES_KEY = Symbol(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-26 02:21:45 +08:00
										 |  |  | class IdleFileCachePlugin { | 
					
						
							| 
									
										
										
										
											2019-07-25 21:44:37 +08:00
										 |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {TODO} strategy cache strategy | 
					
						
							|  |  |  | 	 * @param {number} idleTimeout timeout | 
					
						
							|  |  |  | 	 * @param {number} idleTimeoutForInitialStore initial timeout | 
					
						
							|  |  |  | 	 */ | 
					
						
							| 
									
										
										
										
											2019-01-26 02:21:45 +08:00
										 |  |  | 	constructor(strategy, idleTimeout, idleTimeoutForInitialStore) { | 
					
						
							|  |  |  | 		this.strategy = strategy; | 
					
						
							|  |  |  | 		this.idleTimeout = idleTimeout; | 
					
						
							|  |  |  | 		this.idleTimeoutForInitialStore = idleTimeoutForInitialStore; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							| 
									
										
										
										
											2020-04-23 16:48:36 +08:00
										 |  |  | 	 * Apply the plugin | 
					
						
							|  |  |  | 	 * @param {Compiler} compiler the compiler instance | 
					
						
							| 
									
										
										
										
											2019-01-26 02:21:45 +08:00
										 |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	apply(compiler) { | 
					
						
							| 
									
										
										
										
											2021-03-25 23:35:19 +08:00
										 |  |  | 		let strategy = this.strategy; | 
					
						
							| 
									
										
										
										
											2019-01-26 02:21:45 +08:00
										 |  |  | 		const idleTimeout = this.idleTimeout; | 
					
						
							|  |  |  | 		const idleTimeoutForInitialStore = Math.min( | 
					
						
							|  |  |  | 			idleTimeout, | 
					
						
							|  |  |  | 			this.idleTimeoutForInitialStore | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		const resolvedPromise = Promise.resolve(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-13 04:59:09 +08:00
										 |  |  | 		/** @type {Map<string | typeof BUILD_DEPENDENCIES_KEY, () => Promise>} */ | 
					
						
							| 
									
										
										
										
											2019-01-26 02:21:45 +08:00
										 |  |  | 		const pendingIdleTasks = new Map(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		compiler.cache.hooks.store.tap( | 
					
						
							| 
									
										
										
										
											2019-05-13 17:03:10 +08:00
										 |  |  | 			{ name: "IdleFileCachePlugin", stage: Cache.STAGE_DISK }, | 
					
						
							| 
									
										
										
										
											2019-01-26 02:21:45 +08:00
										 |  |  | 			(identifier, etag, data) => { | 
					
						
							|  |  |  | 				pendingIdleTasks.set(identifier, () => | 
					
						
							|  |  |  | 					strategy.store(identifier, etag, data) | 
					
						
							|  |  |  | 				); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		compiler.cache.hooks.get.tapPromise( | 
					
						
							| 
									
										
										
										
											2019-05-13 17:03:10 +08:00
										 |  |  | 			{ name: "IdleFileCachePlugin", stage: Cache.STAGE_DISK }, | 
					
						
							| 
									
										
										
										
											2019-01-26 02:21:45 +08:00
										 |  |  | 			(identifier, etag, gotHandlers) => { | 
					
						
							| 
									
										
										
										
											2021-04-01 22:59:00 +08:00
										 |  |  | 				const restore = () => | 
					
						
							|  |  |  | 					strategy.restore(identifier, etag).then(cacheEntry => { | 
					
						
							|  |  |  | 						if (cacheEntry === undefined) { | 
					
						
							|  |  |  | 							gotHandlers.push((result, callback) => { | 
					
						
							|  |  |  | 								if (result !== undefined) { | 
					
						
							|  |  |  | 									pendingIdleTasks.set(identifier, () => | 
					
						
							|  |  |  | 										strategy.store(identifier, etag, result) | 
					
						
							|  |  |  | 									); | 
					
						
							|  |  |  | 								} | 
					
						
							|  |  |  | 								callback(); | 
					
						
							|  |  |  | 							}); | 
					
						
							|  |  |  | 						} else { | 
					
						
							|  |  |  | 							return cacheEntry; | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					}); | 
					
						
							|  |  |  | 				const pendingTask = pendingIdleTasks.get(identifier); | 
					
						
							|  |  |  | 				if (pendingTask !== undefined) { | 
					
						
							|  |  |  | 					pendingIdleTasks.delete(identifier); | 
					
						
							|  |  |  | 					return pendingTask().then(restore); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				return restore(); | 
					
						
							| 
									
										
										
										
											2019-01-26 02:21:45 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-13 04:59:09 +08:00
										 |  |  | 		compiler.cache.hooks.storeBuildDependencies.tap( | 
					
						
							| 
									
										
										
										
											2020-07-31 23:38:33 +08:00
										 |  |  | 			{ name: "IdleFileCachePlugin", stage: Cache.STAGE_DISK }, | 
					
						
							| 
									
										
										
										
											2019-08-13 04:59:09 +08:00
										 |  |  | 			dependencies => { | 
					
						
							|  |  |  | 				pendingIdleTasks.set(BUILD_DEPENDENCIES_KEY, () => | 
					
						
							|  |  |  | 					strategy.storeBuildDependencies(dependencies) | 
					
						
							|  |  |  | 				); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-13 17:03:10 +08:00
										 |  |  | 		compiler.cache.hooks.shutdown.tapPromise( | 
					
						
							|  |  |  | 			{ name: "IdleFileCachePlugin", stage: Cache.STAGE_DISK }, | 
					
						
							|  |  |  | 			() => { | 
					
						
							|  |  |  | 				if (idleTimer) { | 
					
						
							|  |  |  | 					clearTimeout(idleTimer); | 
					
						
							|  |  |  | 					idleTimer = undefined; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				isIdle = false; | 
					
						
							| 
									
										
										
										
											2020-08-14 13:27:30 +08:00
										 |  |  | 				const reportProgress = ProgressPlugin.getReporter(compiler); | 
					
						
							|  |  |  | 				const jobs = Array.from(pendingIdleTasks.values()); | 
					
						
							|  |  |  | 				if (reportProgress) reportProgress(0, "process pending cache items"); | 
					
						
							|  |  |  | 				const promises = jobs.map(fn => fn()); | 
					
						
							| 
									
										
										
										
											2019-05-13 17:03:10 +08:00
										 |  |  | 				pendingIdleTasks.clear(); | 
					
						
							| 
									
										
										
										
											2020-01-15 06:00:05 +08:00
										 |  |  | 				promises.push(currentIdlePromise); | 
					
						
							| 
									
										
										
										
											2020-08-14 13:27:30 +08:00
										 |  |  | 				const promise = Promise.all(promises); | 
					
						
							| 
									
										
										
										
											2020-01-15 06:00:05 +08:00
										 |  |  | 				currentIdlePromise = promise.then(() => strategy.afterAllStored()); | 
					
						
							| 
									
										
										
										
											2020-08-14 13:27:30 +08:00
										 |  |  | 				if (reportProgress) { | 
					
						
							|  |  |  | 					currentIdlePromise = currentIdlePromise.then(() => { | 
					
						
							|  |  |  | 						reportProgress(1, `stored`); | 
					
						
							|  |  |  | 					}); | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2021-03-25 23:35:19 +08:00
										 |  |  | 				return currentIdlePromise.then(() => { | 
					
						
							|  |  |  | 					// Reset strategy
 | 
					
						
							|  |  |  | 					if (strategy.clear) strategy.clear(); | 
					
						
							|  |  |  | 				}); | 
					
						
							| 
									
										
										
										
											2019-01-26 02:21:45 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2019-05-13 17:03:10 +08:00
										 |  |  | 		); | 
					
						
							| 
									
										
										
										
											2019-01-26 02:21:45 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-15 06:00:05 +08:00
										 |  |  | 		/** @type {Promise<any>} */ | 
					
						
							|  |  |  | 		let currentIdlePromise = resolvedPromise; | 
					
						
							| 
									
										
										
										
											2019-01-26 02:21:45 +08:00
										 |  |  | 		let isIdle = false; | 
					
						
							|  |  |  | 		let isInitialStore = true; | 
					
						
							|  |  |  | 		const processIdleTasks = () => { | 
					
						
							|  |  |  | 			if (isIdle) { | 
					
						
							|  |  |  | 				if (pendingIdleTasks.size > 0) { | 
					
						
							| 
									
										
										
										
											2020-01-15 06:00:05 +08:00
										 |  |  | 					const promises = [currentIdlePromise]; | 
					
						
							| 
									
										
										
										
											2019-01-26 02:21:45 +08:00
										 |  |  | 					const maxTime = Date.now() + 100; | 
					
						
							|  |  |  | 					let maxCount = 100; | 
					
						
							|  |  |  | 					for (const [filename, factory] of pendingIdleTasks) { | 
					
						
							|  |  |  | 						pendingIdleTasks.delete(filename); | 
					
						
							|  |  |  | 						promises.push(factory()); | 
					
						
							|  |  |  | 						if (maxCount-- <= 0 || Date.now() > maxTime) break; | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2020-01-15 06:00:05 +08:00
										 |  |  | 					currentIdlePromise = Promise.all(promises); | 
					
						
							| 
									
										
										
										
											2019-01-26 02:21:45 +08:00
										 |  |  | 					currentIdlePromise.then(() => { | 
					
						
							| 
									
										
										
										
											2020-03-10 09:59:46 +08:00
										 |  |  | 						// Allow to exit the process between
 | 
					
						
							| 
									
										
										
										
											2019-01-26 02:21:45 +08:00
										 |  |  | 						setTimeout(processIdleTasks, 0).unref(); | 
					
						
							|  |  |  | 					}); | 
					
						
							|  |  |  | 					return; | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2020-10-11 07:17:03 +08:00
										 |  |  | 				currentIdlePromise = currentIdlePromise | 
					
						
							|  |  |  | 					.then(() => strategy.afterAllStored()) | 
					
						
							|  |  |  | 					.catch(err => { | 
					
						
							|  |  |  | 						const logger = compiler.getInfrastructureLogger( | 
					
						
							|  |  |  | 							"IdleFileCachePlugin" | 
					
						
							|  |  |  | 						); | 
					
						
							|  |  |  | 						logger.warn(`Background tasks during idle failed: ${err.message}`); | 
					
						
							|  |  |  | 						logger.debug(err.stack); | 
					
						
							|  |  |  | 					}); | 
					
						
							| 
									
										
										
										
											2019-01-26 02:21:45 +08:00
										 |  |  | 				isInitialStore = false; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 		let idleTimer = undefined; | 
					
						
							| 
									
										
										
										
											2019-05-13 17:03:10 +08:00
										 |  |  | 		compiler.cache.hooks.beginIdle.tap( | 
					
						
							|  |  |  | 			{ name: "IdleFileCachePlugin", stage: Cache.STAGE_DISK }, | 
					
						
							|  |  |  | 			() => { | 
					
						
							|  |  |  | 				idleTimer = setTimeout( | 
					
						
							|  |  |  | 					() => { | 
					
						
							|  |  |  | 						idleTimer = undefined; | 
					
						
							|  |  |  | 						isIdle = true; | 
					
						
							|  |  |  | 						resolvedPromise.then(processIdleTasks); | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 					isInitialStore ? idleTimeoutForInitialStore : idleTimeout | 
					
						
							|  |  |  | 				); | 
					
						
							|  |  |  | 				idleTimer.unref(); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 		compiler.cache.hooks.endIdle.tap( | 
					
						
							|  |  |  | 			{ name: "IdleFileCachePlugin", stage: Cache.STAGE_DISK }, | 
					
						
							|  |  |  | 			() => { | 
					
						
							|  |  |  | 				if (idleTimer) { | 
					
						
							|  |  |  | 					clearTimeout(idleTimer); | 
					
						
							| 
									
										
										
										
											2019-02-06 22:37:11 +08:00
										 |  |  | 					idleTimer = undefined; | 
					
						
							| 
									
										
										
										
											2019-05-13 17:03:10 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 				isIdle = false; | 
					
						
							| 
									
										
										
										
											2019-01-26 02:21:45 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2019-05-13 17:03:10 +08:00
										 |  |  | 		); | 
					
						
							| 
									
										
										
										
											2019-01-26 02:21:45 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module.exports = IdleFileCachePlugin; |