Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat: allow to use String value for the `implementation option
  • Loading branch information
cap-Bernardito committed Jun 10, 2021
1 parent 6658bbf commit b118719
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 11 deletions.
59 changes: 59 additions & 0 deletions README.md
Expand Up @@ -51,6 +51,7 @@ And run `webpack` via your preferred method.
| **[`sourceMap`](#sourcemap)** | `{Boolean}` | `compiler.devtool` | Enables/Disables generation of source maps. |
| **[`webpackImporter`](#webpackimporter)** | `{Boolean}` | `true` | Enables/Disables the default Webpack importer. |
| **[`additionalData`](#additionalData)** | `{String\|Function}` | `undefined` | Prepends/Appends `Stylus` code to the actual entry file. |
| **[`implementation`](#implementation)** | `{String\|Function}` | `stylus` | Setup Stylus implementation to use. |

### `stylusOptions`

Expand Down Expand Up @@ -398,6 +399,64 @@ module.exports = {
};
```

### `implementation`

Type: `Function | String`

The special `implementation` option determines which implementation of Stylus to use. Overrides the locally installed `peerDependency` version of `stylus`.

#### Function

**webpack.config.js**

```js
module.exports = {
module: {
rules: [
{
test: /\.styl/i,
use: [
"style-loader",
"css-loader",
{
loader: "stylus-loader",
options: {
implementation: require("stylus"),
},
},
],
},
],
},
};
```

#### String

**webpack.config.js**

```js
module.exports = {
module: {
rules: [
{
test: /\.styl/i,
use: [
"style-loader",
"css-loader",
{
loader: "stylus-loader",
options: {
implementation: require.resolve("stylus"),
},
},
],
},
],
},
};
```

## Examples

### Normal usage
Expand Down
13 changes: 10 additions & 3 deletions src/index.js
@@ -1,19 +1,26 @@
import path from "path";

import stylus from "stylus";

import schema from "./options.json";
import {
getStylusOptions,
createEvaluator,
urlResolver,
readFile,
normalizeSourceMap,
getStylusImplementation,
} from "./utils";

