├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .travis.yml ├── lib └── index.js ├── license.md ├── package.json ├── readme.md └── test ├── .eslintrc └── lib └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ "es2015" ], 3 | "plugins": [ "add-module-exports" ] 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org/ 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 4 11 | 12 | [*.{json,yml}] 13 | indent_size = 2 14 | 15 | [{.babelrc,.eslintrc}] 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "rebem/configs/common", 4 | "rebem/configs/babel" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | tmp/ 3 | build/ 4 | coverage/ 5 | *.sublime-* 6 | *.log 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # https://docs.travis-ci.com/user/customizing-the-build/ 2 | 3 | sudo: false 4 | 5 | language: node_js 6 | 7 | node_js: 8 | - "0.12" 9 | - "4" 10 | - "5" 11 | 12 | branches: 13 | only: 14 | - master 15 | 16 | matrix: 17 | fast_finish: true 18 | 19 | before_install: 20 | - npm install -g npm 21 | - npm --version 22 | 23 | script: npm start ci 24 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | import postcss from 'postcss'; 2 | 3 | const modDelim = process.env.REBEM_MOD_DELIM || '_'; 4 | const elemDelim = process.env.REBEM_ELEM_DELIM || '__'; 5 | 6 | export default postcss.plugin('rebem-css', () => (css) => { 7 | css.walkRules((rule) => { 8 | rule.selector = rule.selector 9 | // :block(block) → .block 10 | .replace(/:block\(([\w-]+)\)/g, '.$1') 11 | // :elem(elem) → __elem 12 | .replace(/:elem\(([\w-]+)\)/g, elemDelim + '$1') 13 | // :mod(mod) → _mod 14 | // :mod(mod val) → _mod_val 15 | .replace(/:mod\(([\w-]+)\s?([\w-]+)?\)/g, (match, mod, val) => { 16 | if (val) { 17 | return modDelim + mod + modDelim + val; 18 | } 19 | 20 | return modDelim + mod; 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | * Copyright (c) 2015–present Kir Belevich 4 | * Copyright (c) 2015–present Denis Koltsov 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rebem-css", 3 | "version": "0.2.0", 4 | "description": "BEM syntax for CSS", 5 | "keywords": [ "rebem", "bem", "css" ], 6 | "homepage": "https://github.com/rebem/css", 7 | "repository": "rebem/css", 8 | "maintainers": [ 9 | "Kir Belevich (https://github.com/deepsweet)", 10 | "Denis Koltsov (https://github.com/mistadikay)" 11 | ], 12 | "main": "build/index.js", 13 | "files": [ "build/" ], 14 | "dependencies": { 15 | "postcss": "5.0.x" 16 | }, 17 | "devDependencies": { 18 | "start-babel-cli": "1.x.x", 19 | "start-rebem-preset": "0.x.x", 20 | 21 | "babel-preset-es2015": "6.9.x", 22 | "babel-plugin-add-module-exports": "0.2.x", 23 | 24 | "babel-eslint": "6.1.x", 25 | "eslint-plugin-babel": "3.3.x", 26 | "eslint-config-rebem": "1.1.x", 27 | 28 | "require-uncached": "1.0.x", 29 | "husky": "0.11.x" 30 | }, 31 | "scripts": { 32 | "start": "start-runner start-rebem-preset", 33 | "prepush": "npm start prepush", 34 | "prepublish": "npm start build" 35 | }, 36 | "engines": { 37 | "node": ">=0.12.0", 38 | "npm": ">=2.7.0" 39 | }, 40 | "license": "MIT" 41 | } 42 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![maintenance](https://img.shields.io/badge/maintained-no-red.svg?style=flat-square)](http://unmaintained.tech) 2 | [![npm](https://img.shields.io/npm/v/rebem-css.svg?style=flat-square)](https://www.npmjs.com/package/rebem-css) 3 | [![travis](http://img.shields.io/travis/rebem/css.svg?style=flat-square)](https://travis-ci.org/rebem/css) 4 | [![coverage](https://img.shields.io/codecov/c/github/rebem/css.svg?style=flat-square)](https://codecov.io/github/rebem/css) 5 | [![deps](https://img.shields.io/gemnasium/rebem/css.svg?style=flat-square)](https://gemnasium.com/rebem/css) 6 | [![gitter](https://img.shields.io/badge/gitter-join_chat_%E2%86%92-46bc99.svg?style=flat-square)](https://gitter.im/rebem/rebem) 7 | 8 | BEM syntax for CSS. 9 | 10 | ## Overview 11 | 12 | ### Dead simple 13 | 14 | It just replaces substrings in selectors: 15 | 16 | #### `:block()` 17 | 18 | ```css 19 | :block(block) {} 20 | .block {} 21 | ``` 22 | 23 | #### `:elem()` 24 | 25 | ```css 26 | :block(block):elem(elem) {} 27 | .block__elem {} 28 | ``` 29 | 30 | #### `:mod()` 31 | 32 | ```css 33 | :block(block):mod(mod) {} 34 | .block_mod {} 35 | 36 | :block(block):mod(mod val) {} 37 | .block_mod_val {} 38 | ``` 39 | 40 | ```css 41 | :block(block):elem(elem):mod(mod) {} 42 | .block__elem_mod {} 43 | 44 | :block(block):elem(elem):mod(mod val) {} 45 | .block__elem_mod_val {} 46 | ``` 47 | 48 | ### CSS compatible 49 | 50 | It's just a custom pseudo-classes, so you can use it with Less or any other CSS preprocessor: 51 | 52 | ```less 53 | :block(block) { 54 | &:mod(mod) { 55 | 56 | } 57 | 58 | &:elem(elem) { 59 | &:mod(mod val) { 60 | 61 | } 62 | } 63 | } 64 | ``` 65 | 66 | ## Usage 67 | 68 | `rebem-css` is a [PostCSS](https://github.com/postcss/postcss) plugin. 69 | 70 | ### CLI 71 | 72 | ```sh 73 | npm i -S postcss postcss-cli rebem-css 74 | ``` 75 | 76 | ```sh 77 | postcss --use rebem-css test.css -o test.css 78 | ``` 79 | 80 | ### API 81 | 82 | ```sh 83 | npm i -S postcss rebem-css 84 | ``` 85 | 86 | ```js 87 | import postcss from 'postcss'; 88 | import reBEMCSS from 'rebem-css'; 89 | 90 | console.log( 91 | postcss([ reBEMCSS ]).process(':block(block) {}').css 92 | ); 93 | // .block {} 94 | ``` 95 | 96 | ### webpack 97 | 98 | ```sh 99 | npm i -S postcss postcss-loader rebem-css 100 | ``` 101 | 102 | ```js 103 | import reBEMCSS from 'rebem-css'; 104 | 105 | export default { 106 | module: { 107 | loaders: [ 108 | { 109 | test: /\.css$/, 110 | loader: 'style!css!postcss' 111 | } 112 | ] 113 | }, 114 | postcss() { 115 | return [ reBEMCSS ]; 116 | } 117 | }; 118 | ``` 119 | 120 | ### Custom delimeters 121 | 122 | Default delimeters are `_` for modifiers and `__` for elements, but you can change it with special environment variables. For example in webpack you can do this with `DefinePlugin`: 123 | 124 | 125 | ```js 126 | plugins: [ 127 | new webpack.DefinePlugin({ 128 | 'process.env': { 129 | REBEM_MOD_DELIM: JSON.stringify('--'), 130 | REBEM_ELEM_DELIM: JSON.stringify('~~') 131 | } 132 | }) 133 | ] 134 | ``` 135 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "rebem/configs/test" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/lib/index.js: -------------------------------------------------------------------------------- 1 | import postcss from 'postcss'; 2 | import assert from 'assert'; 3 | import requireUncached from 'require-uncached'; 4 | 5 | import Plugin from '../../lib/'; 6 | 7 | const test = (selector, result) => { 8 | assert.strictEqual( 9 | postcss([ Plugin ]).process(selector + '{}').css, 10 | result + '{}' 11 | ); 12 | }; 13 | 14 | describe('plugin', () => { 15 | describe('block', () => { 16 | it('simple', () => { 17 | test( 18 | ':block(block)', 19 | '.block' 20 | ); 21 | }); 22 | 23 | it('multiple blocks', () => { 24 | test( 25 | ':block(block1) :block(block2)', 26 | '.block1 .block2' 27 | ); 28 | }); 29 | }); 30 | 31 | describe('elem', () => { 32 | it('simple', () => { 33 | test( 34 | ':block(block):elem(elem)', 35 | '.block__elem' 36 | ); 37 | }); 38 | 39 | it('multiple blocks elems', () => { 40 | test( 41 | ':block(block1):elem(elem1) :block(block2):elem(elem2)', 42 | '.block1__elem1 .block2__elem2' 43 | ); 44 | }); 45 | }); 46 | 47 | describe('mod', () => { 48 | describe('block', () => { 49 | it('block short mod', () => { 50 | test( 51 | ':block(block):mod(mod)', 52 | '.block_mod' 53 | ); 54 | }); 55 | 56 | it('multiple blocks shorts mods', () => { 57 | test( 58 | ':block(block1):mod(mod1) :block(block2):mod(mod2)', 59 | '.block1_mod1 .block2_mod2' 60 | ); 61 | }); 62 | 63 | it('block mod', () => { 64 | test( 65 | ':block(block):mod(mod val)', 66 | '.block_mod_val' 67 | ); 68 | }); 69 | 70 | it('multiple blocks mods', () => { 71 | test( 72 | ':block(block1):mod(mod1 val1) :block(block2):mod(mod2 val2)', 73 | '.block1_mod1_val1 .block2_mod2_val2' 74 | ); 75 | }); 76 | }); 77 | 78 | describe('elem', () => { 79 | it('elem short mod', () => { 80 | test( 81 | ':block(block):elem(elem):mod(mod)', 82 | '.block__elem_mod' 83 | ); 84 | }); 85 | 86 | it('multiple elems short mods', () => { 87 | test( 88 | ':block(block1):elem(elem1):mod(mod1) :block(block2):elem(elem2):mod(mod2)', 89 | '.block1__elem1_mod1 .block2__elem2_mod2' 90 | ); 91 | }); 92 | 93 | it('elem mod', () => { 94 | test( 95 | ':block(block):elem(elem):mod(mod val)', 96 | '.block__elem_mod_val' 97 | ); 98 | }); 99 | 100 | it('multiple elems mods', () => { 101 | test( 102 | ':block(block1):elem(elem1):mod(mod1 val1) :block(block2):elem(elem2):mod(mod2 val2)', 103 | '.block1__elem1_mod1_val1 .block2__elem2_mod2_val2' 104 | ); 105 | }); 106 | }); 107 | }); 108 | 109 | describe('custom delimeters', function() { 110 | it('mods', function() { 111 | process.env.REBEM_MOD_DELIM = '~~'; 112 | 113 | const CustomPlugin = requireUncached('../../lib/'); 114 | 115 | assert.strictEqual( 116 | postcss([ CustomPlugin ]).process(':block(block):mod(mod val){}').css, 117 | '.block~~mod~~val{}' 118 | ); 119 | }); 120 | 121 | it('elem', function() { 122 | process.env.REBEM_ELEM_DELIM = '--'; 123 | 124 | const CustomPlugin = requireUncached('../../lib/'); 125 | 126 | assert.strictEqual( 127 | postcss([ CustomPlugin ]).process(':block(block):elem(elem){}').css, 128 | '.block--elem{}' 129 | ); 130 | }); 131 | }); 132 | }); 133 | --------------------------------------------------------------------------------