diff --git a/README.md b/README.md index 274cb35..bc78f12 100644 --- a/README.md +++ b/README.md @@ -45,11 +45,12 @@ 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. | -| **[`webpackImporter`](#webpackimporter)** | `{Boolean}` | `true` | Enables/Disables the default Webpack importer. | +| 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. | +| **[`additionalData`](#additionalData)** | `{String\|Function}` | `undefined` | Prepends/Appends `Stylus` code to the actual entry file. | ### `stylusOptions` @@ -210,6 +211,76 @@ module.exports = { }; ``` +### `additionalData` + +Type: `String|Function` +Default: `undefined` + +Prepends `Stylus` code before the actual entry file. +In this case, the `stylus-loader` will not override the source but just **prepend** the entry's content. + +This is especially useful when some of your Stylus variables depend on the environment: + +> ℹ Since you're injecting code, this will break the source mappings in your entry file. Often there's a simpler solution than this, like multiple Stylus entry files. + +#### `String` + +```js +module.exports = { + module: { + rules: [ + { + test: /\.styl/, + use: [ + 'style-loader', + 'css-loader', + { + loader: 'stylus-loader', + options: { + additionalData: `@env: ${process.env.NODE_ENV};`, + }, + }, + ], + }, + ], + }, +}; +``` + +#### `Function` + +```js +module.exports = { + module: { + rules: [ + { + test: /\.styl/, + use: [ + 'style-loader', + 'css-loader', + { + loader: 'stylus-loader', + options: { + additionalData: (content, loaderContext) => { + // More information about available properties https://webpack.js.org/api/loaders/ + const { resourcePath, rootContext } = loaderContext; + const relativePath = path.relative(rootContext, resourcePath); + + if (relativePath === 'styles/foo.styl') { + return 'value = 100px' + content; + } + + return 'value 200px' + content; + }, + }, + }, + ], + }, + ], + }, +}; +``` + ## Examples ### Normal usage diff --git a/src/index.js b/src/index.js index 4140ce9..6c45ef3 100644 --- a/src/index.js +++ b/src/index.js @@ -33,7 +33,16 @@ export default async function stylusLoader(source) { }; } - const styl = stylus(source, stylusOptions); + let data = source; + + if (typeof options.additionalData !== 'undefined') { + data = + typeof options.additionalData === 'function' + ? `${options.additionalData(data, this)}` + : `${options.additionalData}\n${data}`; + } + + const styl = stylus(data, stylusOptions); if (typeof stylusOptions.include !== 'undefined') { for (const included of stylusOptions.include) { diff --git a/src/options.json b/src/options.json index dadca17..8ddde1d 100644 --- a/src/options.json +++ b/src/options.json @@ -20,6 +20,17 @@ "webpackImporter": { "description": "Enables/Disables default `webpack` importer (https://github.com/webpack-contrib/stylus-loader#webpackimporter).", "type": "boolean" + }, + "additionalData": { + "description": "Prepends/Appends `Stylus` code to the actual entry file (https://github.com/webpack-contrib/stylus-loader#additionalData).", + "anyOf": [ + { + "type": "string" + }, + { + "instanceof": "Function" + } + ] } }, "additionalProperties": false diff --git a/test/__snapshots__/additionalData-option.test.js.snap b/test/__snapshots__/additionalData-option.test.js.snap new file mode 100644 index 0000000..b05eff5 --- /dev/null +++ b/test/__snapshots__/additionalData-option.test.js.snap @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`"additionalData" option should work additionalData data as function: css 1`] = ` +"/* RelativePath: additional-data.styl; */ +body { + color: #ff7f50; +} +.custom-class { + background: #808080; +} +" +`; + +exports[`"additionalData" option should work additionalData data as function: errors 1`] = `Array []`; + +exports[`"additionalData" option should work additionalData data as function: warnings 1`] = `Array []`; + +exports[`"additionalData" option should work additionalData data as string: css 1`] = ` +"body { + color: #ff7f50; +} +" +`; + +exports[`"additionalData" option should work additionalData data as string: errors 1`] = `Array []`; + +exports[`"additionalData" option should work additionalData data as string: warnings 1`] = `Array []`; diff --git a/test/__snapshots__/validate-options.test.js.snap b/test/__snapshots__/validate-options.test.js.snap index 6d3bff3..98ef345 100644 --- a/test/__snapshots__/validate-options.test.js.snap +++ b/test/__snapshots__/validate-options.test.js.snap @@ -1,5 +1,65 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`validate options should throw an error on the "additionalData" 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.additionalData should be one of these: + string | function + -> Prepends/Appends \`Stylus\` code to the actual entry file (https://github.com/webpack-contrib/stylus-loader#additionalData). + Details: + * options.additionalData should be a string. + * options.additionalData should be an instance of function." +`; + +exports[`validate options should throw an error on the "additionalData" option with "[]" value 1`] = ` +"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema. + - options.additionalData should be one of these: + string | function + -> Prepends/Appends \`Stylus\` code to the actual entry file (https://github.com/webpack-contrib/stylus-loader#additionalData). + Details: + * options.additionalData should be a string. + * options.additionalData should be an instance of function." +`; + +exports[`validate options should throw an error on the "additionalData" option with "{}" value 1`] = ` +"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema. + - options.additionalData should be one of these: + string | function + -> Prepends/Appends \`Stylus\` code to the actual entry file (https://github.com/webpack-contrib/stylus-loader#additionalData). + Details: + * options.additionalData should be a string. + * options.additionalData should be an instance of function." +`; + +exports[`validate options should throw an error on the "additionalData" 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.additionalData should be one of these: + string | function + -> Prepends/Appends \`Stylus\` code to the actual entry file (https://github.com/webpack-contrib/stylus-loader#additionalData). + Details: + * options.additionalData should be a string. + * options.additionalData should be an instance of function." +`; + +exports[`validate options should throw an error on the "additionalData" 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.additionalData should be one of these: + string | function + -> Prepends/Appends \`Stylus\` code to the actual entry file (https://github.com/webpack-contrib/stylus-loader#additionalData). + Details: + * options.additionalData should be a string. + * options.additionalData should be an instance of function." +`; + +exports[`validate options should throw an error on the "additionalData" 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.additionalData should be one of these: + string | function + -> Prepends/Appends \`Stylus\` code to the actual entry file (https://github.com/webpack-contrib/stylus-loader#additionalData). + Details: + * options.additionalData should be a string. + * options.additionalData 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. @@ -64,49 +124,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? }" + object { 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? }" + object { 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? }" + object { 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? }" + object { 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? }" + object { 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? }" + object { 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? }" + object { 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? }" + object { stylusOptions?, sourceMap?, webpackImporter?, additionalData? }" `; exports[`validate options should throw an error on the "webpackImporter" option with "string" value 1`] = ` diff --git a/test/additionalData-option.test.js b/test/additionalData-option.test.js new file mode 100644 index 0000000..f3d895b --- /dev/null +++ b/test/additionalData-option.test.js @@ -0,0 +1,49 @@ +import { + compile, + getCodeFromBundle, + getCompiler, + getErrors, + getWarnings, +} from './helpers'; + +describe('"additionalData" option', () => { + it('should work additionalData data as string', async () => { + const testId = './additional-data.styl'; + const additionalData = 'color = coral'; + const compiler = getCompiler(testId, { additionalData }); + 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 additionalData data as function', async () => { + const testId = './additional-data.styl'; + const additionalData = (content, loaderContext) => { + const { resourcePath, rootContext } = loaderContext; + // eslint-disable-next-line global-require + const relativePath = require('path').relative(rootContext, resourcePath); + + return ` +/* RelativePath: ${relativePath}; */ + +color = coral; +bg = gray; + +${content} + +.custom-class + background: bg + `; + }; + const compiler = getCompiler(testId, { additionalData }); + 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'); + }); +}); diff --git a/test/fixtures/additional-data.styl b/test/fixtures/additional-data.styl new file mode 100644 index 0000000..0aa282d --- /dev/null +++ b/test/fixtures/additional-data.styl @@ -0,0 +1,2 @@ +body + color color diff --git a/test/validate-options.test.js b/test/validate-options.test.js index fd077ff..e1e6ebc 100644 --- a/test/validate-options.test.js +++ b/test/validate-options.test.js @@ -34,6 +34,10 @@ describe('validate options', () => { success: [true, false], failure: ['string'], }, + additionalData: { + success: ['color = coral', () => 'bg = coral'], + failure: [1, true, false, /test/, [], {}], + }, unknown: { success: [], failure: [1, true, false, 'test', /test/, [], {}, { foo: 'bar' }],