export default async function stylusLoader(source) {
const options = this.getOptions(schema);
const callback = this.async();
const implementation = getStylusImplementation(this, options.implementation);

if (!implementation) {
callback();

return;
}

let data = source;

if (typeof options.additionalData !== "undefined") {
Expand All @@ -24,7 +31,7 @@ export default async function stylusLoader(source) {
}

const stylusOptions = getStylusOptions(this, options);
const styl = stylus(data, stylusOptions);
const styl = implementation(data, stylusOptions);

// include regular CSS on @import
if (stylusOptions.includeCSS) {
Expand Down
11 changes: 11 additions & 0 deletions src/options.json
Expand Up @@ -2,6 +2,17 @@
"title": "Stylus Loader options",
"type": "object",
"properties": {
"implementation": {
"description": "The implementation of the `Stylus` to be used (https://github.com/webpack-contrib/stylus-loade#implementation).",
"anyOf": [
{
"type": "string"
},
{
"instanceof": "Function"
}
]
},
"stylusOptions": {
"description": "Options to pass through to `Stylus` (https://github.com/webpack-contrib/stylus-loader#stylusoptions).",
"anyOf": [
Expand Down
22 changes: 22 additions & 0 deletions src/utils.js
Expand Up @@ -54,6 +54,27 @@ function getStylusOptions(loaderContext, loaderOptions) {
return stylusOptions;
}

function getStylusImplementation(loaderContext, implementation) {
let resolvedImplementation = implementation;

if (!implementation || typeof implementation === "string") {
const stylusImplPkg = implementation || "stylus";

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

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

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

function getPossibleRequests(loaderContext, filename) {
let request = filename;

Expand Down Expand Up @@ -710,4 +731,5 @@ export {
resolveFilename,
readFile,
normalizeSourceMap,
getStylusImplementation,
};
42 changes: 42 additions & 0 deletions test/__snapshots__/implementation-option.test.js.snap
@@ -0,0 +1,42 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`implementation option should throw error when unresolved package: errors 1`] = `
Array [
"ModuleError: Module Error (from \`replaced original path\`):
(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 []`;

exports[`implementation option should work when implementation option is string: css 1`] = `
"body {
font: 12px Helvetica, Arial, sans-serif;
}
a.button {
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
"
`;

exports[`implementation option should work when implementation option is string: errors 1`] = `Array []`;

exports[`implementation option should work when implementation option is string: warnings 1`] = `Array []`;

exports[`implementation option should work: css 1`] = `
"body {
font: 12px Helvetica, Arial, sans-serif;
}
a.button {
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
"
`;

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

exports[`implementation option should work: warnings 1`] = `Array []`;
56 changes: 48 additions & 8 deletions test/__snapshots__/validate-options.test.js.snap
Expand Up @@ -60,6 +60,46 @@ 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 "[]" value 1`] = `
"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema.
- options.implementation should be one of these:
string | function
-> The implementation of the \`Stylus\` to be used (https://github.com/webpack-contrib/stylus-loade#implementation).
Details:
* options.implementation should be a string.
* options.implementation should be an instance of function."
`;

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

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

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

exports[`validate options should throw an error on the "sourceMap" option with "string" value 1`] = `
"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema.
- options.sourceMap should be a boolean.
Expand Down Expand Up @@ -124,49 +164,49 @@ exports[`validate options should throw an error on the "stylusOptions" option wi
exports[`validate options should throw an error on the "unknown" option with "/test/" value 1`] = `
"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { stylusOptions?, sourceMap?, webpackImporter?, additionalData? }"
object { implementation?, stylusOptions?, sourceMap?, webpackImporter?, additionalData? }"
`;
exports[`validate options should throw an error on the "unknown" option with "[]" value 1`] = `
"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { stylusOptions?, sourceMap?, webpackImporter?, additionalData? }"
object { implementation?, stylusOptions?, sourceMap?, webpackImporter?, additionalData? }"
`;
exports[`validate options should throw an error on the "unknown" option with "{"foo":"bar"}" value 1`] = `
"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { stylusOptions?, sourceMap?, webpackImporter?, additionalData? }"
object { implementation?, stylusOptions?, sourceMap?, webpackImporter?, additionalData? }"
`;
exports[`validate options should throw an error on the "unknown" option with "{}" value 1`] = `
"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { stylusOptions?, sourceMap?, webpackImporter?, additionalData? }"
object { implementation?, stylusOptions?, sourceMap?, webpackImporter?, additionalData? }"
`;
exports[`validate options should throw an error on the "unknown" option with "1" value 1`] = `
"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { stylusOptions?, sourceMap?, webpackImporter?, additionalData? }"
object { implementation?, stylusOptions?, sourceMap?, webpackImporter?, additionalData? }"
`;
exports[`validate options should throw an error on the "unknown" option with "false" value 1`] = `
"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { stylusOptions?, sourceMap?, webpackImporter?, additionalData? }"
object { implementation?, stylusOptions?, sourceMap?, webpackImporter?, additionalData? }"
`;
exports[`validate options should throw an error on the "unknown" option with "test" value 1`] = `
"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { stylusOptions?, sourceMap?, webpackImporter?, additionalData? }"
object { implementation?, stylusOptions?, sourceMap?, webpackImporter?, additionalData? }"
`;
exports[`validate options should throw an error on the "unknown" option with "true" value 1`] = `
"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { stylusOptions?, sourceMap?, webpackImporter?, additionalData? }"
object { implementation?, stylusOptions?, sourceMap?, webpackImporter?, additionalData? }"
`;
exports[`validate options should throw an error on the "webpackImporter" option with "string" value 1`] = `
Expand Down
58 changes: 58 additions & 0 deletions test/implementation-option.test.js
@@ -0,0 +1,58 @@
/**
* @jest-environment node
*/

import {
compile,
getCodeFromBundle,
getCodeFromStylus,
getCompiler,
getErrors,
getWarnings,
} from "./helpers";

jest.setTimeout(30000);

describe("implementation option", () => {
it("should work", async () => {
const testId = "./basic.styl";
const compiler = getCompiler(testId, {
// eslint-disable-next-line global-require
implementation: require("stylus"),
});
const stats = await compile(compiler);
const codeFromBundle = getCodeFromBundle(stats, compiler);
const codeFromStylus = await getCodeFromStylus(testId);

expect(codeFromBundle.css).toBe(codeFromStylus.css);
expect(codeFromBundle.css).toMatchSnapshot("css");
expect(getWarnings(stats)).toMatchSnapshot("warnings");
expect(getErrors(stats)).toMatchSnapshot("errors");
});

it("should work when implementation option is string", async () => {
const testId = "./basic.styl";
const compiler = getCompiler(testId, {
implementation: require.resolve("stylus"),
});
const stats = await compile(compiler);
const codeFromBundle = getCodeFromBundle(stats, compiler);
const codeFromStylus = await getCodeFromStylus(testId);

expect(codeFromBundle.css).toBe(codeFromStylus.css);
expect(codeFromBundle.css).toMatchSnapshot("css");
expect(getWarnings(stats)).toMatchSnapshot("warnings");
expect(getErrors(stats)).toMatchSnapshot("errors");
});

it("should throw error when unresolved package", async () => {
const testId = "./basic.styl";
const compiler = getCompiler(testId, {
implementation: "unresolved",
});
const stats = await compile(compiler);

expect(getWarnings(stats)).toMatchSnapshot("warnings");
expect(getErrors(stats)).toMatchSnapshot("errors");
});
});
5 changes: 5 additions & 0 deletions test/validate-options.test.js
Expand Up @@ -38,6 +38,11 @@ describe("validate options", () => {
success: ["color = coral", () => "bg = coral"],
failure: [1, true, false, /test/, [], {}],
},
implementation: {
// eslint-disable-next-line global-require
success: [require("stylus"), "stylus"],
failure: [true, false, {}, []],
},
unknown: {
success: [],
failure: [1, true, false, "test", /test/, [], {}, { foo: "bar" }],
Expand Down

0 comments on commit b118719

Please sign in to comment.