├── test
├── result
│ ├── group
│ │ ├── app.js
│ │ ├── style.css
│ │ └── index.html
│ ├── style.css
│ └── style.alt-postfix.css
├── fixtures
│ ├── group
│ │ ├── app.js
│ │ ├── index.html
│ │ └── style.css
│ ├── style.css
│ ├── style_prefix.css
│ ├── style.alt-postfix.css
│ └── style_prefix_postfix.css
└── main.js
├── .gitignore
├── .npmignore
├── .travis.yml
├── .eslintrc.yml
├── LICENSE
├── package.json
├── index.js
└── README.md
/test/result/group/app.js:
--------------------------------------------------------------------------------
1 | var document;
2 | var $div = document.querySelector('#a0');
3 | $div.id = 'a1';
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .DS_Store
3 | /node_modules
4 | npm-debug.log
5 | test/data/temp
6 | package-lock.json
7 |
--------------------------------------------------------------------------------
/test/fixtures/group/app.js:
--------------------------------------------------------------------------------
1 | var document;
2 | var $div = document.querySelector('#test3--s--');
3 | $div.id = 'test10--s--';
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .DS_Store
3 | /node_modules
4 | npm-debug.log
5 | /test
6 | package-lock.json
7 | .travis.yml
8 | .eslintrc.yml
9 |
--------------------------------------------------------------------------------
/test/result/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | color: red;
3 | }
4 |
5 | #a0 {
6 | color: green;
7 | }
8 |
9 | #a1 {
10 | color: green;
11 | }
12 |
13 | #a2, #a3 {
14 | color: green;
15 | }
16 |
17 | #a1 {width: 100px;}
18 |
--------------------------------------------------------------------------------
/test/result/group/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | color: red;
3 | }
4 |
5 | #a2 {
6 | color: green;
7 | }
8 |
9 | #a3 {
10 | color: green;
11 | }
12 |
13 | #a0, #a4 {
14 | color: green;
15 | }
16 |
17 | #a3 {width: 100px;}
18 |
--------------------------------------------------------------------------------
/test/result/group/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/result/style.alt-postfix.css:
--------------------------------------------------------------------------------
1 | body {
2 | color: red;
3 | }
4 |
5 | #a0 {
6 | color: green;
7 | }
8 |
9 | #a1 {
10 | color: green;
11 | }
12 |
13 | #a2, #a3 {
14 | color: green;
15 | }
16 |
17 | #test2--s-- {width: 100px;}
18 |
--------------------------------------------------------------------------------
/test/fixtures/group/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/fixtures/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | color: red;
3 | }
4 |
5 | #test--s-- {
6 | color: green;
7 | }
8 |
9 | #test2--s-- {
10 | color: green;
11 | }
12 |
13 | #test3--s--, #test4--s-- {
14 | color: green;
15 | }
16 |
17 | #test2--s-- {width: 100px;}
18 |
--------------------------------------------------------------------------------
/test/fixtures/group/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | color: red;
3 | }
4 |
5 | #test--s-- {
6 | color: green;
7 | }
8 |
9 | #test2--s-- {
10 | color: green;
11 | }
12 |
13 | #test3--s--, #test4--s-- {
14 | color: green;
15 | }
16 |
17 | #test2--s-- {width: 100px;}
18 |
--------------------------------------------------------------------------------
/test/fixtures/style_prefix.css:
--------------------------------------------------------------------------------
1 | body {
2 | color: red;
3 | }
4 |
5 | #prefix---test {
6 | color: green;
7 | }
8 |
9 | #prefix---test2 {
10 | color: green;
11 | }
12 |
13 | #prefix---test3, #prefix---test4 {
14 | color: green;
15 | }
16 |
17 | #prefix---test2 {width: 100px;}
18 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 |
4 | matrix:
5 | include:
6 | - os: linux
7 | node_js: "4"
8 | - os: osx
9 | node_js: "8"
10 |
11 | before_script:
12 | - npm run lint
13 |
14 | cache:
15 | yarn: true
16 |
17 | notifications:
18 | email:
19 | on_success: never
20 |
--------------------------------------------------------------------------------
/test/fixtures/style.alt-postfix.css:
--------------------------------------------------------------------------------
1 | body {
2 | color: red;
3 | }
4 |
5 | #test--alt-postfix-- {
6 | color: green;
7 | }
8 |
9 | #test2--alt-postfix-- {
10 | color: green;
11 | }
12 |
13 | #test3--alt-postfix--, #test4--alt-postfix-- {
14 | color: green;
15 | }
16 |
17 | #test2--s-- {width: 100px;}
18 |
--------------------------------------------------------------------------------
/test/fixtures/style_prefix_postfix.css:
--------------------------------------------------------------------------------
1 | body {
2 | color: red;
3 | }
4 |
5 | #prefix---test--s-- {
6 | color: green;
7 | }
8 |
9 | #prefix---test2--s-- {
10 | color: green;
11 | }
12 |
13 | #prefix---test3--s--, #prefix---test4--s-- {
14 | color: green;
15 | }
16 |
17 | #prefix---test2--s-- {width: 100px;}
18 |
--------------------------------------------------------------------------------
/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | ---
2 | extends:
3 | - eslint:recommended
4 | plugins:
5 | - mocha
6 | - chai-expect
7 | overrides:
8 | - files:
9 | - test/**/*.js
10 | env:
11 | mocha: true
12 | env:
13 | node: true
14 | es6: true
15 | parserOptions:
16 | sourceType: module
17 | rules:
18 | object-shorthand: error
19 | no-lone-blocks: error
20 | no-lonely-if: error
21 | no-negated-in-lhs: error
22 | no-new-object: error
23 | no-octal: error
24 | no-new-require: error
25 | no-new-wrappers: error
26 | no-mixed-operators: error
27 | no-self-compare: error
28 | no-sequences: error
29 | no-spaced-func: error
30 | no-tabs: error
31 | no-undefined: error
32 | no-unneeded-ternary: error
33 | prefer-arrow-callback: error
34 | prefer-const: error
35 | space-before-blocks: error
36 | semi: error
37 | globals:
38 | express$Request: true
39 | express$Response: true
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Mikhail Bodrov
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 all
13 | 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 THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gulp-minify-cssnames",
3 | "version": "2.1.0",
4 | "description": "Gulp plugin for minify css classes and css ids",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "mocha",
8 | "lint": "eslint .",
9 | "prepublish": "npm test && npm run lint"
10 | },
11 | "engines": {
12 | "node": ">=4.0.0"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git://github.com/Connormiha/gulp-minify-cssnames.git"
17 | },
18 | "author": "Mikhail Bodrov",
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/Connormiha/gulp-minify-cssnames/issues"
22 | },
23 | "homepage": "https://github.com/Connormiha/gulp-minify-cssnames#readme",
24 | "dependencies": {
25 | "decimal-to-any": "^1.0.5",
26 | "readable-stream": "^2.1.5",
27 | "replacestream": "^4.0.2"
28 | },
29 | "devDependencies": {
30 | "chai": "4.1.1",
31 | "concat-stream": "^1.5.2",
32 | "eslint": "^4.5.0",
33 | "eslint-plugin-chai-expect": "^1.1.1",
34 | "eslint-plugin-mocha": "^4.11.0",
35 | "gulp": "^3.9.0",
36 | "mocha": "^3.0.2",
37 | "stream-assert": "^2.0.3",
38 | "vinyl": "2.1.0"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Transform = require('readable-stream/transform');
4 | const rs = require('replacestream');
5 | const decToAny = require('decimal-to-any');
6 |
7 | const decToAnyOptions = {
8 | alphabet: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_'
9 | };
10 |
11 | class Replacer {
12 | constructor(options) {
13 | const postfix = 'postfix' in options ? options.postfix : '--s--';
14 | const prefix = options.prefix || '';
15 |
16 | this._currentIndex = 0;
17 | this._namesMap = Object.create(null);
18 |
19 | this.regExp = new RegExp(`${prefix}[\\w_-]+${postfix}`, 'ig');
20 | this.replace = this.replace.bind(this);
21 | }
22 |
23 | /**
24 | * Replaces found substring to number from special alphabet
25 | * @param {String} str
26 | * @return {String}
27 | */
28 | replace(str) {
29 | if (!this._namesMap[str]) {
30 | this._namesMap[str] = `a${decToAny(this._currentIndex, decToAnyOptions.alphabet.length, decToAnyOptions)}`;
31 | this._currentIndex++;
32 | }
33 |
34 | return this._namesMap[str];
35 | }
36 | }
37 |
38 | module.exports = (options) => {
39 | const replacer = new Replacer(options || {});
40 |
41 | return new Transform({
42 | objectMode: true,
43 | transform(file, enc, callback) {
44 | if (file.isStream()) {
45 | file.contents = file.contents.pipe(rs(replacer.regExp, replacer.replace));
46 | return callback(null, file);
47 | }
48 |
49 | if (file.isBuffer()) {
50 | file.contents = new Buffer(String(file.contents).replace(replacer.regExp, replacer.replace));
51 | return callback(null, file);
52 | }
53 |
54 | callback(null, file);
55 | }
56 | });
57 | };
58 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # gulp-minify-cssnames [![NPM version][npm-image]][npm-url]
2 | > Gulp plugin for minify css classes and css ids
3 |
4 | > Since version 2.0 supported only Node.js 4+
5 | > If you need support Node 0.12 or older, then install version 1.0.2
6 |
7 | ## Usage
8 | Minifying all names(class, id) with some postfix (default: '--s--') or prefix (default not use).
9 |
10 | ### Example
11 | We have css file:
12 |
13 | ```css
14 | .menu--s-- {color: red;}
15 | .menu_top--s-- {color: black;}
16 | .menu__item--s-- {color: green;}
17 | .menu__item_active--s-- {color: blue;}
18 | .menu__item_active--s--::before {content: 'active'}
19 | ```
20 |
21 | ```javascript
22 | var gulp = require('gulp');
23 | var gulpMinifyCssNames = require('gulp-minify-cssnames');
24 |
25 | gulp.task('minify-css-names', function() {
26 | return gulp.src(['src/*.css'])
27 | .pipe(gulpMinifyCssNames())
28 | .pipe(gulp.dest('build'))
29 | });
30 | ```
31 |
32 | #### Result
33 | ```css
34 | .a0 {color: red;}
35 | .a1 {color: black;}
36 | .a2 {color: green;}
37 | .a3 {color: blue;}
38 | .a3::before {content: 'active'}
39 | ```
40 |
41 | ### Example2
42 | Our project has 3 files:
43 |
44 | ##### style.css
45 | ```css
46 | .menu--s-- {color: red;}
47 | .menu_top--s-- {color: black;}
48 | .menu__item--s-- {color: green;}
49 | .menu__item_active--s-- {color: blue;}
50 | .menu__item_active--s--::before {content: 'active'}
51 | ```
52 |
53 | ##### index.html
54 | ```html
55 |
56 |
57 |
58 |
59 |
60 | ```
61 | ##### app.js
62 | ```javascript
63 | var $menuItems = document.querySelectorAll('.menu__item--s--');
64 | var $mainMenu = document.querySelector('#main-menu--s--');
65 | ```
66 |
67 | ##### Gulp task
68 | ```javascript
69 | var gulp = require('gulp');
70 | var gulpMinifyCssNames = require('gulp-minify-cssnames');
71 |
72 | gulp.task('minify-css-names', function() {
73 | return gulp.src(['src/style.css', 'src/index.html', 'src/app.js'])
74 | .pipe(gulpMinifyCssNames())
75 | .pipe(gulp.dest('build'))
76 | });
77 | ```
78 |
79 | #### Result
80 | style.css
81 | ```css
82 | .a0 {color: red;}
83 | .a1 {color: black;}
84 | .a2 {color: green;}
85 | .a3 {color: blue;}
86 | .a3::before {content: 'active'}
87 | ```
88 | index.html
89 | ```html
90 |
91 |
1
92 |
2
93 |
3
94 |
95 | ```
96 | app.js
97 | ```javascript
98 | var $menuItems = document.querySelectorAll('.a2');
99 | var $mainMenu = document.querySelector('#a4');
100 | ```
101 |
102 | ## API
103 | ### gulp-minify-cssnames([options])
104 |
105 | #### options
106 | Type: `Object`
107 |
108 | ##### options.postfix
109 | Type: `String`
110 | Default: `"--s--"`
111 |
112 | ##### options.prefix
113 | Type: `String`
114 | Default: `""`
115 |
116 | Alternative postfix for css names.
117 | `Important: postfix should be valid for css class and id`
118 |
119 | ### Why need a postfix or prefix?
120 | This plugin match by RegExp in all file/stream content. This will reduce the likelihood of wrong replacement.
121 |
122 | [npm-url]: https://npmjs.org/package/gulp-minify-cssnames
123 | [npm-image]: https://img.shields.io/npm/v/gulp-minify-cssnames.svg
124 |
--------------------------------------------------------------------------------
/test/main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const concatStream = require('concat-stream');
5 | const gulp = require('gulp');
6 | const expect = require('chai').expect;
7 | const streamAssert = require('stream-assert');
8 | const File = require('vinyl');
9 | const minify = require('../');
10 |
11 | describe('gulp-minify-cssnames', () => {
12 | describe('Replace CSS names', () => {
13 | it('should work with buffer', (done) => {
14 | const stream = minify();
15 | const file = new File({
16 | path: 'test/fixtures/style.css',
17 | cwd: 'test/',
18 | base: 'test/fixtures',
19 | contents: fs.readFileSync('test/fixtures/style.css')
20 | });
21 |
22 | stream.on('data', (file) => {
23 | expect(String(file.contents)).to.equal(fs.readFileSync('test/result/style.css', 'utf8'));
24 | done();
25 | });
26 |
27 | stream.write(file);
28 | stream.end();
29 | });
30 |
31 | it('should work with stream', (done) => {
32 | const stream = minify();
33 | const file = new File({
34 | path: 'test/fixtures/style.css',
35 | cwd: 'test/',
36 | base: 'test/fixtures',
37 | contents: fs.createReadStream('test/fixtures/style.css')
38 | });
39 |
40 | stream.on('data', (file) => {
41 | file.contents.pipe(concatStream({encoding: 'string'}, (data) => {
42 | expect(data).to.equal(fs.readFileSync('test/result/style.css', 'utf8'));
43 | done();
44 | }));
45 | });
46 |
47 | stream.write(file);
48 | stream.end();
49 | });
50 |
51 | it('should work with buffer (alternative postfix)', (done) => {
52 | const stream = minify({postfix: '--alt-postfix--'});
53 | const file = new File({
54 | path: 'test/fixtures/style.alt-postfix.css',
55 | cwd: 'test/',
56 | base: 'test/fixtures',
57 | contents: fs.readFileSync('test/fixtures/style.alt-postfix.css')
58 | });
59 |
60 | stream.on('data', (file) => {
61 | expect(String(file.contents)).to.equal(fs.readFileSync('test/result/style.alt-postfix.css', 'utf8'));
62 | done();
63 | });
64 |
65 | stream.write(file);
66 | stream.end();
67 | });
68 |
69 | it('should work with stream (alternative postfix)', (done) => {
70 | const stream = minify({postfix: '--alt-postfix--'});
71 | const file = new File({
72 | path: 'test/fixtures/style.alt-postfix.css',
73 | cwd: 'test/',
74 | base: 'test/fixtures',
75 | contents: fs.createReadStream('test/fixtures/style.alt-postfix.css')
76 | });
77 |
78 | stream.on('data', (file) => {
79 | file.contents.pipe(concatStream({encoding: 'string'}, (data) => {
80 | expect(data).to.equal(fs.readFileSync('test/result/style.alt-postfix.css', 'utf8'));
81 | done();
82 | }));
83 | });
84 |
85 | stream.write(file);
86 | stream.end();
87 | });
88 |
89 | it('should work with group files in real Gulp', (done) => {
90 | const files = ['test/fixtures/group/app.js', 'test/fixtures/group/style.css', 'test/fixtures/group/index.html'];
91 | let count = files.length;
92 | let stream = gulp.src(files)
93 | .pipe(minify())
94 | .pipe(streamAssert.length(count));
95 |
96 | files.forEach((item, index) => {
97 | stream = stream.pipe(streamAssert.nth(index, (d) => {
98 | expect(String(d.contents)).to.equal(fs.readFileSync(item.replace('fixtures', 'result'), 'utf8'));
99 | if (--count === 0) {
100 | done();
101 | }
102 | }));
103 | });
104 | });
105 |
106 | it('should work with group files in real Gulp (stream)', (done) => {
107 | const files = ['test/fixtures/group/app.js', 'test/fixtures/group/style.css', 'test/fixtures/group/index.html'];
108 | let count = files.length;
109 | let stream = gulp.src(files, {buffer: false})
110 | .pipe(minify())
111 | .pipe(streamAssert.length(count));
112 |
113 | files.forEach((item, index) => {
114 | stream = stream.pipe(streamAssert.nth(index, (d) => {
115 | d.contents.pipe(concatStream({encoding: 'string'}, (data) => {
116 | expect(data).to.equal(fs.readFileSync(item.replace('fixtures', 'result'), 'utf8'));
117 | if (--count === 0) {
118 | done();
119 | }
120 | }));
121 | }));
122 | });
123 | });
124 |
125 | it('should work with prefix', (done) => {
126 | const stream = minify({postfix: '', prefix: 'prefix---'});
127 | const file = new File({
128 | path: 'test/fixtures/style_prefix.css',
129 | cwd: 'test/',
130 | base: 'test/fixtures',
131 | contents: fs.readFileSync('test/fixtures/style_prefix.css')
132 | });
133 |
134 | stream.on('data', (file) => {
135 | expect(String(file.contents)).to.equal(fs.readFileSync('test/result/style.css', 'utf8'));
136 | done();
137 | });
138 |
139 | stream.write(file);
140 | stream.end();
141 | });
142 |
143 | it('should work with prefix + postfix', (done) => {
144 | const stream = minify({prefix: 'prefix---'});
145 | const file = new File({
146 | path: 'test/fixtures/style_prefix_postfix.css',
147 | cwd: 'test/',
148 | base: 'test/fixtures',
149 | contents: fs.readFileSync('test/fixtures/style_prefix_postfix.css')
150 | });
151 |
152 | stream.on('data', (file) => {
153 | expect(String(file.contents)).to.equal(fs.readFileSync('test/result/style.css', 'utf8'));
154 | done();
155 | });
156 |
157 | stream.write(file);
158 | stream.end();
159 | });
160 | });
161 |
162 | });
163 |
--------------------------------------------------------------------------------