Skip to content

Commit a248836

Browse files
refactor: logic of sass resolution
1 parent 6641a16 commit a248836

File tree

9 files changed

+97
-12
lines changed

9 files changed

+97
-12
lines changed

src/utils.js

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ function getDefaultSassImplementation() {
2323
}
2424

2525
/**
26-
* @public
27-
* This function is not Webpack-specific and can be used by tools wishing to
28-
* mimic `sass-loader`'s behaviour, so its signature should not be changed.
26+
* 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.
2927
*/
3028
function getSassImplementation(loaderContext, implementation) {
3129
let resolvedImplementation = implementation;
@@ -73,6 +71,10 @@ function getSassImplementation(loaderContext, implementation) {
7371
);
7472
}
7573

74+
/**
75+
* @param {any} loaderContext
76+
* @returns {boolean}
77+
*/
7678
function isProductionLikeMode(loaderContext) {
7779
return loaderContext.mode === "production" || !loaderContext.mode;
7880
}
@@ -236,13 +238,14 @@ const IS_MODULE_IMPORT =
236238
*
237239
* @param {string} url
238240
* @param {boolean} forWebpackResolver
239-
* @param {string} rootContext
241+
* @param {boolean} fromImport
240242
* @returns {Array<string>}
241243
*/
242244
function getPossibleRequests(
243245
// eslint-disable-next-line no-shadow
244246
url,
245-
forWebpackResolver = false
247+
forWebpackResolver = false,
248+
fromImport = false
246249
) {
247250
let request = url;
248251

@@ -261,7 +264,7 @@ function getPossibleRequests(
261264

262265
// Keep in mind: ext can also be something like '.datepicker' when the true extension is omitted and the filename contains a dot.
263266
// @see https://github.com/webpack-contrib/sass-loader/issues/167
264-
const ext = path.extname(request).toLowerCase();
267+
const extension = path.extname(request).toLowerCase();
265268

266269
// 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.
267270
// 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:
@@ -271,18 +274,31 @@ function getPossibleRequests(
271274
// - imports that have media queries.
272275
//
273276
// The `node-sass` package sends `@import` ending on `.css` to importer, it is bug, so we skip resolve
274-
if (ext === ".css") {
277+
if (extension === ".css") {
275278
return [];
276279
}
277280

278281
const dirname = path.dirname(request);
282+
const normalizedDirname = dirname === "." ? "" : `${dirname}/`;
279283
const basename = path.basename(request);
284+
const basenameWithoutExtension = path.basename(request, extension);
280285

281286
return [
282287
...new Set(
283-
[`${dirname}/_${basename}`, request].concat(
284-
forWebpackResolver ? [`${path.dirname(url)}/_${basename}`, url] : []
285-
)
288+
[]
289+
.concat(
290+
fromImport
291+
? [
292+
`${normalizedDirname}_${basenameWithoutExtension}.import${extension}`,
293+
`${normalizedDirname}${basenameWithoutExtension}.import${extension}`,
294+
]
295+
: []
296+
)
297+
.concat([
298+
`${normalizedDirname}_${basename}`,
299+
`${normalizedDirname}${basename}`,
300+
])
301+
.concat(forWebpackResolver ? [url] : [])
286302
),
287303
];
288304
}
@@ -358,6 +374,14 @@ function getWebpackResolver(
358374
}
359375

360376
const isDartSass = implementation.info.includes("dart-sass");
377+
// We only have one difference with the built-in sass resolution logic and out resolution logic:
378+
// First, we look at the files starting with `_`, then without `_` (i.e. `_name.sass`, `_name.scss`, `_name.css`, `name.sass`, `name.scss`, `name.css`),
379+
// although `sass` look together by extensions (i.e. `_name.sass`/`name.sass`/`_name.scss`/`name.scss`/`_name.css`/`name.css`).
380+
// It shouldn't be a problem because `sass` throw errors:
381+
// - on having `_name.sass` and `name.sass` (extension can be `sass`, `scss` or `css`) in the same directory
382+
// - on having `_name.sass` and `_name.scss` in the same directory
383+
//
384+
// Also `sass` prefer `sass`/`scss` over `css`.
361385
const sassModuleResolve = promiseResolve(
362386
resolverFactory({
363387
alias: [],
@@ -455,7 +479,11 @@ function getWebpackResolver(
455479
// 5. Filesystem imports relative to a `SASS_PATH` path.
456480
//
457481
// `sass` run custom importers before `3`, `4` and `5` points, we need to emulate this behavior to avoid wrong resolution.
458-
const sassPossibleRequests = getPossibleRequests(request);
482+
const sassPossibleRequests = getPossibleRequests(
483+
request,
484+
false,
485+
fromImport
486+
);
459487

460488
// `node-sass` calls our importer before `1. Filesystem imports relative to the base file.`, so we need emulate this too
461489
if (!isDartSass) {
@@ -478,7 +506,11 @@ function getWebpackResolver(
478506
);
479507
}
480508

481-
const webpackPossibleRequests = getPossibleRequests(request, true);
509+
const webpackPossibleRequests = getPossibleRequests(
510+
request,
511+
true,
512+
fromImport
513+
);
482514

483515
resolutionMap = resolutionMap.concat({
484516
resolve: fromImport ? webpackImportResolve : webpackModuleResolve,
@@ -551,6 +583,10 @@ function getRenderFunctionFromSassImplementation(implementation) {
551583

552584
const ABSOLUTE_SCHEME = /^[A-Za-z0-9+\-.]+:/;
553585

586+
/**
587+
* @param {string} source
588+
* @returns {"absolute" | "scheme-relative" | "path-absolute" | "path-absolute"}
589+
*/
554590
function getURLType(source) {
555591
if (source[0] === "/") {
556592
if (source[1] === "/") {

test/__snapshots__/loader.test.js.snap

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,26 @@ exports[`loader should prefer "mainFiles" with extension over without (node-sass
404404

405405
exports[`loader should prefer "mainFiles" with extension over without (node-sass) (scss): warnings 1`] = `Array []`;
406406

407+
exports[`loader should prefer "sass)" over CSS (dart-sass) (sass): css 1`] = `
408+
"a {
409+
color: green;
410+
}"
411+
`;
412+
413+
exports[`loader should prefer "sass)" over CSS (dart-sass) (sass): errors 1`] = `Array []`;
414+
415+
exports[`loader should prefer "sass)" over CSS (dart-sass) (sass): warnings 1`] = `Array []`;
416+
417+
exports[`loader should prefer "scss)" over CSS (dart-sass) (scss): css 1`] = `
418+
"a {
419+
color: green;
420+
}"
421+
`;
422+
423+
exports[`loader should prefer "scss)" over CSS (dart-sass) (scss): errors 1`] = `Array []`;
424+
425+
exports[`loader should prefer "scss)" over CSS (dart-sass) (scss): warnings 1`] = `Array []`;
426+
407427
exports[`loader should prefer relative import (dart-sass) (sass): css 1`] = `
408428
".class {
409429
color: blue;

test/loader.test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1643,6 +1643,22 @@ describe("loader", () => {
16431643
expect(getErrors(stats)).toMatchSnapshot("errors");
16441644
});
16451645

1646+
it(`should prefer "${syntax})" over CSS (${implementationName}) (${syntax})`, async () => {
1647+
const testId = getTestId("use-dir-with-css", syntax);
1648+
const options = {
1649+
implementation: getImplementationByName(implementationName),
1650+
};
1651+
const compiler = getCompiler(testId, { loader: { options } });
1652+
const stats = await compile(compiler);
1653+
const codeFromBundle = getCodeFromBundle(stats, compiler);
1654+
const codeFromSass = getCodeFromSass(testId, options);
1655+
1656+
expect(codeFromBundle.css).toBe(codeFromSass.css);
1657+
expect(codeFromBundle.css).toMatchSnapshot("css");
1658+
expect(getWarnings(stats)).toMatchSnapshot("warnings");
1659+
expect(getErrors(stats)).toMatchSnapshot("errors");
1660+
});
1661+
16461662
it(`should work and output deprecation message (${implementationName})`, async () => {
16471663
const testId = getTestId("deprecation", syntax);
16481664
const options = {

test/sass/dir-with-css/_name.sass

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
a
2+
color: green

test/sass/dir-with-css/name.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
a {
2+
color: blue;
3+
}

test/sass/use-dir-with-css.sass

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@use './dir-with-css/name'

test/scss/dir-with-css/_name.scss

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
a {
2+
color: green;
3+
}

test/scss/dir-with-css/name.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
a {
2+
color: blue;
3+
}

test/scss/use-dir-with-css.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@use './dir-with-css/name';

0 commit comments

Comments
 (0)