resolveDependencies(fs, options, callback) {
		const cmf = this;
		const {
			resource,
			resourceQuery,
			resourceFragment,
			recursive,
			regExp,
			include,
			exclude,
			referencedExports,
			category,
			typePrefix,
			attributes
		} = options;
		if (!regExp || !resource) return callback(null, []);

		/**
		 * @param {string} ctx context
		 * @param {string} directory directory
		 * @param {Set<string>} visited visited
		 * @param {ResolveDependenciesCallback} callback callback
		 */
		const addDirectoryChecked = (ctx, directory, visited, callback) => {
			/** @type {NonNullable<InputFileSystem["realpath"]>} */
			(fs.realpath)(directory, (err, _realPath) => {
				if (err) return callback(err);
				const realPath = /** @type {string} */ (_realPath);
				if (visited.has(realPath)) return callback(null, []);
				/** @type {Set<string> | undefined} */
				let recursionStack;
				addDirectory(
					ctx,
					directory,
					(_, dir, callback) => {
						if (recursionStack === undefined) {
							recursionStack = new Set(visited);
							recursionStack.add(realPath);
						}
						addDirectoryChecked(ctx, dir, recursionStack, callback);
					},
					callback
				);
			});
		};

		/**
		 * @param {string} ctx context
		 * @param {string} directory directory
		 * @param {(context: string, subResource: string, callback: () => void) => void} addSubDirectory addSubDirectoryFn
		 * @param {ResolveDependenciesCallback} callback callback
		 */
		const addDirectory = (ctx, directory, addSubDirectory, callback) => {
			fs.readdir(directory, (err, files) => {
				if (err) return callback(err);
				const processedFiles = cmf.hooks.contextModuleFiles.call(
					/** @type {string[]} */ (files).map(file => file.normalize("NFC"))
				);
				if (!processedFiles || processedFiles.length === 0)
					return callback(null, []);
				asyncLib.map(
					processedFiles.filter(p => p.indexOf(".") !== 0),
					(segment, callback) => {
						const subResource = join(fs, directory, segment);

						if (!exclude || !subResource.match(exclude)) {
							fs.stat(subResource, (err, _stat) => {
								if (err) {
									if (err.code === "ENOENT") {
										// ENOENT is ok here because the file may have been deleted between
										// the readdir and stat calls.
										return callback();
									}
									return callback(err);
								}

								const stat = /** @type {IStats} */ (_stat);

								if (stat.isDirectory()) {
									if (!recursive) return callback();
									addSubDirectory(ctx, subResource, callback);
								} else if (
									stat.isFile() &&
									(!include || subResource.match(include))
								) {
									/** @type {{ context: string, request: string }} */
									const obj = {
										context: ctx,
										request: `.${subResource.slice(ctx.length).replace(/\\/g, "/")}`
									};

									this.hooks.alternativeRequests.callAsync(
										[obj],
										options,
										(err, alternatives) => {
											if (err) return callback(err);
											callback(
												null,
												/** @type {ContextAlternativeRequest[]} */
												(alternatives)
													.filter(obj =>
														regExp.test(/** @type {string} */ (obj.request))
													)
													.map(obj => {
														const dep = new ContextElementDependency(
															`${obj.request}${resourceQuery}${resourceFragment}`,
															obj.request,
															typePrefix,
															/** @type {string} */
															(category),
															referencedExports,
															obj.context,
															attributes
														);
														dep.optional = true;
														return dep;
													})
											);
										}
									);
								} else {
									callback();
								}
							});
						} else {
							callback();
						}
					},
					(err, result) => {
						if (err) return callback(err);

						if (!result) return callback(null, []);

						const flattenedResult = [];

						for (const item of result) {
							if (item) flattenedResult.push(...item);
						}

						callback(null, flattenedResult);
					}
				);
			});
		};

		/**
		 * @param {string} ctx context
		 * @param {string} dir dir
		 * @param {ResolveDependenciesCallback} callback callback
		 * @returns {void}
		 */
		const addSubDirectory = (ctx, dir, callback) =>
			addDirectory(ctx, dir, addSubDirectory, callback);

		/**
		 * @param {string} resource resource
		 * @param {ResolveDependenciesCallback} callback callback
		 */
		const visitResource = (resource, callback) => {
			if (typeof fs.realpath === "function") {
				addDirectoryChecked(resource, resource, new Set(), callback);
			} else {
				addDirectory(resource, resource, addSubDirectory, callback);
			}
		};

		if (typeof resource === "string") {
			visitResource(resource, callback);
		} else {
			asyncLib.map(resource, visitResource, (err, _result) => {
				if (err) return callback(err);
				const result = /** @type {ContextElementDependency[][]} */ (_result);

				// result dependencies should have unique userRequest
				// ordered by resolve result
				/** @type {Set<string>} */
				const temp = new Set();
				/** @type {ContextElementDependency[]} */
				const res = [];
				for (let i = 0; i < result.length; i++) {
					const inner = result[i];
					for (const el of inner) {
						if (temp.has(el.userRequest)) continue;
						res.push(el);
						temp.add(el.userRequest);
					}
				}
				callback(null, res);
			});
		}
	}
@param {InputFileSystem} fs file system
@param {ContextModuleOptions} options options
@param {ResolveDependenciesCallback} callback callback function
@returns {void}