sass-loader 16.0.8 → 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.
@@ -8,58 +8,145 @@ exports.getCompileFn = getCompileFn;
8
8
  exports.getModernWebpackImporter = getModernWebpackImporter;
9
9
  exports.getSassImplementation = getSassImplementation;
10
10
  exports.getSassOptions = getSassOptions;
11
- exports.getWebpackImporter = getWebpackImporter;
12
11
  exports.getWebpackResolver = getWebpackResolver;
13
12
  exports.normalizeSourceMap = normalizeSourceMap;
14
13
  var _nodePath = _interopRequireDefault(require("node:path"));
15
14
  var _nodeUrl = _interopRequireDefault(require("node:url"));
16
15
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
17
- /** @typedef {import("sass")} Sass */
18
- /** @typedef {import("sass").StringOptionsWithImporter} SassSassOptions */
19
- /** @typedef {import("sass-embedded")} SassEmbedded */
20
- /** @typedef {import("sass-embedded").StringOptionsWithImporter} SassEmbeddedOptions */
16
+ // eslint-disable-next-line jsdoc/reject-any-type
17
+ /** @typedef {any} EXPECTED_ANY */
21
18
 
22
- /** @typedef {Sass | SassEmbedded} SassImplementation */
23
- /** @typedef {SassSassOptions | SassEmbeddedOptions} SassOptions */
24
- // eslint-disable-next-line jsdoc/no-restricted-syntax
25
- /** @typedef {Record<string, any>} LoaderOptions */
19
+ /**
20
+ * @typedef {object} SourceLocation
21
+ * @property {number} line line number
22
+ * @property {number} column column number
23
+ * @property {number} offset character offset
24
+ */
26
25
 
27
26
  /**
28
- * @returns {Sass | SassEmbedded} sass implementation
27
+ * @typedef {object} SourceSpan
28
+ * @property {SourceLocation} start start location
29
+ * @property {SourceLocation} end end location
30
+ * @property {URL=} url canonical URL of the file
31
+ * @property {string} text covered text
32
+ * @property {string=} context surrounding context
29
33
  */
