_createLoaderContext(resolver, options, compilation, fs, hooks) {
		const { requestShortener } = compilation.runtimeTemplate;
		const getCurrentLoaderName = () => {
			const currentLoader = this.getCurrentLoader(
				/** @type {LoaderContext<EXPECTED_ANY>} */ (loaderContext)
			);
			if (!currentLoader) return "(not in loader scope)";
			return requestShortener.shorten(currentLoader.loader);
		};
		/**
		 * @returns {ResolveContext} resolve context
		 */
		const getResolveContext = () => ({
			fileDependencies: {
				add: d =>
					/** @type {LoaderContext<EXPECTED_ANY>} */ (
						loaderContext
					).addDependency(d)
			},
			contextDependencies: {
				add: d =>
					/** @type {LoaderContext<EXPECTED_ANY>} */ (
						loaderContext
					).addContextDependency(d)
			},
			missingDependencies: {
				add: d =>
					/** @type {LoaderContext<EXPECTED_ANY>} */ (
						loaderContext
					).addMissingDependency(d)
			}
		});
		const getAbsolutify = memoize(() =>
			absolutify.bindCache(compilation.compiler.root)
		);
		const getAbsolutifyInContext = memoize(() =>
			absolutify.bindContextCache(
				/** @type {string} */
				(this.context),
				compilation.compiler.root
			)
		);
		const getContextify = memoize(() =>
			contextify.bindCache(compilation.compiler.root)
		);
		const getContextifyInContext = memoize(() =>
			contextify.bindContextCache(
				/** @type {string} */
				(this.context),
				compilation.compiler.root
			)
		);
		const utils = {
			/**
			 * @param {string} context context
			 * @param {string} request request
			 * @returns {string} result
			 */
			absolutify: (context, request) =>
				context === this.context
					? getAbsolutifyInContext()(request)
					: getAbsolutify()(context, request),
			/**
			 * @param {string} context context
			 * @param {string} request request
			 * @returns {string} result
			 */
			contextify: (context, request) =>
				context === this.context
					? getContextifyInContext()(request)
					: getContextify()(context, request),
			/**
			 * @param {HashFunction=} type type
			 * @returns {Hash} hash
			 */
			createHash: type =>
				createHash(
					type ||
						/** @type {HashFunction} */
						(compilation.outputOptions.hashFunction)
				)
		};
		/** @type {import("../declarations/LoaderContext").NormalModuleLoaderContext<T>} */
		const loaderContext = {
			version: 2,
			/**
			 * @param {import("../declarations/LoaderContext").Schema=} schema schema
			 * @returns {T} options
			 */
			getOptions: schema => {
				const loader = this.getCurrentLoader(
					/** @type {LoaderContext<EXPECTED_ANY>} */ (loaderContext)
				);

				let { options } = /** @type {LoaderItem} */ (loader);

				if (typeof options === "string") {
					if (options.startsWith("{") && options.endsWith("}")) {
						try {
							options = parseJson(options);
						} catch (err) {
							throw new Error(
								`Cannot parse string options: ${/** @type {Error} */ (err).message}`
							);
						}
					} else {
						options = querystring.parse(options, "&", "=", {
							maxKeys: 0
						});
					}
				}

				if (options === null || options === undefined) {
					options = {};
				}

				if (schema) {
					let name = "Loader";
					let baseDataPath = "options";
					let match;
					if (schema.title && (match = /^(.+) (.+)$/.exec(schema.title))) {
						[, name, baseDataPath] = match;
					}
					getValidate()(schema, /** @type {EXPECTED_OBJECT} */ (options), {
						name,
						baseDataPath
					});
				}

				return /** @type {T} */ (options);
			},
			emitWarning: warning => {
				if (!(warning instanceof Error)) {
					warning = new NonErrorEmittedError(warning);
				}
				this.addWarning(
					new ModuleWarning(warning, {
						from: getCurrentLoaderName()
					})
				);
			},
			emitError: error => {
				if (!(error instanceof Error)) {
					error = new NonErrorEmittedError(error);
				}
				this.addError(
					new ModuleError(error, {
						from: getCurrentLoaderName()
					})
				);
			},
			getLogger: name => {
				const currentLoader = this.getCurrentLoader(
					/** @type {LoaderContext<EXPECTED_ANY>} */ (loaderContext)
				);
				return compilation.getLogger(() =>
					[currentLoader && currentLoader.loader, name, this.identifier()]
						.filter(Boolean)
						.join("|")
				);
			},
			resolve(context, request, callback) {
				resolver.resolve({}, context, request, getResolveContext(), callback);
			},
			getResolve(options) {
				const child = options ? resolver.withOptions(options) : resolver;
				return /** @type {ReturnType<import("../declarations/LoaderContext").NormalModuleLoaderContext<T>["getResolve"]>} */ (
					(context, request, callback) => {
						if (callback) {
							child.resolve(
								{},
								context,
								request,
								getResolveContext(),
								callback
							);
						} else {
							return new Promise((resolve, reject) => {
								child.resolve(
									{},
									context,
									request,
									getResolveContext(),
									(err, result) => {
										if (err) reject(err);
										else resolve(result);
									}
								);
							});
						}
					}
				);
			},
			emitFile: (name, content, sourceMap, assetInfo) => {
				const buildInfo = /** @type {BuildInfo} */ (this.buildInfo);

				if (!buildInfo.assets) {
					buildInfo.assets = Object.create(null);
					buildInfo.assetsInfo = new Map();
				}

				const assets =
					/** @type {NonNullable<KnownBuildInfo["assets"]>} */
					(buildInfo.assets);
				const assetsInfo =
					/** @type {NonNullable<KnownBuildInfo["assetsInfo"]>} */
					(buildInfo.assetsInfo);

				assets[name] = this.createSourceForAsset(
					/** @type {string} */ (options.context),
					name,
					content,
					sourceMap,
					compilation.compiler.root
				);
				assetsInfo.set(name, assetInfo);
			},
			addBuildDependency: dep => {
				const buildInfo = /** @type {BuildInfo} */ (this.buildInfo);

				if (buildInfo.buildDependencies === undefined) {
					buildInfo.buildDependencies = new LazySet();
				}
				buildInfo.buildDependencies.add(dep);
			},
			utils,
			rootContext: /** @type {string} */ (options.context),
			webpack: true,
			sourceMap: Boolean(this.useSourceMap),
			mode: options.mode || "production",
			hashFunction: /** @type {string} */ (options.output.hashFunction),
			hashDigest: /** @type {string} */ (options.output.hashDigest),
			hashDigestLength: /** @type {number} */ (options.output.hashDigestLength),
			hashSalt: /** @type {string} */ (options.output.hashSalt),
			_module: this,
			_compilation: compilation,
			_compiler: compilation.compiler,
			fs
		};

		Object.assign(loaderContext, options.loader);

		// After `hooks.loader.call` is called, the loaderContext is typed as LoaderContext<EXPECTED_ANY>
		hooks.loader.call(
			/** @type {LoaderContext<EXPECTED_ANY>} */
			(loaderContext),
			this
		);

		return /** @type {LoaderContext<EXPECTED_ANY>} */ (loaderContext);
	}
@private
@template T
@param {ResolverWithOptions} resolver a resolver
@param {WebpackOptions} options webpack options
@param {Compilation} compilation the compilation
@param {InputFileSystem} fs file system from reading
@param {NormalModuleCompilationHooks} hooks the hooks
@returns {import("../declarations/LoaderContext").LoaderContext<T>} loader context