Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: add webpackImporter option #244

Merged
merged 1 commit into from Sep 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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');
});
});