resolveBuildDependencies(context, deps, callback) {
		const { resolveContext, resolveEsm, resolveCjs, resolveCjsAsChild } =
			this._createBuildDependenciesResolvers();

		/** @type {Files} */
		const files = new Set();
		/** @type {Symlinks} */
		const fileSymlinks = new Set();
		/** @type {Directories} */
		const directories = new Set();
		/** @type {Symlinks} */
		const directorySymlinks = new Set();
		/** @type {Missing} */
		const missing = new Set();
		/** @type {ResolveDependencies["files"]} */
		const resolveFiles = new Set();
		/** @type {ResolveDependencies["directories"]} */
		const resolveDirectories = new Set();
		/** @type {ResolveDependencies["missing"]} */
		const resolveMissing = new Set();
		/** @type {ResolveResults} */
		const resolveResults = new Map();
		/** @type {Set<string>} */
		const invalidResolveResults = new Set();
		const resolverContext = {
			fileDependencies: resolveFiles,
			contextDependencies: resolveDirectories,
			missingDependencies: resolveMissing
		};
		/**
		 * @param {undefined | boolean | string} expected expected result
		 * @returns {string} expected result
		 */
		const expectedToString = expected =>
			expected ? ` (expected ${expected})` : "";
		/** @typedef {{ type: JobType, context: string | undefined, path: string, issuer: Job | undefined, expected: undefined | boolean | string }} Job */

		/**
		 * @param {Job} job job
		 * @returns {`resolve commonjs file ${string}${string}`|`resolve esm file ${string}${string}`|`resolve esm ${string}${string}`|`resolve directory ${string}`|`file ${string}`|`unknown ${string} ${string}`|`resolve commonjs ${string}${string}`|`directory ${string}`|`file dependencies ${string}`|`directory dependencies ${string}`} result
		 */
		const jobToString = job => {
			switch (job.type) {
				case RBDT_RESOLVE_CJS:
					return `resolve commonjs ${job.path}${expectedToString(
						job.expected
					)}`;
				case RBDT_RESOLVE_ESM:
					return `resolve esm ${job.path}${expectedToString(job.expected)}`;
				case RBDT_RESOLVE_DIRECTORY:
					return `resolve directory ${job.path}`;
				case RBDT_RESOLVE_CJS_FILE:
					return `resolve commonjs file ${job.path}${expectedToString(
						job.expected
					)}`;
				case RBDT_RESOLVE_ESM_FILE:
					return `resolve esm file ${job.path}${expectedToString(
						job.expected
					)}`;
				case RBDT_DIRECTORY:
					return `directory ${job.path}`;
				case RBDT_FILE:
					return `file ${job.path}`;
				case RBDT_DIRECTORY_DEPENDENCIES:
					return `directory dependencies ${job.path}`;
				case RBDT_FILE_DEPENDENCIES:
					return `file dependencies ${job.path}`;
			}
			return `unknown ${job.type} ${job.path}`;
		};
		/**
		 * @param {Job} job job
		 * @returns {string} string value
		 */
		const pathToString = job => {
			let result = ` at ${jobToString(job)}`;
			/** @type {Job | undefined} */
			(job) = job.issuer;
			while (job !== undefined) {
				result += `\n at ${jobToString(job)}`;
				job = /** @type {Job} */ (job.issuer);
			}
			return result;
		};
		const logger = /** @type {Logger} */ (this.logger);
		processAsyncTree(
			Array.from(
				deps,
				dep =>
					/** @type {Job} */ ({
						type: RBDT_RESOLVE_CJS,
						context,
						path: dep,
						expected: undefined,
						issuer: undefined
					})
			),
			20,
			(job, push, callback) => {
				const { type, context, path, expected } = job;
				/**
				 * @param {string} path path
				 * @returns {void}
				 */
				const resolveDirectory = path => {
					const key = `d\n${context}\n${path}`;
					if (resolveResults.has(key)) {
						return callback();
					}
					resolveResults.set(key, undefined);
					resolveContext(
						/** @type {string} */ (context),
						path,
						resolverContext,
						(err, _, result) => {
							if (err) {
								if (expected === false) {
									resolveResults.set(key, false);
									return callback();
								}
								invalidResolveResults.add(key);
								err.message += `\nwhile resolving '${path}' in ${context} to a directory`;
								return callback(err);
							}
							const resultPath = /** @type {ResolveRequest} */ (result).path;
							resolveResults.set(key, resultPath);
							push({
								type: RBDT_DIRECTORY,
								context: undefined,
								path: /** @type {string} */ (resultPath),
								expected: undefined,
								issuer: job
							});
							callback();
						}
					);
				};
				/**
				 * @param {string} path path
				 * @param {("f" | "c" | "e")=} symbol symbol
				 * @param {(ResolveFunctionAsync)=} resolve resolve fn
				 * @returns {void}
				 */
				const resolveFile = (path, symbol, resolve) => {
					const key = `${symbol}\n${context}\n${path}`;
					if (resolveResults.has(key)) {
						return callback();
					}
					resolveResults.set(key, undefined);
					/** @type {ResolveFunctionAsync} */
					(resolve)(
						/** @type {string} */ (context),
						path,
						resolverContext,
						(err, _, result) => {
							if (typeof expected === "string") {
								if (!err && result && result.path === expected) {
									resolveResults.set(key, result.path);
								} else {
									invalidResolveResults.add(key);
									logger.warn(
										`Resolving '${path}' in ${context} for build dependencies doesn't lead to expected result '${expected}', but to '${
											err || (result && result.path)
										}' instead. Resolving dependencies are ignored for this path.\n${pathToString(
											job
										)}`
									);
								}
							} else {
								if (err) {
									if (expected === false) {
										resolveResults.set(key, false);
										return callback();
									}
									invalidResolveResults.add(key);
									err.message += `\nwhile resolving '${path}' in ${context} as file\n${pathToString(
										job
									)}`;
									return callback(err);
								}
								const resultPath = /** @type {ResolveRequest} */ (result).path;
								resolveResults.set(key, resultPath);
								push({
									type: RBDT_FILE,
									context: undefined,
									path: /** @type {string} */ (resultPath),
									expected: undefined,
									issuer: job
								});
							}
							callback();
						}
					);
				};
				switch (type) {
					case RBDT_RESOLVE_CJS: {
						const isDirectory = /[\\/]$/.test(path);
						if (isDirectory) {
							resolveDirectory(path.slice(0, -1));
						} else {
							resolveFile(path, "f", resolveCjs);
						}
						break;
					}
					case RBDT_RESOLVE_ESM: {
						const isDirectory = /[\\/]$/.test(path);
						if (isDirectory) {
							resolveDirectory(path.slice(0, -1));
						} else {
							resolveFile(path);
						}
						break;
					}
					case RBDT_RESOLVE_DIRECTORY: {
						resolveDirectory(path);
						break;
					}
					case RBDT_RESOLVE_CJS_FILE: {
						resolveFile(path, "f", resolveCjs);
						break;
					}
					case RBDT_RESOLVE_CJS_FILE_AS_CHILD: {
						resolveFile(path, "c", resolveCjsAsChild);
						break;
					}
					case RBDT_RESOLVE_ESM_FILE: {
						resolveFile(path, "e", resolveEsm);
						break;
					}
					case RBDT_FILE: {
						if (files.has(path)) {
							callback();
							break;
						}
						files.add(path);
						/** @type {NonNullable<InputFileSystem["realpath"]>} */
						(this.fs.realpath)(path, (err, _realPath) => {
							if (err) return callback(err);
							const realPath = /** @type {string} */ (_realPath);
							if (realPath !== path) {
								fileSymlinks.add(path);
								resolveFiles.add(path);
								if (files.has(realPath)) return callback();
								files.add(realPath);
							}
							push({
								type: RBDT_FILE_DEPENDENCIES,
								context: undefined,
								path: realPath,
								expected: undefined,
								issuer: job
							});
							callback();
						});
						break;
					}
					case RBDT_DIRECTORY: {
						if (directories.has(path)) {
							callback();
							break;
						}
						directories.add(path);
						/** @type {NonNullable<InputFileSystem["realpath"]>} */
						(this.fs.realpath)(path, (err, _realPath) => {
							if (err) return callback(err);
							const realPath = /** @type {string} */ (_realPath);
							if (realPath !== path) {
								directorySymlinks.add(path);
								resolveFiles.add(path);
								if (directories.has(realPath)) return callback();
								directories.add(realPath);
							}
							push({
								type: RBDT_DIRECTORY_DEPENDENCIES,
								context: undefined,
								path: realPath,
								expected: undefined,
								issuer: job
							});
							callback();
						});
						break;
					}
					case RBDT_FILE_DEPENDENCIES: {
						// Check for known files without dependencies
						if (/\.json5?$|\.yarn-integrity$|yarn\.lock$|\.ya?ml/.test(path)) {
							process.nextTick(callback);
							break;
						}
						// Check commonjs cache for the module
						/** @type {NodeModule | undefined} */
						const module = require.cache[path];
						if (module && Array.isArray(module.children)) {
							children: for (const child of module.children) {
								const childPath = child.filename;
								if (childPath) {
									push({
										type: RBDT_FILE,
										context: undefined,
										path: childPath,
										expected: undefined,
										issuer: job
									});
									const context = dirname(this.fs, path);
									for (const modulePath of module.paths) {
										if (childPath.startsWith(modulePath)) {
											const subPath = childPath.slice(modulePath.length + 1);
											const packageMatch = /^(@[^\\/]+[\\/])[^\\/]+/.exec(
												subPath
											);
											if (packageMatch) {
												push({
													type: RBDT_FILE,
													context: undefined,
													path: `${
														modulePath +
														childPath[modulePath.length] +
														packageMatch[0] +
														childPath[modulePath.length]
													}package.json`,
													expected: false,
													issuer: job
												});
											}
											let request = subPath.replace(/\\/g, "/");
											if (request.endsWith(".js"))
												request = request.slice(0, -3);
											push({
												type: RBDT_RESOLVE_CJS_FILE_AS_CHILD,
												context,
												path: request,
												expected: child.filename,
												issuer: job
											});
											continue children;
										}
									}
									let request = relative(this.fs, context, childPath);
									if (request.endsWith(".js")) request = request.slice(0, -3);
									request = request.replace(/\\/g, "/");
									if (!request.startsWith("../") && !isAbsolute(request)) {
										request = `./${request}`;
									}
									push({
										type: RBDT_RESOLVE_CJS_FILE,
										context,
										path: request,
										expected: child.filename,
										issuer: job
									});
								}
							}
						} else if (supportsEsm && /\.m?js$/.test(path)) {
							if (!this._warnAboutExperimentalEsmTracking) {
								logger.log(
									"Node.js doesn't offer a (nice) way to introspect the ESM dependency graph yet.\n" +
										"Until a full solution is available webpack uses an experimental ESM tracking based on parsing.\n" +
										"As best effort webpack parses the ESM files to guess dependencies. But this can lead to expensive and incorrect tracking."
								);
								this._warnAboutExperimentalEsmTracking = true;
							}
							const lexer = require("es-module-lexer");
							lexer.init.then(() => {
								this.fs.readFile(path, (err, content) => {
									if (err) return callback(err);
									try {
										const context = dirname(this.fs, path);
										const source = /** @type {Buffer} */ (content).toString();
										const [imports] = lexer.parse(source);
										for (const imp of imports) {
											try {
												let dependency;
												if (imp.d === -1) {
													// import ... from "..."
													dependency = parseString(
														source.substring(imp.s - 1, imp.e + 1)
													);
												} else if (imp.d > -1) {
													// import()
													const expr = source.substring(imp.s, imp.e).trim();
													dependency = parseString(expr);
												} else {
													// e.g. import.meta
													continue;
												}

												// we should not track Node.js build dependencies
												if (dependency.startsWith("node:")) continue;
												if (builtinModules.has(dependency)) continue;

												push({
													type: RBDT_RESOLVE_ESM_FILE,
													context,
													path: dependency,
													expected: imp.d > -1 ? false : undefined,
													issuer: job
												});
											} catch (err1) {
												logger.warn(
													`Parsing of ${path} for build dependencies failed at 'import(${source.substring(
														imp.s,
														imp.e
													)})'.\n` +
														"Build dependencies behind this expression are ignored and might cause incorrect cache invalidation."
												);
												logger.debug(pathToString(job));
												logger.debug(/** @type {Error} */ (err1).stack);
											}
										}
									} catch (err2) {
										logger.warn(
											`Parsing of ${path} for build dependencies failed and all dependencies of this file are ignored, which might cause incorrect cache invalidation..`
										);
										logger.debug(pathToString(job));
										logger.debug(/** @type {Error} */ (err2).stack);
									}
									process.nextTick(callback);
								});
							}, callback);
							break;
						} else {
							logger.log(
								`Assuming ${path} has no dependencies as we were unable to assign it to any module system.`
							);
							logger.debug(pathToString(job));
						}
						process.nextTick(callback);
						break;
					}
					case RBDT_DIRECTORY_DEPENDENCIES: {
						const match =
							/(^.+[\\/]node_modules[\\/](?:@[^\\/]+[\\/])?[^\\/]+)/.exec(path);
						const packagePath = match ? match[1] : path;
						const packageJson = join(this.fs, packagePath, "package.json");
						this.fs.readFile(packageJson, (err, content) => {
							if (err) {
								if (err.code === "ENOENT") {
									resolveMissing.add(packageJson);
									const parent = dirname(this.fs, packagePath);
									if (parent !== packagePath) {
										push({
											type: RBDT_DIRECTORY_DEPENDENCIES,
											context: undefined,
											path: parent,
											expected: undefined,
											issuer: job
										});
									}
									callback();
									return;
								}
								return callback(err);
							}
							resolveFiles.add(packageJson);
							let packageData;
							try {
								packageData = JSON.parse(
									/** @type {Buffer} */ (content).toString("utf-8")
								);
							} catch (parseErr) {
								return callback(/** @type {Error} */ (parseErr));
							}
							const depsObject = packageData.dependencies;
							const optionalDepsObject = packageData.optionalDependencies;
							const allDeps = new Set();
							const optionalDeps = new Set();
							if (typeof depsObject === "object" && depsObject) {
								for (const dep of Object.keys(depsObject)) {
									allDeps.add(dep);
								}
							}
							if (
								typeof optionalDepsObject === "object" &&
								optionalDepsObject
							) {
								for (const dep of Object.keys(optionalDepsObject)) {
									allDeps.add(dep);
									optionalDeps.add(dep);
								}
							}
							for (const dep of allDeps) {
								push({
									type: RBDT_RESOLVE_DIRECTORY,
									context: packagePath,
									path: dep,
									expected: !optionalDeps.has(dep),
									issuer: job
								});
							}
							callback();
						});
						break;
					}
				}
			},
			err => {
				if (err) return callback(err);
				for (const l of fileSymlinks) files.delete(l);
				for (const l of directorySymlinks) directories.delete(l);
				for (const k of invalidResolveResults) resolveResults.delete(k);
				callback(null, {
					files,
					directories,
					missing,
					resolveResults,
					resolveDependencies: {
						files: resolveFiles,
						directories: resolveDirectories,
						missing: resolveMissing
					}
				});
			}
		);
	}
@param {string} context context directory
@param {Iterable<string>} deps dependencies
@param {(err?: Error | null, resolveBuildDependenciesResult?: ResolveBuildDependenciesResult) => void} callback callback function
@returns {void}