Skip to content

Commit af5a072

Browse files
fix: compatibility with custom importers for node-sass (#927)
1 parent 0db6fb6 commit af5a072

File tree

11 files changed

+532
-69
lines changed

11 files changed

+532
-69
lines changed

package-lock.json

Lines changed: 425 additions & 51 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@
3939
"dist"
4040
],
4141
"peerDependencies": {
42-
"webpack": "^5.0.0",
42+
"fibers": ">= 3.1.0",
4343
"node-sass": "^4.0.0 || ^5.0.0",
4444
"sass": "^1.3.0",
45-
"fibers": ">= 3.1.0"
45+
"webpack": "^5.0.0"
4646
},
4747
"peerDependenciesMeta": {
4848
"node-sass": {
@@ -83,17 +83,18 @@
8383
"foundation-sites": "^6.6.3",
8484
"husky": "^4.3.6",
8585
"jest": "^26.6.3",
86-
"lint-staged": "^10.5.3",
86+
"lint-staged": "^10.5.4",
8787
"material-components-web": "^8.0.0",
8888
"memfs": "^3.2.0",
8989
"node-sass": "^5.0.0",
90+
"node-sass-glob-importer": "^5.3.2",
9091
"npm-run-all": "^4.1.5",
9192
"prettier": "^2.2.1",
9293
"sass": "^1.32.0",
9394
"semver": "^7.3.4",
9495
"standard-version": "^9.1.0",
9596
"style-loader": "^2.0.0",
96-
"webpack": "^5.20.2"
97+
"webpack": "^5.21.2"
9798
},
9899
"keywords": [
99100
"sass",

src/utils.js

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -207,15 +207,16 @@ async function getSassOptions(
207207
return options;
208208
}
209209

210+
const MODULE_REQUEST_REGEX = /^[^?]*~/;
211+
210212
// Examples:
211213
// - ~package
212214
// - ~package/
213215
// - ~@org
214216
// - ~@org/
215217
// - ~@org/package
216218
// - ~@org/package/
217-
const isModuleImport = /^~([^/]+|[^/]+\/|@[^/]+[/][^/]+|@[^/]+\/?|@[^/]+[/][^/]+\/)$/;
218-
const MODULE_REQUEST_REGEX = /^[^?]*~/;
219+
const IS_MODULE_IMPORT = /^~([^/]+|[^/]+\/|@[^/]+[/][^/]+|@[^/]+\/?|@[^/]+[/][^/]+\/)$/;
219220

220221
/**
221222
* When `sass`/`node-sass` tries to resolve an import, it uses a special algorithm.
@@ -244,7 +245,7 @@ function getPossibleRequests(
244245
request = request.replace(MODULE_REQUEST_REGEX, "");
245246
}
246247

247-
if (isModuleImport.test(url)) {
248+
if (IS_MODULE_IMPORT.test(url)) {
248249
request = request[request.length - 1] === "/" ? request : `${request}/`;
249250

250251
return [...new Set([request, url])];
@@ -310,15 +311,13 @@ const IS_NATIVE_WIN32_PATH = /^[a-z]:[/\\]|^\\\\/i;
310311
* @param {Object} implementation - The imported Sass implementation, both
311312
* `sass` (Dart Sass) and `node-sass` are supported.
312313
* @param {string[]} [includePaths] - The list of include paths passed to Sass.
313-
* @param {boolean} [rootContext] - The configured Webpack root context.
314314
*
315315
* @throws If a compatible Sass implementation cannot be found.
316316
*/
317317
function getWebpackResolver(
318318
resolverFactory,
319319
implementation,
320-
includePaths = [],
321-
rootContext = false
320+
includePaths = []
322321
) {
323322
async function startResolving(resolutionMap) {
324323
if (resolutionMap.length === 0) {
@@ -380,6 +379,13 @@ function getWebpackResolver(
380379
);
381380

382381
return (context, request) => {
382+
// See https://github.com/webpack/webpack/issues/12340
383+
// Because `node-sass` calls our importer before `1. Filesystem imports relative to the base file.`
384+
// custom importer may not return `{ file: '/path/to/name.ext' }` and therefore our `context` will be relative
385+
if (!isDartSass && !path.isAbsolute(context)) {
386+
return Promise.reject();
387+
}
388+
383389
const originalRequest = request;
384390
const isFileScheme = originalRequest.slice(0, 5).toLowerCase() === "file:";
385391

@@ -415,7 +421,7 @@ function getWebpackResolver(
415421
// 4. Filesystem imports relative to an `includePaths` path.
416422
// 5. Filesystem imports relative to a `SASS_PATH` path.
417423
//
418-
// Because `sass`/`node-sass` run custom importers before `3`, `4` and `5` points, we need to emulate this behavior to avoid wrong resolution.
424+
// `sass` run custom importers before `3`, `4` and `5` points, we need to emulate this behavior to avoid wrong resolution.
419425
const sassPossibleRequests = getPossibleRequests(request);
420426

421427
// `node-sass` calls our importer before `1. Filesystem imports relative to the base file.`, so we need emulate this too
@@ -439,11 +445,7 @@ function getWebpackResolver(
439445
);
440446
}
441447

442-
const webpackPossibleRequests = getPossibleRequests(
443-
request,
444-
true,
445-
rootContext
446-
);
448+
const webpackPossibleRequests = getPossibleRequests(request, true);
447449

448450
resolutionMap = resolutionMap.concat({
449451
resolve: webpackResolve,
@@ -461,8 +463,7 @@ function getWebpackImporter(loaderContext, implementation, includePaths) {
461463
const resolve = getWebpackResolver(
462464
loaderContext.getResolve,
463465
implementation,
464-
includePaths,
465-
loaderContext.rootContext
466+
includePaths
466467
);
467468

468469
return (originalUrl, prev, done) => {
@@ -472,6 +473,7 @@ function getWebpackImporter(loaderContext, implementation, includePaths) {
472473
// Although we're also using stats.includedFiles, this might come in handy when an error occurs.
473474
// In this case, we don't get stats.includedFiles from node-sass/sass.
474475
loaderContext.addDependency(path.normalize(result));
476+
475477
// By removing the CSS file extension, we trigger node-sass to include the CSS file instead of just linking it.
476478
done({ file: result.replace(matchCss, "") });
477479
})

test/__snapshots__/sassOptions-option.test.js.snap

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2010,28 +2010,82 @@ exports[`sassOptions option should work with the "functions" option (node-sass)
20102010

20112011
exports[`sassOptions option should work with the "importer" as a array of functions option (dart-sass) (sass): css 1`] = `""`;
20122012

2013+
exports[`sassOptions option should work with the "importer" as a array of functions option (dart-sass) (sass): css 2`] = `
2014+
".a {
2015+
color: red;
2016+
}
2017+
2018+
.b {
2019+
color: blue;
2020+
}"
2021+
`;
2022+
20132023
exports[`sassOptions option should work with the "importer" as a array of functions option (dart-sass) (sass): errors 1`] = `Array []`;
20142024

2025+
exports[`sassOptions option should work with the "importer" as a array of functions option (dart-sass) (sass): errors 2`] = `Array []`;
2026+
20152027
exports[`sassOptions option should work with the "importer" as a array of functions option (dart-sass) (sass): warnings 1`] = `Array []`;
20162028

2029+
exports[`sassOptions option should work with the "importer" as a array of functions option (dart-sass) (sass): warnings 2`] = `Array []`;
2030+
20172031
exports[`sassOptions option should work with the "importer" as a array of functions option (dart-sass) (scss): css 1`] = `""`;
20182032

2033+
exports[`sassOptions option should work with the "importer" as a array of functions option (dart-sass) (scss): css 2`] = `
2034+
".a {
2035+
color: red;
2036+
}
2037+
2038+
.b {
2039+
color: blue;
2040+
}"
2041+
`;
2042+
20192043
exports[`sassOptions option should work with the "importer" as a array of functions option (dart-sass) (scss): errors 1`] = `Array []`;
20202044

2045+
exports[`sassOptions option should work with the "importer" as a array of functions option (dart-sass) (scss): errors 2`] = `Array []`;
2046+
20212047
exports[`sassOptions option should work with the "importer" as a array of functions option (dart-sass) (scss): warnings 1`] = `Array []`;
20222048

2049+
exports[`sassOptions option should work with the "importer" as a array of functions option (dart-sass) (scss): warnings 2`] = `Array []`;
2050+
20232051
exports[`sassOptions option should work with the "importer" as a array of functions option (node-sass) (sass): css 1`] = `""`;
20242052

2053+
exports[`sassOptions option should work with the "importer" as a array of functions option (node-sass) (sass): css 2`] = `
2054+
".a {
2055+
color: red; }
2056+
2057+
.b {
2058+
color: blue; }
2059+
"
2060+
`;
2061+
20252062
exports[`sassOptions option should work with the "importer" as a array of functions option (node-sass) (sass): errors 1`] = `Array []`;
20262063

2064+
exports[`sassOptions option should work with the "importer" as a array of functions option (node-sass) (sass): errors 2`] = `Array []`;
2065+
20272066
exports[`sassOptions option should work with the "importer" as a array of functions option (node-sass) (sass): warnings 1`] = `Array []`;
20282067

2068+
exports[`sassOptions option should work with the "importer" as a array of functions option (node-sass) (sass): warnings 2`] = `Array []`;
2069+
20292070
exports[`sassOptions option should work with the "importer" as a array of functions option (node-sass) (scss): css 1`] = `""`;
20302071

2072+
exports[`sassOptions option should work with the "importer" as a array of functions option (node-sass) (scss): css 2`] = `
2073+
".a {
2074+
color: red; }
2075+
2076+
.b {
2077+
color: blue; }
2078+
"
2079+
`;
2080+
20312081
exports[`sassOptions option should work with the "importer" as a array of functions option (node-sass) (scss): errors 1`] = `Array []`;
20322082

2083+
exports[`sassOptions option should work with the "importer" as a array of functions option (node-sass) (scss): errors 2`] = `Array []`;
2084+
20332085
exports[`sassOptions option should work with the "importer" as a array of functions option (node-sass) (scss): warnings 1`] = `Array []`;
20342086

2087+
exports[`sassOptions option should work with the "importer" as a array of functions option (node-sass) (scss): warnings 2`] = `Array []`;
2088+
20352089
exports[`sassOptions option should work with the "importer" as a single function option (dart-sass) (sass): css 1`] = `""`;
20362090

20372091
exports[`sassOptions option should work with the "importer" as a single function option (dart-sass) (sass): css 2`] = `

test/sass/glob-dir/a.sass

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

test/sass/glob-dir/b.sass

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.b
2+
color: blue

test/sass/glob-importer.sass

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@import "glob-dir/*"

test/sassOptions-option.test.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import path from "path";
22

3+
import globImporter from "node-sass-glob-importer";
34
import semver from "semver";
45
import nodeSass from "node-sass";
56
import dartSass from "sass";
@@ -233,6 +234,25 @@ describe("sassOptions option", () => {
233234
expect(getErrors(stats)).toMatchSnapshot("errors");
234235
});
235236

237+
it(`should work with the "importer" as a array of functions option (${implementationName}) (${syntax})`, async () => {
238+
const testId = getTestId("glob-importer", syntax);
239+
const options = {
240+
implementation: getImplementationByName(implementationName),
241+
sassOptions: {
242+
importer: [globImporter()],
243+
},
244+
};
245+
const compiler = getCompiler(testId, { loader: { options } });
246+
const stats = await compile(compiler);
247+
const codeFromBundle = getCodeFromBundle(stats, compiler);
248+
const codeFromSass = getCodeFromSass(testId, options);
249+
250+
expect(codeFromBundle.css).toBe(codeFromSass.css);
251+
expect(codeFromBundle.css).toMatchSnapshot("css");
252+
expect(getWarnings(stats)).toMatchSnapshot("warnings");
253+
expect(getErrors(stats)).toMatchSnapshot("errors");
254+
});
255+
236256
it(`should work with the "includePaths" option (${implementationName}) (${syntax})`, async () => {
237257
const testId = getTestId("import-include-paths", syntax);
238258
const options = {

test/scss/glob-dir/a.scss

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

test/scss/glob-dir/b.scss

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

test/scss/glob-importer.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@import "glob-dir/*";

0 commit comments

Comments
 (0)