Skip to content

Commit

Permalink
feat: add webpackImporter option (#244)
Browse files Browse the repository at this point in the history
  • Loading branch information
cap-Bernardito committed Sep 11, 2020
1 parent a5bebfc commit bbe232a
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 14 deletions.
95 changes: 90 additions & 5 deletions README.md
Expand Up @@ -45,10 +45,11 @@ And run `webpack` via your preferred method.

## Options

| Name | Type | Default | Description |
| :-----------------------------------: | :------------------: | :----------------: | :------------------------------------------ |
| **[`stylusOptions`](#stylusOptions)** | `{Object\|Function}` | `{}` | Options for Stylus. |
| **[`sourceMap`](#sourcemap)** | `{Boolean}` | `compiler.devtool` | Enables/Disables generation of source maps. |
| Name | Type | Default | Description |
| :---------------------------------------: | :------------------: | :----------------: | :--------------------------------------------- |
| **[`stylusOptions`](#stylusOptions)** | `{Object\|Function}` | `{}` | Options for Stylus. |
| **[`sourceMap`](#sourcemap)** | `{Boolean}` | `compiler.devtool` | Enables/Disables generation of source maps. |
| **[`webpackImporter`](#webpackimporter)** | `{Boolean}` | `true` | Enables/Disables the default Webpack importer. |

### `stylusOptions`

Expand Down Expand Up @@ -104,7 +105,7 @@ module.exports = {

#### `Function`

Allows setting the options passed through to Less based off of the loader context.
Allows setting the options passed through to Stylus based off of the loader context.

```js
module.exports = {
Expand Down Expand Up @@ -175,6 +176,40 @@ module.exports = {
};
```

### `webpackImporter`

Type: `Boolean`
Default: `true`

Enables/Disables the default Webpack importer.

This can improve performance in some cases.
Use it with caution because aliases and `@import` at-rules starting with `~` will not work.

**webpack.config.js**

```js
module.exports = {
module: {
rules: [
{
test: /\.styl/i,
use: [
'style-loader',
'css-loader',
{
loader: 'stylus-loader',
options: {
webpackImporter: false,
},
},
],
},
],
},
};
```

## Examples

### Normal usage
Expand Down Expand Up @@ -277,6 +312,56 @@ module.exports = {

Usually, it's recommended to extract the style sheets into a dedicated file in production using the [MiniCssExtractPlugin](https://github.com/webpack-contrib/mini-css-extract-plugin). This way your styles are not dependent on JavaScript.

### webpack resolver

Webpack provides an [advanced mechanism to resolve files](https://webpack.js.org/configuration/resolve/).
The `stylus-loader` applies the webpack resolver when processing queries.
Thus you can import your Stylus modules from `node_modules`.
Just prepend them with a `~` which tells webpack to look up the [`modules`](https://webpack.js.org/configuration/resolve/#resolve-modules).

```styl
@import '~bootstrap-styl/bootstrap/index.styl';
```

It's important to only prepend it with `~`, because `~/` resolves to the home-directory.
Webpack needs to distinguish between `bootstrap` and `~bootstrap`, because CSS and Styl files have no special syntax for importing relative files.
Writing `@import "file"` is the same as `@import "./file";`

### Stylus resolver

If you specify the `paths` option, modules will be searched in the given `paths`.
This is Stylus default behavior.

**webpack.config.js**

```js
module.exports = {
module: {
rules: [
{
test: /\.styl/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
},
{
loader: 'stylus-loader',
options: {
stylusOptions: {
paths: [path.resolve(__dirname, 'node_modules')],
},
},
},
],
},
],
},
};
```

### Extracting style sheets

Bundling CSS with webpack has some nice advantages like referencing images and fonts with hashed urls or [hot module replacement](https://webpack.js.org/concepts/hot-module-replacement/) in development. In production, on the other hand, it's not a good idea to apply your style sheets depending on JS execution. Rendering may be delayed or even a [FOUC](https://en.wikipedia.org/wiki/Flash_of_unstyled_content) might be visible. Thus it's often still better to have them as separate files in your final production build.
Expand Down
9 changes: 8 additions & 1 deletion src/index.js
Expand Up @@ -68,7 +68,14 @@ export default async function stylusLoader(source) {
styl.set('include css', true);
}

styl.set('Evaluator', await createEvaluator(source, stylusOptions, this));
const shouldUseWebpackImporter =
typeof options.webpackImporter === 'boolean'
? options.webpackImporter
: true;

if (shouldUseWebpackImporter) {
styl.set('Evaluator', await createEvaluator(source, stylusOptions, this));
}

// keep track of imported files (used by Stylus CLI watch mode)
// eslint-disable-next-line no-underscore-dangle
Expand Down
4 changes: 4 additions & 0 deletions src/options.json
Expand Up @@ -16,6 +16,10 @@
"sourceMap": {
"description": "Enables/Disables generation of source maps (https://github.com/webpack-contrib/stylus-loader#sourcemap).",
"type": "boolean"
},
"webpackImporter": {
"description": "Enables/Disables default `webpack` importer (https://github.com/webpack-contrib/stylus-loader#webpackimporter).",
"type": "boolean"
}
},
"additionalProperties": false
Expand Down
22 changes: 14 additions & 8 deletions test/__snapshots__/validate-options.test.js.snap
Expand Up @@ -64,47 +64,53 @@ 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? }"
object { stylusOptions?, sourceMap?, webpackImporter? }"
`;
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? }"
object { stylusOptions?, sourceMap?, webpackImporter? }"
`;
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? }"
object { stylusOptions?, sourceMap?, webpackImporter? }"
`;
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? }"
object { stylusOptions?, sourceMap?, webpackImporter? }"
`;
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? }"
object { stylusOptions?, sourceMap?, webpackImporter? }"
`;
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? }"
object { stylusOptions?, sourceMap?, webpackImporter? }"
`;
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? }"
object { stylusOptions?, sourceMap?, webpackImporter? }"
`;
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? }"
object { stylusOptions?, sourceMap?, webpackImporter? }"
`;
exports[`validate options should throw an error on the "webpackImporter" 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.webpackImporter should be a boolean.
-> Enables/Disables default \`webpack\` importer (https://github.com/webpack-contrib/stylus-loader#webpackimporter)."
`;
43 changes: 43 additions & 0 deletions test/__snapshots__/webpackImporter-options.test.js.snap
@@ -0,0 +1,43 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`webpackImporter option should throw an error on webpack import when value is "false": errors 1`] = `
Array [
"ModuleBuildError: Module build failed (from \`replaced original path\`):
Error: /test/fixtures/import-webpack.styl:1:9",
]
`;

exports[`webpackImporter option should throw an error on webpack import when value is "false": warnings 1`] = `Array []`;

exports[`webpackImporter option should work when value is "false": css 1`] = `
".other {
font-family: serif;
}
"
`;

exports[`webpackImporter option should work when value is "false": errors 1`] = `Array []`;

exports[`webpackImporter option should work when value is "false": warnings 1`] = `Array []`;

exports[`webpackImporter option should work when value is "true": css 1`] = `
".imported-in-web-modules {
font-size: 2em;
}
"
`;

exports[`webpackImporter option should work when value is "true": errors 1`] = `Array []`;

exports[`webpackImporter option should work when value is "true": warnings 1`] = `Array []`;

exports[`webpackImporter option should work when value is not specify: css 1`] = `
".imported-in-web-modules {
font-size: 2em;
}
"
`;

exports[`webpackImporter option should work when value is not specify: errors 1`] = `Array []`;

exports[`webpackImporter option should work when value is not specify: warnings 1`] = `Array []`;
4 changes: 4 additions & 0 deletions test/validate-options.test.js
Expand Up @@ -30,6 +30,10 @@ describe('validate options', () => {
success: [true, false],
failure: ['string'],
},
webpackImporter: {
success: [true, false],
failure: ['string'],
},
unknown: {
success: [],
failure: [1, true, false, 'test', /test/, [], {}, { foo: 'bar' }],
Expand Down
86 changes: 86 additions & 0 deletions test/webpackImporter-options.test.js
@@ -0,0 +1,86 @@
import path from 'path';

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

describe('webpackImporter option', () => {
it('should work when value is not specify', async () => {
const testId = './import-webpack.styl';
const compiler = getCompiler(
testId,
{},
{
resolve: {
modules: [path.join(__dirname, 'fixtures', 'web_modules')],
},
}
);
const stats = await compile(compiler);
const codeFromBundle = getCodeFromBundle(stats, compiler);

expect(codeFromBundle.css).toMatchSnapshot('css');
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});

it('should work when value is "true"', async () => {
const testId = './import-webpack.styl';
const compiler = getCompiler(
testId,
{
webpackImporter: true,
},
{
resolve: {
modules: [path.join(__dirname, 'fixtures', 'web_modules')],
},
}
);
const stats = await compile(compiler);
const codeFromBundle = getCodeFromBundle(stats, compiler);

expect(codeFromBundle.css).toMatchSnapshot('css');
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});

it('should work when value is "false"', async () => {
const testId = './shallow-paths.styl';
const compiler = getCompiler(testId, {
webpackImporter: false,
stylusOptions: {
paths: ['test/fixtures/paths'],
},
});
const stats = await compile(compiler);
const codeFromBundle = getCodeFromBundle(stats, compiler);

expect(codeFromBundle.css).toMatchSnapshot('css');
expect(getWarnings(stats)).toMatchSnapshot('warnings');
expect(getErrors(stats)).toMatchSnapshot('errors');
});

it('should throw an error on webpack import when value is "false"', async () => {
const testId = './import-webpack.styl';
const compiler = getCompiler(
testId,
{
webpackImporter: false,
},
{
resolve: {
modules: [path.join(__dirname, 'fixtures', 'web_modules')],
},
}
);
const stats = await compile(compiler);

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

0 comments on commit bbe232a

Please sign in to comment.