Skip to content

Commit 382a3ca

Browse files
feat: allow String value for the implementation option
1 parent 68b93f1 commit 382a3ca

8 files changed

+105
-15
lines changed

README.md

+31-2
Original file line numberDiff line numberDiff line change
@@ -121,15 +121,15 @@ Thankfully there are a two solutions to this problem:
121121

122122
| Name | Type | Default | Description |
123123
| :---------------------------------------: | :------------------: | :-------------------------------------: | :---------------------------------------------------------------- |
124-
| **[`implementation`](#implementation)** | `{Object}` | `sass` | Setup Sass implementation to use. |
124+
| **[`implementation`](#implementation)** | `{Object\|String}` | `sass` | Setup Sass implementation to use. |
125125
| **[`sassOptions`](#sassoptions)** | `{Object\|Function}` | defaults values for Sass implementation | Options for Sass. |
126126
| **[`sourceMap`](#sourcemap)** | `{Boolean}` | `compiler.devtool` | Enables/Disables generation of source maps. |
127127
| **[`additionalData`](#additionaldata)** | `{String\|Function}` | `undefined` | Prepends/Appends `Sass`/`SCSS` code before the actual entry file. |
128128
| **[`webpackImporter`](#webpackimporter)** | `{Boolean}` | `true` | Enables/Disables the default Webpack importer. |
129129

130130
### `implementation`
131131

132-
Type: `Object`
132+
Type: `Object | String`
133133
Default: `sass`
134134

135135
The special `implementation` option determines which implementation of Sass to use.
@@ -168,6 +168,8 @@ In order to avoid this situation you can use the `implementation` option.
168168

169169
The `implementation` options either accepts `sass` (`Dart Sass`) or `node-sass` as a module.
170170

171+
#### Object
172+
171173
For example, to use Dart Sass, you'd pass:
172174

173175
```js
@@ -193,6 +195,33 @@ module.exports = {
193195
};
194196
```
195197

198+
#### String
199+
200+
For example, to use Dart Sass, you'd pass:
201+
202+
```js
203+
module.exports = {
204+
module: {
205+
rules: [
206+
{
207+
test: /\.s[ac]ss$/i,
208+
use: [
209+
"style-loader",
210+
"css-loader",
211+
{
212+
loader: "sass-loader",
213+
options: {
214+
// Prefer `dart-sass`
215+
implementation: require.resolve("sass"),
216+
},
217+
},
218+
],
219+
},
220+
],
221+
},
222+
};
223+
```
224+
196225
Note that when using `sass` (`Dart Sass`), **synchronous compilation is twice as fast as asynchronous compilation** by default, due to the overhead of asynchronous callbacks.
197226
To avoid this overhead, you can use the [fibers](https://www.npmjs.com/package/fibers) package to call asynchronous importers from the synchronous code path.
198227

src/options.json

+8-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@
44
"properties": {
55
"implementation": {
66
"description": "The implementation of the sass to be used (https://github.com./webpack-contrib/sass-loader#implementation).",
7-
"type": "object"
7+
"anyOf": [
8+
{
9+
"type": "string"
10+
},
11+
{
12+
"type": "object"
13+
}
14+
]
815
},
916
"sassOptions": {
1017
"description": "Options for `node-sass` or `sass` (`Dart Sass`) implementation. (https://github.com./webpack-contrib/sass-loader#implementation).",

src/utils.js

+12
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,18 @@ function getSassImplementation(loaderContext, implementation) {
3838
}
3939
}
4040

41+
if (typeof resolvedImplementation === "string") {
42+
try {
43+
// eslint-disable-next-line import/no-dynamic-require, global-require
44+
resolvedImplementation = require(resolvedImplementation);
45+
} catch (error) {
46+
loaderContext.emitError(error);
47+
48+
// eslint-disable-next-line consistent-return
49+
return;
50+
}
51+
}
52+
4153
const { info } = resolvedImplementation;
4254

4355
if (!info) {

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

+13
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ exports[`implementation option not specify: errors 1`] = `Array []`;
1212

1313
exports[`implementation option not specify: warnings 1`] = `Array []`;
1414

15+
exports[`implementation option sass_string: errors 1`] = `Array []`;
16+
17+
exports[`implementation option sass_string: warnings 1`] = `Array []`;
18+
1519
exports[`implementation option should not swallow an error when trying to load a sass implementation: errors 1`] = `
1620
Array [
1721
"ModuleError: Module Error (from ../src/cjs.js):
@@ -47,3 +51,12 @@ Unknown Sass implementation.",
4751
`;
4852

4953
exports[`implementation option should throw error when the "info" does not exist: warnings 1`] = `Array []`;
54+
55+
exports[`implementation option should throw error when unresolved package: errors 1`] = `
56+
Array [
57+
"ModuleError: Module Error (from ../src/cjs.js):
58+
(Emitted value instead of an instance of Error) Error: Cannot find module 'unresolved' from 'src/utils.js'",
59+
]
60+
`;
61+
62+
exports[`implementation option should throw error when unresolved package: warnings 1`] = `Array []`;

test/__snapshots__/validate-options.test.js.snap

+15-7
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,26 @@ exports[`validate options should throw an error on the "additionalData" option w
1010
* options.additionalData should be an instance of function."
1111
`;
1212

13-
exports[`validate options should throw an error on the "implementation" option with "string" value 1`] = `
13+
exports[`validate options should throw an error on the "implementation" option with "() => {}" value 1`] = `
1414
"Invalid options object. Sass Loader has been initialized using an options object that does not match the API schema.
15-
- options.implementation should be an object:
16-
object {}
17-
-> The implementation of the sass to be used (https://github.com./webpack-contrib/sass-loader#implementation)."
15+
- options.implementation should be one of these:
16+
string | object {}
17+
-> The implementation of the sass to be used (https://github.com./webpack-contrib/sass-loader#implementation).
18+
Details:
19+
* options.implementation should be a string.
20+
* options.implementation should be an object:
21+
object {}"
1822
`;
1923

2024
exports[`validate options should throw an error on the "implementation" option with "true" value 1`] = `
2125
"Invalid options object. Sass Loader has been initialized using an options object that does not match the API schema.
22-
- options.implementation should be an object:
23-
object {}
24-
-> The implementation of the sass to be used (https://github.com./webpack-contrib/sass-loader#implementation)."
26+
- options.implementation should be one of these:
27+
string | object {}
28+
-> The implementation of the sass to be used (https://github.com./webpack-contrib/sass-loader#implementation).
29+
Details:
30+
* options.implementation should be a string.
31+
* options.implementation should be an object:
32+
object {}"
2533
`;
2634

2735
exports[`validate options should throw an error on the "sassOptions" option with "string" value 1`] = `

test/helpers/getImplementationByName.js

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ function getImplementationByName(implementationName) {
55
} else if (implementationName === "dart-sass") {
66
// eslint-disable-next-line global-require
77
return require("sass");
8+
} else if (implementationName === "sass_string") {
9+
return require.resolve("sass");
810
}
911

1012
throw new Error(`Can't find ${implementationName}`);

test/implementation-option.test.js

+22-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
jest.setTimeout(30000);
1717

1818
let Fiber;
19-
const implementations = [nodeSass, dartSass];
19+
const implementations = [nodeSass, dartSass, "sass_string"];
2020

2121
describe("implementation option", () => {
2222
beforeAll(async () => {
@@ -34,7 +34,11 @@ describe("implementation option", () => {
3434
});
3535

3636
implementations.forEach((implementation) => {
37-
const [implementationName] = implementation.info.split("\t");
37+
let implementationName = implementation;
38+
39+
if (implementation.info) {
40+
[implementationName] = implementation.info.split("\t");
41+
}
3842

3943
it(`${implementationName}`, async () => {
4044
const nodeSassSpy = jest.spyOn(nodeSass, "render");
@@ -57,7 +61,10 @@ describe("implementation option", () => {
5761
if (implementationName === "node-sass") {
5862
expect(nodeSassSpy).toHaveBeenCalledTimes(1);
5963
expect(dartSassSpy).toHaveBeenCalledTimes(0);
60-
} else if (implementationName === "dart-sass") {
64+
} else if (
65+
implementationName === "dart-sass" ||
66+
implementationName === "dart-sass_string"
67+
) {
6168
expect(nodeSassSpy).toHaveBeenCalledTimes(0);
6269
expect(dartSassSpy).toHaveBeenCalledTimes(1);
6370
}
@@ -67,6 +74,18 @@ describe("implementation option", () => {
6774
});
6875
});
6976

77+
it("should throw error when unresolved package", async () => {
78+
const testId = getTestId("language", "scss");
79+
const options = {
80+
implementation: "unresolved",
81+
};
82+
const compiler = getCompiler(testId, { loader: { options } });
83+
const stats = await compile(compiler);
84+
85+
expect(getWarnings(stats)).toMatchSnapshot("warnings");
86+
expect(getErrors(stats)).toMatchSnapshot("errors");
87+
});
88+
7089
it("not specify", async () => {
7190
const nodeSassSpy = jest.spyOn(nodeSass, "render");
7291
const dartSassSpy = jest.spyOn(dartSass, "render");

test/validate-options.test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ describe("validate options", () => {
2727
const tests = {
2828
implementation: {
2929
// eslint-disable-next-line global-require
30-
success: [require("sass"), require("node-sass")],
31-
failure: [true, "string"],
30+
success: [require("sass"), require("node-sass"), "sass", "node-sass"],
31+
failure: [true, () => {}],
3232
},
3333
sassOptions: {
3434
success: [

0 commit comments

Comments
 (0)