Repository: webpack/webpack
File: lib/CleanPlugin.js (JavaScript)

Code before:
 * @param {string} outputPath output path
 * @param {Map<string, number>} currentAssets filename of the current assets (must not start with .. or ., must only use / as path separator)
 * @param {(err?: Error | null, set?: Diff) => void} callback returns the filenames of the assets that shouldn't be there
 * @returns {void}
 */
const getDiffToFs = (fs, outputPath, currentAssets, callback) => {
	const directories = getDirectories(currentAssets);
	const diff = new Set();
	asyncLib.forEachLimit(
		directories,
		10,
		(directory, callback) => {
			/** @type {NonNullable<OutputFileSystem["readdir"]>} */
			(fs.readdir)(join(fs, outputPath, directory), (err, entries) => {
				if (err) {
					if (err.code === "ENOENT") return callback();
					if (err.code === "ENOTDIR") {
						diff.add(directory);
						return callback();
					}
					return callback(err);
				}
				for (const entry of /** @type {string[]} */ (entries)) {
					const file = entry;
					const filename = directory ? `${directory}/${file}` : file;
					const normalizedFilename = path.normalize(filename);
					if (
						!directories.has(normalizedFilename) &&
						!currentAssets.has(normalizedFilename)
					) {
						diff.add(normalizedFilename);
					}
				}
				callback();
			});
		},
		(err) => {
			if (err) return callback(err);

			callback(null, diff);
		}
	);
};

/**
 * @param {Assets} currentAssets assets list
 * @param {Assets} oldAssets old assets list
 * @returns {Diff} diff
 */
const getDiffToOldAssets = (currentAssets, oldAssets) => {
	const diff = new Set();

Review comment (performance):
I think we can cache it, look at `lib/util/identifier.js`, I think we can use the same logic here

Code after:
 * @param {string} outputPath output path
 * @param {Map<string, number>} currentAssets filename of the current assets (must not start with .. or ., must only use / as path separator)
 * @param {(err?: Error | null, set?: Diff) => void} callback returns the filenames of the assets that shouldn't be there
 * @returns {void}
 */
const getDiffToFs = (fs, outputPath, currentAssets, callback) => {
	const directories = getDirectories(currentAssets);
	const diff = new Set();
	asyncLib.forEachLimit(
		directories,
		10,
		(directory, callback) => {
			/** @type {NonNullable<OutputFileSystem["readdir"]>} */
			(fs.readdir)(join(fs, outputPath, directory), (err, entries) => {
				if (err) {
					if (err.code === "ENOENT") return callback();
					if (err.code === "ENOTDIR") {
						diff.add(directory);
						return callback();
					}
					return callback(err);
				}
				for (const entry of /** @type {string[]} */ (entries)) {
					const file = entry;
					// 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)) {
						diff.add(filename);
					}
				}
				callback();
			});
		},
		(err) => {
			if (err) return callback(err);

			callback(null, diff);
		}
	);
};

/**
 * @param {Assets} currentAssets assets list
 * @param {Assets} oldAssets old assets list
 * @returns {Diff} diff
 */
const getDiffToOldAssets = (currentAssets, oldAssets) => {
	const diff = new Set();
	const now = Date.now();