├── .babelrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── examples ├── postcss.js └── sample.css ├── gulpfile.babel.js ├── index.js ├── package.json ├── src └── index.babel.js └── test ├── expect.css └── test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "transform-runtime", 4 | 5 | "transform-es2015-modules-commonjs", 6 | "transform-es2015-destructuring", 7 | "transform-es2015-spread", 8 | 9 | "transform-async-to-generator", 10 | "transform-es2015-parameters", 11 | "transform-function-bind", 12 | "transform-object-assign", 13 | "transform-object-rest-spread", 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | node_modules/ 3 | src/ 4 | test/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "stable" 5 | - "4.4.5" 6 | script: 7 | - npm run build 8 | - npm test 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 nju33(totora0155) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # postcss-namespace 2 | 3 | [![npm version](https://badge.fury.io/js/postcss-namespace.svg)](https://badge.fury.io/js/postcss-namespace) 4 | [![Build Status](https://travis-ci.org/totora0155/postcss-namespace.svg?branch=master)](https://travis-ci.org/totora0155/postcss-namespace) 5 | [![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo) 6 | 7 |

PostCSS plugin that prefix a namespace to a selector

8 | 9 | --- 10 | 11 | ## Install 12 | 13 | ``` 14 | npm i postcss-namespace 15 | ``` 16 | 17 | ## Usage 18 | 19 | Write `@prefix` atrule to your css file. 20 | (e.g. input.css) 21 | ```css 22 | .outside {} 23 | 24 | @prefix block not(.not-target, /ignore/); 25 | 26 | .box {} 27 | 28 | .inner .target {} 29 | .inner .not-target {} 30 | .inner .ignore-1 {} 31 | .inner .ignore-2, 32 | .inner .target {} 33 | 34 | @prefix ; 35 | 36 | .box {} 37 | 38 | @prefix block2; 39 | 40 | .box {} 41 | &:hover {} 42 | [href^="https"][target="_blank"] {} 43 | 44 | @media screen and (min-width: 768px) { 45 | #media {} 46 | #media #inner, 47 | .media .inner.box {} 48 | } 49 | 50 | ``` 51 | 52 | Use this plugin in PostCSS 53 | (e.g.) 54 | ```javascript 55 | const fs = require('fs'); 56 | const postcss = require('postcss'); 57 | const namespace = require('postcss-namespace'); 58 | 59 | const css = fs.readFileSync('./sample.css', 'utf-8'); 60 | 61 | // or postcss([namespace.bem]) 62 | postcss([namespace({token: '__'})]) 63 | .process(css) 64 | .then(result => console.log(result.css)); 65 | 66 | ``` 67 | 68 | Will get `output` like following CSS 69 | 70 | ```css 71 | .outside {} 72 | 73 | .block__box {} 74 | 75 | .block__inner .block__target {} 76 | .block__inner .not-target {} 77 | .block__inner .ignore-1 {} 78 | .block__inner .ignore-2, 79 | .block__inner .block__target {} 80 | 81 | .box {} 82 | 83 | .block2__box {} 84 | &:hover {} 85 | [href^="https"][target="_blank"] {} 86 | 87 | @media screen and (min-width: 768px) { 88 | #block2__media {} 89 | #block2__media #block2__inner, 90 | .block2__media .block2__inner.block2__box {} 91 | } 92 | 93 | ``` 94 | 95 | ## AtRule Function 96 | 97 | - `not` (string|regexp)... 98 | Specify selector or pattern which Don't want a prefix 99 | 100 | ## Plugin Function 101 | 102 | - `namespace.bem` 103 | Same as `namespace({token: '__'})` 104 | 105 | ## Options 106 | 107 | - `token` 108 | Token for consolidate(e.g.) `namespace({token: '__'})` 109 | `-` by default 110 | 111 | ## Run to example 112 | 113 | **1** Close this 114 | 115 | ``` 116 | git clone git@github.com:totora0155/postcss-namespace.git 117 | ``` 118 | 119 | **2** Change directory 120 | ``` 121 | cd postcss-namespace 122 | ``` 123 | 124 | **3** Install modules 125 | ``` 126 | npm install 127 | ``` 128 | 129 | **4** Run to script 130 | ``` 131 | cd examples && node postcss.js 132 | ``` 133 | 134 | ## Change log 135 | 136 | |version|log| 137 | |:-:|:--| 138 | |1.1.0|Add `bem` function. (Alias `{token: '__'}`)| 139 | |1.0.1|Fix `node.nodes`| 140 | |1.0.0|Rewrite with es2015 & Add not func in AtRule| 141 | |0.2.5|Bug fix for `:nth*` selector & Revert v0.2.2 | 142 | |0.2.4|Bug fix for pseudo selector| 143 | |0.2.3|Bug fix (Tag not output after atrule)| 144 | |0.2.2|Fix, occured error to [postcss-selector-not](https://github.com/postcss/postcss-selector-not) syntax| 145 | |0.2.0|Change at-rule keyword to `@prefix` from `@namespace` [#1](https://github.com/totora0155/postcss-namespace/issues/1)| 146 | -------------------------------------------------------------------------------- /examples/postcss.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const postcss = require('postcss'); 3 | const namespace = require('..'); 4 | 5 | const css = fs.readFileSync('./sample.css', 'utf-8'); 6 | 7 | postcss([namespace({token: '__'})]) 8 | .process(css) 9 | .then(result => console.log(result.css)); 10 | -------------------------------------------------------------------------------- /examples/sample.css: -------------------------------------------------------------------------------- 1 | .outside {} 2 | 3 | @prefix block not(.not-target, /ignore/); 4 | 5 | .box {} 6 | 7 | .inner .target {} 8 | .inner .not-target {} 9 | .inner .ignore-1 {} 10 | .inner .ignore-2, 11 | .inner .target {} 12 | 13 | @prefix ; 14 | 15 | .box {} 16 | 17 | @prefix block2; 18 | 19 | .box {} 20 | &:hover {} 21 | [href^="https"][target="_blank"] {} 22 | 23 | @media screen and (min-width: 768px) { 24 | #media {} 25 | #media #inner, 26 | .media .inner.box {} 27 | } 28 | -------------------------------------------------------------------------------- /gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import plumber from 'gulp-plumber'; 3 | import babel from 'gulp-babel'; 4 | import rename from 'gulp-rename'; 5 | 6 | { 7 | const src = 'src/**/*.js'; 8 | const dest = 'build/'; 9 | 10 | gulp.task('babel', () => { 11 | gulp.src(src) 12 | .pipe(plumber()) 13 | .pipe(babel()) 14 | .pipe(rename(path => { 15 | path.basename = path.basename.match(/^[^.]+/)[0]; 16 | })) 17 | .pipe(gulp.dest(dest)); 18 | }); 19 | } 20 | 21 | { 22 | const src = 'src/**/*.js'; 23 | gulp.task('watch', ['babel'], () => { 24 | gulp.watch(src, ['babel']); 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const plugin = require('./build').default; 2 | 3 | plugin.bem = plugin({token: '__'}); 4 | module.exports = plugin; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-namespace", 3 | "description": "PostCSS plugin that prefix a namespace to a selector", 4 | "version": "1.1.1", 5 | "author": { 6 | "name": "nju33", 7 | "email": "sski0155+npm@gmail.com", 8 | "url": "http://nju33.work/" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/totora0155/postcss-namespace/issues" 12 | }, 13 | "dependencies": { 14 | "lodash": "^4.13.1", 15 | "postcss": "^5.0.10" 16 | }, 17 | "devDependencies": { 18 | "ava": "^0.15.2", 19 | "babel-core": "^6.7.4", 20 | "babel-plugin-syntax-async-functions": "^6.5.0", 21 | "babel-plugin-transform-async-to-generator": "^6.5.0", 22 | "babel-plugin-transform-es2015-destructuring": "^6.9.0", 23 | "babel-plugin-transform-es2015-modules-commonjs": "^6.8.0", 24 | "babel-plugin-transform-es2015-parameters": "^6.6.4", 25 | "babel-plugin-transform-es2015-spread": "^6.8.0", 26 | "babel-plugin-transform-function-bind": "^6.5.2", 27 | "babel-plugin-transform-object-assign": "^6.5.0", 28 | "babel-plugin-transform-object-rest-spread": "^6.6.4", 29 | "babel-plugin-transform-remove-console": "^6.5.0", 30 | "babel-plugin-transform-runtime": "^6.6.0", 31 | "babel-register": "^6.7.2", 32 | "babel-runtime": "^6.5.0", 33 | "babel-template": "^6.7.0", 34 | "babel-types": "^6.7.2", 35 | "gulp": "^3.9.1", 36 | "gulp-babel": "^6.1.2", 37 | "gulp-plumber": "^1.1.0", 38 | "gulp-rename": "^1.2.2", 39 | "xo": "^0.16.0" 40 | }, 41 | "homepage": "https://github.com/totora0155/postcss-namespace#readme", 42 | "keywords": [ 43 | "css", 44 | "postcss", 45 | "postcss-plugin" 46 | ], 47 | "license": "MIT", 48 | "main": "index.js", 49 | "repository": { 50 | "type": "git", 51 | "url": "git@github.com:totora0155/postcss-namespace.git" 52 | }, 53 | "scripts": { 54 | "prepublish": "npm run build", 55 | "build": "gulp babel", 56 | "start": "gulp watch", 57 | "test": "xo && ava" 58 | }, 59 | "ava": { 60 | "files": [ 61 | "test/**/*.js" 62 | ], 63 | "require": [ 64 | "babel-register" 65 | ], 66 | "babel": "inherit" 67 | }, 68 | "xo": { 69 | "env": [ 70 | "node" 71 | ], 72 | "esnext": true, 73 | "space": 2, 74 | "ignores": [ 75 | "build/**" 76 | ] 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/index.babel.js: -------------------------------------------------------------------------------- 1 | import postcss from 'postcss'; 2 | import _ from 'lodash'; 3 | 4 | const WARNING_TEXT = '@namespace is deprecated! please use @prefix'; 5 | 6 | const defaultOpts = { 7 | token: '-' 8 | }; 9 | 10 | export default postcss.plugin('postcss-namespace', (opts = {}) => { 11 | opts = Object.assign({}, defaultOpts, opts); 12 | 13 | return (css, result) => { 14 | css.walkAtRules(/namespace|prefix/, rule => { 15 | if (rule.name === 'namespace') { 16 | result.warn(WARNING_TEXT, {node: rule}); 17 | return rule; 18 | } 19 | const prefix = rule.params.match(/^[^\s]*/)[0]; 20 | const ignored = (params => { 21 | const matches = params.match(/not\((.+)\)/); 22 | if (!matches) { 23 | return []; 24 | } 25 | 26 | const ignored = _.compact(matches[1].split(/\s*,\s*/)); 27 | return _.map(ignored, target => { 28 | const matches = target.match(/\/(.+)\//); 29 | if (!matches) { 30 | return target; 31 | } 32 | return new RegExp(_.escapeRegExp(matches[1])); 33 | }); 34 | })(rule.params); 35 | process(prefix, rule, ignored); 36 | rule.remove(); 37 | }); 38 | }; 39 | 40 | function process(prefix, target, ignored) { 41 | if (!prefix) { 42 | return; 43 | } 44 | 45 | while ((target = target.next())) { 46 | if (isPrefixAtRule(target)) { 47 | break; 48 | } 49 | 50 | if (target.constructor.name === 'AtRule') { 51 | if (typeof target.nodes !== 'undefined' && target.nodes.length) { 52 | process(prefix, wrapNodes(target.nodes), ignored); 53 | } 54 | } 55 | 56 | if (target.constructor.name !== 'Rule') { 57 | continue; 58 | } 59 | 60 | const re = /[#.][^\s#.%[:]+/g; 61 | target.selector = target.selector.replace(re, selector => { 62 | if (ignored.length && isIgnored(selector)) { 63 | return selector; 64 | } 65 | return `${selector[0]}${prefix}${opts.token}${selector.slice(1)}`; 66 | }); 67 | } 68 | 69 | function isPrefixAtRule(target) { 70 | return Boolean(target.constructor.name === 'AtRule' && 71 | target.name === 'prefix'); 72 | } 73 | 74 | function isIgnored(selector) { 75 | return _.some(_.map(ignored, target => { 76 | if (target.constructor.name === 'String') { 77 | return selector === target; 78 | } else if (target.constructor.name === 'RegExp') { 79 | return target.test(selector); 80 | } 81 | })); 82 | } 83 | 84 | function wrapNodes(nodes) { 85 | return { 86 | next() { 87 | return nodes[0]; 88 | } 89 | }; 90 | } 91 | } 92 | }); 93 | -------------------------------------------------------------------------------- /test/expect.css: -------------------------------------------------------------------------------- 1 | .outside {} 2 | 3 | .block__box {} 4 | 5 | .block__inner .block__target {} 6 | .block__inner .not-target {} 7 | .block__inner .ignore-1 {} 8 | .block__inner .ignore-2, 9 | .block__inner .block__target {} 10 | 11 | .box {} 12 | 13 | .block2__box {} 14 | &:hover {} 15 | [href^="https"][target="_blank"] {} 16 | 17 | @media screen and (min-width: 768px) { 18 | #block2__media {} 19 | #block2__media #block2__inner, 20 | .block2__media .block2__inner.block2__box {} 21 | } 22 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import test from 'ava'; 3 | import postcss from 'postcss'; 4 | import namespace from '..'; 5 | 6 | const css = fs.readFileSync('../examples/sample.css', 'utf-8'); 7 | const expect = fs.readFileSync('./expect.css', 'utf-8'); 8 | 9 | function transform(plugin) { 10 | return new Promise(resolve => { 11 | postcss([plugin]) 12 | .process(css) 13 | .then(result => resolve(result.css)); 14 | }); 15 | } 16 | 17 | test('namespace token', async t => { 18 | t.is(await transform(namespace({token: '__'})), expect); 19 | }); 20 | 21 | test('namespace bem', async t => { 22 | t.is(await transform(namespace.bem), expect); 23 | }); 24 | --------------------------------------------------------------------------------