30
- function getDefaultSassImplementation() {
31
- let sassImplPkg = "sass";
32
- try {
33
- require.resolve("sass-embedded");
34
- sassImplPkg = "sass-embedded";
35
- } catch {
36
- try {
37
- require.resolve("sass");
38
- } catch {
39
- try {
40
- require.resolve("node-sass");
41
- sassImplPkg = "node-sass";
42
- } catch {
43
- sassImplPkg = "sass";
44
- }
45
- }
34
+
35
+ /** @typedef {{ deprecation?: boolean, span?: SourceSpan, stack?: string }} LoggerWarnOptions */
36
+
37
+ /**
38
+ * @typedef {object} Logger
39
+ * @property {((message: string, options: LoggerWarnOptions) => void)=} warn warn handler
40
+ * @property {((message: string, options: { span: SourceSpan }) => void)=} debug debug handler
41
+ */
42
+
43
+ /**
44
+ * @typedef {object} CompileResult
45
+ * @property {Buffer | string} css css output
46
+ * @property {RawSourceMap=} sourceMap source map
47
+ * @property {URL[]} loadedUrls loaded URLs
48
+ */
49
+
50
+ /**
51
+ * @typedef {object} Importer
52
+ * @property {(originalUrl: string, context: { containingUrl: URL | null, fromImport: boolean }) => Promise<URL | null>} canonicalize canonicalize
53
+ * @property {(canonicalUrl: URL) => Promise<{ contents: string, syntax: "scss" | "indented" | "css", sourceMapUrl?: URL } | null>} load load
54
+ */
55
+
56
+ /** @typedef {"expanded" | "compressed"} OutputStyle */
57
+
58
+ /**
59
+ * @typedef {object} KnownSassOptions
60
+ * @property {"scss" | "indented" | "css"=} syntax syntax
61
+ * @property {URL=} url url
62
+ * @property {"expanded" | "compressed"=} style style
63
+ * @property {string[]=} loadPaths load paths
64
+ * @property {boolean=} sourceMap source map
65
+ * @property {boolean=} sourceMapIncludeSources source map include sources
66
+ * @property {Importer[]=} importers importers
67
+ * @property {Logger=} logger logger
68
+ */
69
+
70
+ /** @typedef {KnownSassOptions & Record<string, EXPECTED_ANY>} SassOptions */
71
+
72
+ /**
73
+ * @typedef {object} AsyncCompiler
74
+ * @property {(source: string, options?: SassOptions) => Promise<CompileResult>} compileStringAsync compile a string
75
+ * @property {() => Promise<void>} dispose dispose the compiler
76
+ */
77
+
78
+ /** @typedef {{ info: string; compileStringAsync(source: string, options?: SassOptions): Promise<CompileResult>; initAsyncCompiler?(): Promise<AsyncCompiler> }} SassImplementation */
79
+
80
+ /** @typedef {"auto" | "modern" | "modern-compiler"} ApiType */
81
+
82
+ /** @typedef {import("webpack").LoaderContext<LoaderOptions>} LoaderContext */
83
+
84
+ /**
85
+ * @typedef {object} LoaderOptions
86
+ * @property {SassImplementation=} implementation SaSS implementation
87
+ * @property {SassOptions | ((loaderContext: LoaderContext) => SassOptions)=} sassOptions SaSS options
88
+ * @property {boolean=} sourceMap true if source map is enabled, otherwise false
89
+ * @property {string | ((content: string, loaderContext: LoaderContext) => string)=} additionalData prepends Sass/SCSS code before the actual entry file
90
+ * @property {boolean=} webpackImporter true if webpack importer is enabled, otherwise false
91
+ * @property {ApiType=} api API type
92
+ * @property {boolean=} warnRuleAsWarning true if treats the `@warn` rule as a webpack warning, otherwise false
93
+ */
94
+
95
+ /** @typedef {(context: string, request: string, fromImport?: boolean) => Promise<string>} Resolver */
96
+ /** @typedef {{ resolve: (context: string, request: string) => Promise<string>, context: string, possibleRequests: string[] }[]} ResolutionMap */
97
+ /** @typedef {{ version: number, sources: string[], names: string[], sourceRoot?: string, sourcesContent?: string[], mappings: string, file: string, debugId?: string, ignoreList?: number[] }} RawSourceMap */
98
+ /** @typedef {Error & { formatted?: string, span?: { url?: URL, start: { line: number, column: number }, context?: string } }} SassError */
99
+
100
+ /**
101
+ * Convert a string `implementation` option into something the ECMAScript
102
+ * `import()` expression actually accepts. Bare package specifiers and
103
+ * `file:` URLs are passed through unchanged; absolute filesystem paths
104
+ * (including Windows paths like `C:\\...`) are converted to `file:` URLs
105
+ * — dynamic `import()` rejects those otherwise.
106
+ * @param {string} specifier import specifier
107
+ * @returns {string} a valid dynamic import specifier
108
+ */
109
+ function normalizeImportSpecifier(specifier) {
110
+ if (specifier.startsWith("file:")) {
111
+ return specifier;
46
112
  }
47
- return require(sassImplPkg);
113
+ if (_nodePath.default.isAbsolute(specifier)) {
114
+ return _nodeUrl.default.pathToFileURL(specifier).href;
115
+ }
116
+ return specifier;
48
117
  }
49
118
 
50
119
  /**
51
120
  * 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.
52
- * @param {LoaderContext} loaderContext loader context
53
- * @param {SassImplementation} implementation sass implementation
54
- * @returns {SassImplementation} resolved sass implementation
121
+ * @param {SassImplementation | string | undefined} implementation sass implementation
122
+ * @returns {Promise<SassImplementation>} resolved sass implementation
55
123
  */
56
- function getSassImplementation(loaderContext, implementation) {
57
- let resolvedImplementation = implementation;
58
- if (!resolvedImplementation) {
59
- resolvedImplementation = getDefaultSassImplementation();
60
- }
61
- if (typeof resolvedImplementation === "string") {
62
- resolvedImplementation = require(resolvedImplementation);
124
+ async function getSassImplementation(implementation) {
125
+ /** @type {SassImplementation} */
126
+ let resolvedImplementation;
127
+ if (!implementation) {
128
+ try {
129
+ resolvedImplementation = /** @type {SassImplementation} */
130
+ await import("sass-embedded");
131
+ } catch (err) {
132
+ // Only fall back to `sass` when `sass-embedded` is not installed.
133
+ // Any other failure (e.g. a broken install or a side-effect throw at
134
+ // module-load time) should surface so the user can diagnose it
135
+ // instead of being silently masked by the `sass` fallback.
136
+ const {
137
+ code
138
+ } = /** @type {NodeJS.ErrnoException} */err;
139
+ if (code !== "ERR_MODULE_NOT_FOUND" && code !== "MODULE_NOT_FOUND") {
140
+ throw err;
141
+ }
142
+ resolvedImplementation = /** @type {SassImplementation} */
143
+ await import("sass");
144
+ }
145
+ } else if (typeof implementation === "string") {
146
+ resolvedImplementation = /** @type {SassImplementation} */
147
+ await import(normalizeImportSpecifier(implementation));
148
+ } else {
149
+ resolvedImplementation = implementation;
63
150
  }
64
151
  const {
65
152
  info
@@ -74,8 +161,6 @@ function getSassImplementation(loaderContext, implementation) {
74
161
  const [implementationName] = infoParts;
75
162
  if (implementationName === "dart-sass") {
76
163
  return resolvedImplementation;
77
- } else if (implementationName === "node-sass") {
78
- return resolvedImplementation;
79
164
  } else if (implementationName === "sass-embedded") {
80
165
  return resolvedImplementation;
81
166
  }
@@ -90,33 +175,18 @@ function isProductionLikeMode(loaderContext) {
90
175
  return loaderContext.mode === "production" || !loaderContext.mode;
91
176
  }
92
177
 
93
- /**
94
- * @param {Importer[]} importers importers
95
- * @param {LoaderContext} loaderContext loader context
96
- * @returns {Importer[]} proxied importers
97
- */
98
- function proxyCustomImporters(importers, loaderContext) {
99
- return [importers].flat().map(importer => function proxyImporter(...args) {
100
- const self = {
101
- ...this,
102
- webpackLoaderContext: loaderContext
103
- };
104
- return importer.apply(self, args);
105
- });
106
- }
107
-
108
178
  /**
109
179
  * Derives the sass options from the loader context and normalizes its values with sane defaults.
110
180
  * @param {LoaderContext} loaderContext loader context
111
181
  * @param {LoaderOptions} loaderOptions loader options
112
182
  * @param {string} content content
113
- * @param {SassImplementation} implementation sass implementation
114
183
  * @param {boolean} useSourceMap true when need to generate source maps, otherwise false
115
- * @param {"legacy" | "modern" | "modern-compiler"} apiType api type
116
- * @returns {SassOptions} sass options
184
+ * @returns {Promise<Required<KnownSassOptions> & { data: string }>} sass options
117
185
  */
118
- async function getSassOptions(loaderContext, loaderOptions, content, implementation, useSourceMap, apiType) {
186
+ async function getSassOptions(loaderContext, loaderOptions, content, useSourceMap) {
187
+ /** @type {SassOptions} */
119
188
  const options = loaderOptions.sassOptions ? typeof loaderOptions.sassOptions === "function" ? loaderOptions.sassOptions(loaderContext) || {} : loaderOptions.sassOptions : {};
189
+ /** @type {KnownSassOptions & { data: string }} */
120
190
  const sassOptions = {
121
191
  ...options,
122
192
  data: loaderOptions.additionalData ? typeof loaderOptions.additionalData === "function" ? await loaderOptions.additionalData(content, loaderContext) : `${loaderOptions.additionalData}\n${content}` : content
@@ -124,9 +194,22 @@ async function getSassOptions(loaderContext, loaderOptions, content, implementat
124
194
  if (!sassOptions.logger) {
125
195
  const needEmitWarning = loaderOptions.warnRuleAsWarning !== false;
126
196
  const logger = loaderContext.getLogger("sass-loader");
197
+ /**
198
+ * @param {SourceSpan} span span
199
+ * @returns {string} formatted span
200
+ */
127
201
  const formatSpan = span => `Warning on line ${span.start.line}, column ${span.start.column} of ${span.url || "-"}:${span.start.line}:${span.start.column}:\n`;
202
+ /**
203
+ * @param {SourceSpan} span span
204
+ * @returns {string} formatted debug span
205
+ */
128
206
  const formatDebugSpan = span => `[debug:${span.start.line}:${span.start.column}] `;
129
207
  sassOptions.logger = {
208
+ /**
209
+ * @param {string} message message
210
+ * @param {{ span: SourceSpan }} loggerOptions logger options
211
+ * @returns {void}
212
+ */
130
213
  debug(message, loggerOptions) {
131
214
  let builtMessage = "";
132
215
  if (loggerOptions.span) {
@@ -135,6 +218,11 @@ async function getSassOptions(loaderContext, loaderOptions, content, implementat
135
218
  builtMessage += message;
136
219
  logger.debug(builtMessage);
137
220
  },
221
+ /**
222
+ * @param {string} message message
223
+ * @param {LoggerWarnOptions} loggerOptions logger options
224
+ * @returns {void}
225
+ */
138
226
  warn(message, loggerOptions) {
139
227
  let builtMessage = "";
140
228
  if (loggerOptions.deprecation) {
@@ -153,7 +241,7 @@ async function getSassOptions(loaderContext, loaderOptions, content, implementat
153
241
  if (needEmitWarning) {
154
242
  const warning = new Error(builtMessage);
155
243
  warning.name = "SassWarning";
156
- warning.stack = null;
244
+ warning.stack = undefined;
157
245
  loaderContext.emitWarning(warning);
158
246
  } else {
159
247
  logger.warn(builtMessage);
@@ -161,81 +249,36 @@ async function getSassOptions(loaderContext, loaderOptions, content, implementat
161
249
  }
162
250
  };
163
251
  }
164
- const isModernAPI = apiType === "modern" || apiType === "modern-compiler";
165
252
  const {
166
253
  resourcePath
167
254
  } = loaderContext;
168
- if (isModernAPI) {
169
- sassOptions.url = _nodeUrl.default.pathToFileURL(resourcePath);
255
+ sassOptions.url = _nodeUrl.default.pathToFileURL(resourcePath);
170
256
 
171
- // opt.outputStyle
172
- if (!sassOptions.style && isProductionLikeMode(loaderContext)) {
173
- sassOptions.style = "compressed";
174
- }
175
- if (useSourceMap) {
176
- sassOptions.sourceMap = true;
177
- sassOptions.sourceMapIncludeSources = true;
178
- }
179
-
180
- // If we are compiling sass and indentedSyntax isn't set, automatically set it.
181
- if (typeof sassOptions.syntax === "undefined") {
182
- const ext = _nodePath.default.extname(resourcePath);
183
- if (ext && ext.toLowerCase() === ".scss") {
184
- sassOptions.syntax = "scss";
185
- } else if (ext && ext.toLowerCase() === ".sass") {
186
- sassOptions.syntax = "indented";
187
- } else if (ext && ext.toLowerCase() === ".css") {
188
- sassOptions.syntax = "css";
189
- }
190
- }
191
- sassOptions.loadPaths = [
192
- // We use `loadPaths` in context for resolver, so it should be always absolute
193
- ...(sassOptions.loadPaths ? [...sassOptions.loadPaths] : []).map(includePath => _nodePath.default.isAbsolute(includePath) ? includePath : _nodePath.default.join(process.cwd(), includePath)), ...(process.env.SASS_PATH ? process.env.SASS_PATH.split(process.platform === "win32" ? ";" : ":") : [])];
194
- sassOptions.importers = sassOptions.importers ? Array.isArray(sassOptions.importers) ? [...sassOptions.importers] : [sassOptions.importers] : [];
195
- } else {
196
- sassOptions.file = resourcePath;
257
+ // opt.outputStyle
258
+ if (!sassOptions.style && isProductionLikeMode(loaderContext)) {
259
+ sassOptions.style = "compressed";
260
+ }
261
+ if (useSourceMap) {
262
+ sassOptions.sourceMap = true;
263
+ sassOptions.sourceMapIncludeSources = true;
264
+ }
197
265
 
198
- // opt.outputStyle
199
- if (!sassOptions.outputStyle && isProductionLikeMode(loaderContext)) {
200
- sassOptions.outputStyle = "compressed";
201
- }
202
- if (useSourceMap) {
203
- // Deliberately overriding the sourceMap option here.
204
- // node-sass won't produce source maps if the data option is used and options.sourceMap is not a string.
205
- // In case it is a string, options.sourceMap should be a path where the source map is written.
206
- // But since we're using the data option, the source map will not actually be written, but
207
- // all paths in sourceMap.sources will be relative to that path.
208
- // Pretty complicated... :(
209
- sassOptions.sourceMap = true;
210
- sassOptions.outFile = _nodePath.default.join(loaderContext.rootContext, "style.css.map");
211
- sassOptions.sourceMapContents = true;
212
- sassOptions.omitSourceMapUrl = true;
213
- sassOptions.sourceMapEmbed = false;
214
- }
266
+ // If we are compiling sass and indentedSyntax isn't set, automatically set it.
267
+ if (typeof sassOptions.syntax === "undefined") {
215
268
  const ext = _nodePath.default.extname(resourcePath);
216
-
217
- // If we are compiling sass and indentedSyntax isn't set, automatically set it.
218
- if (ext && ext.toLowerCase() === ".sass" && typeof sassOptions.indentedSyntax === "undefined") {
219
- sassOptions.indentedSyntax = true;
220
- } else {
221
- sassOptions.indentedSyntax = Boolean(sassOptions.indentedSyntax);
222
- }
223
-
224
- // Allow passing custom importers to `sass`/`node-sass`. Accepts `Function` or an array of `Function`s.
225
- sassOptions.importer = sassOptions.importer ? proxyCustomImporters(Array.isArray(sassOptions.importer) ? [...sassOptions.importer] : [sassOptions.importer], loaderContext) : [];
226
-
227
- // Regression on the `sass-embedded` side
228
- if (loaderOptions.webpackImporter === false && sassOptions.importer.length === 0) {
229
- sassOptions.importer = undefined;
230
- }
231
- sassOptions.includePaths = [process.cwd(), ...
232
- // We use `includePaths` in context for resolver, so it should be always absolute
233
- (sassOptions.includePaths ? [...sassOptions.includePaths] : []).map(includePath => _nodePath.default.isAbsolute(includePath) ? includePath : _nodePath.default.join(process.cwd(), includePath)), ...(process.env.SASS_PATH ? process.env.SASS_PATH.split(process.platform === "win32" ? ";" : ":") : [])];
234
- if (typeof sassOptions.charset === "undefined") {
235
- sassOptions.charset = true;
269
+ if (ext && ext.toLowerCase() === ".scss") {
270
+ sassOptions.syntax = "scss";
271
+ } else if (ext && ext.toLowerCase() === ".sass") {
272
+ sassOptions.syntax = "indented";
273
+ } else if (ext && ext.toLowerCase() === ".css") {
274
+ sassOptions.syntax = "css";
236
275
  }
237
276
  }
238
- return sassOptions;
277
+ sassOptions.loadPaths = [
278
+ // We use `loadPaths` in context for resolver, so it should be always absolute
279
+ ...(sassOptions.loadPaths ? [...sassOptions.loadPaths] : []).map(includePath => _nodePath.default.isAbsolute(includePath) ? includePath : _nodePath.default.join(process.cwd(), includePath)), ...(process.env.SASS_PATH ? process.env.SASS_PATH.split(process.platform === "win32" ? ";" : ":") : [])];
280
+ sassOptions.importers = sassOptions.importers ? Array.isArray(sassOptions.importers) ? [...sassOptions.importers] : [sassOptions.importers] : [];
281
+ return /** @type {Required<KnownSassOptions> & { data: string }} */sassOptions;
239
282
  }
240
283
  const MODULE_REQUEST_REGEX = /^[^?]*~/;
241
284
 
@@ -250,7 +293,7 @@ const IS_MODULE_IMPORT = /^~([^/]+|[^/]+\/|@[^/]+[/][^/]+|@[^/]+\/?|@[^/]+[/][^/
250
293
  const IS_PKG_SCHEME = /^pkg:/i;
251
294
 
252
295
  /**
253
- * When `sass`/`node-sass` tries to resolve an import, it uses a special algorithm.
296
+ * When `sass` tries to resolve an import, it uses a special algorithm.
254
297
  * Since the `sass-loader` uses webpack to resolve the modules, we need to simulate that algorithm.
255
298
  * This function returns an array of import paths to try.
256
299
  * The last entry in the array is always the original url to enable straight-forward webpack.config aliases.
@@ -291,8 +334,7 @@ function getPossibleRequests(url, forWebpackResolver = false, fromImport = false
291
334
  // - imports where the URL is written as a url().
292
335
  // - imports that have media queries.
293
336
  //
294
- // The `node-sass` package sends `@import` ending on `.css` to importer, it is bug, so we skip resolve
295
- // Also sass outputs as is `@import "style.css"`, but `@use "style.css"` should include CSS content
337
+ // sass outputs as is `@import "style.css"`, but `@use "style.css"` should include CSS content
296
338
  if (extension === ".css") {
297
339
  return fromImport ? [] : [url];
298
340
  }
@@ -349,7 +391,7 @@ async function startResolving(resolutionMap) {
349
391
  return startResolving(resolutionMap);
350
392
  }
351
393
  }
352
- const IS_SPECIAL_MODULE_IMPORT = /^~[^/]+$/;
394
+
353
395
  // `[drive_letter]:\` + `\\[server]\[sharename]\`
354
396
  const IS_NATIVE_WIN32_PATH = /^[a-z]:[/\\]|^\\\\/i;
355
397
 
@@ -359,14 +401,10 @@ const IS_NATIVE_WIN32_PATH = /^[a-z]:[/\\]|^\\\\/i;
359
401
  * in a Jest transform. Such usages will want to wrap `resolve.create` from
360
402
  * [`enhanced-resolve`]{@link https://github.com/webpack/enhanced-resolve} to
361
403
  * pass as the `resolverFactory` argument.
362
- * @param {ResolveFactory} resolverFactory a factory function for creating a Webpack resolver.
363
- * @param {Sass} implementation the imported Sass implementation, both `sass` (Dart Sass) and `node-sass` are supported.
364
- * @param {string[]=} includePaths the list of include paths passed to Sass.
404
+ * @param {LoaderContext["getResolve"]} resolverFactory a factory function for creating a Webpack resolver.
365
405
  * @returns {Resolver} webpack resolver
366
- * @throws If a compatible Sass implementation cannot be found.
367
406
  */
368
- function getWebpackResolver(resolverFactory, implementation, includePaths = []) {
369
- const isModernSass = implementation && typeof implementation.compileStringAsync !== "undefined";
407
+ function getWebpackResolver(resolverFactory) {
370
408
  // We only have one difference with the built-in sass resolution logic and out resolution logic:
371
409
  // First, we look at the files starting with `_`, then without `_` (i.e. `_name.sass`, `_name.scss`, `_name.css`, `name.sass`, `name.scss`, `name.css`),
372
410
  // although `sass` look together by extensions (i.e. `_name.sass`/`name.sass`/`_name.scss`/`name.scss`/`_name.css`/`name.css`).
@@ -375,32 +413,6 @@ function getWebpackResolver(resolverFactory, implementation, includePaths = [])
375
413
  // - on having `_name.sass` and `_name.scss` in the same directory
376
414
  //
377
415
  // Also `sass` prefer `sass`/`scss` over `css`.
378
- const sassModuleResolve = promiseResolve(resolverFactory({
379
- alias: [],
380
- aliasFields: [],
381
- conditionNames: [],
382
- descriptionFiles: [],
383
- extensions: [".sass", ".scss", ".css"],
384
- exportsFields: [],
385
- mainFields: [],
386
- mainFiles: ["_index", "index"],
387
- modules: [],
388
- restrictions: [/\.((sa|sc|c)ss)$/i],
389
- preferRelative: true
390
- }));
391
- const sassImportResolve = promiseResolve(resolverFactory({
392
- alias: [],
393
- aliasFields: [],
394
- conditionNames: [],
395
- descriptionFiles: [],
396
- extensions: [".sass", ".scss", ".css"],
397
- exportsFields: [],
398
- mainFields: [],
399
- mainFiles: ["_index.import", "_index", "index.import", "index"],
400
- modules: [],
401
- restrictions: [/\.((sa|sc|c)ss)$/i],
402
- preferRelative: true
403
- }));
404
416
  const webpackModuleResolve = promiseResolve(resolverFactory({
405
417
  dependencyType: "sass",
406
418
  conditionNames: ["sass", "style", "..."],
@@ -420,12 +432,6 @@ function getWebpackResolver(resolverFactory, implementation, includePaths = [])
420
432
  preferRelative: true
421
433
  }));
422
434
  return (context, request, fromImport) => {
423
- // See https://github.com/webpack/webpack/issues/12340
424
- // Because `node-sass` calls our importer before `1. Filesystem imports relative to the base file.`
425
- // custom importer may not return `{ file: '/path/to/name.ext' }` and therefore our `context` will be relative
426
- if (!isModernSass && !_nodePath.default.isAbsolute(context)) {
427
- return Promise.reject();
428
- }
429
435
  const originalRequest = request;
430
436
  const isFileScheme = originalRequest.slice(0, 5).toLowerCase() === "file:";
431
437
  if (isFileScheme) {
@@ -435,43 +441,9 @@ function getWebpackResolver(resolverFactory, implementation, includePaths = [])
435
441
  request = request.slice(7);
436
442
  }
437
443
  }
444
+
445
+ /** @type {ResolutionMap} */
438
446
  let resolutionMap = [];
439
- const needEmulateSassResolver =
440
- // `sass` doesn't support module import
441
- !IS_SPECIAL_MODULE_IMPORT.test(request) &&
442
- // don't handle `pkg:` scheme
443
- !IS_PKG_SCHEME.test(request) &&
444
- // We need improve absolute paths handling.
445
- // Absolute paths should be resolved:
446
- // - Server-relative URLs - `<context>/path/to/file.ext` (where `<context>` is root context)
447
- // - Absolute path - `/full/path/to/file.ext` or `C:\\full\path\to\file.ext`
448
- !isFileScheme && !originalRequest.startsWith("/") && !IS_NATIVE_WIN32_PATH.test(originalRequest);
449
- if (includePaths.length > 0 && needEmulateSassResolver) {
450
- // The order of import precedence is as follows:
451
- //
452
- // 1. Filesystem imports relative to the base file.
453
- // 2. Custom importer imports.
454
- // 3. Filesystem imports relative to the working directory.
455
- // 4. Filesystem imports relative to an `includePaths` path.
456
- // 5. Filesystem imports relative to a `SASS_PATH` path.
457
- //
458
- // `sass` run custom importers before `3`, `4` and `5` points, we need to emulate this behavior to avoid wrong resolution.
459
- const sassPossibleRequests = getPossibleRequests(request, false, fromImport);
460
-
461
- // `node-sass` calls our importer before `1. Filesystem imports relative to the base file.`, so we need emulate this too
462
- if (!isModernSass) {
463
- resolutionMap = [...resolutionMap, {
464
- resolve: fromImport ? sassImportResolve : sassModuleResolve,
465
- context: _nodePath.default.dirname(context),
466
- possibleRequests: sassPossibleRequests
467
- }];
468
- }
469
- resolutionMap = [...resolutionMap, ...includePaths.map(context => ({
470
- resolve: fromImport ? sassImportResolve : sassModuleResolve,
471
- context,
472
- possibleRequests: sassPossibleRequests
473
- }))];
474
- }
475
447
  const webpackPossibleRequests = getPossibleRequests(request, true, fromImport);
476
448
  resolutionMap = [...resolutionMap, {
477
449
  resolve: fromImport ? webpackImportResolve : webpackModuleResolve,
@@ -481,17 +453,19 @@ function getWebpackResolver(resolverFactory, implementation, includePaths = [])
481
453
  return startResolving(resolutionMap);
482
454
  };
483
455
  }
484
- const MATCH_CSS = /\.css$/i;
485
456
 
486
457
  /**
487
458
  * @param {LoaderContext} loaderContext loader context
488
- * @param {SassImplementation} implementation sass implementation
489
- * @param {string[]} loadPaths load paths
490
459
  * @returns {Importer} the modern webpack importer
491
460
  */
492
- function getModernWebpackImporter(loaderContext, implementation, loadPaths) {
493
- const resolve = getWebpackResolver(loaderContext.getResolve, implementation, loadPaths);
461
+ function getModernWebpackImporter(loaderContext) {
462
+ const resolve = getWebpackResolver(loaderContext.getResolve);
494
463
  return {
464
+ /**
465
+ * @param {string} originalUrl original url
466
+ * @param {{ fromImport: boolean, containingUrl: URL | null }} context context
467
+ * @returns {Promise<URL | null>} canonicalized URL
468
+ */
495
469
  async canonicalize(originalUrl, context) {
496
470
  const {
497
471
  fromImport
@@ -507,8 +481,14 @@ function getModernWebpackImporter(loaderContext, implementation, loadPaths) {
507
481
  loaderContext.addDependency(_nodePath.default.normalize(result));
508
482
  return _nodeUrl.default.pathToFileURL(result);
509
483
  },
484
+ /**
485
+ * @param {URL} canonicalUrl canonical url
486
+ * @returns {Promise<{ contents: string, syntax: "scss" | "indented" | "css", sourceMapUrl: URL } | null>} load result
487
+ */
510
488
  async load(canonicalUrl) {
511
489
  const ext = _nodePath.default.extname(canonicalUrl.pathname);
490
+
491
+ /** @type {"scss" | "indented" | "css"} */
512
492
  let syntax;
513
493
  if (ext && ext.toLowerCase() === ".scss") {
514
494
  syntax = "scss";
@@ -521,12 +501,19 @@ function getModernWebpackImporter(loaderContext, implementation, loadPaths) {
521
501
  syntax = "scss";
522
502
  }
523
503
  try {
524
- const contents = await new Promise((resolve, reject) => {
504
+ const contents = /** @type {string} */
505
+ await new Promise((resolve, reject) => {
525
506
  // Old version of `enhanced-resolve` supports only path as a string
526
507
  // TODO simplify in the next major release and pass URL
527
508
  const canonicalPath = _nodeUrl.default.fileURLToPath(canonicalUrl);
528
- loaderContext.fs.readFile(canonicalPath, (err, content) => {
529
- if (err) {
509
+ loaderContext.fs.readFile(canonicalPath,
510
+ /**
511
+ * @param {NodeJS.ErrnoException | null} err error
512
+ * @param {Buffer | undefined} content content
513
+ * @returns {void}
514
+ */
515
+ (err, content) => {
516
+ if (err || !content) {
530
517
  reject(err);
531
518
  return;
532
519
  }
@@ -545,132 +532,67 @@ function getModernWebpackImporter(loaderContext, implementation, loadPaths) {
545
532
  };
546
533
  }
547
534
 
548
- /**
549
- * @param {LoaderContext} loaderContext loader context
550
- * @param {SassImplementation} implementation sass implementation
551
- * @param {string[]} includePaths include paths
552
- * @returns {Importer} the webpack importer
553
- */
554
- function getWebpackImporter(loaderContext, implementation, includePaths) {
555
- const resolve = getWebpackResolver(loaderContext.getResolve, implementation, includePaths);
556
- return function importer(originalUrl, prev, done) {
557
- const {
558
- fromImport
559
- } = this;
560
- resolve(prev, originalUrl,
561
- // For `node-sass`
562
- typeof fromImport === "undefined" ? true : fromImport).then(result => {
563
- // Add the result as dependency.
564
- // Although we're also using stats.includedFiles, this might come in handy when an error occurs.
565
- // In this case, we don't get stats.includedFiles from node-sass/sass.
566
- loaderContext.addDependency(_nodePath.default.normalize(result));
567
-
568
- // By removing the CSS file extension, we trigger node-sass to include the CSS file instead of just linking it.
569
- done({
570
- file: result.replace(MATCH_CSS, "")
571
- });
572
- })
573
- // Catch all resolving errors, return the original file and pass responsibility back to other custom importers
574
- .catch(() => {
575
- done({
576
- file: originalUrl
577
- });
578
- });
579
- };
580
- }
581
- let nodeSassJobQueue = null;
535
+ /** @type {WeakMap<import("webpack").Compiler, AsyncCompiler>} */
582
536
  const sassModernCompilers = new WeakMap();
583
537
 
584
538
  /**
585
539
  * Verifies that the implementation and version of Sass is supported by this loader.
540
+ * @template {SassImplementation} T
586
541
  * @param {LoaderContext} loaderContext loader context
587
- * @param {SassImplementation} implementation sass implementation
588
- * @param {"legacy" | "modern" | "modern-compiler"} apiType api type
589
- * @returns {SassCompileFunction} compile function
542
+ * @param {T} implementation sass implementation
543
+ * @param {ApiType=} apiType api type
544
+ * @returns {(sassOptions: SassOptions & { data: string }) => Promise<CompileResult>} compile function
590
545
  */
591
- function getCompileFn(loaderContext, implementation, apiType) {
592
- if (typeof implementation.compileStringAsync !== "undefined") {
593
- if (apiType === "modern") {
594
- return sassOptions => {
595
- const {
596
- data,
597
- ...rest
598
- } = sassOptions;
599
- return implementation.compileStringAsync(data, rest);
600
- };
601
- }
602
- if (apiType === "modern-compiler") {
603
- return async sassOptions => {
604
- const webpackCompiler = loaderContext._compiler;
605
- const {
606
- data,
607
- ...rest
608
- } = sassOptions;
609
-
610
- // Some people can run the loader in a multi-threading way;
611
- // there is no webpack compiler object in such case.
612
- if (webpackCompiler) {
546
+ function getCompileFn(loaderContext, implementation, apiType = "auto") {
547
+ const {
548
+ initAsyncCompiler
549
+ } = implementation;
550
+ if (apiType === "modern-compiler" || apiType === "auto" && typeof initAsyncCompiler === "function") {
551
+ return async (/** @type {SassOptions & { data: string }} */sassOptions) => {
552
+ const webpackCompiler = /** @type {LoaderContext & { _compiler?: import("webpack").Compiler }} */
553
+ loaderContext._compiler;
554
+ const {
555
+ data,
556
+ ...rest
557
+ } = sassOptions;
558
+
559
+ // Some people can run the loader in a multi-threading way;
560
+ // there is no webpack compiler object in such case.
561
+ if (webpackCompiler && initAsyncCompiler) {
562
+ if (!sassModernCompilers.has(webpackCompiler)) {
563
+ // Create a long-running compiler process that can be reused
564
+ // for compiling individual files.
565
+ const compiler = await initAsyncCompiler();
566
+
567
+ // Check again because awaiting the initialization function
568
+ // introduces a race condition.
613
569
  if (!sassModernCompilers.has(webpackCompiler)) {
614
- // Create a long-running compiler process that can be reused
615
- // for compiling individual files.
616
- const compiler = await implementation.initAsyncCompiler();
617
-
618
- // Check again because awaiting the initialization function
619
- // introduces a race condition.
620
- if (!sassModernCompilers.has(webpackCompiler)) {
621
- sassModernCompilers.set(webpackCompiler, compiler);
622
- webpackCompiler.hooks.shutdown.tap("sass-loader", () => {
623
- compiler.dispose();
624
- });
625
- } else {
570
+ sassModernCompilers.set(webpackCompiler, compiler);
571
+ webpackCompiler.hooks.shutdown.tap("sass-loader", () => {
626
572
  compiler.dispose();
627
- }
573
+ });
574
+ } else {
575
+ compiler.dispose();
628
576
  }
629
- return sassModernCompilers.get(webpackCompiler).compileStringAsync(data, rest);
630
577
  }
631
- return implementation.compileStringAsync(data, rest);
632
- };
633
- }
634
- return sassOptions => new Promise((resolve, reject) => {
635
- implementation.render(sassOptions, (error, result) => {
636
- if (error) {
637
- reject(error);
638
- return;
639
- }
640
- resolve(result);
641
- });
642
- });
643
- }
644
- if (apiType === "modern" || apiType === "modern-compiler") {
645
- throw new Error("Modern API is not supported for 'node-sass'");
646
- }
647
-
648
- // There is an issue with node-sass when async custom importers are used
649
- // See https://github.com/sass/node-sass/issues/857#issuecomment-93594360
650
- // We need to use a job queue to make sure that one thread is always available to the UV lib
651
- if (nodeSassJobQueue === null) {
652
- const threadPoolSize = Number(process.env.UV_THREADPOOL_SIZE || 4);
653
-
654
- // Only used for `node-sass`, so let's load it lazily
655
-
656
- const async = require("neo-async");
657
- nodeSassJobQueue = async.queue(implementation.render.bind(implementation), threadPoolSize - 1);
658
- }
659
- return sassOptions => new Promise((resolve, reject) => {
660
- nodeSassJobQueue.push.bind(nodeSassJobQueue)(sassOptions, (error, result) => {
661
- if (error) {
662
- reject(error);
663
- return;
578
+ return /** @type {AsyncCompiler} */sassModernCompilers.get(webpackCompiler).compileStringAsync(/** @type {string} */data, rest);
664
579
  }
665
- resolve(result);
666
- });
667
- });
580
+ return implementation.compileStringAsync(/** @type {string} */data, rest);
581
+ };
582
+ }
583
+ return (/** @type {SassOptions & { data: string }} */sassOptions) => {
584
+ const {
585
+ data,
586
+ ...rest
587
+ } = sassOptions;
588
+ return implementation.compileStringAsync(/** @type {string} */data, rest);
589
+ };
668
590
  }
669
591
  const ABSOLUTE_SCHEME = /^[A-Za-z0-9+\-.]+:/;
670
592
 
671
593
  /**
672
594
  * @param {string} source source
673
- * @returns {"absolute" | "scheme-relative" | "path-absolute" | "path-absolute"} a type of URL
595
+ * @returns {"absolute" | "scheme-relative" | "path-absolute" | "path-relative"} a type of URL
674
596
  */
675
597
  function getURLType(source) {
676
598
  if (source[0] === "/") {
@@ -697,15 +619,16 @@ function normalizeSourceMap(map, rootContext) {
697
619
  // Since we don't know the final filename in the webpack build chain yet, it makes no sense to have it.
698
620
 
699
621
  if (typeof newMap.file !== "undefined") {
622
+ // @ts-expect-error need to fix on webpack side
700
623
  delete newMap.file;
701
624
  }
702
625
  newMap.sourceRoot = "";
703
626
 
704
- // node-sass returns POSIX paths, that's why we need to transform them back to native paths.
627
+ // sass returns POSIX paths, that's why we need to transform them back to native paths.
705
628
  // This fixes an error on windows where the source-map module cannot resolve the source maps.
706
629
  // @see https://github.com/webpack/sass-loader/issues/366#issuecomment-279460722
707
630
 
708
- newMap.sources = newMap.sources.map(source => {
631
+ newMap.sources = newMap.sources.map((/** @type {string} */source) => {
709
632
  const sourceType = getURLType(source);
710
633
 
711
634
  // Do no touch `scheme-relative`, `path-absolute` and `absolute` types (except `file:`)
@@ -724,11 +647,12 @@ function normalizeSourceMap(map, rootContext) {
724
647
  * @returns {Error} a new error
725
648
  */
726
649
  function errorFactory(error) {
727
- const message = error.formatted ? error.formatted.replace(/^(.+)?Error: /, "") : (error.message || error.toString()).replace(/^(.+)?Error: /, "");
650
+ const sassError = /** @type {SassError} */error;
651
+ const message = sassError.formatted ? sassError.formatted.replace(/^(.+)?Error: /, "") : (error.message || error.toString()).replace(/^(.+)?Error: /, "");
728
652
  const obj = new Error(message, {
729
653
  cause: error
730
654
  });
731
655
  obj.name = error.name;
732
- obj.stack = null;
656
+ obj.stack = undefined;
733
657
  return obj;
734
658
  }