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.
- package/README.md +22 -49
- package/dist/cjs/index.js +78 -0
- package/dist/{options.json → cjs/options.json} +3 -3
- package/dist/cjs/package.json +1 -0
- package/dist/{utils.js → cjs/utils.js} +250 -326
- package/dist/esm/index.js +71 -0
- package/dist/esm/options.json +64 -0
- package/dist/esm/utils.js +647 -0
- package/package.json +55 -66
- package/types/index.d.ts +19 -0
- package/types/utils.d.ts +294 -0
- package/dist/cjs.js +0 -4
- package/dist/index.js +0 -103
|
@@ -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
|
-
|
|
18
|
-
/** @typedef {
|
|
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
|
-
/**
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
* @
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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 {
|
|
53
|
-
* @
|
|
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(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
* @
|
|
116
|
-
* @returns {SassOptions} sass options
|
|
184
|
+
* @returns {Promise<Required<KnownSassOptions> & { data: string }>} sass options
|
|
117
185
|
*/
|
|
118
|
-
async function getSassOptions(loaderContext, loaderOptions, content,
|
|
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 =
|
|
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
|
-
|
|
169
|
-
sassOptions.url = _nodeUrl.default.pathToFileURL(resourcePath);
|
|
255
|
+
sassOptions.url = _nodeUrl.default.pathToFileURL(resourcePath);
|
|
170
256
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
199
|
-
|
|
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
|
-
|
|
218
|
-
if (ext && ext.toLowerCase() === ".sass"
|
|
219
|
-
sassOptions.
|
|
220
|
-
} else {
|
|
221
|
-
sassOptions.
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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
|
|
493
|
-
const resolve = getWebpackResolver(loaderContext.getResolve
|
|
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 =
|
|
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,
|
|
529
|
-
|
|
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 {
|
|
588
|
-
* @param {
|
|
589
|
-
* @returns {
|
|
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
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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
|
-
|
|
615
|
-
|
|
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
|
|
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
|
-
|
|
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-
|
|
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
|
-
//
|
|
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
|
|
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 =
|
|
656
|
+
obj.stack = undefined;
|
|
733
657
|
return obj;
|
|
734
658
|
}
|