├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── index.js
├── lib
├── processors
│ ├── css.js
│ ├── html.js
│ ├── js-strings.js
│ └── remove-unused.js
└── utils
│ ├── expressions.js
│ ├── generate-shortname.js
│ ├── library.js
│ └── processor-utils.js
├── package.json
└── test
├── example
├── gulpfile.js
├── index.html
├── script.js
└── style.css
├── expressions
├── expressions.class-selector.test.js
├── expressions.element-attribute.test.js
├── expressions.id-selector.test.js
├── expressions.js-string.test.js
└── expressions.selector-name.test.js
├── generate-shortname.test.js
├── library.test.js
├── processor-utils.test.js
└── processors
├── css.test.js
└── html.test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules
3 | coverage
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | test
2 | .travis.yml
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 0.10
4 | after_script:
5 | - npm run test-report
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Caleb Brewer
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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # This project is looking for a maintainer
2 | I am no longer maintaining this repository and would be happy to transfer it to someone who still actively uses it. Respond to the [maintainer issue](https://github.com/cazzer/gulp-selectors/issues/26) if you are interested in taking ownership. In case you are interested, I use [css-modules](https://github.com/css-modules/css-modules) now instead.
3 |
4 | # gulp-selectors
5 | [![Build Status][travis-image]][travis-url] [![Code Climate][cc-image]][cc-url] [![Test Coverage][coverage-image]][coverage-url] [![NPM Version][npm-image]][npm-url]
6 |
7 | > Minify those pesky selector names down to nothing with this fancy gulp plugin. Minified selectors will be applied consistently across all files piped into it.
8 |
9 | Input | Output
10 | ----------------------------------------|----------
11 | `.class-name { ... }` |`.a { ... }`
12 | `.another-class { ... }` |`.b { ... }`
13 | `#an-id { ... }` |`#a { ... }`
14 | `
...
` |`
...
`
15 |
16 | *You're like: `.some-super-descriptive-selector-name {...}`, and it's like: `.a {...}`*
17 |
18 | ## Usage
19 |
20 | First and foremost:
21 | `npm install gulp-selectors`
22 |
23 | ```js
24 | var gulp = require('gulp');
25 | var gs = require('gulp-selectors');
26 |
27 | gulp.src(['src/**/*.css', 'src/**/*.html'])
28 | .pipe(gs.run())
29 | .pipe(gulp.dest('dist'));
30 | ```
31 |
32 | You can also pass some options into run:
33 |
34 | ` gs.run(processors, ignores)`
35 |
36 | CSS and HTML files are processed well by default, just pass in your glob of files and all classes and IDs will be reduced to a minified form. Of course you can use it for some more specific functions if you like. See the included [sample gulpfile](https://github.com/calebthebrewer/gulp-selectors/blob/master/test/example/gulpfile.js) for a full example of how to effectively use gulp-selectors in your gulp workflow.
37 |
38 | ### Defaults
39 |
40 | All arguments are optional. If omitted, processors will default to `css` and `html` and ignores
41 | will be empty:
42 |
43 | ```js
44 | gs.run({
45 | 'css': ['css'],
46 | 'html': ['html']
47 | }, {
48 | });
49 | ```
50 |
51 | ### Advanced Usage
52 |
53 | ```js
54 | var processors = {
55 | 'css': ['scss', 'css'], // run the css processor on .scss and .css files
56 | 'html': ['haml'], // run the html processor on .haml files
57 | 'js-strings': ['js'] // run the js-strings plugin on js files
58 | },
59 | ignores = {
60 | classes: ['hidden', 'active'] // ignore these class selectors,
61 | ids: '*' // ignore all IDs
62 | };
63 |
64 | gs.run(processors, ignores);
65 | ```
66 |
67 | Two processors are built in for your convenience: `css` and `html` are stable but `js-strings` and `remove-unused` are beta and may be moved to their own repositories.
68 |
69 | - css: matches .selectors and #selectors
70 | - html: matches id="selector"s, class="selector"s, and for="selector"s
71 | - js: matches exact strings by looping through the library, which is dangerous if you use common words as selectors
72 | - remove-unused: should be run last, and only on stylesheets - it removes all declarations present in the library which haven't been used
73 |
74 | If a processor is listed which isn't built in, gulp-selectors will attempt to `require` it.
75 |
76 | ## How gulp-selectors works
77 |
78 | Calling `gs.run()` builds a library which persists for all processors used in the call. Processors are run on all associated files and all selectors, besides those that have been ignored, will be minified.
79 |
80 | ### Processors
81 |
82 | ```js
83 | {
84 | 'css': ['css', 'scss'],
85 | 'html': ['html', 'tpl.js'],
86 | 'js-strings': ['js', '!tpl.js'],
87 | 'your-custom-processor': ['.ext']
88 | }
89 | ```
90 |
91 | `css` and `html` are built in. Additional processors referenced will be injected where needed so it is important to ensure all are installed. Processors are used like this:
92 |
93 | ```js
94 | processor(file, classLibrary, idLibrary)
95 | ```
96 |
97 | `File` is the string containing the file contents. Each of the two libraries exposes the following API:
98 |
99 | - set(selectorName): returns a minified selector name
100 | - has(selectorName): tests if the name exists
101 | - get(selectorName, [dontCount]): ...
102 |
103 | ```js
104 | libraries
105 | ```
106 |
107 | ### Ignores
108 |
109 | ```js
110 | {
111 | ids: ['content', 'target'],
112 | classes: ['hidden', 'active']
113 | }
114 | ```
115 |
116 |
117 | [travis-url]: https://travis-ci.org/calebthebrewer/gulp-selectors
118 | [travis-image]: https://travis-ci.org/calebthebrewer/gulp-selectors.svg?branch=master
119 | [cc-image]: https://codeclimate.com/github/calebthebrewer/gulp-selectors/badges/gpa.svg
120 | [cc-url]: https://codeclimate.com/github/calebthebrewer/gulp-selectors
121 | [coverage-image]: https://codeclimate.com/github/calebthebrewer/gulp-selectors/badges/coverage.svg
122 | [coverage-url]: https://codeclimate.com/github/calebthebrewer/gulp-selectors
123 | [npm-image]: https://badge.fury.io/js/gulp-selectors.svg
124 | [npm-url]: http://badge.fury.io/js/gulp-selectors
125 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var es = require('event-stream'),
3 | _ = require('lodash'),
4 | utils = require('gulp-util'),
5 | processorUtils = require('./lib/utils/processor-utils'),
6 | Library = require('./lib/utils/library');
7 |
8 | var processor;
9 |
10 | module.exports = {
11 | run: run,
12 | minify: run,
13 | info: info,
14 | create: create
15 | };
16 |
17 | class Processor {
18 | constructor(processors, ignores) {
19 | this.ignores = _.extend({classes: [], ids: []}, ignores);
20 |
21 | //ensure processor names are set as expected
22 | this.processors = processorUtils.extendDefaults(processors);
23 |
24 | //build new libraries to use
25 | this.classLibrary = new Library(this.ignores.classes || []);
26 | this.idLibrary = new Library(this.ignores.ids || []);
27 | }
28 |
29 | run() {
30 | /**
31 | * Main task for mini selectors uglify classes. Processes files based on type.
32 | *
33 | * @param file Stream from es.map
34 | * @param callback for es.map
35 | */
36 | var miniSelectors = (file, callback) => {
37 | var extensions = file.path.split('.'),
38 | extension = extensions[extensions.length - 1],
39 | reducedFile = String(file.contents);
40 |
41 | processorUtils.getForExtension(this.processors, extension).forEach((processor) => {
42 | reducedFile = processor(reducedFile, this.classLibrary, this.idLibrary);
43 | });
44 |
45 | file.contents = new Buffer(reducedFile);
46 | callback(null, file);
47 | }
48 |
49 | return es.map(miniSelectors);
50 | }
51 |
52 | info() {
53 | return es.map((file, callback) => {
54 | utils.log(file.history[0]);
55 | utils.log('Class library:', this.classLibrary.stats());
56 | utils.log('ID library:', this.idLibrary.stats());
57 | callback(null, file);
58 | });
59 | }
60 | }
61 |
62 | function run(processors, ignores) {
63 | processor = new Processor(processors, ignores);
64 | return processor.run();
65 | }
66 |
67 | function info() {
68 | return processor.info();
69 | }
70 |
71 | function create(processors, ignores) {
72 | return new Processor(processors, ignores);
73 | }
74 |
--------------------------------------------------------------------------------
/lib/processors/css.js:
--------------------------------------------------------------------------------
1 | var expressions = require('../utils/expressions');
2 |
3 | /**
4 | * Replaces all class names with shortnames. Also builds a library of shortnames which can be
5 | * used to reduce other file types.
6 | *
7 | * @param {string} File
8 | * @returns {string} Minified file
9 | */
10 | module.exports = function(file, classLibrary, idLibrary) {
11 | var selectorNameMatch = expressions.selectorName;
12 |
13 | file = file.replace(expressions.classSelector, function(selector) {
14 | //exclude property values (matches ending in ')')
15 | if (selector[selector.length - 1] === ')') {
16 | return selector;
17 | }
18 | return selector.replace(selectorNameMatch, function(selectorName) {
19 | return classLibrary.get(selectorName, true);
20 | });
21 | });
22 |
23 | file = file.replace(expressions.idSelector, function(selector) {
24 | //exclude property values (matches ending in '; or }')
25 | if (selector[selector.length - 1] === ';' || selector[selector.length - 1] === '}') {
26 | return selector;
27 | }
28 | return selector.replace(selectorNameMatch, function(selectorName) {
29 | return idLibrary.get(selectorName, true);
30 | });
31 | });
32 |
33 | return file;
34 | };
--------------------------------------------------------------------------------
/lib/processors/html.js:
--------------------------------------------------------------------------------
1 | var expressions = require('../utils/expressions');
2 |
3 | /**
4 | * Replaces all class and id attributes found in the library. Only tested on *.html files with
5 | * classes declared in *class= attributes.
6 | *
7 | * @param {string} File
8 | * @returns {string} Minified file
9 | */
10 | module.exports = function (file, classLibrary, idLibrary) {
11 | return file.replace(expressions.elementAttribute, function(attributes) {
12 | var attribute = attributes.split('=');
13 | return attribute[0] + '=' + attribute[1]
14 | .replace(expressions.selectorName, function(selectorName) {
15 | switch (attribute[0]) {
16 | case 'id':
17 | case 'for':
18 | return idLibrary.get(selectorName);
19 | default: //class
20 | return classLibrary.get(selectorName);
21 | }
22 | });
23 | });
24 | };
--------------------------------------------------------------------------------
/lib/processors/js-strings.js:
--------------------------------------------------------------------------------
1 | var expressions = require('../utils/expressions');
2 |
3 | /**
4 | * Dumb search for all strings in all JS files. This will only work on libraries which are fully built.
5 | *
6 | * @param file String
7 | * @returns {reducedFile String}
8 | */
9 | module.exports = function(file, classLibrary, idLibrary) {
10 |
11 | classLibrary.getFullNames().forEach(function(selector) {
12 | file = file.replace(expressions.jsString(selector), function() {
13 | return "'" + classLibrary.get(selector) + "'";
14 | });
15 | });
16 |
17 | idLibrary.getFullNames().forEach(function(selector) {
18 | file = file.replace(expressions.jsString(selector), function() {
19 | return "'" + idLibrary.get(selector) + "'";
20 | });
21 | });
22 |
23 | return file;
24 | };
--------------------------------------------------------------------------------
/lib/processors/remove-unused.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Removes all CSS declarations in the library which are unused.
3 | * This doesn't get anything with a secondary selector. Help my regex!
4 | *
5 | * @param file
6 | * @param classLibrary
7 | * @returns {*}
8 | */
9 | module.exports = function(file, classLibrary) {
10 |
11 | classLibrary.getUnused().forEach(function(selector) {
12 | var expression = new RegExp('[,?\\s*]\\.' + selector + '\\s*{[^}]*}', 'g');
13 | file = file.replace(expression, function(match) {
14 | if (match[0] !== ',') {
15 | //remove declaration
16 | return '';
17 | } else {
18 | //only remove selector
19 | var subExpression = new RegExp('[,?\\s*]\\.' + selector);
20 | return match.replace(subExpression, function() {
21 | return '';
22 | });
23 | }
24 | });
25 | });
26 |
27 | return file;
28 | };
--------------------------------------------------------------------------------
/lib/utils/expressions.js:
--------------------------------------------------------------------------------
1 | var selectorName = '(-?[_a-zA-Z]+[_\\w-]*)';
2 | var selectorSuffix = '((?=[\\s:#\\.\\{,\\>\\~+\\[\\]]))(?=(?:.|\n|\r)*{)';
3 |
4 | /**
5 | * Some awesome expressions that I'll explain in a bit.
6 | * Test these dope boats live on http://scriptular.com/ to make your life easier.
7 | */
8 | module.exports = {
9 | /**
10 | * Selector name extracts valid selector names from selectors. Basically we use the selector
11 | * expression to find the selectors, then use this to extract the actual name which need to be
12 | * shortened.
13 | *
14 | * Should match:
15 | * - selector
16 | * - -selector
17 | * - _selector
18 | * - selector0
19 | *
20 | * Shouldn't match:
21 | * - 3selector
22 | * - :selector
23 | */
24 | selectorName: new RegExp(selectorName, 'g'),
25 | /**
26 | * Selectors should match any classes and ids defined in a stylesheet.
27 | *
28 | * NOTE: This will also match hex values but will capture the close ; or } so that it can be
29 | * ignored later.
30 | *
31 | * Should match:
32 | * - #selector
33 | * - .selector
34 | * - #e6e6e6;
35 | *
36 | * Shouldn't match:
37 | * - body
38 | * - #666
39 | * - :pseudo
40 | * - url('foobar.classname')
41 | * - other stupid stuff...I dunno check the tests
42 | */
43 | classSelector: new RegExp('(\\.|\\[class[\\^\\$\\|\\*]?=)' + selectorName + selectorSuffix, 'gi'),
44 | idSelector: new RegExp('(#|\\[id[\\^\\$\\|\\*]?=)' + selectorName + selectorSuffix, 'gi'),
45 | /**
46 | * Matches HTML class, id, and for attributes. I think those are the only ones we care about...
47 | *
48 | * NOTE: This expression should also match attributes with spaces between the assignment, e.g.
49 | *
50 | * Should match:
51 | * - class="selector"
52 | * - class="selector selector"
53 | * - id="selector"
54 | * - for="selector"
55 | *
56 | * Shouldn't match:
57 | * - name="selector"
58 | * - href="selector"
59 | */
60 | elementAttribute: /(class|id|for|aria-labelledby)\s*=\s*["'](-?[_a-zA-Z]+[_\w-\s]*)["']/g,
61 | /**
62 | * Matches ID Values
63 | *
64 | * All values can be single values or lists and lists can be either comma separated (each val is surrounded by single or double quotes OR a space separated list with not internal quotes (only quotes at beginning and end).
65 | * .getElementById
66 | * .id
67 | * $ Only matches values with "#"
68 | * jQuery Only matches values with "#"
69 | * .attr This has a known bug. There is no way to differentiate the leading "class" from "id" in attr without look-behind, which javascript does not support. Therefore, all matches using this regex need to be filtered to remove results which are lists that have "class" as the first value.
70 | */
71 | idList: /(?:\$|jQuery|(?:\.(?:getElementById|id|jQuery|attr)))\s*[\(=]{1}\s*(["']{1}#?-?[_a-zA-Z]+[_\w-]*(?:(?:(?:["']{1}\s*,\s*["']{1})|\s+){1}#?-?[_a-zA-Z]+[_\w-]*)*["']{1})/,
72 | /**
73 | * Matches Class Values
74 | *
75 | * All values can be single values or lists and lists can be either comma separated (each val is surrounded by single or double quotes OR a space separated list with not internal quotes (only quotes at beginning and end).
76 | * .getElementsByClassName
77 | * .classList.add
78 | * .classList.remove
79 | * .className
80 | * $ Only matches values with "."
81 | * jQuery Only matches values with "."
82 | * .addClass
83 | * .toggleClass
84 | * .removeClass
85 | * .attr This has a known bug. There is no way to differentiate the leading "class" from "id" in attr without look-behind, which javascript does not support. Therefore, all matches using this regex need to be filtered to remove results which are lists that have "id" as the first value.
86 | * .hasClass
87 | */
88 | classList: /(?:\$|jQuery|(?:\.(?:getElementsByClassName|classList\.add|classList\.remove|className|jQuery|addClass|toggleClass|removeClass|attr|hasClass)))\s*[\(=]{1}\s*(["']{1}\.?-?[_a-zA-Z]+[_\w-]*(?:(?:(?:["']{1}\s*,\s*["']{1})|\s+){1}\.?-?[_a-zA-Z]+[_\w-]*)*["']{1})/,
89 | /**
90 | * Builds a regular expression which will match a quoted string.
91 | *
92 | * @param name String of selector name
93 | * @returns {RegExp}
94 | */
95 | jsString: function(name) {
96 | return new RegExp('[\'|"]' + name + '[\'|"]', 'g');
97 | }
98 | };
99 |
--------------------------------------------------------------------------------
/lib/utils/generate-shortname.js:
--------------------------------------------------------------------------------
1 | module.exports = generateShortname;
2 |
3 | /**
4 | * Helper function for generating shortnames based on an alphabetic library.
5 | *
6 | * @param seed Integer
7 | * @returns {string Shortname}
8 | */
9 | function generateShortname(seed) {
10 | if (seed !== parseInt(seed, 10)) {
11 | throw new Error('Seed must be a number');
12 | }
13 |
14 | if (seed < 0) {
15 | throw new Error('Seed must be at least 0');
16 | }
17 |
18 | var library = 'abcdefghijklmnopqrstuvwxyz',
19 | libraryLength = library.length,
20 | prefix = '';
21 | //break the seed down if it is larger than the library
22 | if (seed >= libraryLength) {
23 | prefix = generateShortname(Math.floor(seed / libraryLength) - 1);
24 | }
25 | //return the prefixed shortname
26 | return prefix + library[seed % libraryLength];
27 | }
--------------------------------------------------------------------------------
/lib/utils/library.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash'),
2 | multimatch = require('multimatch'),
3 | generateShortname = require('./generate-shortname');
4 |
5 | /**
6 | * Provides libraries.
7 | */
8 | module.exports = Library;
9 |
10 | /**
11 | * This handy function returns an empty library. This should be a module on its own so that the
12 | * logic of retrieving a library does not get mixed up with the library logic itself.
13 | *
14 | * @returns {object} Library
15 | * @constructor
16 | */
17 | function Library(ignores) {
18 | var _library = {},
19 | _ignores = ignores || [],
20 | size = 0;
21 |
22 | /**
23 | * Tests if a value exists in the library.
24 | *
25 | * @params {string} name
26 | * @returns {boolean} Bool for if it exists
27 | */
28 | this.has = function(name) {
29 | return _library[name] !== undefined;
30 | };
31 |
32 | /**
33 | * Ensures the name is set and returns it. If generates an ignored name,
34 | * will increase size and try again
35 | *
36 | * @param name String name to get shortname for from the library
37 | * @param dontCount Bool to not to count this as a use in the code
38 | * @returns {string} Shortname of the minified name
39 | */
40 | this.get = function(name, dontCount) {
41 | //catch all for ignoring IDs
42 | if (ignores === true) return name;
43 |
44 | var shortname;
45 |
46 | if (_library[name]) {
47 | shortname = _library[name].shortname;
48 | if (!dontCount) {
49 | _library[name].hits++;
50 | }
51 | } else if (!multimatch(name, _ignores).length) {
52 | do {
53 | shortname = generateShortname(size);
54 | size++;
55 | } while (~_ignores.indexOf(shortname));
56 |
57 | _library[name] = {
58 | shortname: shortname,
59 | hits: dontCount ? 0 : 1
60 | };
61 | } else {
62 | shortname = name;
63 | }
64 |
65 | return shortname;
66 | };
67 |
68 | /**
69 | * Returns all of the shortnames in the library.
70 | * Does not count towards usage.
71 | *
72 | * @returns {array} Of all shortnames.
73 | */
74 | this.getAll = function() {
75 | return _.pluck(_library, 'shortname');
76 | };
77 |
78 | /**
79 | * Retrieves shortnames which are not used in the code processed.
80 | *
81 | * @returns {array} Of unused names
82 | */
83 | this.getUnused = function() {
84 | return _.pluck(
85 | _.filter(
86 | _library,
87 | function(entry) {
88 | return entry.hits === 0;
89 | }), 'shortname');
90 | };
91 |
92 | /**
93 | * Returns the size of the library.
94 | *
95 | * @returns {number} Number of entries in the library
96 | */
97 | this.size = function() {
98 | return size;
99 | };
100 |
101 | /**
102 | * Returns the full selector names which have been entered so far.
103 | *
104 | * @returns {Array} All full selector names
105 | */
106 | this.getFullNames = function() {
107 | return Object.keys(_library);
108 | };
109 |
110 | this.stats = function() {
111 | return {
112 | size: this.size(),
113 | unused: this.getUnused().length
114 | };
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/lib/utils/processor-utils.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash');
2 |
3 | module.exports = {
4 | extendDefaults: extendDefaults,
5 | getForExtension: getProcessorsForExtension
6 | };
7 |
8 | function extendDefaults(processors) {
9 |
10 | processors = _.extend({
11 | css: ['css'],
12 | html: ['html']
13 | }, processors);
14 |
15 | return processors;
16 | }
17 |
18 | function getProcessorsForExtension(processors, extension) {
19 | var selectedProcessors = [];
20 |
21 | for (var processor in processors) {
22 | if (typeof processors[processor] === 'object' &&
23 | processors[processor].indexOf(extension) > -1) {
24 | switch (processor) {
25 | //these guys are special since they're built in
26 | case 'css':
27 | case 'html':
28 | case 'js-strings':
29 | case 'remove-unused':
30 | selectedProcessors.push(require('../processors/' + processor));
31 | break;
32 | default:
33 | selectedProcessors.push(require(processor));
34 | }
35 | }
36 | }
37 |
38 | return selectedProcessors;
39 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gulp-selectors",
3 | "description": "Minify CSS selectors.",
4 | "version": "0.1.10",
5 | "author": {
6 | "name": "Caleb Brewer",
7 | "email": "calebthebrewer@gmail.com"
8 | },
9 | "dependencies": {
10 | "event-stream": "~3.1.7",
11 | "gulp-util": "~3.0.1",
12 | "lodash": "~2.4.1",
13 | "multimatch": "^1.0.1"
14 | },
15 | "homepage": "https://github.com/calebthebrewer/gulp-selectors",
16 | "bugs": "https://github.com/calebthebrewer/gulp-selectors/issues",
17 | "keywords": [
18 | "gulp",
19 | "gulpplugin",
20 | "munch",
21 | "minify",
22 | "css",
23 | "selectors",
24 | "styles",
25 | "compress"
26 | ],
27 | "license": "MIT",
28 | "main": "index.js",
29 | "repository": {
30 | "type": "git",
31 | "url": "https://github.com/calebthebrewer/gulp-selectors.git"
32 | },
33 | "readmeFilename": "README.md",
34 | "devDependencies": {
35 | "vows": "~0.7.0",
36 | "jscoverage": "~0.5.6",
37 | "istanbul": "^0.3.2",
38 | "codeclimate-test-reporter": "0.0.4",
39 | "gulp": "~3.8.11"
40 | },
41 | "scripts": {
42 | "test": "vows --spec",
43 | "coverage": "istanbul cover ./node_modules/vows/bin/vows --spec",
44 | "codeclimate": "cat ./coverage/lcov.info | codeclimate",
45 | "test-report": "npm run coverage && npm run codeclimate"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/test/example/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var selectors = require('../../index');
3 |
4 | gulp.task('default', function() {
5 | return gulp.src(['index.html', 'style.css', 'script.js'])
6 | .pipe(selectors.run({
7 | 'js-strings': ['js']
8 | }))
9 | .pipe(selectors.info())
10 | .pipe(gulp.dest('./dist'));
11 | });
--------------------------------------------------------------------------------
/test/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Gulp Selectors Demo
4 |
5 |
6 |
7 |
If things aren't as they say, I'm broken!
8 |
9 |
This should be blue.
10 |
This should be big.
11 |
This should be big and blue.
12 |
And I should wiggle a bit.
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/test/example/script.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | wiggle(document.getElementById('wiggle'), 0);
3 |
4 | function wiggle(element, time) {
5 | element.style.marginLeft = Math.sin(time) * 10;
6 | time += .1;
7 | setTimeout(wiggle.bind(this, element, time), 10);
8 | }
9 | })();
--------------------------------------------------------------------------------
/test/example/style.css:
--------------------------------------------------------------------------------
1 | .blue {
2 | color: blue;
3 | }
4 |
5 | .big {
6 | font-size: 2em;
7 | }
--------------------------------------------------------------------------------
/test/expressions/expressions.class-selector.test.js:
--------------------------------------------------------------------------------
1 | var vows = require('vows'),
2 | assert = require('assert'),
3 | expressions = require('../../lib/utils/expressions');
4 |
5 | vows.describe('Expressions: class selectors').addBatch({
6 | 'An class selector': {
7 | topic: '.selector {'.match(expressions.classSelector),
8 | 'should return one match': function(topic) {
9 | assert.equal(topic.length, 1);
10 | }
11 | },
12 | 'An uncommon class selector': {
13 | topic: '[class=selector] {'.match(expressions.classSelector),
14 | 'should return a match': function(topic) {
15 | assert.equal(topic.length, 1);
16 | }
17 | },
18 | 'An uncommon extended class selector': {
19 | topic: '[class*=selector] {'.match(expressions.classSelector),
20 | 'should return a match': function(topic) {
21 | assert.equal(topic.length, 1);
22 | }
23 | },
24 | 'Nested class selectors': {
25 | topic: '.selector .child {'.match(expressions.classSelector),
26 | 'should return two matches': function(topic) {
27 | assert.equal(topic.length, 2);
28 | }
29 | },
30 | 'An url selector': {
31 | topic: 'url(\'foobar.selector\')'.match(expressions.classSelector),
32 | 'should not return a match': function(topic) {
33 | assert.equal(topic, null);
34 | }
35 | },
36 | 'A class with a pseudo selector': {
37 | topic: '.selector:hover {'.match(expressions.classSelector),
38 | 'should return one match': function(topic) {
39 | assert.equal(topic.length, 1);
40 | }
41 | },
42 | 'A pseudo selector': {
43 | topic: ':hover {'.match(expressions.classSelector),
44 | 'should not return a match': function(topic) {
45 | assert.equal(topic, null);
46 | }
47 | },
48 | 'A tag selector': {
49 | topic: 'body {'.match(expressions.classSelector),
50 | 'should return a match': function(topic) {
51 | assert.equal(topic, null);
52 | }
53 | }
54 | }).export(module);
55 |
--------------------------------------------------------------------------------
/test/expressions/expressions.element-attribute.test.js:
--------------------------------------------------------------------------------
1 | var vows = require('vows'),
2 | assert = require('assert'),
3 | expressions = require('../../lib/utils/expressions');
4 |
5 | vows.describe('Expressions: element attribute').addBatch({
6 | 'An id attribute': {
7 | topic: 'id="selector"'.match(expressions.elementAttribute),
8 | 'should return a match': function(topic) {
9 | assert.equal(topic.length, 1);
10 | }
11 | },
12 | 'A class attribute': {
13 | topic: 'class="selector"'.match(expressions.elementAttribute),
14 | 'should return a match': function(topic) {
15 | assert.equal(topic.length, 1);
16 | }
17 | },
18 | 'A for attribute': {
19 | topic: 'for="selector"'.match(expressions.elementAttribute),
20 | 'should return a match': function(topic) {
21 | assert.equal(topic.length, 1);
22 | }
23 | },
24 | 'An href attribute': {
25 | topic: 'href="selector"'.match(expressions.elementAttribute),
26 | 'should not return a match': function(topic) {
27 | assert.equal(topic, null);
28 | }
29 | },
30 | 'A class attribute with different quotes': {
31 | topic: "class='selector'".match(expressions.elementAttribute),
32 | 'should return a match': function(topic) {
33 | assert.equal(topic.length, 1);
34 | }
35 | },
36 | 'A class attribute with awkward spacing': {
37 | topic: 'class = "selector"'.match(expressions.elementAttribute),
38 | 'should return a match': function(topic) {
39 | assert.equal(topic.length, 1);
40 | }
41 | },
42 | 'An aria-labelledby attribute': {
43 | topic: 'aria-labelledby="selector"'.match(expressions.elementAttribute),
44 | 'should return a match': function(topic) {
45 | assert.equal(topic.length, 1);
46 | }
47 | }
48 | }).export(module);
--------------------------------------------------------------------------------
/test/expressions/expressions.id-selector.test.js:
--------------------------------------------------------------------------------
1 | var vows = require('vows'),
2 | assert = require('assert'),
3 | expressions = require('../../lib/utils/expressions');
4 |
5 | vows.describe('Expressions: selector').addBatch({
6 | 'An id selector': {
7 | topic: '#selector {'.match(expressions.idSelector),
8 | 'should return a match': function(topic) {
9 | assert.equal(topic.length, 1);
10 | }
11 | },
12 | 'An uncommon id selector': {
13 | topic: '[id=selector] {'.match(expressions.idSelector),
14 | 'should return a match': function(topic) {
15 | assert.equal(topic.length, 1);
16 | }
17 | },
18 | 'An uncommon extended id selector': {
19 | topic: '[id*=selector] {'.match(expressions.idSelector),
20 | 'should return a match': function(topic) {
21 | assert.equal(topic.length, 1);
22 | }
23 | },
24 | 'A compressed id selector': {
25 | topic: '#selector{'.match(expressions.idSelector),
26 | 'should return a match': function(topic) {
27 | assert.equal(topic.length, 1);
28 | }
29 | },
30 | 'A hash value': {
31 | topic: '#e6e6e6;'.match(expressions.idSelector),
32 | 'should not return a match': function(topic) {
33 | assert.equal(topic, null);
34 | }
35 | },
36 | 'An id with a pseudo selector': {
37 | topic: '#selector:hover {'.match(expressions.idSelector),
38 | 'should return one match': function(topic) {
39 | assert.equal(topic.length, 1);
40 | }
41 | },
42 | 'A string beginning with a number': {
43 | topic: '#666 {'.match(expressions.idSelector),
44 | 'should not return a match': function(topic) {
45 | assert.equal(topic, null);
46 | }
47 | },
48 | 'A string beginning with a weird character': {
49 | topic: '#:selector {'.match(expressions.idSelector),
50 | 'should not return a match': function(topic) {
51 | assert.equal(topic, null);
52 | }
53 | }
54 | }).export(module);
55 |
--------------------------------------------------------------------------------
/test/expressions/expressions.js-string.test.js:
--------------------------------------------------------------------------------
1 | var vows = require('vows'),
2 | assert = require('assert'),
3 | expressions = require('../../lib/utils/expressions');
4 |
5 | vows.describe('Expressions: js string selectors').addBatch({
6 | 'An custom selector with a present name': {
7 | topic: '"selector"'.match(expressions.jsString('selector')),
8 | 'should return one match': function(topic) {
9 | assert.equal(topic.length, 1);
10 | }
11 | },
12 | 'An custom selector without a present name': {
13 | topic: '"selector"'.match(expressions.jsString('undefined')),
14 | 'should not return a match': function(topic) {
15 | assert.equal(topic, null);
16 | }
17 | }
18 | }).export(module);
--------------------------------------------------------------------------------
/test/expressions/expressions.selector-name.test.js:
--------------------------------------------------------------------------------
1 | var vows = require('vows'),
2 | assert = require('assert'),
3 | expressions = require('../../lib/utils/expressions');
4 |
5 | vows.describe('Expressions: selector name').addBatch({
6 | 'A class definition': {
7 | topic: '.selector'.match(expressions.selectorName),
8 | 'should return just the class': function(topic) {
9 | assert.equal(topic[0], 'selector');
10 | }
11 | },
12 | 'An id definition': {
13 | topic: '#selector'.match(expressions.selectorName),
14 | 'should return just the id': function(topic) {
15 | assert.equal(topic[0], 'selector');
16 | }
17 | },
18 | 'A quoted string': {
19 | topic: '"selector"'.match(expressions.selectorName),
20 | 'should return just the class': function(topic) {
21 | assert.equal(topic[0], 'selector');
22 | }
23 | },
24 | 'A differently quoted attribute': {
25 | topic: "'selector'".match(expressions.selectorName),
26 | 'should return just the class': function(topic) {
27 | assert.equal(topic[0], 'selector');
28 | }
29 | },
30 | 'Multiple classes in a string': {
31 | topic: '"selector1, selector2'.match(expressions.selectorName),
32 | 'should all be returned': function(topic) {
33 | assert.equal(topic.length, 2);
34 | }
35 | }
36 | }).export(module);
--------------------------------------------------------------------------------
/test/generate-shortname.test.js:
--------------------------------------------------------------------------------
1 | var vows = require('vows'),
2 | assert = require('assert'),
3 | generateShortname = require('../lib/utils/generate-shortname');
4 |
5 | vows.describe('Generating Shortnames').addBatch({
6 | //standard cases
7 | 'A seed of 0': {
8 | topic: generateShortname(0),
9 | 'should return the shortname "a"': function(topic) {
10 | assert.equal(topic, 'a');
11 | }
12 | },
13 | 'A seed of 1': {
14 | topic: generateShortname(1),
15 | 'should return the shortname "ab"': function(topic) {
16 | assert.equal(topic, 'b');
17 | }
18 | },
19 | 'A seed of 26': {
20 | topic: generateShortname(26),
21 | 'should return the shortname "aa"': function(topic) {
22 | assert.equal(topic, 'aa');
23 | }
24 | },
25 | 'A seed of 27': {
26 | topic: generateShortname(27),
27 | 'should return the shortname "ab"': function(topic) {
28 | assert.equal(topic, 'ab');
29 | }
30 | },
31 | //edge cases
32 | 'A seed of 1234': {
33 | topic: generateShortname(1234),
34 | 'should return the shortname "aum"': function(topic) {
35 | assert.equal(topic, 'aum');
36 | }
37 | }
38 | }).export(module);
39 |
--------------------------------------------------------------------------------
/test/library.test.js:
--------------------------------------------------------------------------------
1 | var vows = require('vows'),
2 | assert = require('assert'),
3 | Library = require('../lib/utils/library');
4 |
5 | var testLibrary = new Library();
6 |
7 | vows.describe('Libraries').addBatch({
8 | 'Calling the module with a new string': {
9 | topic: new Library(),
10 | 'should give us a new library to play with': function(topic) {
11 | //this one is here to make sure the API is intact
12 | assert(topic.has);
13 | assert(topic.get);
14 | assert(topic.getAll);
15 | assert(topic.getUnused);
16 | assert(topic.size);
17 | }
18 | },
19 | 'Calling has with an undefined name': {
20 | topic: testLibrary.has('undefined'),
21 | 'should return false' : function(topic) {
22 | assert.equal(topic, false);
23 | }
24 | },
25 | 'Calling has with a defined name': {
26 | topic: function() {
27 | testLibrary.get('defined');
28 | return testLibrary.has('defined');
29 | },
30 | 'should return false' : function(topic) {
31 | assert.equal(topic, true);
32 | }
33 | },
34 | 'Calling get twice for the same name': {
35 | topic: testLibrary.get('defined'),
36 | 'should return the same shortname' : function(topic) {
37 | assert.equal(topic, testLibrary.get('defined'));
38 | }
39 | },
40 | 'Get all': {
41 | topic: function() {
42 | var library = new Library();
43 | library.get('defined');
44 | library.get('another');
45 | return library;
46 | },
47 | 'should return all shortnames in the library' : function(topic) {
48 | assert.deepEqual(topic.getAll(), [
49 | topic.get('defined'),
50 | topic.get('another')
51 | ]);
52 | }
53 | },
54 | 'Get unused': {
55 | topic: function() {
56 | var library = new Library();
57 | library.get('defined');
58 | library.get('defined');
59 | library.get('unused', true);
60 | return library;
61 | },
62 | 'should return only unused shortnames': function(topic) {
63 | assert.deepEqual(topic.getUnused(), [
64 | topic.get('unused')
65 | ]);
66 | }
67 | },
68 | 'Size of an empty library': {
69 | topic: function() {
70 | return new Library().size();
71 | },
72 | 'should be 0': function(topic) {
73 | assert.equal(topic, 0);
74 | }
75 | },
76 | 'Size of a non empty library': {
77 | topic: function() {
78 | var library = new Library();
79 | library.get('defined');
80 | return library.size();
81 | },
82 | 'should be 1': function(topic) {
83 | assert.equal(topic, 1);
84 | }
85 | },
86 | 'Adding ignored entries': {
87 | topic: function() {
88 | var library = new Library(['ignored']);
89 | return library.get('ignored');
90 | },
91 | 'should return the full name': function(topic) {
92 | assert.equal(topic, 'ignored');
93 | }
94 | },
95 | 'Setting dontCount to true': {
96 | topic: function() {
97 | var library = new Library();
98 | library.get('defined', true);
99 | return library.getUnused().length;
100 | },
101 | 'should leave the selector as unused': function(topic) {
102 | assert.deepEqual(topic, 1)
103 | }
104 | }
105 | }).export(module);
--------------------------------------------------------------------------------
/test/processor-utils.test.js:
--------------------------------------------------------------------------------
1 | var vows = require('vows'),
2 | assert = require('assert'),
3 | utils = require('../lib/utils/processor-utils');
4 |
5 | var testProcessors = {
6 | css: ['html', 'js']
7 | },
8 | mappedProcessors = utils.extendDefaults(testProcessors);
9 |
10 | vows.describe('Processor utilities').addBatch({
11 | 'Extending defaults names with an empty argument': {
12 | topic: utils.extendDefaults(),
13 | 'should return css and html default processors': function(topic) {
14 | assert.deepEqual(topic, {
15 | css: ['css'],
16 | html: ['html']
17 | });
18 | }
19 | },
20 | 'Extending defaults names with redefined css arguments': {
21 | topic: utils.extendDefaults(testProcessors),
22 | 'should return different css values': function(topic) {
23 | assert.deepEqual(topic, {
24 | css: ['html', 'js'],
25 | html: ['html']
26 | });
27 | }
28 | },
29 | 'Extending defaults names with a random argument': {
30 | topic: utils.extendDefaults({'js-strings': ['js']}),
31 | 'should return that random argument': function(topic) {
32 | assert.deepEqual(topic['js-strings'], ['js']);
33 | }
34 | },
35 | 'Getting processors for an extension': {
36 | topic: function() {
37 | return utils.getForExtension(mappedProcessors, 'js');
38 | },
39 | 'should return the module for that extension': function(topic) {
40 | assert.deepEqual(topic, [
41 | require('../lib/processors/css')
42 | ]);
43 | }
44 | },
45 | 'Getting processors for an extension with multiple processors': {
46 | topic: function() {
47 | return utils.getForExtension(mappedProcessors, 'html');
48 | },
49 | 'should return all processors in the correct order': function(topic) {
50 | assert.deepEqual(topic, [
51 | require('../lib/processors/css'),
52 | require('../lib/processors/html')
53 | ]);
54 | }
55 | }
56 | }).export(module);
--------------------------------------------------------------------------------
/test/processors/css.test.js:
--------------------------------------------------------------------------------
1 | var vows = require('vows'),
2 | assert = require('assert'),
3 | css = require('../../lib/processors/css'),
4 | Library = require('../../lib/utils/library');
5 |
6 | vows.describe('CSS Replacer').addBatch({
7 | 'A class name': {
8 | topic: '.hello-selector{property:value;}',
9 | 'should be minified': function(topic) {
10 | var classLibrary = new Library(),
11 | idLibrary = new Library(),
12 | minified = css(topic, classLibrary, idLibrary);
13 |
14 | assert.equal(minified, '.a{property:value;}')
15 | }
16 | },
17 | 'A class name with an ignore': {
18 | topic: '.a{color: red;} .cat{color:blue}',
19 | 'should not redefine other classes with the ignored class name': function(topic) {
20 | var classLibrary = new Library(['a']),
21 | minified = css(topic, classLibrary);
22 |
23 | assert.equal(minified, '.a{color: red;} .b{color:blue}');
24 | }
25 | },
26 | 'A capitalized class name with an ignore': {
27 | topic: '.a{color: red;} .Cat{color:blue}',
28 | 'should not redefine other classes with the ignored class name': function(topic) {
29 | var classLibrary = new Library(['Cat']),
30 | minified = css(topic, classLibrary);
31 |
32 | assert.equal(minified, '.a{color: red;} .Cat{color:blue}');
33 | }
34 | },
35 | 'An id name': {
36 | topic: '#hello-selector{property:value;}',
37 | 'should be minified': function(topic) {
38 | var classLibrary = new Library(),
39 | idLibrary = new Library(),
40 | minified = css(topic, classLibrary, idLibrary);
41 |
42 | assert.equal(minified, '#a{property:value;}')
43 | }
44 | },
45 | 'A hex value': {
46 | topic: '.a{color:#eee;}.b{color:#666}',
47 | 'should not be minified': function(topic) {
48 | var classLibrary = new Library(),
49 | idLibrary = new Library(),
50 | minified = css(topic, classLibrary, idLibrary);
51 |
52 | assert.equal(minified, '.a{color:#eee;}.b{color:#666}')
53 | }
54 | },
55 | 'A file extension in a stylesheet': {
56 | topic: '.selector{background: url(file.extension);}',
57 | 'should not be matched': function(topic) {
58 | var classLibrary = new Library(),
59 | minified = css(topic, classLibrary);
60 |
61 | assert.equal(minified, '.a{background: url(file.extension);}');
62 | }
63 | }
64 | }).export(module);
65 |
--------------------------------------------------------------------------------
/test/processors/html.test.js:
--------------------------------------------------------------------------------
1 | var vows = require('vows'),
2 | assert = require('assert'),
3 | html = require('../../lib/processors/html'),
4 | Library = require('../../lib/utils/library');
5 |
6 | vows.describe('HTML Replacer').addBatch({
7 | 'A class attribute': {
8 | topic: '
',
9 | 'should be minified': function(topic) {
10 | var classLibrary = new Library(),
11 | idLibrary = new Library(),
12 | minified = html(topic, classLibrary, idLibrary);
13 |
14 | assert.equal(minified, '