| 
									
										
										
										
											2020-09-30 23:33:30 +08:00
										 |  |  | /* | 
					
						
							|  |  |  | 	MIT License http://www.opensource.org/licenses/mit-license.php
 | 
					
						
							|  |  |  | 	Author Sergey Melyukov @smelukov | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | "use strict"; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-10 22:18:43 +08:00
										 |  |  | const path = require("path"); | 
					
						
							| 
									
										
										
										
											2025-07-03 17:06:45 +08:00
										 |  |  | const asyncLib = require("neo-async"); | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | const { SyncBailHook } = require("tapable"); | 
					
						
							| 
									
										
										
										
											2024-07-31 06:15:03 +08:00
										 |  |  | const Compilation = require("./Compilation"); | 
					
						
							| 
									
										
										
										
											2021-04-16 21:35:18 +08:00
										 |  |  | const createSchemaValidation = require("./util/create-schema-validation"); | 
					
						
							| 
									
										
										
										
											2020-10-07 18:48:51 +08:00
										 |  |  | const { join } = require("./util/fs"); | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | const processAsyncTree = require("./util/processAsyncTree"); | 
					
						
							| 
									
										
										
										
											2020-09-30 23:33:30 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-02 20:46:30 +08:00
										 |  |  | /** @typedef {import("../declarations/WebpackOptions").CleanOptions} CleanOptions */ | 
					
						
							| 
									
										
										
										
											2020-09-30 23:33:30 +08:00
										 |  |  | /** @typedef {import("./Compiler")} Compiler */ | 
					
						
							| 
									
										
										
										
											2021-02-02 15:36:58 +08:00
										 |  |  | /** @typedef {import("./logging/Logger").Logger} Logger */ | 
					
						
							| 
									
										
										
										
											2024-02-21 22:55:02 +08:00
										 |  |  | /** @typedef {import("./util/fs").IStats} IStats */ | 
					
						
							| 
									
										
										
										
											2021-02-02 15:36:58 +08:00
										 |  |  | /** @typedef {import("./util/fs").OutputFileSystem} OutputFileSystem */ | 
					
						
							| 
									
										
										
										
											2021-11-11 00:39:49 +08:00
										 |  |  | /** @typedef {import("./util/fs").StatsCallback} StatsCallback */ | 
					
						
							| 
									
										
										
										
											2020-09-30 23:33:30 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-16 23:12:59 +08:00
										 |  |  | /** @typedef {Map<string, number>} Assets */ | 
					
						
							| 
									
										
										
										
											2020-10-02 23:08:03 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
										
											2024-06-11 21:09:50 +08:00
										 |  |  |  * @typedef {object} CleanPluginCompilationHooks | 
					
						
							| 
									
										
										
										
											2024-10-24 22:09:39 +08:00
										 |  |  |  * @property {SyncBailHook<[string], boolean | void>} keep when returning true the file/directory will be kept during cleaning, returning false will clean it and ignore the following plugins and config | 
					
						
							| 
									
										
										
										
											2020-10-02 23:08:03 +08:00
										 |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-30 21:48:58 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @callback KeepFn | 
					
						
							|  |  |  |  * @param {string} path path | 
					
						
							| 
									
										
										
										
											2025-09-11 08:10:10 +08:00
										 |  |  |  * @returns {boolean | undefined} true, if the path should be kept | 
					
						
							| 
									
										
										
										
											2024-07-30 21:48:58 +08:00
										 |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 21:35:18 +08:00
										 |  |  | const validate = createSchemaValidation( | 
					
						
							|  |  |  | 	undefined, | 
					
						
							|  |  |  | 	() => { | 
					
						
							|  |  |  | 		const { definitions } = require("../schemas/WebpackOptions.json"); | 
					
						
							| 
									
										
										
										
											2025-07-02 20:10:54 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 21:35:18 +08:00
										 |  |  | 		return { | 
					
						
							|  |  |  | 			definitions, | 
					
						
							|  |  |  | 			oneOf: [{ $ref: "#/definitions/CleanOptions" }] | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		name: "Clean Plugin", | 
					
						
							|  |  |  | 		baseDataPath: "options" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | ); | 
					
						
							| 
									
										
										
										
											2022-02-16 23:12:59 +08:00
										 |  |  | const _10sec = 10 * 1000; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
										
											2025-02-27 19:00:58 +08:00
										 |  |  |  * merge assets map 2 into map 1 | 
					
						
							| 
									
										
										
										
											2022-02-16 23:12:59 +08:00
										 |  |  |  * @param {Assets} as1 assets | 
					
						
							|  |  |  |  * @param {Assets} as2 assets | 
					
						
							|  |  |  |  * @returns {void} | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const mergeAssets = (as1, as2) => { | 
					
						
							|  |  |  | 	for (const [key, value1] of as2) { | 
					
						
							|  |  |  | 		const value2 = as1.get(key); | 
					
						
							|  |  |  | 		if (!value2 || value1 > value2) as1.set(key, value1); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2021-02-02 20:46:30 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-10 22:18:43 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @param {Map<string, number>} assets current assets | 
					
						
							|  |  |  |  * @returns {Set<string>} Set of directory paths | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function getDirectories(assets) { | 
					
						
							|  |  |  | 	const directories = new Set(); | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {string} filename asset filename | 
					
						
							|  |  |  | 	 */ | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 	const addDirectory = (filename) => { | 
					
						
							| 
									
										
										
										
											2025-06-10 22:18:43 +08:00
										 |  |  | 		directories.add(path.dirname(filename)); | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// get directories of assets
 | 
					
						
							|  |  |  | 	for (const [asset] of assets) { | 
					
						
							|  |  |  | 		addDirectory(asset); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// and all parent directories
 | 
					
						
							|  |  |  | 	for (const directory of directories) { | 
					
						
							|  |  |  | 		addDirectory(directory); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return directories; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-12 09:56:14 +08:00
										 |  |  | /** @typedef {Set<string>} Diff */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @param {OutputFileSystem} fs filesystem | 
					
						
							|  |  |  |  * @param {string} outputPath output path | 
					
						
							| 
									
										
										
										
											2022-02-16 23:12:59 +08:00
										 |  |  |  * @param {Map<string, number>} currentAssets filename of the current assets (must not start with .. or ., must only use / as path separator) | 
					
						
							| 
									
										
										
										
											2025-03-12 09:56:14 +08:00
										 |  |  |  * @param {(err?: Error | null, set?: Diff) => void} callback returns the filenames of the assets that shouldn't be there | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  |  * @returns {void} | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const getDiffToFs = (fs, outputPath, currentAssets, callback) => { | 
					
						
							| 
									
										
										
										
											2025-06-10 22:18:43 +08:00
										 |  |  | 	const directories = getDirectories(currentAssets); | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 	const diff = new Set(); | 
					
						
							|  |  |  | 	asyncLib.forEachLimit( | 
					
						
							|  |  |  | 		directories, | 
					
						
							|  |  |  | 		10, | 
					
						
							|  |  |  | 		(directory, callback) => { | 
					
						
							| 
									
										
										
										
											2023-05-25 06:41:32 +08:00
										 |  |  | 			/** @type {NonNullable<OutputFileSystem["readdir"]>} */ | 
					
						
							|  |  |  | 			(fs.readdir)(join(fs, outputPath, directory), (err, entries) => { | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 				if (err) { | 
					
						
							|  |  |  | 					if (err.code === "ENOENT") return callback(); | 
					
						
							|  |  |  | 					if (err.code === "ENOTDIR") { | 
					
						
							|  |  |  | 						diff.add(directory); | 
					
						
							|  |  |  | 						return callback(); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					return callback(err); | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2024-03-12 00:33:52 +08:00
										 |  |  | 				for (const entry of /** @type {string[]} */ (entries)) { | 
					
						
							|  |  |  | 					const file = entry; | 
					
						
							| 
									
										
										
										
											2025-07-26 19:03:53 +08:00
										 |  |  | 					// Since path.normalize("./file") === path.normalize("file"),
 | 
					
						
							|  |  |  | 					// return file directly when directory === "."
 | 
					
						
							|  |  |  | 					const filename = | 
					
						
							|  |  |  | 						directory && directory !== "." ? `${directory}/${file}` : file; | 
					
						
							|  |  |  | 					if (!directories.has(filename) && !currentAssets.has(filename)) { | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 						diff.add(filename); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				callback(); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 		(err) => { | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 			if (err) return callback(err); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			callback(null, diff); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	); | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2020-10-02 23:08:03 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
										
											2022-02-16 23:12:59 +08:00
										 |  |  |  * @param {Assets} currentAssets assets list | 
					
						
							|  |  |  |  * @param {Assets} oldAssets old assets list | 
					
						
							| 
									
										
										
										
											2025-03-12 09:56:14 +08:00
										 |  |  |  * @returns {Diff} diff | 
					
						
							| 
									
										
										
										
											2020-10-02 23:08:03 +08:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | const getDiffToOldAssets = (currentAssets, oldAssets) => { | 
					
						
							|  |  |  | 	const diff = new Set(); | 
					
						
							| 
									
										
										
										
											2022-02-16 23:12:59 +08:00
										 |  |  | 	const now = Date.now(); | 
					
						
							|  |  |  | 	for (const [asset, ts] of oldAssets) { | 
					
						
							|  |  |  | 		if (ts >= now) continue; | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 		if (!currentAssets.has(asset)) diff.add(asset); | 
					
						
							| 
									
										
										
										
											2020-10-02 23:08:03 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 	return diff; | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2020-10-02 23:08:03 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-11 00:39:49 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @param {OutputFileSystem} fs filesystem | 
					
						
							|  |  |  |  * @param {string} filename path to file | 
					
						
							|  |  |  |  * @param {StatsCallback} callback callback for provided filename | 
					
						
							|  |  |  |  * @returns {void} | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const doStat = (fs, filename, callback) => { | 
					
						
							|  |  |  | 	if ("lstat" in fs) { | 
					
						
							| 
									
										
										
										
											2023-05-25 06:41:32 +08:00
										 |  |  | 		/** @type {NonNullable<OutputFileSystem["lstat"]>} */ | 
					
						
							|  |  |  | 		(fs.lstat)(filename, callback); | 
					
						
							| 
									
										
										
										
											2021-11-11 00:39:49 +08:00
										 |  |  | 	} else { | 
					
						
							|  |  |  | 		fs.stat(filename, callback); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @param {OutputFileSystem} fs filesystem | 
					
						
							|  |  |  |  * @param {string} outputPath output path | 
					
						
							|  |  |  |  * @param {boolean} dry only log instead of fs modification | 
					
						
							|  |  |  |  * @param {Logger} logger logger | 
					
						
							| 
									
										
										
										
											2025-03-12 09:56:14 +08:00
										 |  |  |  * @param {Diff} diff filenames of the assets that shouldn't be there | 
					
						
							| 
									
										
										
										
											2025-09-11 08:10:10 +08:00
										 |  |  |  * @param {KeepFn} isKept check if the entry is ignored | 
					
						
							| 
									
										
										
										
											2025-03-12 09:56:14 +08:00
										 |  |  |  * @param {(err?: Error, assets?: Assets) => void} callback callback | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  |  * @returns {void} | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const applyDiff = (fs, outputPath, dry, logger, diff, isKept, callback) => { | 
					
						
							| 
									
										
										
										
											2023-05-25 06:41:32 +08:00
										 |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {string} msg message | 
					
						
							|  |  |  | 	 */ | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 	const log = (msg) => { | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 		if (dry) { | 
					
						
							|  |  |  | 			logger.info(msg); | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			logger.log(msg); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 	/** @typedef {{ type: "check" | "unlink" | "rmdir", filename: string, parent: { remaining: number, job: Job } | undefined }} Job */ | 
					
						
							|  |  |  | 	/** @type {Job[]} */ | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 	const jobs = Array.from(diff.keys(), (filename) => ({ | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 		type: "check", | 
					
						
							|  |  |  | 		filename, | 
					
						
							|  |  |  | 		parent: undefined | 
					
						
							|  |  |  | 	})); | 
					
						
							| 
									
										
										
										
											2022-02-16 23:12:59 +08:00
										 |  |  | 	/** @type {Assets} */ | 
					
						
							|  |  |  | 	const keptAssets = new Map(); | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 	processAsyncTree( | 
					
						
							|  |  |  | 		jobs, | 
					
						
							|  |  |  | 		10, | 
					
						
							|  |  |  | 		({ type, filename, parent }, push, callback) => { | 
					
						
							| 
									
										
										
										
											2025-06-27 01:13:31 +08:00
										 |  |  | 			const path = join(fs, outputPath, filename); | 
					
						
							| 
									
										
										
										
											2023-05-25 06:41:32 +08:00
										 |  |  | 			/** | 
					
						
							|  |  |  | 			 * @param {Error & { code?: string }} err error | 
					
						
							|  |  |  | 			 * @returns {void} | 
					
						
							|  |  |  | 			 */ | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 			const handleError = (err) => { | 
					
						
							| 
									
										
										
										
											2025-06-27 01:13:31 +08:00
										 |  |  | 				const isAlreadyRemoved = () => | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 					new Promise((resolve) => { | 
					
						
							| 
									
										
										
										
											2025-06-27 01:13:31 +08:00
										 |  |  | 						if (err.code === "ENOENT") { | 
					
						
							|  |  |  | 							resolve(true); | 
					
						
							|  |  |  | 						} else if (err.code === "EPERM") { | 
					
						
							|  |  |  | 							// https://github.com/isaacs/rimraf/blob/main/src/fix-eperm.ts#L37
 | 
					
						
							|  |  |  | 							// fs.existsSync(path) === false https://github.com/webpack/webpack/actions/runs/15493412975/job/43624272783?pr=19586
 | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 							doStat(fs, path, (err) => { | 
					
						
							| 
									
										
										
										
											2025-06-27 01:13:31 +08:00
										 |  |  | 								if (err) { | 
					
						
							|  |  |  | 									resolve(err.code === "ENOENT"); | 
					
						
							|  |  |  | 								} else { | 
					
						
							|  |  |  | 									resolve(false); | 
					
						
							|  |  |  | 								} | 
					
						
							|  |  |  | 							}); | 
					
						
							|  |  |  | 						} else { | 
					
						
							|  |  |  | 							resolve(false); | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					}); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 				isAlreadyRemoved().then((isRemoved) => { | 
					
						
							| 
									
										
										
										
											2025-06-27 01:13:31 +08:00
										 |  |  | 					if (isRemoved) { | 
					
						
							|  |  |  | 						log(`${filename} was removed during cleaning by something else`); | 
					
						
							|  |  |  | 						handleParent(); | 
					
						
							|  |  |  | 						return callback(); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					return callback(err); | 
					
						
							|  |  |  | 				}); | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 			}; | 
					
						
							|  |  |  | 			const handleParent = () => { | 
					
						
							|  |  |  | 				if (parent && --parent.remaining === 0) push(parent.job); | 
					
						
							|  |  |  | 			}; | 
					
						
							|  |  |  | 			switch (type) { | 
					
						
							|  |  |  | 				case "check": | 
					
						
							|  |  |  | 					if (isKept(filename)) { | 
					
						
							| 
									
										
										
										
											2022-02-28 20:20:44 +08:00
										 |  |  | 						keptAssets.set(filename, 0); | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 						// do not decrement parent entry as we don't want to delete the parent
 | 
					
						
							|  |  |  | 						log(`${filename} will be kept`); | 
					
						
							|  |  |  | 						return process.nextTick(callback); | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2021-11-11 00:39:49 +08:00
										 |  |  | 					doStat(fs, path, (err, stats) => { | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 						if (err) return handleError(err); | 
					
						
							| 
									
										
										
										
											2024-02-21 22:55:02 +08:00
										 |  |  | 						if (!(/** @type {IStats} */ (stats).isDirectory())) { | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 							push({ | 
					
						
							|  |  |  | 								type: "unlink", | 
					
						
							|  |  |  | 								filename, | 
					
						
							|  |  |  | 								parent | 
					
						
							|  |  |  | 							}); | 
					
						
							|  |  |  | 							return callback(); | 
					
						
							|  |  |  | 						} | 
					
						
							| 
									
										
										
										
											2023-05-25 06:41:32 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 						/** @type {NonNullable<OutputFileSystem["readdir"]>} */ | 
					
						
							| 
									
										
										
										
											2024-03-12 00:33:52 +08:00
										 |  |  | 						(fs.readdir)(path, (err, _entries) => { | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 							if (err) return handleError(err); | 
					
						
							|  |  |  | 							/** @type {Job} */ | 
					
						
							|  |  |  | 							const deleteJob = { | 
					
						
							|  |  |  | 								type: "rmdir", | 
					
						
							|  |  |  | 								filename, | 
					
						
							|  |  |  | 								parent | 
					
						
							|  |  |  | 							}; | 
					
						
							| 
									
										
										
										
											2024-03-12 00:33:52 +08:00
										 |  |  | 							const entries = /** @type {string[]} */ (_entries); | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 							if (entries.length === 0) { | 
					
						
							|  |  |  | 								push(deleteJob); | 
					
						
							|  |  |  | 							} else { | 
					
						
							|  |  |  | 								const parentToken = { | 
					
						
							|  |  |  | 									remaining: entries.length, | 
					
						
							|  |  |  | 									job: deleteJob | 
					
						
							|  |  |  | 								}; | 
					
						
							|  |  |  | 								for (const entry of entries) { | 
					
						
							|  |  |  | 									const file = /** @type {string} */ (entry); | 
					
						
							|  |  |  | 									if (file.startsWith(".")) { | 
					
						
							|  |  |  | 										log( | 
					
						
							|  |  |  | 											`${filename} will be kept (dot-files will never be removed)` | 
					
						
							|  |  |  | 										); | 
					
						
							|  |  |  | 										continue; | 
					
						
							|  |  |  | 									} | 
					
						
							|  |  |  | 									push({ | 
					
						
							|  |  |  | 										type: "check", | 
					
						
							|  |  |  | 										filename: `${filename}/${file}`, | 
					
						
							|  |  |  | 										parent: parentToken | 
					
						
							|  |  |  | 									}); | 
					
						
							|  |  |  | 								} | 
					
						
							|  |  |  | 							} | 
					
						
							|  |  |  | 							return callback(); | 
					
						
							|  |  |  | 						}); | 
					
						
							|  |  |  | 					}); | 
					
						
							|  |  |  | 					break; | 
					
						
							|  |  |  | 				case "rmdir": | 
					
						
							|  |  |  | 					log(`${filename} will be removed`); | 
					
						
							|  |  |  | 					if (dry) { | 
					
						
							|  |  |  | 						handleParent(); | 
					
						
							|  |  |  | 						return process.nextTick(callback); | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2021-02-03 02:15:34 +08:00
										 |  |  | 					if (!fs.rmdir) { | 
					
						
							|  |  |  | 						logger.warn( | 
					
						
							|  |  |  | 							`${filename} can't be removed because output file system doesn't support removing directories (rmdir)` | 
					
						
							|  |  |  | 						); | 
					
						
							|  |  |  | 						return process.nextTick(callback); | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 					fs.rmdir(path, (err) => { | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 						if (err) return handleError(err); | 
					
						
							|  |  |  | 						handleParent(); | 
					
						
							|  |  |  | 						callback(); | 
					
						
							|  |  |  | 					}); | 
					
						
							|  |  |  | 					break; | 
					
						
							|  |  |  | 				case "unlink": | 
					
						
							|  |  |  | 					log(`${filename} will be removed`); | 
					
						
							|  |  |  | 					if (dry) { | 
					
						
							|  |  |  | 						handleParent(); | 
					
						
							|  |  |  | 						return process.nextTick(callback); | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2021-02-03 02:15:34 +08:00
										 |  |  | 					if (!fs.unlink) { | 
					
						
							|  |  |  | 						logger.warn( | 
					
						
							|  |  |  | 							`${filename} can't be removed because output file system doesn't support removing files (rmdir)` | 
					
						
							|  |  |  | 						); | 
					
						
							|  |  |  | 						return process.nextTick(callback); | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 					fs.unlink(path, (err) => { | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 						if (err) return handleError(err); | 
					
						
							|  |  |  | 						handleParent(); | 
					
						
							|  |  |  | 						callback(); | 
					
						
							|  |  |  | 					}); | 
					
						
							|  |  |  | 					break; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 		(err) => { | 
					
						
							| 
									
										
										
										
											2022-02-16 23:12:59 +08:00
										 |  |  | 			if (err) return callback(err); | 
					
						
							|  |  |  | 			callback(undefined, keptAssets); | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 	); | 
					
						
							| 
									
										
										
										
											2020-10-02 23:08:03 +08:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | /** @type {WeakMap<Compilation, CleanPluginCompilationHooks>} */ | 
					
						
							|  |  |  | const compilationHooksMap = new WeakMap(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-23 20:03:37 +08:00
										 |  |  | const PLUGIN_NAME = "CleanPlugin"; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-30 23:33:30 +08:00
										 |  |  | class CleanPlugin { | 
					
						
							| 
									
										
										
										
											2020-10-02 23:08:03 +08:00
										 |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {Compilation} compilation the compilation | 
					
						
							|  |  |  | 	 * @returns {CleanPluginCompilationHooks} the attached hooks | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	static getCompilationHooks(compilation) { | 
					
						
							|  |  |  | 		if (!(compilation instanceof Compilation)) { | 
					
						
							|  |  |  | 			throw new TypeError( | 
					
						
							|  |  |  | 				"The 'compilation' argument must be an instance of Compilation" | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		let hooks = compilationHooksMap.get(compilation); | 
					
						
							|  |  |  | 		if (hooks === undefined) { | 
					
						
							|  |  |  | 			hooks = { | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 				keep: new SyncBailHook(["ignore"]) | 
					
						
							| 
									
										
										
										
											2020-10-02 23:08:03 +08:00
										 |  |  | 			}; | 
					
						
							|  |  |  | 			compilationHooksMap.set(compilation, hooks); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return hooks; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-16 21:35:18 +08:00
										 |  |  | 	/** @param {CleanOptions} options options */ | 
					
						
							| 
									
										
										
										
											2020-10-07 20:30:14 +08:00
										 |  |  | 	constructor(options = {}) { | 
					
						
							| 
									
										
										
										
											2021-04-16 21:35:18 +08:00
										 |  |  | 		validate(options); | 
					
						
							| 
									
										
										
										
											2021-02-02 20:46:30 +08:00
										 |  |  | 		this.options = { dry: false, ...options }; | 
					
						
							| 
									
										
										
										
											2020-09-30 23:33:30 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 	 * Apply the plugin | 
					
						
							| 
									
										
										
										
											2020-09-30 23:33:30 +08:00
										 |  |  | 	 * @param {Compiler} compiler the compiler instance | 
					
						
							|  |  |  | 	 * @returns {void} | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	apply(compiler) { | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 		const { dry, keep } = this.options; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-30 21:48:58 +08:00
										 |  |  | 		/** @type {KeepFn} */ | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 		const keepFn = | 
					
						
							|  |  |  | 			typeof keep === "function" | 
					
						
							|  |  |  | 				? keep | 
					
						
							|  |  |  | 				: typeof keep === "string" | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 					? (path) => path.startsWith(keep) | 
					
						
							| 
									
										
										
										
											2024-01-14 09:41:34 +08:00
										 |  |  | 					: typeof keep === "object" && keep.test | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 						? (path) => keep.test(path) | 
					
						
							| 
									
										
										
										
											2024-01-14 09:41:34 +08:00
										 |  |  | 						: () => false; | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// We assume that no external modification happens while the compiler is active
 | 
					
						
							|  |  |  | 		// So we can store the old assets and only diff to them to avoid fs access on
 | 
					
						
							|  |  |  | 		// incremental builds
 | 
					
						
							| 
									
										
										
										
											2022-02-16 23:12:59 +08:00
										 |  |  | 		/** @type {undefined|Assets} */ | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 		let oldAssets; | 
					
						
							| 
									
										
										
										
											2020-09-30 23:33:30 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-07 18:48:51 +08:00
										 |  |  | 		compiler.hooks.emit.tapAsync( | 
					
						
							| 
									
										
										
										
											2020-09-30 23:33:30 +08:00
										 |  |  | 			{ | 
					
						
							| 
									
										
										
										
											2025-04-23 20:03:37 +08:00
										 |  |  | 				name: PLUGIN_NAME, | 
					
						
							| 
									
										
										
										
											2020-09-30 23:33:30 +08:00
										 |  |  | 				stage: 100 | 
					
						
							|  |  |  | 			}, | 
					
						
							| 
									
										
										
										
											2020-10-07 18:48:51 +08:00
										 |  |  | 			(compilation, callback) => { | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 				const hooks = CleanPlugin.getCompilationHooks(compilation); | 
					
						
							| 
									
										
										
										
											2025-04-23 20:03:37 +08:00
										 |  |  | 				const logger = compilation.getLogger(`webpack.${PLUGIN_NAME}`); | 
					
						
							| 
									
										
										
										
											2024-03-12 00:33:52 +08:00
										 |  |  | 				const fs = /** @type {OutputFileSystem} */ (compiler.outputFileSystem); | 
					
						
							| 
									
										
										
										
											2021-02-03 02:15:34 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				if (!fs.readdir) { | 
					
						
							|  |  |  | 					return callback( | 
					
						
							|  |  |  | 						new Error( | 
					
						
							| 
									
										
										
										
											2025-04-23 20:03:37 +08:00
										 |  |  | 							`${PLUGIN_NAME}: Output filesystem doesn't support listing directories (readdir)` | 
					
						
							| 
									
										
										
										
											2021-02-03 02:15:34 +08:00
										 |  |  | 						) | 
					
						
							|  |  |  | 					); | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-16 23:12:59 +08:00
										 |  |  | 				/** @type {Assets} */ | 
					
						
							|  |  |  | 				const currentAssets = new Map(); | 
					
						
							|  |  |  | 				const now = Date.now(); | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 				for (const asset of Object.keys(compilation.assets)) { | 
					
						
							|  |  |  | 					if (/^[A-Za-z]:\\|^\/|^\\\\/.test(asset)) continue; | 
					
						
							|  |  |  | 					let normalizedAsset; | 
					
						
							|  |  |  | 					let newNormalizedAsset = asset.replace(/\\/g, "/"); | 
					
						
							|  |  |  | 					do { | 
					
						
							|  |  |  | 						normalizedAsset = newNormalizedAsset; | 
					
						
							|  |  |  | 						newNormalizedAsset = normalizedAsset.replace( | 
					
						
							|  |  |  | 							/(^|\/)(?!\.\.)[^/]+\/\.\.\//g, | 
					
						
							|  |  |  | 							"$1" | 
					
						
							|  |  |  | 						); | 
					
						
							|  |  |  | 					} while (newNormalizedAsset !== normalizedAsset); | 
					
						
							|  |  |  | 					if (normalizedAsset.startsWith("../")) continue; | 
					
						
							| 
									
										
										
										
											2022-02-16 23:12:59 +08:00
										 |  |  | 					const assetInfo = compilation.assetsInfo.get(asset); | 
					
						
							|  |  |  | 					if (assetInfo && assetInfo.hotModuleReplacement) { | 
					
						
							|  |  |  | 						currentAssets.set(normalizedAsset, now + _10sec); | 
					
						
							|  |  |  | 					} else { | 
					
						
							| 
									
										
										
										
											2022-02-28 20:20:44 +08:00
										 |  |  | 						currentAssets.set(normalizedAsset, 0); | 
					
						
							| 
									
										
										
										
											2022-02-16 23:12:59 +08:00
										 |  |  | 					} | 
					
						
							| 
									
										
										
										
											2020-09-30 23:33:30 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-07 18:48:51 +08:00
										 |  |  | 				const outputPath = compilation.getPath(compiler.outputPath, {}); | 
					
						
							| 
									
										
										
										
											2020-10-02 23:08:03 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-25 06:41:32 +08:00
										 |  |  | 				/** | 
					
						
							|  |  |  | 				 * @param {string} path path | 
					
						
							| 
									
										
										
										
											2025-09-11 08:10:10 +08:00
										 |  |  | 				 * @returns {boolean | undefined} true, if needs to be kept | 
					
						
							| 
									
										
										
										
											2023-05-25 06:41:32 +08:00
										 |  |  | 				 */ | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 				const isKept = (path) => { | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 					const result = hooks.keep.call(path); | 
					
						
							|  |  |  | 					if (result !== undefined) return result; | 
					
						
							|  |  |  | 					return keepFn(path); | 
					
						
							|  |  |  | 				}; | 
					
						
							| 
									
										
										
										
											2020-10-07 18:48:51 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-16 23:12:59 +08:00
										 |  |  | 				/** | 
					
						
							| 
									
										
										
										
											2023-05-25 06:41:32 +08:00
										 |  |  | 				 * @param {(Error | null)=} err err | 
					
						
							| 
									
										
										
										
											2025-03-12 09:56:14 +08:00
										 |  |  | 				 * @param {Diff=} diff diff | 
					
						
							| 
									
										
										
										
											2022-02-16 23:12:59 +08:00
										 |  |  | 				 */ | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 				const diffCallback = (err, diff) => { | 
					
						
							|  |  |  | 					if (err) { | 
					
						
							|  |  |  | 						oldAssets = undefined; | 
					
						
							| 
									
										
										
										
											2022-02-16 23:12:59 +08:00
										 |  |  | 						callback(err); | 
					
						
							|  |  |  | 						return; | 
					
						
							| 
									
										
										
										
											2020-09-30 23:33:30 +08:00
										 |  |  | 					} | 
					
						
							| 
									
										
										
										
											2022-02-16 23:12:59 +08:00
										 |  |  | 					applyDiff( | 
					
						
							|  |  |  | 						fs, | 
					
						
							|  |  |  | 						outputPath, | 
					
						
							|  |  |  | 						dry, | 
					
						
							|  |  |  | 						logger, | 
					
						
							| 
									
										
										
										
											2025-03-12 09:56:14 +08:00
										 |  |  | 						/** @type {Diff} */ (diff), | 
					
						
							| 
									
										
										
										
											2022-02-16 23:12:59 +08:00
										 |  |  | 						isKept, | 
					
						
							| 
									
										
										
										
											2022-02-28 20:20:44 +08:00
										 |  |  | 						(err, keptAssets) => { | 
					
						
							| 
									
										
										
										
											2022-02-16 23:12:59 +08:00
										 |  |  | 							if (err) { | 
					
						
							|  |  |  | 								oldAssets = undefined; | 
					
						
							|  |  |  | 							} else { | 
					
						
							|  |  |  | 								if (oldAssets) mergeAssets(currentAssets, oldAssets); | 
					
						
							|  |  |  | 								oldAssets = currentAssets; | 
					
						
							| 
									
										
										
										
											2022-02-28 20:20:44 +08:00
										 |  |  | 								if (keptAssets) mergeAssets(oldAssets, keptAssets); | 
					
						
							| 
									
										
										
										
											2022-02-16 23:12:59 +08:00
										 |  |  | 							} | 
					
						
							|  |  |  | 							callback(err); | 
					
						
							| 
									
										
										
										
											2020-10-07 18:48:51 +08:00
										 |  |  | 						} | 
					
						
							| 
									
										
										
										
											2022-02-16 23:12:59 +08:00
										 |  |  | 					); | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 				}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				if (oldAssets) { | 
					
						
							|  |  |  | 					diffCallback(null, getDiffToOldAssets(currentAssets, oldAssets)); | 
					
						
							|  |  |  | 				} else { | 
					
						
							| 
									
										
										
										
											2021-02-03 02:15:34 +08:00
										 |  |  | 					getDiffToFs(fs, outputPath, currentAssets, diffCallback); | 
					
						
							| 
									
										
										
										
											2020-10-07 18:48:51 +08:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2021-02-03 01:49:45 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		); | 
					
						
							| 
									
										
										
										
											2020-09-30 23:33:30 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module.exports = CleanPlugin; | 
					
						
							| 
									
										
										
										
											2025-06-10 22:18:43 +08:00
										 |  |  | module.exports._getDirectories = getDirectories; |