Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat: validate loader options (#737)
  • Loading branch information
evilebottnawi committed Aug 20, 2019
1 parent 9c5028b commit 7b543fc
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 20 deletions.
6 changes: 4 additions & 2 deletions README.md
Expand Up @@ -203,7 +203,7 @@ module.exports = {

Type: `Object|Function`

Setups options for [Node Sass](https://github.com/sass/node-sass) or [Dart Sass](http://sass-lang.com/dart-sass).
Options for [Node Sass](https://github.com/sass/node-sass) or [Dart Sass](http://sass-lang.com/dart-sass) implementation.

> ℹ️ The `indentedSyntax` option has `true` value for the `sass` extension.
Expand Down Expand Up @@ -403,7 +403,7 @@ module.exports = {
Type: `Boolean`
Default: `true`

Allows to disable default `webpack` importer.
Enables/Disables default `webpack` importer.

This can improve performance in some cases. Use it with caution because aliases and `@import` at-rules starts with `~` will not work, but you can pass own `importer` to solve this (see [`importer docs`](https://github.com/sass/node-sass#importer--v200---experimental)).

Expand Down Expand Up @@ -476,6 +476,8 @@ module.exports = {

### Source maps

Enables/Disables generation of source maps.

To enable CSS source maps, you'll need to pass the `sourceMap` option to the sass-loader _and_ the css-loader.

**webpack.config.js**
Expand Down
69 changes: 52 additions & 17 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -41,6 +41,7 @@
"clone-deep": "^4.0.1",
"loader-utils": "^1.2.3",
"neo-async": "^2.6.1",
"schema-utils": "^2.1.0",
"semver": "^6.3.0"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion src/getSassOptions.js
Expand Up @@ -25,7 +25,7 @@ function getSassOptions(loaderContext, loaderOptions, content) {
const options = cloneDeep(
loaderOptions.sassOptions
? typeof loaderOptions.sassOptions === 'function'
? loaderOptions.sassOptions(loaderContext)
? loaderOptions.sassOptions(loaderContext) || {}
: loaderOptions.sassOptions
: {}
);
Expand Down
7 changes: 7 additions & 0 deletions src/index.js
@@ -1,9 +1,11 @@
import path from 'path';

import validateOptions from 'schema-utils';
import async from 'neo-async';
import semver from 'semver';
import { getOptions } from 'loader-utils';

import schema from './options.json';
import formatSassError from './formatSassError';
import webpackImporter from './webpackImporter';
import getSassOptions from './getSassOptions';
Expand All @@ -19,6 +21,11 @@ let nodeSassJobQueue = null;
function loader(content) {
const options = getOptions(this) || {};

validateOptions(schema, options, {
name: 'Sass Loader',
baseDataPath: 'options',
});

const callback = this.async();
const addNormalizedDependency = (file) => {
// node-sass returns POSIX paths
Expand Down
40 changes: 40 additions & 0 deletions src/options.json
@@ -0,0 +1,40 @@
{
"type": "object",
"properties": {
"implementation": {
"description": "The implementation of the sass to be used (https://github.com/webpack-contrib/sass-loader#implementation)."
},
"sassOptions": {
"description": "Options for `node-sass` or `sass` (`Dart Sass`) implementation. (https://github.com/webpack-contrib/sass-loader#implementation).",
"anyOf": [
{
"type": "object",
"additionalProperties": true
},
{
"instanceof": "Function"
}
]
},
"prependData": {
"description": "Prepends `Sass`/`SCSS` code before the actual entry file (https://github.com/webpack-contrib/sass-loader#prependdata).",
"anyOf": [
{
"type": "string"
},
{
"instanceof": "Function"
}
]
},
"sourceMap": {
"description": "Enables/Disables generation of source maps (https://github.com/webpack-contrib/sass-loader#sourcemap).",
"type": "boolean"
},
"webpackImporter": {
"description": "Enables/Disables default `webpack` importer (https://github.com/webpack-contrib/sass-loader#webpackimporter).",
"type": "boolean"
}
},
"additionalProperties": false
}
34 changes: 34 additions & 0 deletions test/__snapshots__/validate-options.test.js.snap
@@ -0,0 +1,34 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`validate options 1`] = `
"Invalid options object. Sass Loader has been initialised using an options object that does not match the API schema.
- options.sassOptions should be one of these:
object {} | function
-> Options for \`node-sass\` or \`sass\` (\`Dart Sass\`) implementation. (https://github.com/webpack-contrib/sass-loader#implementation).
Details:
* options.sassOptions should be an object:
object {}
* options.sassOptions should be an instance of function."
`;

exports[`validate options 2`] = `
"Invalid options object. Sass Loader has been initialised using an options object that does not match the API schema.
- options.prependData should be one of these:
string | function
-> Prepends \`Sass\`/\`SCSS\` code before the actual entry file (https://github.com/webpack-contrib/sass-loader#prependdata).
Details:
* options.prependData should be a string.
* options.prependData should be an instance of function."
`;

exports[`validate options 3`] = `
"Invalid options object. Sass Loader has been initialised using an options object that does not match the API schema.
- options.webpackImporter should be a boolean.
-> Enables/Disables default \`webpack\` importer (https://github.com/webpack-contrib/sass-loader#webpackimporter)."
`;

exports[`validate options 4`] = `
"Invalid options object. Sass Loader has been initialised using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { implementation?, sassOptions?, prependData?, sourceMap?, webpackImporter? }"
`;
51 changes: 51 additions & 0 deletions test/validate-options.test.js
@@ -0,0 +1,51 @@
import loader from '../src/cjs';

it('validate options', () => {
const validate = (options) =>
loader.call(
Object.assign(
{},
{
query: options,
loaders: [],
resourcePath: 'file.scss',
getResolve: () => () => {},
async: () => (error) => {
if (error) {
throw error;
}
},
}
),
'a { color: red; }'
);

// eslint-disable-next-line global-require
// expect(() => validate({ implementation: require('node-sass') })).not.toThrow();
// eslint-disable-next-line global-require
// expect(() => validate({ implementation: require('sass') })).not.toThrow();
// expect(() => validate({ implementation: true })).not.toThrow();

expect(() => validate({ sassOptions: {} })).not.toThrow();
expect(() =>
validate({
sassOptions: () => {
return {};
},
})
).not.toThrow();
expect(() => validate({ sassOptions: () => {} })).not.toThrow();
expect(() => validate({ sassOptions: true })).toThrowErrorMatchingSnapshot();

expect(() => validate({ prependData: '$color: red;' })).not.toThrow();
expect(() => validate({ prependData: () => '$color: red;' })).not.toThrow();
expect(() => validate({ prependData: true })).toThrowErrorMatchingSnapshot();

expect(() => validate({ webpackImporter: true })).not.toThrow();
expect(() => validate({ webpackImporter: false })).not.toThrow();
expect(() =>
validate({ webpackImporter: 'unknown' })
).toThrowErrorMatchingSnapshot();

expect(() => validate({ unknown: 'unknown' })).toThrowErrorMatchingSnapshot();
});

0 comments on commit 7b543fc

Please sign in to comment.