├── .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 | [](https://badge.fury.io/js/postcss-namespace)
4 | [](https://travis-ci.org/totora0155/postcss-namespace)
5 | [](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 |
--------------------------------------------------------------------------------