diff --git a/README.md b/README.md index f934398..0aed17e 100644 --- a/README.md +++ b/README.md @@ -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` @@ -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 = { @@ -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 @@ -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. diff --git a/src/index.js b/src/index.js index 6663cef..4140ce9 100644 --- a/src/index.js +++ b/src/index.js @@ -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 diff --git a/src/options.json b/src/options.json index 4370928..dadca17 100644 --- a/src/options.json +++ b/src/options.json @@ -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 diff --git a/test/__snapshots__/validate-options.test.js.snap b/test/__snapshots__/validate-options.test.js.snap index 82b09f1..6d3bff3 100644 --- a/test/__snapshots__/validate-options.test.js.snap +++ b/test/__snapshots__/validate-options.test.js.snap @@ -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)." `; diff --git a/test/__snapshots__/webpackImporter-options.test.js.snap b/test/__snapshots__/webpackImporter-options.test.js.snap new file mode 100644 index 0000000..fec2966 --- /dev/null +++ b/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 []`; diff --git a/test/validate-options.test.js b/test/validate-options.test.js index 3cc667c..fd077ff 100644 --- a/test/validate-options.test.js +++ b/test/validate-options.test.js @@ -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' }], diff --git a/test/webpackImporter-options.test.js b/test/webpackImporter-options.test.js new file mode 100644 index 0000000..9cdc641 --- /dev/null +++ b/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'); + }); +});