├── .all-contributorsrc ├── .babelrc ├── .editorconfig ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE-MIT ├── README.md ├── package-lock.json ├── package.json └── src ├── __tests__ ├── fixtures │ ├── arrayOptions.css │ ├── error.css │ ├── node_modules │ │ └── postcss-nobg │ │ │ ├── index.js │ │ │ └── package.json │ ├── options.css │ └── test.css └── index.js └── index.js /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "postcss-use", 3 | "projectOwner": "postcss", 4 | "files": [ 5 | "README.md" 6 | ], 7 | "imageSize": 100, 8 | "commit": false, 9 | "contributors": [ 10 | { 11 | "login": "ben-eb", 12 | "name": "Ben Briggs", 13 | "avatar_url": "https://avatars.githubusercontent.com/u/1282980?v=3", 14 | "profile": "http://beneb.info", 15 | "contributions": [ 16 | "code", 17 | "doc", 18 | "review", 19 | "test" 20 | ] 21 | }, 22 | { 23 | "login": "jonathantneal", 24 | "name": "Jonathan Neal", 25 | "avatar_url": "https://avatars.githubusercontent.com/u/188426?v=3", 26 | "profile": "//jonathantneal.com", 27 | "contributions": [ 28 | "code", 29 | "test" 30 | ] 31 | }, 32 | { 33 | "login": "yisibl", 34 | "name": "一丝", 35 | "avatar_url": "https://avatars.githubusercontent.com/u/2784308?v=3", 36 | "profile": "www.iyunlu.com/view", 37 | "contributions": [ 38 | "code" 39 | ] 40 | }, 41 | { 42 | "login": "MoOx", 43 | "name": "Maxime Thirouin", 44 | "avatar_url": "https://avatars.githubusercontent.com/u/157534?v=3", 45 | "profile": "https://moox.io/", 46 | "contributions": [ 47 | "doc" 48 | ] 49 | }, 50 | { 51 | "login": "TrySound", 52 | "name": "Bogdan Chadkin", 53 | "avatar_url": "https://avatars.githubusercontent.com/u/5635476?v=3", 54 | "profile": "https://github.com/TrySound", 55 | "contributions": [ 56 | "doc", 57 | "review" 58 | ] 59 | }, 60 | { 61 | "login": "rexxars", 62 | "name": "Espen Hovlandsdal", 63 | "avatar_url": "https://avatars.githubusercontent.com/u/48200?v=3", 64 | "profile": "https://espen.codes/", 65 | "contributions": [ 66 | "code", 67 | "test" 68 | ] 69 | }, 70 | { 71 | "login": "ai", 72 | "name": "Andrey Sitnik", 73 | "avatar_url": "https://avatars.githubusercontent.com/u/19343?v=3", 74 | "profile": "http://sitnik.ru", 75 | "contributions": [ 76 | "review" 77 | ] 78 | } 79 | ] 80 | } -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": ["add-module-exports"], 4 | "env": { 5 | "development": { 6 | "sourceMaps": "inline" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output 2 | coverage 3 | dist 4 | node_modules 5 | !src/__tests__/fixtures/node_modules 6 | npm-debug.log 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # https://docs.travis-ci.com/user/travis-lint 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - 6 7 | - 8 8 | 9 | after_success: 10 | - './node_modules/.bin/nyc report --reporter=text-lcov | ./node_modules/.bin/coveralls' 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 3.0.0 2 | 3 | * postcss-use now uses commas instead of semicolons to separate options in 4 | order to support PostCSS 6. 5 | * Removed: Dependencies on `balanced-match` and `lodash.isplainobject`. 6 | * Updated: `postcss` from 5 to 6. 7 | * Updated: `resolve-from` from 2 to 4. 8 | 9 | # 2.3.0 10 | 11 | * postcss-use now accepts an object of default options to supply to plugins 12 | passed to PostCSS via the @use rule (thanks to @rexxars). 13 | 14 | # 2.2.0 15 | 16 | * postcss-use will now throw a more descriptive error when failing to load a 17 | plugin via the `resolveFromFile` option (thanks to @rexxars). 18 | * Now compiled with Babel 6. 19 | 20 | # 2.1.0 21 | 22 | * Added `resolveFromFile` option (thanks to @rexxars). 23 | 24 | # 2.0.2 25 | 26 | * postcss-use no longer uses the old `Node#removeSelf` method from PostCSS 4, 27 | replaced with `Node#remove` (thanks to @TrySound). 28 | 29 | # 2.0.1 30 | 31 | * Corrected repository link (thanks to @MoOx). 32 | * Reduced size of package with npm files filter (thanks to @TrySound). 33 | * Fixed lint errors. 34 | 35 | # 2.0.0 36 | 37 | * Changes: Use PostCSS 5.0 API(Fix[#5](https://github.com/postcss/postcss-use/issues/5)). 38 | 39 | # 1.2.1 40 | 41 | * Fixes a behaviour where plugins would be loaded/unloaded globally instead of 42 | per-file. 43 | 44 | # 1.2.0 45 | 46 | * Adds `'*'` as a legal value to `modules`, to lift the restrictions on 47 | whitelisting module loading. This is to enable use cases for postcss-use 48 | outside browser environments. 49 | 50 | # 1.1.0 51 | 52 | * Adds a more *CSS-like* block syntax - `@use {option: value}`. 53 | 54 | # 1.0.2 55 | 56 | * Fixes a crash when multiple options were specified. 57 | 58 | # 1.0.1 59 | 60 | * Fixes a bug where postcss-use was not properly injecting plugins into the 61 | processor instance from a plugin pack. 62 | 63 | # 1.0.0 64 | 65 | * Initial release. 66 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) Ben Briggs (http://beneb.info) 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [postcss][postcss]-use [![Build Status](https://travis-ci.org/postcss/postcss-use.svg?branch=master)][ci] 2 | 3 | > Enable PostCSS plugins directly in your stylesheet. 4 | 5 | ```css 6 | @use postcss-preset-env(stage: 0, browsers: "last 2 versions"); 7 | 8 | h1 { 9 | & a { 10 | color: red 11 | } 12 | } 13 | ``` 14 | 15 | ## Install 16 | 17 | With [npm](https://npmjs.org/package/postcss-use) do: 18 | 19 | ``` 20 | npm install postcss-use --save 21 | ``` 22 | 23 | ## Example 24 | 25 | Options may be passed into plugins as a JSON object, an array, a hash map, or 26 | as declarations. Hash maps will follow the format of 27 | `option: value, option2: value2`. 28 | 29 | ### Input 30 | 31 | #### Standard syntax 32 | 33 | With [postcss-discard-comments]: 34 | 35 | ```css 36 | @use postcss-discard-comments(removeAll: true); 37 | /*! test */ 38 | h1 { 39 | color: red 40 | } 41 | ``` 42 | 43 | #### Alternative syntax 44 | 45 | You may also use configuration blocks that are more *CSS-like*. Note that root 46 | array options cannot be parsed by this method. 47 | 48 | ```css 49 | @use postcss-discard-comments { 50 | removeAll: true 51 | } 52 | ``` 53 | 54 | ### Output 55 | 56 | ```css 57 | h1 { 58 | color: red 59 | } 60 | ``` 61 | 62 | ## API 63 | 64 | ### use(options) 65 | 66 | #### options 67 | 68 | ##### modules 69 | 70 | Type: `array|string` 71 | *Required option*. 72 | 73 | The `modules` option specifies a list of allowable PostCSS Plugins, expressed 74 | as a `String`, `Array`, or `RegExp`. By default, all plugins are disabled in 75 | order to prevent malicious usage in browser environments. 76 | 77 | ```js 78 | use({ 79 | // allow plugins starting with autoprefixer, postcss, precss, and cssnano 80 | modules: [ 81 | /^autoprefixer/, 82 | /^postcss/, 83 | /^precss/, 84 | /^cssnano/ 85 | ] 86 | }) 87 | ``` 88 | 89 | ```js 90 | use({ 91 | // allow autoprefixer, postcss-preset-env, and postcss-flexbugs-fixes 92 | modules: [ 'autoprefixer', 'postcss-preset-env', 'postcss-flexbugs-fixes' ] 93 | }) 94 | ``` 95 | 96 | Setting the option to `"*"` will allow PostCSS Use to require any plugins. This 97 | is not recommended for environments where you may be accepting arbitrary user 98 | input; use at your own risk. 99 | 100 | ##### resolveFromFile 101 | 102 | Type: `boolean` (default: `false`) 103 | 104 | The `resolveFromFile` option specifies whether plugins should be resolved 105 | relative to the file that referenced them. This may be used to enable the usage 106 | of different versions of the same plugin. By default, it is disabled. 107 | 108 | ```js 109 | use({ 110 | resolveFromFile: true 111 | }) 112 | ``` 113 | 114 | ##### options 115 | 116 | Type: `object` (default: `{}`) 117 | 118 | The `options` option specifies individual options for specific plugins by 119 | plugin name. 120 | 121 | ```js 122 | use({ 123 | options: { 124 | 'postcss-preset-env': { 125 | stage: 0, 126 | browsers: 'last two versions' 127 | } 128 | } 129 | }) 130 | ``` 131 | 132 | ## Usage 133 | 134 | See the [PostCSS documentation](https://github.com/postcss/postcss#usage) for 135 | examples for your environment. 136 | 137 | ## Contributors 138 | 139 | Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)): 140 | 141 | 142 | | [
Ben Briggs](http://beneb.info)
[💻](https://github.com/postcss/postcss-use/commits?author=ben-eb) [📖](https://github.com/postcss/postcss-use/commits?author=ben-eb) 👀 [⚠️](https://github.com/postcss/postcss-use/commits?author=ben-eb) | [
Jonathan Neal](//jonathantneal.com)
[💻](https://github.com/postcss/postcss-use/commits?author=jonathantneal) [⚠️](https://github.com/postcss/postcss-use/commits?author=jonathantneal) | [
一丝](www.iyunlu.com/view)
[💻](https://github.com/postcss/postcss-use/commits?author=yisibl) | [
Maxime Thirouin](https://moox.io/)
[📖](https://github.com/postcss/postcss-use/commits?author=MoOx) | [
Bogdan Chadkin](https://github.com/TrySound)
[📖](https://github.com/postcss/postcss-use/commits?author=TrySound) 👀 | [
Espen Hovlandsdal](https://espen.codes/)
[💻](https://github.com/postcss/postcss-use/commits?author=rexxars) [⚠️](https://github.com/postcss/postcss-use/commits?author=rexxars) | [
Andrey Sitnik](http://sitnik.ru)
👀 | 143 | | :---: | :---: | :---: | :---: | :---: | :---: | :---: | 144 | 145 | 146 | This project follows the [all-contributors] specification. Contributions of 147 | any kind welcome! 148 | 149 | ## License 150 | 151 | MIT © [Ben Briggs](http://beneb.info) 152 | 153 | 154 | [all-contributors]: https://github.com/kentcdodds/all-contributors 155 | [ci]: https://travis-ci.org/postcss/postcss-use 156 | [postcss]: https://github.com/postcss/postcss 157 | [postcss-discard-comments]: https://github.com/ben-eb/postcss-discard-comments 158 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-use", 3 | "version": "3.0.0", 4 | "description": "Enable PostCSS plugins directly in your stylesheet.", 5 | "main": "dist/index.js", 6 | "files": [ 7 | "dist", 8 | "LICENSE-MIT" 9 | ], 10 | "scripts": { 11 | "contributorAdd": "all-contributors add", 12 | "contributorGenerate": "all-contributors generate", 13 | "pretest": "eslint src/*.js src/__test__/*.js", 14 | "prepublish": "del-cli dist && cross-env BABEL_ENV=publish babel src --out-dir dist --ignore /__tests__/", 15 | "report": "nyc report --reporter=html", 16 | "test": "nyc ava src/__tests__/*.js" 17 | }, 18 | "keywords": [ 19 | "css", 20 | "postcss", 21 | "postcss-plugin" 22 | ], 23 | "license": "MIT", 24 | "devDependencies": { 25 | "all-contributors-cli": "^4.10.1", 26 | "autoprefixer": "^9.2.1", 27 | "ava": "^0.25.0", 28 | "babel-cli": "^6.26.0", 29 | "babel-core": "^6.26.0", 30 | "babel-eslint": "^8.2.1", 31 | "babel-plugin-add-module-exports": "^0.2.1", 32 | "babel-preset-env": "^1.6.1", 33 | "babel-register": "^6.26.0", 34 | "coveralls": "^3.0.0", 35 | "cross-env": "^5.1.3", 36 | "del-cli": "^1.1.0", 37 | "eslint": "^3.1.0", 38 | "eslint-config-cssnano": "^3.1.3", 39 | "eslint-plugin-babel": "^4.1.2", 40 | "eslint-plugin-import": "^2.8.0", 41 | "nyc": "^11.4.1", 42 | "postcss-discard-comments": "^4.0.1", 43 | "postcss-discard-font-face": "^3.0.0", 44 | "postcss-nesting": "^7.0.0" 45 | }, 46 | "homepage": "https://github.com/postcss/postcss-use", 47 | "author": { 48 | "name": "Ben Briggs", 49 | "email": "beneb.info@gmail.com", 50 | "url": "http://beneb.info" 51 | }, 52 | "repository": "postcss/postcss-use", 53 | "dependencies": { 54 | "postcss": "^7.0.5", 55 | "resolve-from": "^4.0.0" 56 | }, 57 | "ava": { 58 | "require": "babel-register" 59 | }, 60 | "eslintConfig": { 61 | "extends": "cssnano" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/arrayOptions.css: -------------------------------------------------------------------------------- 1 | @use postcss-nobg(["background-repeat"]); 2 | 3 | .foo { 4 | background-image: url(/foo.jpg); 5 | } 6 | 7 | .bar { 8 | background: #bf1942; 9 | background-repeat: no-repeat; 10 | } 11 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/error.css: -------------------------------------------------------------------------------- 1 | @use postcss-fourohfour; 2 | 3 | .foo {background: blue;color: red;} 4 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/node_modules/postcss-nobg/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict, prefer-arrow-callback, no-var */ 2 | 'use strict'; 3 | 4 | var postcss = require('postcss'); 5 | 6 | module.exports = postcss.plugin('nobg', function (opts) { 7 | var options = opts || {}; 8 | return function nobg (css) { 9 | var matchers = [/^background/]; 10 | // Let's pretend this is a new release which has to support an 11 | // earlier API where you explicitly defined declarations 12 | if (Array.isArray(options)) { 13 | matchers = options.map(function (decl) { 14 | return new RegExp('^' + decl + '$'); 15 | }); 16 | } else if (options.onlyImages) { 17 | matchers = [/^background-image/]; 18 | } 19 | 20 | matchers.forEach(function (matcher) { 21 | css.walkDecls(matcher, function (decl) { 22 | decl.remove(); 23 | }); 24 | }); 25 | }; 26 | }); 27 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/node_modules/postcss-nobg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-nobg", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC" 11 | } 12 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/options.css: -------------------------------------------------------------------------------- 1 | @use postcss-nobg(onlyImages: false); 2 | 3 | .foo { 4 | background-image: url(/foo.jpg); 5 | } 6 | 7 | .bar { 8 | background: #bf1942; 9 | } 10 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/test.css: -------------------------------------------------------------------------------- 1 | @use postcss-nobg; 2 | 3 | .foo {background: blue;color: red;} 4 | -------------------------------------------------------------------------------- /src/__tests__/index.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import test from 'ava'; 4 | import postcss from 'postcss'; 5 | import plugin from '..'; 6 | import {name} from '../../package.json'; 7 | 8 | const tests = [{ 9 | message: 'should enable all modules from css', 10 | fixture: '/* test comment */@use postcss-discard-comments;/* test comment */', 11 | expected: '', 12 | options: {modules: '*'}, 13 | }, { 14 | message: 'should enable postcss-discard-comments from css', 15 | fixture: '/* test comment */@use postcss-discard-comments;/* test comment */', 16 | expected: '', 17 | options: {modules: ['postcss-discard-comments']}, 18 | }, { 19 | message: 'should enable postcss-discard-comments from css, with options', 20 | fixture: '/*! license comment */@use postcss-discard-comments(removeAll: true);', 21 | expected: '', 22 | options: {modules: ['postcss-discard-comments']}, 23 | }, { 24 | message: 'should enable postcss-discard-font-face from css, with array as options', 25 | fixture: '@use postcss-discard-font-face(["svg", "woff"]); @font-face { font-family: A; src: url("a.svg") format("svg"), url("a.ttf") format("truetype")}', 26 | expected: '@font-face { font-family: A; src: url("a.svg") format("svg")}', 27 | options: {modules: ['postcss-discard-font-face']}, 28 | }, { 29 | message: 'should enable autoprefixer from css', 30 | fixture: '@use autoprefixer(remove: false, browsers: "> 1%, firefox 32");main{-webkit-border-radius:10px;border-radius:10px;display:flex;}', 31 | expected: 'main{-webkit-border-radius:10px;border-radius:10px;display:flex;}', 32 | options: {modules: ['autoprefixer']}, 33 | }, { 34 | message: 'should enable autoprefixer from css, with nested stringy options', 35 | fixture: '@use autoprefixer { remove: false; browsers: "> 0%, firefox 32" };main{-webkit-border-radius:10px;border-radius:10px;display:flex;}', 36 | expected: 'main{-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;display:-webkit-box;display:-webkit-flex;display:-moz-box;display:-ms-flexbox;display:flex;}', 37 | options: {modules: ['autoprefixer']}, 38 | }, { 39 | message: 'should enable autoprefixer from css, with deeply nested options', 40 | fixture: '@use autoprefixer { remove: false; browsers: "> 0%, firefox 32"; foo: { bar: true } /* ignores comments */ };main{-webkit-border-radius:10px;border-radius:10px;display:flex;}', 41 | expected: 'main{-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;display:-webkit-box;display:-webkit-flex;display:-moz-box;display:-ms-flexbox;display:flex;}', 42 | options: {modules: ['autoprefixer']}, 43 | }, { 44 | message: 'should enable postcss-nesting', 45 | fixture: '@use postcss-nesting;h1{&{color: red}}', 46 | expected: 'h1{color: red}', 47 | options: {modules: ['postcss-nesting']}, 48 | }]; 49 | 50 | function process (css, options) { 51 | return postcss(plugin(options)).process(css).css; 52 | } 53 | 54 | tests.forEach(({message, fixture, expected, options}) => { 55 | test(message, t => { 56 | t.deepEqual(process(fixture, options), expected); 57 | }); 58 | }); 59 | 60 | test('multiple runs', t => { 61 | const processor = postcss(plugin({modules: ['postcss-discard-comments']})); 62 | 63 | return processor.process('@use postcss-discard-comments;/*test*/', { 64 | from: `${__dirname}/index.css`, 65 | }).then(({css}) => { 66 | t.deepEqual(css, ''); 67 | }).then(processor.process('/*test*/', { 68 | from: `${__dirname}/index.css`, 69 | }).then(({css}) => { 70 | t.deepEqual(css, '/*test*/'); 71 | })); 72 | }); 73 | 74 | function shouldThrow (t, css, opts = {}) { 75 | t.throws(() => process(css, opts, { from: __dirname })); 76 | } 77 | 78 | test('should not support arrows', shouldThrow, '@use postcss-discard-comments(foo => bar)', {modules: ['postcss-discard-comments']}); 79 | test('should not support function syntax', shouldThrow, '@use postcss-discard-comments(function () { alert(1); })', {modules: ['postcss-discard-comments']}); 80 | test('should not support unclosed keys', shouldThrow, '@use postcss-discard-comments(removeAll:)', {modules: ['postcss-discard-comments']}); 81 | test('should not support unclosed arrays', shouldThrow, '@use postcss-discard-font-face(["svg")', {modules: ['postcss-discard-font-face']}); 82 | test('should not support plugins that are not whitelisted', shouldThrow, '@use postcss-discard-comments;'); 83 | test('should not support null', shouldThrow, '@use postcss-discard-comments;', {modules: null}); 84 | test('should not support false', shouldThrow, '@use postcss-discard-comments;', {modules: false}); 85 | test('should not support strings that are not "*"', shouldThrow, '@use postcss-discard-comments;', {modules: 'all'}); 86 | 87 | test('should ignore unknown options', t => { 88 | return postcss( 89 | plugin({modules: ['postcss-discard-comments']}) 90 | ).process('@use postcss-discard-comments(removeAll:something)', { 91 | from: `${__dirname}/index.css`, 92 | }).then(({css}) => { 93 | t.deepEqual(css, ''); 94 | }); 95 | }); 96 | 97 | test('should ignore unknown options', t => { 98 | return postcss( 99 | plugin({modules: ['postcss-discard-comments']}) 100 | ).process('@use postcss-discard-comments{removeAll:something}', { 101 | from: `${__dirname}/index.css`, 102 | }).then(({css}) => { 103 | t.deepEqual(css, ''); 104 | }); 105 | }); 106 | 107 | test('should use the postcss plugin api', t => { 108 | t.truthy(plugin().postcssVersion, 'should be able to access version'); 109 | t.deepEqual(plugin().postcssPlugin, name, 'should be able to access name'); 110 | }); 111 | 112 | test('should use plugins relative to CSS file when using resolveFromFile', t => { 113 | const inputFile = path.join(__dirname, 'fixtures', 'test.css'); 114 | const outputFile = path.join(__dirname, 'fixtures', 'test.out.css'); 115 | const inputCss = fs.readFileSync(inputFile); 116 | return postcss(plugin({modules: '*', resolveFromFile: true})).process(inputCss, { 117 | from: inputFile, 118 | to: outputFile, 119 | }).then(({css}) => { 120 | t.deepEqual(css, '.foo {color: red;}\n', 'should remove background decls'); 121 | }); 122 | }); 123 | 124 | test('should give meaningful error when module is not found', t => { 125 | const inputFile = path.join(__dirname, 'fixtures', 'error.css'); 126 | const outputFile = path.join(__dirname, 'fixtures', 'error.out.css'); 127 | const inputCss = fs.readFileSync(inputFile); 128 | return postcss(plugin({modules: '*', resolveFromFile: true})).process(inputCss, { 129 | from: inputFile, 130 | to: outputFile, 131 | }).catch(err => { 132 | t.deepEqual(err.message, `Cannot find module 'postcss-fourohfour'`); 133 | }); 134 | }); 135 | 136 | test('should not resolve plugins relative to CSS file by default', t => { 137 | const inputFile = path.join(__dirname, 'fixtures', 'test.css'); 138 | const outputFile = path.join(__dirname, 'fixtures', 'test.out.css'); 139 | const inputCss = fs.readFileSync(inputFile); 140 | return postcss(plugin({modules: '*'})).process(inputCss, { 141 | from: inputFile, 142 | to: outputFile, 143 | }).catch(err => { 144 | t.deepEqual(err.message, `Cannot find module 'postcss-nobg'`); 145 | }); 146 | }); 147 | 148 | test('should be able to specify default options for plugins', t => { 149 | const inputFile = path.join(__dirname, 'fixtures', 'test.css'); 150 | const outputFile = path.join(__dirname, 'fixtures', 'test.out.css'); 151 | const inputCss = fs.readFileSync(inputFile); 152 | const options = { 153 | 'postcss-nobg': {onlyImages: true}, 154 | }; 155 | return postcss(plugin({modules: '*', resolveFromFile: true, options})).process(inputCss, { 156 | from: inputFile, 157 | to: outputFile, 158 | }).then(({css}) => { 159 | const normalized = css.replace(/\s+/g, ' ').trim(); 160 | t.deepEqual(normalized, '.foo {background: blue;color: red;}', 'should remove only background image decls'); 161 | }); 162 | }); 163 | 164 | test('should be able to override default options', t => { 165 | const inputFile = path.join(__dirname, 'fixtures', 'options.css'); 166 | const outputFile = path.join(__dirname, 'fixtures', 'options.out.css'); 167 | const inputCss = fs.readFileSync(inputFile); 168 | const options = { 169 | nobg: {onlyImages: true}, 170 | }; 171 | return postcss(plugin({modules: '*', resolveFromFile: true, options})).process(inputCss, { 172 | from: inputFile, 173 | to: outputFile, 174 | }).then(({css}) => { 175 | const normalized = css.replace(/\s+/g, ' ').trim(); 176 | t.deepEqual(normalized, '.foo { } .bar { }', 'should remove only background image decls'); 177 | }); 178 | }); 179 | 180 | test('should use specified options if specified options is not an object', t => { 181 | const inputFile = path.join(__dirname, 'fixtures', 'arrayOptions.css'); 182 | const outputFile = path.join(__dirname, 'fixtures', 'arrayOptions.out.css'); 183 | const inputCss = fs.readFileSync(inputFile); 184 | const options = { 185 | nobg: {onlyImages: true}, 186 | }; 187 | return postcss(plugin({modules: '*', resolveFromFile: true, options})).process(inputCss, { 188 | from: inputFile, 189 | to: outputFile, 190 | }).then(({css}) => { 191 | const normalized = css.replace(/\s+/g, ' ').trim(); 192 | const expected = '.foo { background-image: url(/foo.jpg); } .bar { background: #bf1942; }'; 193 | t.deepEqual(normalized, expected, 'should remove only background-repeat decls'); 194 | }); 195 | }); 196 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import postcss from 'postcss'; 3 | import resolveFrom from 'resolve-from'; 4 | 5 | const useParamsRegExp = /^([^\(\s]+)(?:\s*\(\s*([\W\w]+)\s*\))?/; 6 | const optionRegExp = /^\s*([\W\w]+?)\s*:\s*([\W\w]+)\s*$/; 7 | 8 | export default postcss.plugin('postcss-use', opts => { 9 | // options 10 | const { 11 | modules = [], 12 | options = {}, 13 | resolveFromFile = false, 14 | } = Object(opts); 15 | 16 | return (root, result) => { 17 | // preserved plugins list 18 | const preservedPlugins = result.processor.plugins.slice(); 19 | 20 | // walk @use rules 21 | root.walkAtRules('use', rule => { 22 | // match plugin and plugin params 23 | const paramsMatch = rule.params.match(useParamsRegExp); 24 | 25 | if (paramsMatch) { 26 | const [ , pluginName, pluginParams = '' ] = paramsMatch; 27 | 28 | // whether the plugin is whitelisted 29 | const isAllowablePlugin = [].concat(modules).some( 30 | mod => typeof mod === 'string' 31 | ? mod === '*' || mod === pluginName 32 | : pluginName.match(mod) 33 | ); 34 | 35 | if (isAllowablePlugin) { 36 | // plugin options 37 | const defaultOpts = Object(options)[pluginName]; 38 | const paramOpts = getOptionsFromParams(pluginParams); 39 | const childOpts = getOptionsFromRuleChildren(rule); 40 | 41 | const pluginOpts = defaultOpts === undefined && Array.isArray(paramOpts) 42 | ? paramOpts 43 | : Object.assign({}, defaultOpts, paramOpts, childOpts); 44 | 45 | try { 46 | // add plugin to plugins list 47 | const pluginPath = resolveFromFile && rule.source.input.file 48 | ? resolveFrom( 49 | path.dirname(rule.source.input.file), 50 | pluginName 51 | ) 52 | : pluginName; 53 | 54 | const plugin = require(pluginPath)(pluginOpts); 55 | 56 | result.processor.plugins.push(plugin); 57 | } catch (error) { 58 | throw new Error(`Cannot find module '${pluginName}'`); 59 | } 60 | } else { 61 | throw new ReferenceError(`'${ pluginName }' is not a valid PostCSS plugin.`); 62 | } 63 | } 64 | 65 | rule.remove(); 66 | }); 67 | 68 | result.processor.plugins.push( 69 | postcss.plugin('postcss-use#reset', () => { 70 | return () => { 71 | // restore preserved plugins list 72 | result.processor.plugins = preservedPlugins; 73 | }; 74 | })() 75 | ); 76 | }; 77 | }); 78 | 79 | // get options from params using functional notation 80 | function getOptionsFromParams (params) { 81 | try { 82 | // as json 83 | return JSON.parse(params); 84 | } catch (error) { 85 | // as properties, split as declarations 86 | const options = {}; 87 | const decls = postcss.list.comma(params); 88 | 89 | for (let decl of decls) { 90 | if (decl) { 91 | const declMatch = decl.match(optionRegExp); 92 | 93 | if (declMatch) { 94 | const [ , property, value ] = declMatch; 95 | 96 | try { 97 | options[property] = JSON.parse(value); 98 | } catch (error2) { 99 | options[property] = value; 100 | } 101 | } else { 102 | throw new SyntaxError(`Options must include a property and value`); 103 | } 104 | } 105 | } 106 | 107 | return options; 108 | } 109 | } 110 | 111 | // get options from rule childrem 112 | function getOptionsFromRuleChildren (rule) { 113 | const options = {}; 114 | 115 | if (rule.nodes) { 116 | for (let node of rule.nodes) { 117 | const { 118 | prop, 119 | selector, 120 | type, 121 | value, 122 | } = node; 123 | 124 | if (type === 'decl') { 125 | try { 126 | // as json 127 | options[prop] = JSON.parse(value); 128 | } catch (error) { 129 | // as a string 130 | options[prop] = value; 131 | } 132 | } else if (type === 'rule') { 133 | // as nested options 134 | options[selector] = getOptionsFromRuleChildren(node); 135 | } 136 | } 137 | } 138 | 139 | return options; 140 | } 141 | --------------------------------------------------------------------------------