diff --git a/lib/main.js b/lib/main.js index c8e005c..7e3d9ac 100644 --- a/lib/main.js +++ b/lib/main.js @@ -1,7 +1,7 @@ 'use strict'; -var cheerio = require('cheerio'); var Hashids = require('hashids'); +var posthtml = require('posthtml'); var postcssSafeParser = require('postcss-safe-parser'); var postcssSelectorParser = require('postcss-selector-parser'); @@ -18,11 +18,11 @@ function HTMLUglify(config) { this.whitelist = config.whitelist || []; } -HTMLUglify.prototype.checkForStandardPointer = function(lookups, type, value) { +HTMLUglify.prototype.checkForStandardPointer = function(type, value, lookups) { return lookups[type] && lookups[type][value]; }; -HTMLUglify.prototype.checkForAttributePointer = function(lookups, type, value) { +HTMLUglify.prototype.checkForAttributePointer = function(type, value, lookups) { var typeLookups = lookups[type] || {}; var keys = Object.keys(typeLookups); var pointer; @@ -47,8 +47,8 @@ HTMLUglify.prototype.generatePointer = function(lookups) { }; HTMLUglify.prototype.pointer = function(type, value, lookups) { - return this.checkForStandardPointer(lookups, type, value) || - this.checkForAttributePointer(lookups, type, value) || + return this.checkForStandardPointer(type, value, lookups) || + this.checkForAttributePointer(type, value, lookups) || this.generatePointer(lookups); }; @@ -69,12 +69,12 @@ HTMLUglify.prototype.createLookup = function(type, value, lookups) { }; HTMLUglify.prototype.isWhitelisted = function(type, value) { - switch(type) { + switch (type) { case 'class': - value = ['.', value].join(''); + value = '.' + value; break; case 'id': - value = ['#', value].join(''); + value = '#' + value; break; default: break; @@ -83,29 +83,26 @@ HTMLUglify.prototype.isWhitelisted = function(type, value) { return this.whitelist.indexOf(value) >= 0; }; -HTMLUglify.prototype.pointerizeClass = function($element, lookups) { +HTMLUglify.prototype.pointerizeClass = function(node, lookups) { var self = this; - var value = $element.attr('class'); + var classes = node.attrs.class; - if (value) { - var splitClasses = value.split(/\s+/); - - splitClasses.forEach(function(value) { + if (classes) { + node.attrs.class = classes.split(/\s+/).map(function(value) { var pointer = self.createLookup('class', value, lookups); if (pointer) { - $element.removeClass(value); - $element.addClass(pointer); + return pointer; } - }); + + return value; + }).join(' '); } }; -HTMLUglify.prototype.pointerizeIdAndFor = function(type, $element, lookups) { - var value = $element.attr(type); - - var pointer = this.createLookup('id', value, lookups); +HTMLUglify.prototype.pointerizeIdAndFor = function(type, node, lookups) { + var pointer = this.createLookup('id', node.attrs[type], lookups); if (pointer) { - $element.attr(type, pointer); + node.attrs[type] = pointer; } }; @@ -121,13 +118,13 @@ HTMLUglify.prototype.processRules = function(rules, lookups) { selectors.eachInside(function(selector) { var pointer; - if ((selector.type === 'id') + if ((selector.type === 'class') + || (selector.type === 'attribute' && selector.attribute === 'class')) { + pointer = self.createLookup('class', selector.value, lookups); + } else if ((selector.type === 'id') || (selector.type === 'attribute' && selector.attribute === 'id') || (selector.type === 'attribute' && selector.attribute === 'for')) { pointer = self.createLookup('id', selector.value, lookups); - } else if ((selector.type === 'class') - || (selector.type === 'attribute' && selector.attribute === 'class')) { - pointer = self.createLookup('class', selector.value, lookups); } if (pointer) { @@ -141,49 +138,61 @@ HTMLUglify.prototype.processRules = function(rules, lookups) { }); }; -HTMLUglify.prototype.rewriteElements = function($, lookups) { +HTMLUglify.prototype.rewriteElements = function(tree, lookups) { var self = this; lookups = lookups || {}; - $('*[id]').each(function() { - self.pointerizeIdAndFor('id', $(this), lookups); - }); + return tree.walk(function(node) { + if (node.attrs) { + if (node.attrs.class) { + self.pointerizeClass(node, lookups); + } - $('*[for]').each(function() { - self.pointerizeIdAndFor('for', $(this), lookups); - }); + if (node.attrs.id) { + self.pointerizeIdAndFor('id', node, lookups); + } - $('*[class]').each(function() { - self.pointerizeClass($(this), lookups); + if (node.attrs.for) { + self.pointerizeIdAndFor('for', node, lookups); + } + } + return node; }); - - return $; }; -HTMLUglify.prototype.rewriteStyles = function($, lookups) { +HTMLUglify.prototype.rewriteStyles = function(tree, lookups) { var self = this; lookups = lookups || {}; - $('style').each(function() { - var $style = $(this); - var ast = postcssSafeParser($style.text()); - self.processRules(ast.nodes, lookups); - $style.text(ast.toString()); + return tree.walk(function(node) { + if (node.tag === 'style' && node.content) { + var ast = postcssSafeParser([].concat(node.content).join('')); + self.processRules(ast.nodes, lookups); + node.content = ast.toString(); + } + return node; }); - - return $; }; -HTMLUglify.prototype.process = function(html) { +HTMLUglify.prototype.process = function(tree) { var lookups = {}; - var $ = cheerio.load(html); - $ = this.rewriteStyles($, lookups); - $ = this.rewriteElements($, lookups); + tree = this.rewriteStyles(tree, lookups); + tree = this.rewriteElements(tree, lookups); - return $.html(); + return tree; }; -module.exports = HTMLUglify; +module.exports = function(options) { + return function(tree) { + return new HTMLUglify(options).process(tree); + } +}; + +module.exports.HTMLUglify = HTMLUglify; + +module.exports.process = function(html, options) { + return posthtml().use(module.exports(options)).process(html, { sync: true }); +}; diff --git a/package.json b/package.json index 2bb06f3..0878aa1 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,10 @@ "cheerio": "^0.19.0", "hashids": "^1.0.2", "postcss-safe-parser": "^1.0.4", - "postcss-selector-parser": "^1.3.0" + "postcss-selector-parser": "^1.3.0", + "posthtml": "^0.8.1", + "posthtml-parser": "^0.1.1", + "posthtml-render": "^1.0.5" }, "devDependencies": { "benchmark": "^2.0.0", diff --git a/test/benchmark.js b/test/benchmark.js index 7284ca1..791e007 100644 --- a/test/benchmark.js +++ b/test/benchmark.js @@ -2,20 +2,26 @@ var fs = require('fs'); var Benchmark = require('benchmark'); -var HTMLUglify = require('../lib/main.js'); +var posthtml = require('posthtml'); +var uglify = require('../lib/main.js'); var suite = new Benchmark.Suite(); -var htmlUglify = new HTMLUglify(); - -console.log('Running benchmark'); +var htmlUglify = posthtml().use(uglify()); var html = fs.readFileSync('./test/test.html'); +console.log('Running benchmark'); + suite -.add('#process', function() { - htmlUglify.process(html); -}) -.on('cycle', function(event) { - console.log(String(event.target)); -}) -.run({ 'async': true }); + .add('#process', { + defer: true, + fn: function(deferred) { + htmlUglify.process(html).then(function() { + deferred.resolve(); + }); + } + }) + .on('cycle', function(event) { + console.log(String(event.target)); + }) + .run({ async: true }); diff --git a/test/main.js b/test/main.js index 3becbef..9a219cf 100644 --- a/test/main.js +++ b/test/main.js @@ -1,15 +1,24 @@ 'use strict'; var assert = require('chai').assert; -var cheerio = require('cheerio'); -var HTMLUglify = require('../lib/main.js'); +var parser = require('posthtml-parser'); +var render = require('posthtml-render'); +var walk = require('posthtml/lib/api').walk; + +var plugin = require('../lib/main'); +var HTMLUglify = plugin.HTMLUglify; var htmlUglify = new HTMLUglify(); +function parse(html) { + var tree = parser(html); + tree.walk = walk; + return tree; +} + describe('HTMLUglify', function() { describe('#isWhitelisted', function() { var whitelist; - var htmlUglify; beforeEach(function() { whitelist = ['#theid', '.theclass', '#★', '.★']; @@ -36,13 +45,14 @@ describe('HTMLUglify', function() { assert.isTrue(whitelisted); }); }); + describe('#checkForStandardPointer', function() { it('returns undefined when name not found', function() { var lookups = { 'class': { 'something': 'zzz' } }; var value = 'other'; - var pointer = htmlUglify.checkForStandardPointer(lookups, 'class', value); + var pointer = htmlUglify.checkForStandardPointer('class', value, lookups); assert.isUndefined(pointer); }); @@ -51,18 +61,19 @@ describe('HTMLUglify', function() { 'class': { 'something': 'zzz' } }; var value = 'something'; - var pointer = htmlUglify.checkForStandardPointer(lookups, 'class', value); + var pointer = htmlUglify.checkForStandardPointer('class', value, lookups); assert.equal(pointer, 'zzz'); }); }); + describe('#checkForAttributePointer', function() { it('returns undefined when not found', function() { var lookups = { 'class': { 'something': 'zzz' } }; var value = 'other'; - var pointer = htmlUglify.checkForAttributePointer(lookups, 'class', value); + var pointer = htmlUglify.checkForAttributePointer('class', value, lookups); assert.isUndefined(pointer); }); @@ -71,11 +82,25 @@ describe('HTMLUglify', function() { 'class': { 'something': 'zzz' } }; var value = 'somethingElse'; - var pointer = htmlUglify.checkForAttributePointer(lookups, 'class', value); + var pointer = htmlUglify.checkForAttributePointer('class', value, lookups); assert.equal(pointer, 'zzzElse'); }); }); + + describe('#insertLookup', function() { + var lookups; + + beforeEach(function() { + lookups = {}; + }); + + it('updates lookups', function() { + htmlUglify.insertLookup('class', 'testClass', 'xz', lookups); + assert.equal(lookups['class'].testClass, 'xz'); + }); + }); + describe('#generatePointer', function() { it('returns xz for counter 0 lookups', function() { var lookups = {}; @@ -93,6 +118,7 @@ describe('HTMLUglify', function() { assert.equal(pointer, 'en'); }); }); + describe('#pointer', function() { it('generates a new pointer', function() { var lookups = {}; @@ -136,317 +162,272 @@ describe('HTMLUglify', function() { } }; var pointer = htmlUglify.pointer('class', 'test', lookups); - assert.equal(pointer, 'xz'); }); }); + describe('#pointerizeIdAndFor', function() { + var node; + + beforeEach(function() { + var html = '
'; + node = parse(html)[0]; + }); + + it('works with empty lookups', function() { + var lookups = {}; + htmlUglify.pointerizeIdAndFor('id', node, lookups); + assert.deepEqual(lookups, { id: { one: 'xz' } }); + }); + it('works with existing lookup', function() { + var lookups = { class: { one: 'ab' } }; + htmlUglify.pointerizeClass(node, lookups); + assert.deepEqual(lookups, { class: { one: 'ab' } }); + }); + it('works with whitelist', function() { + var lookups = {}; + htmlUglify.whitelist = [ '#one' ]; + htmlUglify.pointerizeClass(node, lookups); + assert.deepEqual(lookups, {}); + }); + }); + + + describe('#pointerizeClass', function() { + var node; + + beforeEach(function() { + var html = ''; + node = parse(html)[0]; + }); + + it('works with empty lookups', function() { + var lookups = {}; + htmlUglify.pointerizeClass(node, lookups); + assert.deepEqual(lookups, { class: { one: 'xz', two: 'wk' } }); + }); + it('works with single lookup', function() { + var lookups = { class: { one: 'ab' } }; + htmlUglify.pointerizeClass(node, lookups); + assert.deepEqual(lookups, { class: { one: 'ab', two: 'wk' } }); + }); + it('works with whitelist', function() { + var lookups = {}; + htmlUglify.whitelist = ['.two']; + htmlUglify.pointerizeClass(node, lookups); + assert.deepEqual(lookups, { class: { one: 'xz' } }); + }); + }); + describe('#rewriteStyles', function() { it('rewrites an id given lookups', function() { - var lookups = { 'id=abe': 'xz' }; - var html = ''; - var $ = cheerio.load(html); - var results = htmlUglify.rewriteStyles($, lookups).html(); - assert.equal(results, ''); + var lookups = { 'id': { 'foo': 'bar' } }; + var html = ''; + var result = htmlUglify.rewriteStyles(parse(html), lookups); + assert.equal(render(result), ''); }); it('rewrites an id', function() { var lookups = { }; var html = ''; - var $ = cheerio.load(html); - var results = htmlUglify.rewriteStyles($, lookups).html(); - assert.equal(results, ''); + var result = htmlUglify.rewriteStyles(parse(html), lookups); + assert.equal(render(result), ''); }); it('rewrites an id with the same name as the element', function() { var lookups = {'id': {'label': 'ab' }}; var html = ''; - var $ = cheerio.load(html); - var results = htmlUglify.rewriteStyles($, lookups).html(); - assert.equal(results, ''); + var result = htmlUglify.rewriteStyles(parse(html), lookups); + assert.equal(render(result), ''); }); it('rewrites a for= given lookups', function() { var lookups = { 'id': {'email': 'ab'} }; var html = ''; - var $ = cheerio.load(html); - var results = htmlUglify.rewriteStyles($, lookups).html(); - assert.equal(results, ""); + var result = htmlUglify.rewriteStyles(parse(html), lookups); + assert.equal(render(result), ""); }); - it('does rewrites a for=', function() { + it('rewrites a for=', function() { var lookups = {}; var html = ''; - var $ = cheerio.load(html); - var results = htmlUglify.rewriteStyles($, lookups).html(); - assert.equal(results, ""); + var result = htmlUglify.rewriteStyles(parse(html), lookups); + assert.equal(render(result), ""); }); it('rewrites a for= with quotes given lookups', function() { var lookups = { 'id': {'email': 'ab'} }; var html = ''; - var $ = cheerio.load(html); - var results = htmlUglify.rewriteStyles($, lookups).html(); - assert.equal(results, ''); + var result = htmlUglify.rewriteStyles(parse(html), lookups); + assert.equal(render(result), ''); }); it('rewrites a for= with the same name as the element', function() { var lookups = { 'id': { 'label': 'ab' }}; var html = ''; - var $ = cheerio.load(html); - var results = htmlUglify.rewriteStyles($, lookups).html(); - assert.equal(results, ''); + var result = htmlUglify.rewriteStyles(parse(html), lookups); + assert.equal(render(result), ''); }); it('rewrites an id= given lookups', function() { var lookups = { 'id': {'email': 'ab'} }; var html = ''; - var $ = cheerio.load(html); - var results = htmlUglify.rewriteStyles($, lookups).html(); - assert.equal(results, ''); + var result = htmlUglify.rewriteStyles(parse(html), lookups); + assert.equal(render(result), ''); }); it('rewrites an id= with quotes given lookups', function() { var lookups = { 'id': { 'email': 'ab' } }; var html = ''; - var $ = cheerio.load(html); - var results = htmlUglify.rewriteStyles($, lookups).html(); - assert.equal(results, ''); + var result = htmlUglify.rewriteStyles(parse(html), lookups); + assert.equal(render(result), ''); }); it('rewrites an id= with quotes and with the same name as the element', function() { var lookups = { 'id': {'label': 'ab'} }; var html = ''; - var $ = cheerio.load(html); - var results = htmlUglify.rewriteStyles($, lookups).html(); - assert.equal(results, ''); + var result = htmlUglify.rewriteStyles(parse(html), lookups); + assert.equal(render(result), ''); }); it('rewrites a class given lookups', function() { var lookups = { 'class': { 'email': 'ab' }}; var html = ''; - var $ = cheerio.load(html); - var results = htmlUglify.rewriteStyles($, lookups).html(); - assert.equal(results, ''); + var result = htmlUglify.rewriteStyles(parse(html), lookups); + assert.equal(render(result), ''); }); it('rewrites a class with the same name as the element', function() { var lookups = { 'class': { 'label': 'ab' }}; var html = ''; - var $ = cheerio.load(html); - var results = htmlUglify.rewriteStyles($, lookups).html(); - assert.equal(results, ''); + var result = htmlUglify.rewriteStyles(parse(html), lookups); + assert.equal(render(result), ''); }); it('rewrites a class= given lookups', function() { var lookups = { 'class': { 'email': 'ab' }}; var html = ''; - var $ = cheerio.load(html); - var results = htmlUglify.rewriteStyles($, lookups).html(); - assert.equal(results, ""); + var result = htmlUglify.rewriteStyles(parse(html), lookups); + assert.equal(render(result), ""); }); it('rewrites multi-selector rule', function() { var lookups = { 'class': { 'email': 'ab' }}; var html = ''; - var $ = cheerio.load(html); - var results = htmlUglify.rewriteStyles($, lookups).html(); - assert.equal(results, ''); + var result = htmlUglify.rewriteStyles(parse(html), lookups); + assert.equal(render(result), ''); }); it('rewrites css media queries', function() { var lookups = { 'id': { 'abe': 'wz' }}; - var html = ''; - var $ = cheerio.load(html); - var results = htmlUglify.rewriteStyles($, lookups).html(); - assert.equal(results, ''); + var result = htmlUglify.rewriteStyles(parse(html), lookups); + assert.equal(render(result), ''); }); it('rewrites nested css media queries', function() { var lookups = { 'id': { 'abe': 'wz' }}; - var html = ''; - var $ = cheerio.load(html); - var results = htmlUglify.rewriteStyles($, lookups).html(); - assert.equal(results, ''); + var result = htmlUglify.rewriteStyles(parse(html), lookups); + assert.equal(render(result), ''); }); it('handles malformed syntax', function() { var html = ''; - var $ = cheerio.load(html); - var results = htmlUglify.rewriteStyles($).html(); - assert.equal(results, ''); + var result = htmlUglify.rewriteStyles(parse(html)); + assert.equal(render(result), ''); }); }); - describe('#pointerizeClass', function() { - var $element; - - beforeEach(function() { - var html = ''; - var $ = cheerio.load(html); - - $element = $('p').first(); - }); - - it('works with empty lookups', function() { - var lookups = {}; - htmlUglify.pointerizeClass($element, lookups); - assert.deepEqual(lookups, { class: { one: 'xz', two: 'wk' } }); - }); - it('works with single lookup', function() { - var lookups = { class: { one: 'ab' } }; - htmlUglify.pointerizeClass($element, lookups); - assert.deepEqual(lookups, { class: { one: 'ab', two: 'wk' } }); - }); - it('works with whitelist', function() { - var lookups = {}; - htmlUglify.whitelist = [ '.two' ]; - htmlUglify.pointerizeClass($element, lookups); - assert.deepEqual(lookups, { class: { one: 'xz' } }); - }); - }); - describe('#pointerizeIdAndFor', function() { - var $element; - - beforeEach(function() { - var html = ''; - var $ = cheerio.load(html); - - $element = $('p').first(); - }); - - it('works with empty lookups', function() { - var lookups = {}; - htmlUglify.pointerizeIdAndFor('id', $element, lookups); - assert.deepEqual(lookups, { id: { one: 'xz' } }); - }); - it('works with existing lookup', function() { - var lookups = { class: { one: 'ab' } }; - htmlUglify.pointerizeClass($element, lookups); - assert.deepEqual(lookups, { class: { one: 'ab' } }); - }); - it('works with whitelist', function() { - var lookups = {}; - htmlUglify.whitelist = [ '#one' ]; - htmlUglify.pointerizeClass($element, lookups); - assert.deepEqual(lookups, {}); - }); - }); describe('#rewriteElements', function() { it('rewrites an id', function() { var html = '