sass-loader 16.0.7 → 17.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,647 @@
1
+ import path from "node:path";
2
+ import url from "node:url";
3
+
4
+ // eslint-disable-next-line jsdoc/reject-any-type
5
+ /** @typedef {any} EXPECTED_ANY */
6
+
7
+ /**
8
+ * @typedef {object} SourceLocation
9
+ * @property {number} line line number
10
+ * @property {number} column column number
11
+ * @property {number} offset character offset
12
+ */
13
+
14
+ /**
15
+ * @typedef {object} SourceSpan
16
+ * @property {SourceLocation} start start location
17
+ * @property {SourceLocation} end end location
18
+ * @property {URL=} url canonical URL of the file
19
+ * @property {string} text covered text
20
+ * @property {string=} context surrounding context
21
+ */
22
+
23
+ /** @typedef {{ deprecation?: boolean, span?: SourceSpan, stack?: string }} LoggerWarnOptions */
24
+
25
+ /**
26
+ * @typedef {object} Logger
27
+ * @property {((message: string, options: LoggerWarnOptions) => void)=} warn warn handler
28
+ * @property {((message: string, options: { span: SourceSpan }) => void)=} debug debug handler
29
+ */
30
+
31
+ /**
32
+ * @typedef {object} CompileResult
33
+ * @property {Buffer | string} css css output
34
+ * @property {RawSourceMap=} sourceMap source map
35
+ * @property {URL[]} loadedUrls loaded URLs
36
+ */
37
+
38
+ /**
39
+ * @typedef {object} Importer
40
+ * @property {(originalUrl: string, context: { containingUrl: URL | null, fromImport: boolean }) => Promise<URL | null>} canonicalize canonicalize
41
+ * @property {(canonicalUrl: URL) => Promise<{ contents: string, syntax: "scss" | "indented" | "css", sourceMapUrl?: URL } | null>} load load
42
+ */
43
+
44
+ /** @typedef {"expanded" | "compressed"} OutputStyle */
45
+
46
+ /**
47
+ * @typedef {object} KnownSassOptions
48
+ * @property {"scss" | "indented" | "css"=} syntax syntax
49
+ * @property {URL=} url url
50
+ * @property {"expanded" | "compressed"=} style style
51
+ * @property {string[]=} loadPaths load paths
52
+ * @property {boolean=} sourceMap source map
53
+ * @property {boolean=} sourceMapIncludeSources source map include sources
54
+ * @property {Importer[]=} importers importers
55
+ * @property {Logger=} logger logger
56
+ */
57
+
58
+ /** @typedef {KnownSassOptions & Record<string, EXPECTED_ANY>} SassOptions */
59
+
60
+ /**
61
+ * @typedef {object} AsyncCompiler
62
+ * @property {(source: string, options?: SassOptions) => Promise<CompileResult>} compileStringAsync compile a string
63
+ * @property {() => Promise<void>} dispose dispose the compiler
64
+ */
65
+
66
+ /** @typedef {{ info: string; compileStringAsync(source: string, options?: SassOptions): Promise<CompileResult>; initAsyncCompiler?(): Promise<AsyncCompiler> }} SassImplementation */
67
+
68
+ /** @typedef {"auto" | "modern" | "modern-compiler"} ApiType */
69
+
70
+ /** @typedef {import("webpack").LoaderContext<LoaderOptions>} LoaderContext */
71
+
72
+ /**
73
+ * @typedef {object} LoaderOptions
74
+ * @property {SassImplementation=} implementation SaSS implementation
75
+ * @property {SassOptions | ((loaderContext: LoaderContext) => SassOptions)=} sassOptions SaSS options
76
+ * @property {boolean=} sourceMap true if source map is enabled, otherwise false
77
+ * @property {string | ((content: string, loaderContext: LoaderContext) => string)=} additionalData prepends Sass/SCSS code before the actual entry file
78
+ * @property {boolean=} webpackImporter true if webpack importer is enabled, otherwise false
79
+ * @property {ApiType=} api API type
80
+ * @property {boolean=} warnRuleAsWarning true if treats the `@warn` rule as a webpack warning, otherwise false
81
+ */
82
+
83
+ /** @typedef {(context: string, request: string, fromImport?: boolean) => Promise<string>} Resolver */
84
+ /** @typedef {{ resolve: (context: string, request: string) => Promise<string>, context: string, possibleRequests: string[] }[]} ResolutionMap */
85
+ /** @typedef {{ version: number, sources: string[], names: string[], sourceRoot?: string, sourcesContent?: string[], mappings: string, file: string, debugId?: string, ignoreList?: number[] }} RawSourceMap */
86
+ /** @typedef {Error & { formatted?: string, span?: { url?: URL, start: { line: number, column: number }, context?: string } }} SassError */
87
+
88
+ /**
89
+ * Convert a string `implementation` option into something the ECMAScript
90
+ * `import()` expression actually accepts. Bare package specifiers and
91
+ * `file:` URLs are passed through unchanged; absolute filesystem paths
92
+ * (including Windows paths like `C:\\...`) are converted to `file:` URLs
93
+ * — dynamic `import()` rejects those otherwise.
94
+ * @param {string} specifier import specifier
95
+ * @returns {string} a valid dynamic import specifier
96
+ */
97
+ function normalizeImportSpecifier(specifier) {
98
+ if (specifier.startsWith("file:")) {
99
+ return specifier;
100
+ }
101
+ if (path.isAbsolute(specifier)) {
102
+ return url.pathToFileURL(specifier).href;
103
+ }
104
+ return specifier;
105
+ }
106
+
107
+ /**
108
+ * This function is not Webpack-specific and can be used by tools wishing to mimic `sass-loader`'s behaviour, so its signature should not be changed.
109
+ * @param {SassImplementation | string | undefined} implementation sass implementation
110
+ * @returns {Promise<SassImplementation>} resolved sass implementation
111
+ */
112
+ async function getSassImplementation(implementation) {
113
+ /** @type {SassImplementation} */
114
+ let resolvedImplementation;
115
+ if (!implementation) {
116
+ try {
117
+ resolvedImplementation = /** @type {SassImplementation} */
118
+ await import("sass-embedded");
119
+ } catch (err) {
120
+ // Only fall back to `sass` when `sass-embedded` is not installed.
121
+ // Any other failure (e.g. a broken install or a side-effect throw at
122
+ // module-load time) should surface so the user can diagnose it
123
+ // instead of being silently masked by the `sass` fallback.
124
+ const {
125
+ code
126
+ } = /** @type {NodeJS.ErrnoException} */err;
127
+ if (code !== "ERR_MODULE_NOT_FOUND" && code !== "MODULE_NOT_FOUND") {
128
+ throw err;
129
+ }
130
+ resolvedImplementation = /** @type {SassImplementation} */
131
+ await import("sass");
132
+ }
133
+ } else if (typeof implementation === "string") {
134
+ resolvedImplementation = /** @type {SassImplementation} */
135
+ await import(normalizeImportSpecifier(implementation));
136
+ } else {
137
+ resolvedImplementation = implementation;
138
+ }
139
+ const {
140
+ info
141
+ } = resolvedImplementation;
142
+ if (!info) {
143
+ throw new Error("Unknown Sass implementation.");
144
+ }
145
+ const infoParts = info.split("\t");
146
+ if (infoParts.length < 2) {
147
+ throw new Error(`Unknown Sass implementation "${info}".`);
148
+ }
149
+ const [implementationName] = infoParts;
150
+ if (implementationName === "dart-sass") {
151
+ return resolvedImplementation;
152
+ } else if (implementationName === "sass-embedded") {
153
+ return resolvedImplementation;
154
+ }
155
+ throw new Error(`Unknown Sass implementation "${implementationName}".`);
156
+ }
157
+
158
+ /**
159
+ * @param {LoaderContext} loaderContext loader context
160
+ * @returns {boolean} true when mode is production, otherwise false
161
+ */
162
+ function isProductionLikeMode(loaderContext) {
163
+ return loaderContext.mode === "production" || !loaderContext.mode;
164
+ }
165
+
166
+ /**
167
+ * Derives the sass options from the loader context and normalizes its values with sane defaults.
168
+ * @param {LoaderContext} loaderContext loader context
169
+ * @param {LoaderOptions} loaderOptions loader options
170
+ * @param {string} content content
171
+ * @param {boolean} useSourceMap true when need to generate source maps, otherwise false
172
+ * @returns {Promise<Required<KnownSassOptions> & { data: string }>} sass options
173
+ */
174
+ async function getSassOptions(loaderContext, loaderOptions, content, useSourceMap) {
175
+ /** @type {SassOptions} */
176
+ const options = loaderOptions.sassOptions ? typeof loaderOptions.sassOptions === "function" ? loaderOptions.sassOptions(loaderContext) || {} : loaderOptions.sassOptions : {};
177
+ /** @type {KnownSassOptions & { data: string }} */
178
+ const sassOptions = {
179
+ ...options,
180
+ data: loaderOptions.additionalData ? typeof loaderOptions.additionalData === "function" ? await loaderOptions.additionalData(content, loaderContext) : `${loaderOptions.additionalData}\n${content}` : content
181
+ };
182
+ if (!sassOptions.logger) {
183
+ const needEmitWarning = loaderOptions.warnRuleAsWarning !== false;
184
+ const logger = loaderContext.getLogger("sass-loader");
185
+ /**
186
+ * @param {SourceSpan} span span
187
+ * @returns {string} formatted span
188
+ */
189
+ const formatSpan = span => `Warning on line ${span.start.line}, column ${span.start.column} of ${span.url || "-"}:${span.start.line}:${span.start.column}:\n`;
190
+ /**
191
+ * @param {SourceSpan} span span
192
+ * @returns {string} formatted debug span
193
+ */
194
+ const formatDebugSpan = span => `[debug:${span.start.line}:${span.start.column}] `;
195
+ sassOptions.logger = {
196
+ /**
197
+ * @param {string} message message
198
+ * @param {{ span: SourceSpan }} loggerOptions logger options
199
+ * @returns {void}
200
+ */
201
+ debug(message, loggerOptions) {
202
+ let builtMessage = "";
203
+ if (loggerOptions.span) {
204
+ builtMessage = formatDebugSpan(loggerOptions.span);
205
+ }
206
+ builtMessage += message;
207
+ logger.debug(builtMessage);
208
+ },
209
+ /**
210
+ * @param {string} message message
211
+ * @param {LoggerWarnOptions} loggerOptions logger options
212
+ * @returns {void}
213
+ */
214
+ warn(message, loggerOptions) {
215
+ let builtMessage = "";
216
+ if (loggerOptions.deprecation) {
217
+ builtMessage += "Deprecation ";
218
+ }
219
+ if (loggerOptions.span) {
220
+ builtMessage += formatSpan(loggerOptions.span);
221
+ }
222
+ builtMessage += message;
223
+ if (loggerOptions.span && loggerOptions.span.context) {
224
+ builtMessage += `\n\n${loggerOptions.span.start.line} | ${loggerOptions.span.context}`;
225
+ }
226
+ if (loggerOptions.stack && loggerOptions.stack !== "null") {
227
+ builtMessage += `\n\n${loggerOptions.stack}`;
228
+ }
229
+ if (needEmitWarning) {
230
+ const warning = new Error(builtMessage);
231
+ warning.name = "SassWarning";
232
+ warning.stack = undefined;
233
+ loaderContext.emitWarning(warning);
234
+ } else {
235
+ logger.warn(builtMessage);
236
+ }
237
+ }
238
+ };
239
+ }
240
+ const {
241
+ resourcePath
242
+ } = loaderContext;
243
+ sassOptions.url = url.pathToFileURL(resourcePath);
244
+
245
+ // opt.outputStyle
246
+ if (!sassOptions.style && isProductionLikeMode(loaderContext)) {
247
+ sassOptions.style = "compressed";
248
+ }
249
+ if (useSourceMap) {
250
+ sassOptions.sourceMap = true;
251
+ sassOptions.sourceMapIncludeSources = true;
252
+ }
253
+
254
+ // If we are compiling sass and indentedSyntax isn't set, automatically set it.
255
+ if (typeof sassOptions.syntax === "undefined") {
256
+ const ext = path.extname(resourcePath);
257
+ if (ext && ext.toLowerCase() === ".scss") {
258
+ sassOptions.syntax = "scss";
259
+ } else if (ext && ext.toLowerCase() === ".sass") {
260
+ sassOptions.syntax = "indented";
261
+ } else if (ext && ext.toLowerCase() === ".css") {
262
+ sassOptions.syntax = "css";
263
+ }
264
+ }
265
+ sassOptions.loadPaths = [
266
+ // We use `loadPaths` in context for resolver, so it should be always absolute
267
+ ...(sassOptions.loadPaths ? [...sassOptions.loadPaths] : []).map(includePath => path.isAbsolute(includePath) ? includePath : path.join(process.cwd(), includePath)), ...(process.env.SASS_PATH ? process.env.SASS_PATH.split(process.platform === "win32" ? ";" : ":") : [])];
268
+ sassOptions.importers = sassOptions.importers ? Array.isArray(sassOptions.importers) ? [...sassOptions.importers] : [sassOptions.importers] : [];
269
+ return /** @type {Required<KnownSassOptions> & { data: string }} */sassOptions;
270
+ }
271
+ const MODULE_REQUEST_REGEX = /^[^?]*~/;
272
+
273
+ // Examples:
274
+ // - ~package
275
+ // - ~package/
276
+ // - ~@org
277
+ // - ~@org/
278
+ // - ~@org/package
279
+ // - ~@org/package/
280
+ const IS_MODULE_IMPORT = /^~([^/]+|[^/]+\/|@[^/]+[/][^/]+|@[^/]+\/?|@[^/]+[/][^/]+\/)$/;
281
+ const IS_PKG_SCHEME = /^pkg:/i;
282
+
283
+ /**
284
+ * When `sass` tries to resolve an import, it uses a special algorithm.
285
+ * Since the `sass-loader` uses webpack to resolve the modules, we need to simulate that algorithm.
286
+ * This function returns an array of import paths to try.
287
+ * The last entry in the array is always the original url to enable straight-forward webpack.config aliases.
288
+ *
289
+ * We don't need emulate `dart-sass` "It's not clear which file to import." errors (when "file.ext" and "_file.ext" files are present simultaneously in the same directory).
290
+ * This reduces performance and `dart-sass` always do it on own side.
291
+ * @param {string} url url
292
+ * @param {boolean} forWebpackResolver true when for webpack resolver, otherwise false
293
+ * @param {boolean} fromImport true when from `@import`, otherwise false
294
+ * @returns {string[]} possible requests
295
+ */
296
+ function getPossibleRequests(url, forWebpackResolver = false, fromImport = false) {
297
+ let request = url;
298
+
299
+ // In case there is module request, send this to webpack resolver
300
+ if (forWebpackResolver) {
301
+ if (MODULE_REQUEST_REGEX.test(url)) {
302
+ request = request.replace(MODULE_REQUEST_REGEX, "");
303
+ }
304
+ if (IS_PKG_SCHEME.test(url)) {
305
+ request = `${request.slice(4)}`;
306
+ return [...new Set([request, url])];
307
+ }
308
+ if (IS_MODULE_IMPORT.test(url) || IS_PKG_SCHEME.test(url)) {
309
+ request = request[request.length - 1] === "/" ? request : `${request}/`;
310
+ return [...new Set([request, url])];
311
+ }
312
+ }
313
+
314
+ // Keep in mind: ext can also be something like '.datepicker' when the true extension is omitted and the filename contains a dot.
315
+ // @see https://github.com/webpack/sass/issues/167
316
+ const extension = path.extname(request).toLowerCase();
317
+
318
+ // Because @import is also defined in CSS, Sass needs a way of compiling plain CSS @imports without trying to import the files at compile time.
319
+ // To accomplish this, and to ensure SCSS is as much of a superset of CSS as possible, Sass will compile any @imports with the following characteristics to plain CSS imports:
320
+ // - imports where the URL ends with .css.
321
+ // - imports where the URL begins http:// or https://.
322
+ // - imports where the URL is written as a url().
323
+ // - imports that have media queries.
324
+ //
325
+ // sass outputs as is `@import "style.css"`, but `@use "style.css"` should include CSS content
326
+ if (extension === ".css") {
327
+ return fromImport ? [] : [url];
328
+ }
329
+ const dirname = path.dirname(request).replaceAll("\\", "/");
330
+ const normalizedDirname = dirname === "." ? "" : `${dirname}/`;
331
+ const basename = path.basename(request);
332
+ const basenameWithoutExtension = path.basename(request, extension);
333
+ return [...new Set([...[fromImport ? [`${normalizedDirname}_${basenameWithoutExtension}.import${extension}`, `${normalizedDirname}${basenameWithoutExtension}.import${extension}`] : []].flat(), `${normalizedDirname}_${basename}`, `${normalizedDirname}${basename}`, ...(forWebpackResolver ? [url] : [])])];
334
+ }
335
+
336
+ /**
337
+ * @param {(context: string, request: string, callback: (error: Error | null, result: string) => void) => void} callbackResolve callback resolve
338
+ * @returns {(context: string, request: string) => Promise<string>} promise resolve
339
+ */
340
+ function promiseResolve(callbackResolve) {
341
+ return (context, request) => new Promise((resolve, reject) => {
342
+ callbackResolve(context, request, (error, result) => {
343
+ if (error) {
344
+ reject(error);
345
+ } else {
346
+ resolve(result);
347
+ }
348
+ });
349
+ });
350
+ }
351
+
352
+ /**
353
+ * @param {ResolutionMap} resolutionMap resolution map
354
+ * @returns {Promise<string>} resolved value
355
+ */
356
+ async function startResolving(resolutionMap) {
357
+ if (resolutionMap.length === 0) {
358
+ throw new Error("Next");
359
+ }
360
+ const [{
361
+ possibleRequests
362
+ }] = resolutionMap;
363
+ if (possibleRequests.length === 0) {
364
+ throw new Error("Next");
365
+ }
366
+ const [{
367
+ resolve,
368
+ context
369
+ }] = resolutionMap;
370
+ try {
371
+ return await resolve(context, possibleRequests[0]);
372
+ } catch {
373
+ const [, ...tailResult] = possibleRequests;
374
+ if (tailResult.length === 0) {
375
+ const [, ...tailResolutionMap] = resolutionMap;
376
+ return startResolving(tailResolutionMap);
377
+ }
378
+ resolutionMap[0].possibleRequests = tailResult;
379
+ return startResolving(resolutionMap);
380
+ }
381
+ }
382
+
383
+ // `[drive_letter]:\` + `\\[server]\[sharename]\`
384
+ const IS_NATIVE_WIN32_PATH = /^[a-z]:[/\\]|^\\\\/i;
385
+
386
+ /**
387
+ * Create the resolve function used in the custom Sass importer.
388
+ * Can be used by external tools to mimic how `sass-loader` works, for example
389
+ * in a Jest transform. Such usages will want to wrap `resolve.create` from
390
+ * [`enhanced-resolve`]{@link https://github.com/webpack/enhanced-resolve} to
391
+ * pass as the `resolverFactory` argument.
392
+ * @param {LoaderContext["getResolve"]} resolverFactory a factory function for creating a Webpack resolver.
393
+ * @returns {Resolver} webpack resolver
394
+ */
395
+ function getWebpackResolver(resolverFactory) {
396
+ // We only have one difference with the built-in sass resolution logic and out resolution logic:
397
+ // First, we look at the files starting with `_`, then without `_` (i.e. `_name.sass`, `_name.scss`, `_name.css`, `name.sass`, `name.scss`, `name.css`),
398
+ // although `sass` look together by extensions (i.e. `_name.sass`/`name.sass`/`_name.scss`/`name.scss`/`_name.css`/`name.css`).
399
+ // It shouldn't be a problem because `sass` throw errors:
400
+ // - on having `_name.sass` and `name.sass` (extension can be `sass`, `scss` or `css`) in the same directory
401
+ // - on having `_name.sass` and `_name.scss` in the same directory
402
+ //
403
+ // Also `sass` prefer `sass`/`scss` over `css`.
404
+ const webpackModuleResolve = promiseResolve(resolverFactory({
405
+ dependencyType: "sass",
406
+ conditionNames: ["sass", "style", "..."],
407
+ mainFields: ["sass", "style", "main", "..."],
408
+ mainFiles: ["_index", "index", "..."],
409
+ extensions: [".sass", ".scss", ".css"],
410
+ restrictions: [/\.((sa|sc|c)ss)$/i],
411
+ preferRelative: true
412
+ }));
413
+ const webpackImportResolve = promiseResolve(resolverFactory({
414
+ dependencyType: "sass",
415
+ conditionNames: ["sass", "style", "..."],
416
+ mainFields: ["sass", "style", "main", "..."],
417
+ mainFiles: ["_index.import", "_index", "index.import", "index", "..."],
418
+ extensions: [".sass", ".scss", ".css"],
419
+ restrictions: [/\.((sa|sc|c)ss)$/i],
420
+ preferRelative: true
421
+ }));
422
+ return (context, request, fromImport) => {
423
+ const originalRequest = request;
424
+ const isFileScheme = originalRequest.slice(0, 5).toLowerCase() === "file:";
425
+ if (isFileScheme) {
426
+ try {
427
+ request = url.fileURLToPath(originalRequest);
428
+ } catch {
429
+ request = request.slice(7);
430
+ }
431
+ }
432
+
433
+ /** @type {ResolutionMap} */
434
+ let resolutionMap = [];
435
+ const webpackPossibleRequests = getPossibleRequests(request, true, fromImport);
436
+ resolutionMap = [...resolutionMap, {
437
+ resolve: fromImport ? webpackImportResolve : webpackModuleResolve,
438
+ context: path.dirname(context),
439
+ possibleRequests: webpackPossibleRequests
440
+ }];
441
+ return startResolving(resolutionMap);
442
+ };
443
+ }
444
+
445
+ /**
446
+ * @param {LoaderContext} loaderContext loader context
447
+ * @returns {Importer} the modern webpack importer
448
+ */
449
+ function getModernWebpackImporter(loaderContext) {
450
+ const resolve = getWebpackResolver(loaderContext.getResolve);
451
+ return {
452
+ /**
453
+ * @param {string} originalUrl original url
454
+ * @param {{ fromImport: boolean, containingUrl: URL | null }} context context
455
+ * @returns {Promise<URL | null>} canonicalized URL
456
+ */
457
+ async canonicalize(originalUrl, context) {
458
+ const {
459
+ fromImport
460
+ } = context;
461
+ const prev = context.containingUrl ? url.fileURLToPath(context.containingUrl.toString()) : loaderContext.resourcePath;
462
+ let result;
463
+ try {
464
+ result = await resolve(prev, originalUrl, fromImport);
465
+ } catch {
466
+ // If no stylesheets are found, the importer should return null.
467
+ return null;
468
+ }
469
+ loaderContext.addDependency(path.normalize(result));
470
+ return url.pathToFileURL(result);
471
+ },
472
+ /**
473
+ * @param {URL} canonicalUrl canonical url
474
+ * @returns {Promise<{ contents: string, syntax: "scss" | "indented" | "css", sourceMapUrl: URL } | null>} load result
475
+ */
476
+ async load(canonicalUrl) {
477
+ const ext = path.extname(canonicalUrl.pathname);
478
+
479
+ /** @type {"scss" | "indented" | "css"} */
480
+ let syntax;
481
+ if (ext && ext.toLowerCase() === ".scss") {
482
+ syntax = "scss";
483
+ } else if (ext && ext.toLowerCase() === ".sass") {
484
+ syntax = "indented";
485
+ } else if (ext && ext.toLowerCase() === ".css") {
486
+ syntax = "css";
487
+ } else {
488
+ // Fallback to default value
489
+ syntax = "scss";
490
+ }
491
+ try {
492
+ const contents = /** @type {string} */
493
+ await new Promise((resolve, reject) => {
494
+ // Old version of `enhanced-resolve` supports only path as a string
495
+ // TODO simplify in the next major release and pass URL
496
+ const canonicalPath = url.fileURLToPath(canonicalUrl);
497
+ loaderContext.fs.readFile(canonicalPath,
498
+ /**
499
+ * @param {NodeJS.ErrnoException | null} err error
500
+ * @param {Buffer | undefined} content content
501
+ * @returns {void}
502
+ */
503
+ (err, content) => {
504
+ if (err || !content) {
505
+ reject(err);
506
+ return;
507
+ }
508
+ resolve(content.toString("utf8"));
509
+ });
510
+ });
511
+ return {
512
+ contents,
513
+ syntax,
514
+ sourceMapUrl: canonicalUrl
515
+ };
516
+ } catch {
517
+ return null;
518
+ }
519
+ }
520
+ };
521
+ }
522
+
523
+ /** @type {WeakMap<import("webpack").Compiler, AsyncCompiler>} */
524
+ const sassModernCompilers = new WeakMap();
525
+
526
+ /**
527
+ * Verifies that the implementation and version of Sass is supported by this loader.
528
+ * @template {SassImplementation} T
529
+ * @param {LoaderContext} loaderContext loader context
530
+ * @param {T} implementation sass implementation
531
+ * @param {ApiType=} apiType api type
532
+ * @returns {(sassOptions: SassOptions & { data: string }) => Promise<CompileResult>} compile function
533
+ */
534
+ function getCompileFn(loaderContext, implementation, apiType = "auto") {
535
+ const {
536
+ initAsyncCompiler
537
+ } = implementation;
538
+ if (apiType === "modern-compiler" || apiType === "auto" && typeof initAsyncCompiler === "function") {
539
+ return async (/** @type {SassOptions & { data: string }} */sassOptions) => {
540
+ const webpackCompiler = /** @type {LoaderContext & { _compiler?: import("webpack").Compiler }} */
541
+ loaderContext._compiler;
542
+ const {
543
+ data,
544
+ ...rest
545
+ } = sassOptions;
546
+
547
+ // Some people can run the loader in a multi-threading way;
548
+ // there is no webpack compiler object in such case.
549
+ if (webpackCompiler && initAsyncCompiler) {
550
+ if (!sassModernCompilers.has(webpackCompiler)) {
551
+ // Create a long-running compiler process that can be reused
552
+ // for compiling individual files.
553
+ const compiler = await initAsyncCompiler();
554
+
555
+ // Check again because awaiting the initialization function
556
+ // introduces a race condition.
557
+ if (!sassModernCompilers.has(webpackCompiler)) {
558
+ sassModernCompilers.set(webpackCompiler, compiler);
559
+ webpackCompiler.hooks.shutdown.tap("sass-loader", () => {
560
+ compiler.dispose();
561
+ });
562
+ } else {
563
+ compiler.dispose();
564
+ }
565
+ }
566
+ return /** @type {AsyncCompiler} */sassModernCompilers.get(webpackCompiler).compileStringAsync(/** @type {string} */data, rest);
567
+ }
568
+ return implementation.compileStringAsync(/** @type {string} */data, rest);
569
+ };
570
+ }
571
+ return (/** @type {SassOptions & { data: string }} */sassOptions) => {
572
+ const {
573
+ data,
574
+ ...rest
575
+ } = sassOptions;
576
+ return implementation.compileStringAsync(/** @type {string} */data, rest);
577
+ };
578
+ }
579
+ const ABSOLUTE_SCHEME = /^[A-Za-z0-9+\-.]+:/;
580
+
581
+ /**
582
+ * @param {string} source source
583
+ * @returns {"absolute" | "scheme-relative" | "path-absolute" | "path-relative"} a type of URL
584
+ */
585
+ function getURLType(source) {
586
+ if (source[0] === "/") {
587
+ if (source[1] === "/") {
588
+ return "scheme-relative";
589
+ }
590
+ return "path-absolute";
591
+ }
592
+ if (IS_NATIVE_WIN32_PATH.test(source)) {
593
+ return "path-absolute";
594
+ }
595
+ return ABSOLUTE_SCHEME.test(source) ? "absolute" : "path-relative";
596
+ }
597
+
598
+ /**
599
+ * @param {RawSourceMap} map source map
600
+ * @param {string} rootContext root context
601
+ * @returns {RawSourceMap} normalized source map
602
+ */
603
+ function normalizeSourceMap(map, rootContext) {
604
+ const newMap = map;
605
+
606
+ // result.map.file is an optional property that provides the output filename.
607
+ // Since we don't know the final filename in the webpack build chain yet, it makes no sense to have it.
608
+
609
+ if (typeof newMap.file !== "undefined") {
610
+ // @ts-expect-error need to fix on webpack side
611
+ delete newMap.file;
612
+ }
613
+ newMap.sourceRoot = "";
614
+
615
+ // sass returns POSIX paths, that's why we need to transform them back to native paths.
616
+ // This fixes an error on windows where the source-map module cannot resolve the source maps.
617
+ // @see https://github.com/webpack/sass-loader/issues/366#issuecomment-279460722
618
+
619
+ newMap.sources = newMap.sources.map((/** @type {string} */source) => {
620
+ const sourceType = getURLType(source);
621
+
622
+ // Do no touch `scheme-relative`, `path-absolute` and `absolute` types (except `file:`)
623
+ if (sourceType === "absolute" && /^file:/i.test(source)) {
624
+ return url.fileURLToPath(source);
625
+ } else if (sourceType === "path-relative") {
626
+ return path.resolve(rootContext, path.normalize(source));
627
+ }
628
+ return source;
629
+ });
630
+ return newMap;
631
+ }
632
+
633
+ /**
634
+ * @param {Error | SassError} error the original sass error
635
+ * @returns {Error} a new error
636
+ */
637
+ function errorFactory(error) {
638
+ const sassError = /** @type {SassError} */error;
639
+ const message = sassError.formatted ? sassError.formatted.replace(/^(.+)?Error: /, "") : (error.message || error.toString()).replace(/^(.+)?Error: /, "");
640
+ const obj = new Error(message, {
641
+ cause: error
642
+ });
643
+ obj.name = error.name;
644
+ obj.stack = undefined;
645
+ return obj;
646
+ }
647
+ export { errorFactory, getCompileFn, getModernWebpackImporter, getSassImplementation, getSassOptions, getWebpackResolver, normalizeSourceMap };