Improve closing tag minification; update bench results

This commit is contained in:
Wilson Lin 2020-01-09 20:45:14 +11:00
parent 82a2e6e983
commit 1cb9bf9817
12 changed files with 113 additions and 87 deletions

View File

@ -15,7 +15,7 @@ Available as:
## Performance
Speed and effectiveness of Node.js version compared to other JS minifiers.
Speed and effectiveness of Node.js version compared to [html-minfier](https://github.com/kangax/html-minifier) and [minimize](https://github.com/Swaagie/minimize).
![Chart showing speed of HTML minifiers](./bench/speed.png) ![Chart showing effectiveness of HTML minifiers](./bench/minification.png)

1
bench/.gitignore vendored
View File

@ -1 +1,2 @@
node_modules/
min/

View File

@ -1,57 +1,11 @@
"use strict";
const benchmark = require("benchmark");
const benchmark = require('benchmark');
const chartjs = require('chartjs-node');
const fs = require("fs");
const htmlMinifier = require("html-minifier");
const hyperbuild = require("hyperbuild");
const minimize = require("minimize");
const path = require("path");
const testsDir = path.join(__dirname, "tests");
const tests = fs.readdirSync(testsDir).map(name => ({
name,
content: fs.readFileSync(path.join(testsDir, name), "utf8"),
}));
const programs = {
'hyperbuild-nodejs': content => hyperbuild.minify_in_place(Buffer.from(content)),
'html-minifier': content => htmlMinifier.minify(content, {
caseSensitive: false,
collapseBooleanAttributes: true,
collapseInlineTagWhitespace: true,
collapseWhitespace: true,
conservativeWhitespace: false,
customEventAttributes: [],
decodeEntities: true,
html5: true,
ignoreCustomComments: [],
ignoreCustomFragments: [/<\?[\s\S]*?\?>/],
includeAutoGeneratedTags: true,
keepClosingSlash: false,
minifyCSS: false,
minifyJS: false,
minifyURLs: false,
preserveLineBreaks: false,
preventAttributesEscaping: false,
processConditionalComments: true,
processScripts: [],
removeAttributeQuotes: true,
removeComments: true,
removeEmptyAttributes: false,
removeEmptyElements: false,
removeOptionalTags: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
removeTagWhitespace: true,
sortAttributes: false,
sortClassName: false,
trimCustomFragments: false,
useShortDoctype: true,
}).length,
'minimize': content => new minimize().parse(content).length,
};
const fs = require('fs');
const path = require('path');
const programs = require('./minifiers');
const tests = require('./tests');
const colours = [
{
@ -129,7 +83,7 @@ const setSize = (program, test, result) => {
for (const t of tests) {
for (const p of Object.keys(programs)) {
try {
setSize(p, t.name, programs[p](t.content));
setSize(p, t.name, programs[p](t.content).length);
} catch (err) {
console.error(`Failed to run ${p} on test ${t.name}:`);
console.error(err);

View File

@ -5,8 +5,8 @@
"relative": 1
},
"hyperbuild-nodejs": {
"absolute": 367738,
"relative": 0.6420287198289032
"absolute": 362616,
"relative": 0.6330862904281787
},
"html-minifier": {
"absolute": 488839,
@ -23,8 +23,8 @@
"relative": 1
},
"hyperbuild-nodejs": {
"absolute": 233690,
"relative": 0.5830777944394404
"absolute": 224376,
"relative": 0.559838517716393
},
"html-minifier": {
"absolute": 298773,
@ -41,8 +41,8 @@
"relative": 1
},
"hyperbuild-nodejs": {
"absolute": 98036,
"relative": 0.6298045110849859
"absolute": 96856,
"relative": 0.6222239353466829
},
"html-minifier": {
"absolute": 137061,
@ -59,8 +59,8 @@
"relative": 1
},
"hyperbuild-nodejs": {
"absolute": 272938,
"relative": 0.7387984354487258
"absolute": 271250,
"relative": 0.7342292960872684
},
"html-minifier": {
"absolute": 270621,
@ -77,8 +77,8 @@
"relative": 1
},
"hyperbuild-nodejs": {
"absolute": 82104,
"relative": 0.6514329239264972
"absolute": 79380,
"relative": 0.6298200514138818
},
"html-minifier": {
"absolute": 79394,
@ -95,8 +95,8 @@
"relative": 1
},
"hyperbuild-nodejs": {
"absolute": 232603,
"relative": 0.5920112597765861
"absolute": 232058,
"relative": 0.5906241489629755
},
"html-minifier": {
"absolute": 383586,
@ -113,8 +113,8 @@
"relative": 1
},
"hyperbuild-nodejs": {
"absolute": 32033,
"relative": 0.5724983468268011
"absolute": 28860,
"relative": 0.5157900380676639
},
"html-minifier": {
"absolute": 28464,
@ -131,8 +131,8 @@
"relative": 1
},
"hyperbuild-nodejs": {
"absolute": 1391016,
"relative": 0.7014896018348458
"absolute": 1383721,
"relative": 0.6978107321127253
},
"html-minifier": {
"absolute": 1888047,
@ -149,8 +149,8 @@
"relative": 1
},
"hyperbuild-nodejs": {
"absolute": 865816,
"relative": 0.5611814537429229
"absolute": 831178,
"relative": 0.5387307214917895
},
"html-minifier": {
"absolute": 1115700,
@ -167,8 +167,8 @@
"relative": 1
},
"hyperbuild-nodejs": {
"absolute": 90282,
"relative": 0.5828443049987411
"absolute": 86946,
"relative": 0.5613076908178878
},
"html-minifier": {
"absolute": 89329,
@ -185,8 +185,8 @@
"relative": 1
},
"hyperbuild-nodejs": {
"absolute": 279810,
"relative": 0.930210137531873
"absolute": 270831,
"relative": 0.9003600363028295
},
"html-minifier": {
"absolute": 273191,
@ -203,8 +203,8 @@
"relative": 1
},
"hyperbuild-nodejs": {
"absolute": 1535741,
"relative": 0.6294206222478697
"absolute": 1347041,
"relative": 0.5520822745589214
},
"html-minifier": {
"absolute": 1307604,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

42
bench/minifiers.js Normal file
View File

@ -0,0 +1,42 @@
const htmlMinifier = require("html-minifier");
const hyperbuild = require("hyperbuild");
const minimize = require("minimize");
module.exports = {
'hyperbuild-nodejs': content => hyperbuild.minify(Buffer.from(content)),
'html-minifier': content => htmlMinifier.minify(content, {
caseSensitive: false,
collapseBooleanAttributes: true,
collapseInlineTagWhitespace: true,
collapseWhitespace: true,
conservativeWhitespace: false,
customEventAttributes: [],
decodeEntities: true,
html5: true,
ignoreCustomComments: [],
ignoreCustomFragments: [/<\?[\s\S]*?\?>/],
includeAutoGeneratedTags: true,
keepClosingSlash: false,
minifyCSS: false,
minifyJS: false,
minifyURLs: false,
preserveLineBreaks: false,
preventAttributesEscaping: false,
processConditionalComments: true,
processScripts: [],
removeAttributeQuotes: true,
removeComments: true,
removeEmptyAttributes: false,
removeEmptyElements: false,
removeOptionalTags: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
removeTagWhitespace: true,
sortAttributes: false,
sortClassName: false,
trimCustomFragments: false,
useShortDoctype: true,
}),
'minimize': content => new minimize().parse(content),
};

View File

@ -7,6 +7,7 @@
"html-minifier": "3.5.19",
"hyperbuild": "file:../nodejs",
"minimize": "2.2.0",
"mkdirp": "^0.5.1",
"prettier": "^1.19.1",
"request": "^2.88.0",
"request-promise-native": "^1.0.8"

19
bench/run.js Normal file
View File

@ -0,0 +1,19 @@
const fs = require('fs');
const mkdirp = require('mkdirp');
const path = require('path');
const programs = require('./minifiers');
const tests = require('./tests');
for (const t of tests) {
for (const p of Object.keys(programs)) {
try {
const minPath = path.join(__dirname, 'min', p, t.name);
mkdirp.sync(path.dirname(minPath));
fs.writeFileSync(minPath, programs[p](t.content));
} catch (err) {
console.error(`Failed to run ${p} on test ${t.name}:`);
console.error(err);
process.exit(1);
}
}
}

View File

@ -1,14 +1,14 @@
[
{
"name": "hyperbuild-nodejs",
"ops": 13.777334566242345
"ops": 10.158435796714862
},
{
"name": "html-minifier",
"ops": 0.9361359019226175
"ops": 0.9598865156998558
},
{
"name": "minimize",
"ops": 3.537829104467651
"ops": 3.6888006698975837
}
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

8
bench/tests.js Normal file
View File

@ -0,0 +1,8 @@
const fs = require('fs');
const path = require('path');
const testsDir = path.join(__dirname, "tests");
module.exports = fs.readdirSync(testsDir).map(name => ({
name,
content: fs.readFileSync(path.join(testsDir, name), "utf8"),
}));

View File

@ -45,13 +45,15 @@ enum TagType {
pub struct ProcessedTag {
pub name: ProcessorRange,
pub closing_tag: Option<ProcessorRange>,
pub has_closing_tag: bool,
}
impl ProcessedTag {
pub fn write_closing_tag(&self, proc: &mut Processor) -> () {
if let Some(tag) = self.closing_tag {
proc.write_range(tag);
if self.has_closing_tag {
proc.write_slice(b"</");
proc.write_range(self.name);
proc.write(b'>');
};
}
}
@ -153,7 +155,7 @@ pub fn process_tag(proc: &mut Processor, prev_sibling_closing_tag: Option<Proces
// Write discarded tag closing characters.
if is_void_tag { proc.write_slice(b">"); } else { proc.write_slice(b"/>"); };
};
return Ok(ProcessedTag { name: tag_name, closing_tag: None });
return Ok(ProcessedTag { name: tag_name, has_closing_tag: false });
};
match tag_type {
@ -163,10 +165,9 @@ pub fn process_tag(proc: &mut Processor, prev_sibling_closing_tag: Option<Proces
};
// Require closing tag for non-void.
let closing_tag = proc.checkpoint();
chain!(proc.match_seq(b"</").require()?.discard());
chain!(proc.match_seq(b"</").require_with_reason("closing tag")?.discard());
chain!(proc.match_while_pred(is_valid_tag_name_char).require_with_reason("closing tag name")?.discard());
chain!(proc.match_while_pred(is_whitespace).discard());
chain!(proc.match_char(b'>').require()?.discard());
Ok(ProcessedTag { name: tag_name, closing_tag: Some(proc.consumed_range(closing_tag)) })
Ok(ProcessedTag { name: tag_name, has_closing_tag: true })
}