├── 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 | 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 | --------------------------------------------------------------------------------