├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── README.md ├── bin ├── bemify └── create-by-css ├── classic ├── build-structure.js ├── consts.js ├── entity-to-file-path.js ├── index.js ├── test │ └── index.js └── write-files.js ├── index.js ├── lib └── entity-by-selector.js ├── package-lock.json ├── package.json └── test ├── expected.js ├── index.js └── test.css /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [14.x, 16.x, 18.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v1 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | - run: npm ci 29 | - run: npm run build --if-present 30 | - run: npm test 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # css-to-bem-file-structure 2 | 3 | Generate [BEM file structure](https://en.bem.info/methodology/filestructure/) by CSS file. 4 | 5 | ## Installation 6 | 7 | ```sh 8 | npm i css-to-bem-file-structure --save-dev 9 | ``` 10 | 11 | ## Usage 12 | 13 | To generate [nested structure](https://en.bem.info/methodology/filestructure/#nested) use 14 | ```sh 15 | ./node_modules/.bin/bemify path-to-styles.css 16 | ``` 17 | 18 | In this case you may customize separators with environment variables `ELEM_SEPARATOR` and `ELEM_MOD_SEPARATOR`. 19 | 20 | ## Advanced usage 21 | 22 | To customize [file structure organization](https://en.bem.info/methodology/filestructure/) use `css-to-bem-file-structure` binary. It supports the same [bem-config](https://github.com/bem/bem-sdk/tree/master/packages/config#config) file as [bem-tools-create](https://www.npmjs.com/package/bem-tools-create#configuration) package. 23 | 24 | NOTE: such configuration was never tested and considered deprecated. List of imports won't be generated in this case. 25 | 26 | ```sh 27 | ./node_modules/.bin/css-to-bem-file-structure path-to-styles.css 28 | ``` 29 | 30 | ```sh 31 | ./node_modules/.bin/css-to-bem-file-structure path-to-styles.css blocks css 32 | ``` 33 | 34 | ## How it works 35 | 36 | For file `test.css` with 37 | 38 | ```css 39 | .b1 { color: red; } 40 | 41 | .b1__e1 { color: yellow; } 42 | 43 | .b1_m1_v1 { color: lightcoral; } 44 | 45 | .b2 { color: green; } 46 | 47 | .b2__e1_m1 { color: #eee; } 48 | ``` 49 | 50 | following files will be generated: 51 | 52 | ``` 53 | blocks/ 54 | b1/ 55 | __e1/ 56 | b1__e1.css 57 | _m1/ 58 | b1_m1_v1.css 59 | b1.css 60 | 61 | b2/ 62 | __e1/ 63 | _m1/ 64 | b2__e1_m1.css 65 | b2.css 66 | ``` 67 | -------------------------------------------------------------------------------- /bin/bemify: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const [pathToFile, level, tech] = process.argv.slice(2); 5 | const css = fs.readFileSync(pathToFile, 'utf8'); 6 | 7 | require('../classic')(css, { level: level || 'blocks', tech: tech || 'css', buildImports: true }); 8 | -------------------------------------------------------------------------------- /bin/create-by-css: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const [pathToFile, level, tech] = process.argv.slice(2); 5 | const css = fs.readFileSync(pathToFile, 'utf8'); 6 | 7 | require('..')(css, { level: level || 'blocks', tech: tech || 'css' }); 8 | -------------------------------------------------------------------------------- /classic/build-structure.js: -------------------------------------------------------------------------------- 1 | const postcss = require('postcss'); 2 | const getEntityBySelector = require('../lib/entity-by-selector'); 3 | const getFilePathByEntity = require('./entity-to-file-path'); 4 | 5 | module.exports = function buildStructure(css, tech) { 6 | const ast = postcss.parse(css); 7 | 8 | const entities = {}; 9 | 10 | ast.walkAtRules('keyframes', atRule => { 11 | console.warn(`ATTENTION: unhandled @keyframes ${atRule.params} rule found!`); 12 | }); 13 | 14 | ast.walkRules(rule => { 15 | const selector = rule.selector; 16 | if (!selector.startsWith('.')) { 17 | return; 18 | }; 19 | 20 | const parent = rule.parent; 21 | selector.split(',').forEach(subSelector => { 22 | const entity = getEntityBySelector(subSelector); 23 | 24 | const subRule = rule.clone(); 25 | subRule.selector = subSelector.trim(); 26 | 27 | let css = ''; 28 | if (parent.type === 'atrule') { 29 | css = [ 30 | `@${parent.name} ${parent.params} {`, 31 | subRule.toString() 32 | .split('\n') 33 | .map(line => ` ${line}`) 34 | .join('\n'), 35 | '}', 36 | ].join('\n'); 37 | } else { 38 | css = subRule.toString(); 39 | } 40 | 41 | if (!entities[entity]) { 42 | entities[entity] = [css]; 43 | } else { 44 | entities[entity].push(css); 45 | } 46 | }); 47 | }); 48 | 49 | return Object.entries(entities).reduce((acc, [entity, content]) => { 50 | const filePath = getFilePathByEntity(entity, tech); 51 | acc[filePath] = content.join('\n'); 52 | 53 | return acc; 54 | }, {}); 55 | }; 56 | -------------------------------------------------------------------------------- /classic/consts.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | elemSeparator: process.env.ELEM_SEPARATOR || '__', 3 | modSeparator: process.env.ELEM_MOD_SEPARATOR || '_' 4 | }; 5 | -------------------------------------------------------------------------------- /classic/entity-to-file-path.js: -------------------------------------------------------------------------------- 1 | const { elemSeparator, modSeparator } = require('./consts'); 2 | 3 | module.exports = function getFilePathByEntity(entity, tech) { 4 | const [block, elem] = entity.split(elemSeparator); 5 | const [blockName, blockModName] = block.split(modSeparator); 6 | 7 | if (elem) { 8 | const [elemName, elemModName] = elem.split(modSeparator); 9 | 10 | return [ 11 | `${blockName}`, 12 | `${elemSeparator}${elemName}`, 13 | elemModName && `${modSeparator}${elemModName}`, 14 | `${entity}.${tech}` 15 | ].filter(Boolean).join('/'); 16 | } 17 | 18 | return [ 19 | `${blockName}`, 20 | blockModName && `${modSeparator}${blockModName}`, 21 | `${entity}.${tech}` 22 | ].filter(Boolean).join('/'); 23 | } 24 | -------------------------------------------------------------------------------- /classic/index.js: -------------------------------------------------------------------------------- 1 | const buildStructure = require('./build-structure'); 2 | const writeFiles = require('./write-files'); 3 | 4 | module.exports = function(css, { level, tech, buildImports }) { 5 | const structureWithLevel = Object.entries(buildStructure(css, tech)) 6 | .reduce((acc, [filePath, content]) => { 7 | acc[level + '/' + filePath] = content; 8 | 9 | return acc; 10 | }, {}); 11 | 12 | if (buildImports) { 13 | const indexCSS = Object.keys(structureWithLevel) 14 | .map(filePath => `@import url(${filePath});`) 15 | .join('\n'); 16 | 17 | console.log(indexCSS); 18 | } 19 | 20 | writeFiles(structureWithLevel); 21 | }; 22 | -------------------------------------------------------------------------------- /classic/test/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const assert = require('assert'); 4 | const buildStructure = require('../build-structure'); 5 | 6 | const css = fs.readFileSync(path.join(__dirname, '../../test/test.css'), 'utf-8'); 7 | const expected = require('../../test/expected'); 8 | 9 | assert.deepEqual(buildStructure(css, 'css'), expected); 10 | -------------------------------------------------------------------------------- /classic/write-files.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | module.exports = function writeFiles(fsStructure) { 5 | for (const [filePath, content] of Object.entries(fsStructure)) { 6 | fs.mkdirSync(path.dirname(filePath), { recursive: true }); 7 | fs.writeFileSync(filePath, content); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const postcss = require('postcss'); 2 | const create = require('bem-tools-create'); 3 | const getEntityBySelector = require('./lib/entity-by-selector'); 4 | 5 | module.exports = async function(css, { level, tech }) { 6 | const ast = postcss.parse(css); 7 | 8 | const entities = {}; 9 | 10 | ast.walkAtRules('keyframes', atRule => { 11 | console.warn(`ATTENTION: unhandled @keyframes ${atRule.params} rule found!`); 12 | }); 13 | 14 | ast.walkRules(rule => { 15 | const selector = rule.selector; 16 | if (!selector.startsWith('.')) { 17 | return; 18 | }; 19 | 20 | const parent = rule.parent; 21 | selector.split(',').forEach(subSelector => { 22 | const entity = getEntityBySelector(subSelector); 23 | 24 | const subRule = rule.clone(); 25 | subRule.selector = subSelector.trim(); 26 | 27 | let css = ''; 28 | if (parent.type === 'atrule') { 29 | css = [ 30 | `@${parent.name} ${parent.params} {`, 31 | subRule.toString() 32 | .split('\n') 33 | .map(line => ` ${line}`) 34 | .join('\n'), 35 | '}', 36 | ].join('\n'); 37 | } else { 38 | css = subRule.toString(); 39 | } 40 | 41 | if (!entities[entity]) { 42 | entities[entity] = [css]; 43 | } else { 44 | entities[entity].push(css); 45 | } 46 | }); 47 | }); 48 | 49 | for (const [entity, content] of Object.entries(entities)) { 50 | await create([entity], [level], tech, { fileContent: content.join('\n') }); 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /lib/entity-by-selector.js: -------------------------------------------------------------------------------- 1 | const postcssSelectorParser = require('postcss-selector-parser'); 2 | 3 | module.exports = function getEntityBySelector(selectors) { 4 | let result = ''; 5 | 6 | postcssSelectorParser(selectors => { 7 | selectors.walkClasses(node => { 8 | if (!result) { 9 | result = node.value; 10 | } 11 | }); 12 | }).processSync(selectors); 13 | 14 | return result; 15 | }; 16 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-to-bem-file-structure", 3 | "version": "0.0.5", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@bem/cell": { 8 | "version": "0.2.6", 9 | "resolved": "https://registry.npmjs.org/@bem/cell/-/cell-0.2.6.tgz", 10 | "integrity": "sha1-jTHQvFkG7nuWyyvdREPVoCclIRQ=", 11 | "requires": { 12 | "@bem/entity-name": "1.4.0", 13 | "depd": "1.1.0" 14 | }, 15 | "dependencies": { 16 | "@bem/entity-name": { 17 | "version": "1.4.0", 18 | "resolved": "https://registry.npmjs.org/@bem/entity-name/-/entity-name-1.4.0.tgz", 19 | "integrity": "sha1-2YDNmfs5L3CU1egkAyPz2gNw5/M=", 20 | "requires": { 21 | "@bem/naming": "2.0.0-5" 22 | } 23 | }, 24 | "@bem/naming": { 25 | "version": "2.0.0-5", 26 | "resolved": "https://registry.npmjs.org/@bem/naming/-/naming-2.0.0-5.tgz", 27 | "integrity": "sha1-ulDWrZc6curRIx0dzl7zKNeBj3Y=", 28 | "requires": { 29 | "@bem/entity-name": "1.3.1" 30 | }, 31 | "dependencies": { 32 | "@bem/entity-name": { 33 | "version": "1.3.1", 34 | "resolved": "https://registry.npmjs.org/@bem/entity-name/-/entity-name-1.3.1.tgz", 35 | "integrity": "sha1-rl+yNPSbukV+MWPwa+jMW6hZBdo=", 36 | "requires": { 37 | "bem-naming": "1.0.1" 38 | } 39 | } 40 | } 41 | } 42 | } 43 | }, 44 | "@bem/entity-name": { 45 | "version": "1.5.0", 46 | "resolved": "https://registry.npmjs.org/@bem/entity-name/-/entity-name-1.5.0.tgz", 47 | "integrity": "sha1-19wgGAwwPAt6ioAs+rcJCcNRmwo=", 48 | "requires": { 49 | "@bem/naming": "2.0.0-5", 50 | "depd": "1.1.0", 51 | "es6-error": "4.0.2" 52 | }, 53 | "dependencies": { 54 | "@bem/naming": { 55 | "version": "2.0.0-5", 56 | "resolved": "https://registry.npmjs.org/@bem/naming/-/naming-2.0.0-5.tgz", 57 | "integrity": "sha1-ulDWrZc6curRIx0dzl7zKNeBj3Y=", 58 | "requires": { 59 | "@bem/entity-name": "1.3.1" 60 | }, 61 | "dependencies": { 62 | "@bem/entity-name": { 63 | "version": "1.3.1", 64 | "resolved": "https://registry.npmjs.org/@bem/entity-name/-/entity-name-1.3.1.tgz", 65 | "integrity": "sha1-rl+yNPSbukV+MWPwa+jMW6hZBdo=", 66 | "requires": { 67 | "bem-naming": "1.0.1" 68 | } 69 | } 70 | } 71 | } 72 | } 73 | }, 74 | "@bem/fs-scheme": { 75 | "version": "2.2.0", 76 | "resolved": "https://registry.npmjs.org/@bem/fs-scheme/-/fs-scheme-2.2.0.tgz", 77 | "integrity": "sha512-51u7dfzP0VPWOQc2SegcJZlvGWMDrFg1KsjaFd8xlDI2cudZKLgppY5eHA06XaZnknYM0unaFF3LQ3Uwp6KEKQ==", 78 | "requires": { 79 | "@bem/cell": "0.2.5", 80 | "@bem/naming": "2.0.0-6" 81 | }, 82 | "dependencies": { 83 | "@bem/cell": { 84 | "version": "0.2.5", 85 | "resolved": "https://registry.npmjs.org/@bem/cell/-/cell-0.2.5.tgz", 86 | "integrity": "sha1-GD3vpFsLh/KvOwIdsM1O/ypJ09E=", 87 | "requires": { 88 | "@bem/entity-name": "1.3.1" 89 | } 90 | }, 91 | "@bem/entity-name": { 92 | "version": "1.3.1", 93 | "resolved": "https://registry.npmjs.org/@bem/entity-name/-/entity-name-1.3.1.tgz", 94 | "integrity": "sha1-rl+yNPSbukV+MWPwa+jMW6hZBdo=", 95 | "requires": { 96 | "bem-naming": "1.0.1" 97 | } 98 | } 99 | } 100 | }, 101 | "@bem/naming": { 102 | "version": "2.0.0-6", 103 | "resolved": "https://registry.npmjs.org/@bem/naming/-/naming-2.0.0-6.tgz", 104 | "integrity": "sha1-DCxh07yM2J+1uLxtwNv/wK6gVw4=", 105 | "requires": { 106 | "@bem/entity-name": "1.4.0" 107 | }, 108 | "dependencies": { 109 | "@bem/entity-name": { 110 | "version": "1.4.0", 111 | "resolved": "https://registry.npmjs.org/@bem/entity-name/-/entity-name-1.4.0.tgz", 112 | "integrity": "sha1-2YDNmfs5L3CU1egkAyPz2gNw5/M=", 113 | "requires": { 114 | "@bem/naming": "2.0.0-5" 115 | }, 116 | "dependencies": { 117 | "@bem/naming": { 118 | "version": "2.0.0-5", 119 | "resolved": "https://registry.npmjs.org/@bem/naming/-/naming-2.0.0-5.tgz", 120 | "integrity": "sha1-ulDWrZc6curRIx0dzl7zKNeBj3Y=", 121 | "requires": { 122 | "@bem/entity-name": "1.3.1" 123 | }, 124 | "dependencies": { 125 | "@bem/entity-name": { 126 | "version": "1.3.1", 127 | "resolved": "https://registry.npmjs.org/@bem/entity-name/-/entity-name-1.3.1.tgz", 128 | "integrity": "sha1-rl+yNPSbukV+MWPwa+jMW6hZBdo=", 129 | "requires": { 130 | "bem-naming": "1.0.1" 131 | } 132 | } 133 | } 134 | } 135 | } 136 | } 137 | } 138 | }, 139 | "@bem/sdk.config": { 140 | "version": "0.0.8", 141 | "resolved": "https://registry.npmjs.org/@bem/sdk.config/-/sdk.config-0.0.8.tgz", 142 | "integrity": "sha512-kYqNI9xRFp/JG30/wknk88+WnRElhVFTz2Xa5RbesOMxVIhC5kdjOhbCTzf7FxoK5ZYKs9ouU7AAQPKEThQ03w==", 143 | "requires": { 144 | "betterc": "^1.3.0", 145 | "glob": "^7.0.5", 146 | "is-glob": "^3.1.0", 147 | "lodash.clonedeep": "^4.5.0", 148 | "lodash.flatten": "^4.4.0", 149 | "lodash.isequal": "^4.5.0", 150 | "lodash.mergewith": "^4.6.0", 151 | "lodash.uniqwith": "^4.5.0", 152 | "pinkie-promise": "^2.0.1" 153 | } 154 | }, 155 | "balanced-match": { 156 | "version": "1.0.0", 157 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 158 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 159 | }, 160 | "bem-naming": { 161 | "version": "1.0.1", 162 | "resolved": "https://registry.npmjs.org/bem-naming/-/bem-naming-1.0.1.tgz", 163 | "integrity": "sha1-15cjOcueWd0FWJqdH9G91059/Cw=" 164 | }, 165 | "bem-tools-create": { 166 | "version": "2.3.0", 167 | "resolved": "https://registry.npmjs.org/bem-tools-create/-/bem-tools-create-2.3.0.tgz", 168 | "integrity": "sha512-L+X/pNB7VuUBFVKQqT+IEWMM3+/7i9+yWLHZq8sKRG3Orgmfe3qAee7JCiobh3hf/Qh9felvPRaY60kX8tCdhg==", 169 | "requires": { 170 | "@bem/cell": "^0.2.5", 171 | "@bem/entity-name": "^1.3.2", 172 | "@bem/fs-scheme": "^2.1.1-0", 173 | "@bem/naming": "^2.0.0-5", 174 | "@bem/sdk.config": "0.0.8", 175 | "brace-expansion": "^1.1.6", 176 | "mkdirp": "^0.5.1", 177 | "pinkie-promise": "^2.0.1", 178 | "uniq": "^1.0.1" 179 | } 180 | }, 181 | "betterc": { 182 | "version": "1.3.0", 183 | "resolved": "https://registry.npmjs.org/betterc/-/betterc-1.3.0.tgz", 184 | "integrity": "sha1-EfbTMwkg82g7GA9IGr8rPV/u2+E=", 185 | "requires": { 186 | "lodash": "^4.13.1", 187 | "minimist": "^1.2.0", 188 | "node-eval": "^1.0.2", 189 | "os-homedir": "^1.0.1", 190 | "pinkie-promise": "^2.0.1" 191 | } 192 | }, 193 | "brace-expansion": { 194 | "version": "1.1.11", 195 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 196 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 197 | "requires": { 198 | "balanced-match": "^1.0.0", 199 | "concat-map": "0.0.1" 200 | } 201 | }, 202 | "concat-map": { 203 | "version": "0.0.1", 204 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 205 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 206 | }, 207 | "cssesc": { 208 | "version": "3.0.0", 209 | "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", 210 | "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" 211 | }, 212 | "depd": { 213 | "version": "1.1.0", 214 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", 215 | "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=" 216 | }, 217 | "es6-error": { 218 | "version": "4.0.2", 219 | "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.0.2.tgz", 220 | "integrity": "sha1-7sXHJurO9Rt/a3PCDbbhsTsGnJg=" 221 | }, 222 | "fs.realpath": { 223 | "version": "1.0.0", 224 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 225 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 226 | }, 227 | "glob": { 228 | "version": "7.1.6", 229 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 230 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 231 | "requires": { 232 | "fs.realpath": "^1.0.0", 233 | "inflight": "^1.0.4", 234 | "inherits": "2", 235 | "minimatch": "^3.0.4", 236 | "once": "^1.3.0", 237 | "path-is-absolute": "^1.0.0" 238 | } 239 | }, 240 | "inflight": { 241 | "version": "1.0.6", 242 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 243 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 244 | "requires": { 245 | "once": "^1.3.0", 246 | "wrappy": "1" 247 | } 248 | }, 249 | "inherits": { 250 | "version": "2.0.4", 251 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 252 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 253 | }, 254 | "is-extglob": { 255 | "version": "2.1.1", 256 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 257 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" 258 | }, 259 | "is-glob": { 260 | "version": "3.1.0", 261 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", 262 | "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", 263 | "requires": { 264 | "is-extglob": "^2.1.0" 265 | } 266 | }, 267 | "lodash": { 268 | "version": "4.17.21", 269 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 270 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 271 | }, 272 | "lodash.clonedeep": { 273 | "version": "4.5.0", 274 | "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", 275 | "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" 276 | }, 277 | "lodash.flatten": { 278 | "version": "4.4.0", 279 | "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", 280 | "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" 281 | }, 282 | "lodash.isequal": { 283 | "version": "4.5.0", 284 | "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", 285 | "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" 286 | }, 287 | "lodash.mergewith": { 288 | "version": "4.6.2", 289 | "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", 290 | "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" 291 | }, 292 | "lodash.uniqwith": { 293 | "version": "4.5.0", 294 | "resolved": "https://registry.npmjs.org/lodash.uniqwith/-/lodash.uniqwith-4.5.0.tgz", 295 | "integrity": "sha1-egy/ZfQ7WShiWp1NDcVLGMrcfvM=" 296 | }, 297 | "minimatch": { 298 | "version": "3.0.4", 299 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 300 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 301 | "requires": { 302 | "brace-expansion": "^1.1.7" 303 | } 304 | }, 305 | "minimist": { 306 | "version": "1.2.5", 307 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 308 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 309 | }, 310 | "mkdirp": { 311 | "version": "0.5.5", 312 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 313 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 314 | "requires": { 315 | "minimist": "^1.2.5" 316 | } 317 | }, 318 | "nanoid": { 319 | "version": "3.3.4", 320 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", 321 | "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" 322 | }, 323 | "node-eval": { 324 | "version": "1.1.1", 325 | "resolved": "https://registry.npmjs.org/node-eval/-/node-eval-1.1.1.tgz", 326 | "integrity": "sha512-bXlCTkee8GZCoULxbSpEXSPIu98paZDPTwNo4qk64HxfEs+RdlXzojFGpGhAxr7JyFiDGwTX6EFTDYMkIZiB+A==", 327 | "requires": { 328 | "path-is-absolute": "1.0.1" 329 | } 330 | }, 331 | "once": { 332 | "version": "1.4.0", 333 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 334 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 335 | "requires": { 336 | "wrappy": "1" 337 | } 338 | }, 339 | "os-homedir": { 340 | "version": "1.0.2", 341 | "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", 342 | "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" 343 | }, 344 | "path-is-absolute": { 345 | "version": "1.0.1", 346 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 347 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 348 | }, 349 | "picocolors": { 350 | "version": "1.0.0", 351 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 352 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" 353 | }, 354 | "pinkie": { 355 | "version": "2.0.4", 356 | "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", 357 | "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" 358 | }, 359 | "pinkie-promise": { 360 | "version": "2.0.1", 361 | "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", 362 | "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", 363 | "requires": { 364 | "pinkie": "^2.0.0" 365 | } 366 | }, 367 | "postcss": { 368 | "version": "8.4.21", 369 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", 370 | "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", 371 | "requires": { 372 | "nanoid": "^3.3.4", 373 | "picocolors": "^1.0.0", 374 | "source-map-js": "^1.0.2" 375 | } 376 | }, 377 | "postcss-selector-parser": { 378 | "version": "6.0.11", 379 | "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", 380 | "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", 381 | "requires": { 382 | "cssesc": "^3.0.0", 383 | "util-deprecate": "^1.0.2" 384 | } 385 | }, 386 | "source-map-js": { 387 | "version": "1.0.2", 388 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 389 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" 390 | }, 391 | "uniq": { 392 | "version": "1.0.1", 393 | "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", 394 | "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=" 395 | }, 396 | "util-deprecate": { 397 | "version": "1.0.2", 398 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 399 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 400 | }, 401 | "wrappy": { 402 | "version": "1.0.2", 403 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 404 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 405 | } 406 | } 407 | } 408 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-to-bem-file-structure", 3 | "version": "0.0.5", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node test && node classic/test" 8 | }, 9 | "keywords": [ 10 | "bem", 11 | "css", 12 | "files" 13 | ], 14 | "bin": { 15 | "css-to-bem-file-structure": "./bin/create-by-css", 16 | "bemify": "./bin/bemify" 17 | }, 18 | "author": "Vladimir Grinenko (https://github.com/tadatuta)", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/tadatuta/bem-tools-create-by-css/issues/new" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git://github.com/tadatuta/bem-tools-create-by-css.git" 26 | }, 27 | "dependencies": { 28 | "bem-tools-create": "^2.3.0", 29 | "postcss": "^8.4.21", 30 | "postcss-selector-parser": "^6.0.11" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/expected.js: -------------------------------------------------------------------------------- 1 | const expected = { 2 | 'b1/b1.css': [ 3 | '.b1 { color: red; color: green; }', 4 | '.b1 { animation-name: test; }', 5 | '.b1.b2 { color: yellowgreen; }', 6 | '@media (max-width: 1250px) {', 7 | ' .b1 { width: 50%; }', 8 | '}', 9 | '.b1 { color: pink; }', 10 | '.b1:hover { color: brown; }', 11 | ].join('\n'), 12 | 'b2/b2.css': [ 13 | '@media (max-width: 1250px) {', 14 | ' .b2 { width: 75%; }', 15 | '}', 16 | '.b2 { color: pink; }', 17 | '.b2 { color: green; }', 18 | ].join('\n'), 19 | 'b1/__e1/b1__e1.css': [ 20 | '.b1__e1 { color: yellow; }', 21 | '.b1__e1::after { content: ""; }', 22 | ].join('\n'), 23 | 'b1/_m1/b1_m1.css': '.b1_m1 { color: honeydew; }', 24 | 'b1/_m1/b1_m1_v1.css': '.b1_m1_v1 { color: lightcoral; }', 25 | 'b1/_m1/b1_m1_v2.css': '.b1_m1_v2 .b1__e1 { color: hotpink; }', 26 | 'b2/__e1/_m1/b2__e1_m1.css': '.b2__e1_m1 { color: #eee; }' 27 | } 28 | 29 | module.exports = expected; 30 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const assert = require('assert'); 3 | const expected = require('./expected'); 4 | 5 | const pathToBlocks = 'test/blocks'; 6 | fs.rmSync(pathToBlocks, { force: true, recursive: true }); 7 | 8 | const css = fs.readFileSync('test/test.css', 'utf8'); 9 | 10 | require('..')(css, { level: pathToBlocks, tech: 'css' }).then(() => { 11 | // TODO: fixme 12 | setTimeout(() => { 13 | for ([filePath, reference] of Object.entries(expected)) { 14 | const actual = fs.readFileSync(`${pathToBlocks}/${filePath}`, 'utf-8'); 15 | assert.strictEqual(actual, reference); 16 | } 17 | 18 | fs.rmSync(pathToBlocks, { force: true, recursive: true }); 19 | }, 500); 20 | }); 21 | -------------------------------------------------------------------------------- /test/test.css: -------------------------------------------------------------------------------- 1 | .b1 { color: red; color: green; } 2 | .b1 { animation-name: test; } 3 | .b1.b2 { color: yellowgreen; } 4 | 5 | @media (max-width: 1250px) { 6 | .b1 { width: 50%; } 7 | .b2 { width: 75%; } 8 | } 9 | 10 | .b1, .b2 { color: pink; } 11 | 12 | .b1:hover { color: brown; } 13 | 14 | .b1__e1 { color: yellow; } 15 | .b1__e1::after { content: ""; } 16 | 17 | .b1_m1 { color: honeydew; } 18 | 19 | .b1_m1_v1 { color: lightcoral; } 20 | 21 | .b1_m1_v2 .b1__e1 { color: hotpink; } 22 | 23 | .b2 { color: green; } 24 | 25 | .b2__e1_m1 { color: #eee; } 26 | 27 | /* 28 | :root { 29 | --my-var: brown; 30 | } 31 | 32 | .my-block { 33 | color: var(--my-var); 34 | } 35 | */ 36 | 37 | @keyframes test { 38 | from { 39 | transform: translateX(0%); 40 | } 41 | 42 | to { 43 | transform: translateX(100%); 44 | } 45 | } 46 | --------------------------------------------------------------------------------