Skip to content

Commit

Permalink
feat: allow String value for the implementation option
Browse files Browse the repository at this point in the history
  • Loading branch information
cap-Bernardito committed Jun 10, 2021
1 parent 68b93f1 commit 382a3ca
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 15 deletions.
33 changes: 31 additions & 2 deletions README.md
Expand Up @@ -121,15 +121,15 @@ Thankfully there are a two solutions to this problem:

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

### `implementation`

Type: `Object`
Type: `Object | String`
Default: `sass`

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

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

#### Object

For example, to use Dart Sass, you'd pass:

```js
Expand All @@ -193,6 +195,33 @@ module.exports = {
};
```

#### String

For example, to use Dart Sass, you'd pass:

```js
module.exports = {
module: {
rules: [
{
test: /\.s[ac]ss$/i,
use: [
"style-loader",
"css-loader",
{
loader: "sass-loader",
options: {
// Prefer `dart-sass`
implementation: require.resolve("sass"),
},
},
],
},
],
},
};
```

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.
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.

Expand Down
9 changes: 8 additions & 1 deletion src/options.json
Expand Up @@ -4,7 +4,14 @@
"properties": {
"implementation": {
"description": "The implementation of the sass to be used (https://github.com/webpack-contrib/sass-loader#implementation).",
"type": "object"
"anyOf": [
{
"type": "string"
},
{
"type": "object"
}
]
},
"sassOptions": {
"description": "Options for `node-sass` or `sass` (`Dart Sass`) implementation. (https://github.com/webpack-contrib/sass-loader#implementation).",
Expand Down
12 changes: 12 additions & 0 deletions src/utils.js
Expand Up @@ -38,6 +38,18 @@ function getSassImplementation(loaderContext, implementation) {
}
}

if (typeof resolvedImplementation === "string") {
try {
// eslint-disable-next-line import/no-dynamic-require, global-require
resolvedImplementation = require(resolvedImplementation);
} catch (error) {
loaderContext.emitError(error);

// eslint-disable-next-line consistent-return
return;
}
}

const { info } = resolvedImplementation;

if (!info) {
Expand Down
13 changes: 13 additions & 0 deletions test/__snapshots__/implementation-option.test.js.snap
Expand Up @@ -12,6 +12,10 @@ exports[`implementation option not specify: errors 1`] = `Array []`;

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

exports[`implementation option sass_string: errors 1`] = `Array []`;

exports[`implementation option sass_string: warnings 1`] = `Array []`;

exports[`implementation option should not swallow an error when trying to load a sass implementation: errors 1`] = `
Array [
"ModuleError: Module Error (from ../src/cjs.js):
Expand Down Expand Up @@ -47,3 +51,12 @@ Unknown Sass implementation.",
`;

exports[`implementation option should throw error when the "info" does not exist: warnings 1`] = `Array []`;

exports[`implementation option should throw error when unresolved package: errors 1`] = `
Array [
"ModuleError: Module Error (from ../src/cjs.js):
(Emitted value instead of an instance of Error) Error: Cannot find module 'unresolved' from 'src/utils.js'",
]
`;

exports[`implementation option should throw error when unresolved package: warnings 1`] = `Array []`;
22 changes: 15 additions & 7 deletions test/__snapshots__/validate-options.test.js.snap
Expand Up @@ -10,18 +10,26 @@ exports[`validate options should throw an error on the "additionalData" option w
* options.additionalData should be an instance of function."
`;

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

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

exports[`validate options should throw an error on the "sassOptions" option with "string" value 1`] = `
Expand Down
2 changes: 2 additions & 0 deletions test/helpers/getImplementationByName.js
Expand Up @@ -5,6 +5,8 @@ function getImplementationByName(implementationName) {
} else if (implementationName === "dart-sass") {
// eslint-disable-next-line global-require
return require("sass");
} else if (implementationName === "sass_string") {
return require.resolve("sass");
}

throw new Error(`Can't find ${implementationName}`);
Expand Down
25 changes: 22 additions & 3 deletions test/implementation-option.test.js
Expand Up @@ -16,7 +16,7 @@ import {
jest.setTimeout(30000);

let Fiber;
const implementations = [nodeSass, dartSass];
const implementations = [nodeSass, dartSass, "sass_string"];

describe("implementation option", () => {
beforeAll(async () => {
Expand All @@ -34,7 +34,11 @@ describe("implementation option", () => {
});

implementations.forEach((implementation) => {
const [implementationName] = implementation.info.split("\t");
let implementationName = implementation;

if (implementation.info) {
[implementationName] = implementation.info.split("\t");
}

it(`${implementationName}`, async () => {
const nodeSassSpy = jest.spyOn(nodeSass, "render");
Expand All @@ -57,7 +61,10 @@ describe("implementation option", () => {
if (implementationName === "node-sass") {
expect(nodeSassSpy).toHaveBeenCalledTimes(1);
expect(dartSassSpy).toHaveBeenCalledTimes(0);
} else if (implementationName === "dart-sass") {
} else if (
implementationName === "dart-sass" ||
implementationName === "dart-sass_string"
) {
expect(nodeSassSpy).toHaveBeenCalledTimes(0);
expect(dartSassSpy).toHaveBeenCalledTimes(1);
}
Expand All @@ -67,6 +74,18 @@ describe("implementation option", () => {
});
});

it("should throw error when unresolved package", async () => {
const testId = getTestId("language", "scss");
const options = {
implementation: "unresolved",
};
const compiler = getCompiler(testId, { loader: { options } });
const stats = await compile(compiler);

expect(getWarnings(stats)).toMatchSnapshot("warnings");
expect(getErrors(stats)).toMatchSnapshot("errors");
});

it("not specify", async () => {
const nodeSassSpy = jest.spyOn(nodeSass, "render");
const dartSassSpy = jest.spyOn(dartSass, "render");
Expand Down
4 changes: 2 additions & 2 deletions test/validate-options.test.js
Expand Up @@ -27,8 +27,8 @@ describe("validate options", () => {
const tests = {
implementation: {
// eslint-disable-next-line global-require
success: [require("sass"), require("node-sass")],
failure: [true, "string"],
success: [require("sass"), require("node-sass"), "sass", "node-sass"],
failure: [true, () => {}],
},
sassOptions: {
success: [
Expand Down

0 comments on commit 382a3ca

Please sign in to comment.