5 var packages = require('./package.json').benchmarkDependencies;
6 packages = Object.keys(packages).map(function(name) {
7 return name + '@' + packages[name];
9 packages.unshift('install', '--no-save', '--no-optional');
10 var installed = require('child_process').spawnSync('npm', packages, {
14 if (installed.error) {
15 throw installed.error;
17 else if (installed.status) {
18 console.log(installed.stdout);
19 console.error(installed.stderr);
20 process.exit(installed.status);
23 var brotli = require('brotli'),
24 chalk = require('chalk'),
25 fork = require('child_process').fork,
27 https = require('https'),
28 lzma = require('lzma'),
29 Minimize = require('minimize'),
30 path = require('path'),
31 Progress = require('progress'),
32 querystring = require('querystring'),
33 Table = require('cli-table'),
35 zlib = require('zlib');
37 var urls = require('./benchmarks');
38 var fileNames = Object.keys(urls);
40 var minimize = new Minimize();
42 var progress = new Progress('[:bar] :etas :fileName', {
44 total: fileNames.length
47 var table = new Table({
48 head: ['File', 'Before', 'After', 'Minimize', 'Will Peavy', 'htmlcompressor.com', 'Savings', 'Time'],
49 colWidths: [fileNames.reduce(function(length, fileName) {
50 return Math.max(length, fileName.length);
51 }, 0) + 2, 25, 25, 25, 25, 25, 20, 10]
54 function toKb(size, precision) {
55 return (size / 1024).toFixed(precision || 0);
58 function redSize(size) {
59 return chalk.red.bold(size) + chalk.white(' (' + toKb(size, 2) + ' KB)');
62 function greenSize(size) {
63 return chalk.green.bold(size) + chalk.white(' (' + toKb(size, 2) + ' KB)');
66 function blueSavings(oldSize, newSize) {
67 var savingsPercent = (1 - newSize / oldSize) * 100;
68 var savings = oldSize - newSize;
69 return chalk.cyan.bold(savingsPercent.toFixed(2)) + chalk.white('% (' + toKb(savings, 2) + ' KB)');
72 function blueTime(time) {
73 return chalk.cyan.bold(time) + chalk.white(' ms');
76 function readBuffer(filePath, callback) {
77 fs.readFile(filePath, function(err, data) {
79 throw new Error('There was an error reading ' + filePath);
85 function readText(filePath, callback) {
86 fs.readFile(filePath, { encoding: 'utf8' }, function(err, data) {
88 throw new Error('There was an error reading ' + filePath);
94 function writeBuffer(filePath, data, callback) {
95 fs.writeFile(filePath, data, function(err) {
97 throw new Error('There was an error writing ' + filePath);
103 function writeText(filePath, data, callback) {
104 fs.writeFile(filePath, data, { encoding: 'utf8' }, function(err) {
106 throw new Error('There was an error writing ' + filePath);
114 function readSize(filePath, callback) {
115 fs.stat(filePath, function(err, stats) {
117 throw new Error('There was an error reading ' + filePath);
119 callback(stats.size);
123 function gzip(inPath, outPath, callback) {
124 fs.createReadStream(inPath).pipe(zlib.createGzip({
125 level: zlib.Z_BEST_COMPRESSION
126 })).pipe(fs.createWriteStream(outPath)).on('finish', callback);
129 function run(tasks, done) {
132 function callback() {
133 if (i < tasks.length) {
134 tasks[i++](callback);
146 function generateMarkdownTable() {
149 'Original size *(KB)*',
155 fileNames.forEach(function(fileName) {
156 var row = rows[fileName].report;
157 row[2] = '**' + row[2] + '**';
159 var widths = headers.map(function(header, index) {
160 var width = header.length;
161 fileNames.forEach(function(fileName) {
162 width = Math.max(width, rows[fileName].report[index].length);
168 function output(row) {
169 widths.forEach(function(width, index) {
170 var text = row[index];
171 content += '| ' + text + new Array(width - text.length + 2).join(' ');
177 widths.forEach(function(width, index) {
179 content += index === 1 ? ':' : ' ';
180 content += new Array(width + 1).join('-');
181 content += index === 0 ? ' ' : ':';
184 fileNames.sort(function(a, b) {
185 var r = +rows[a].report[1];
186 var s = +rows[b].report[1];
187 return r < s ? -1 : r > s ? 1 : a < b ? -1 : a > b ? 1 : 0;
188 }).forEach(function(fileName) {
189 output(rows[fileName].report);
194 function displayTable() {
195 fileNames.forEach(function(fileName) {
196 table.push(rows[fileName].display);
199 console.log(table.toString());
202 run(fileNames.map(function(fileName) {
203 var filePath = path.join('benchmarks/', fileName + '.html');
205 function processFile(site, done) {
208 gzFilePath: path.join('benchmarks/generated/', fileName + '.html.gz'),
209 lzFilePath: path.join('benchmarks/generated/', fileName + '.html.lz'),
210 brFilePath: path.join('benchmarks/generated/', fileName + '.html.br')
213 ['minifier', 'minimize', 'willpeavy', 'compressor'].forEach(function(name) {
215 filePath: path.join('benchmarks/generated/', fileName + '.' + name + '.html'),
216 gzFilePath: path.join('benchmarks/generated/', fileName + '.' + name + '.html.gz'),
217 lzFilePath: path.join('benchmarks/generated/', fileName + '.' + name + '.html.lz'),
218 brFilePath: path.join('benchmarks/generated/', fileName + '.' + name + '.html.br')
222 function readSizes(info, done) {
223 info.endTime = Date.now();
225 // Apply Gzip on minified output
227 gzip(info.filePath, info.gzFilePath, function() {
228 info.gzTime = Date.now();
229 // Open and read the size of the minified+gzip output
230 readSize(info.gzFilePath, function(size) {
236 // Apply LZMA on minified output
238 readBuffer(info.filePath, function(data) {
239 lzma.compress(data, 1, function(result, error) {
243 writeBuffer(info.lzFilePath, new Buffer(result), function() {
244 info.lzTime = Date.now();
245 // Open and read the size of the minified+lzma output
246 readSize(info.lzFilePath, function(size) {
254 // Apply Brotli on minified output
256 readBuffer(info.filePath, function(data) {
257 var output = new Buffer(brotli.compress(data, true).buffer);
258 writeBuffer(info.brFilePath, output, function() {
259 info.brTime = Date.now();
260 // Open and read the size of the minified+brotli output
261 readSize(info.brFilePath, function(size) {
268 // Open and read the size of the minified output
270 readSize(info.filePath, function(size) {
278 function testHTMLMinifier(done) {
279 var info = infos.minifier;
280 info.startTime = Date.now();
281 var args = [filePath, '-c', 'sample-cli-config-file.conf', '--minify-urls', site, '-o', info.filePath];
282 fork('./cli', args).on('exit', function() {
283 readSizes(info, done);
287 function testMinimize(done) {
288 readBuffer(filePath, function(data) {
289 minimize.parse(data, function(error, data) {
290 var info = infos.minimize;
291 writeBuffer(info.filePath, data, function() {
292 readSizes(info, done);
298 function testWillPeavy(done) {
299 readText(filePath, function(data) {
300 var options = url.parse('https://www.willpeavy.com/minifier/');
301 options.method = 'POST';
303 'Content-Type': 'application/x-www-form-urlencoded'
305 https.request(options, function(res) {
306 res.setEncoding('utf8');
308 res.on('data', function(chunk) {
310 }).on('end', function() {
311 var info = infos.willpeavy;
312 if (res.statusCode === 200) {
313 // Extract result from <textarea/>
314 var start = response.indexOf('>', response.indexOf('<textarea'));
315 var end = response.lastIndexOf('</textarea>');
316 var result = response.slice(start + 1, end).replace(/<\\\//g, '</');
317 writeText(info.filePath, result, function() {
318 readSizes(info, done);
321 // Site refused to process content
330 }).end(querystring.stringify({
336 function testHTMLCompressor(done) {
337 readText(filePath, function(data) {
338 var options = url.parse('https://htmlcompressor.com/compress_ajax_v2.php');
339 options.method = 'POST';
341 'Accept-Encoding': 'gzip',
342 'Content-Type': 'application/x-www-form-urlencoded'
344 var info = infos.compressor;
347 // Site refused to process content
358 https.request(options, function(res) {
359 if (res.headers['content-encoding'] === 'gzip') {
360 res = res.pipe(zlib.createGunzip());
362 res.setEncoding('utf8');
364 res.on('data', function(chunk) {
366 }).on('end', function() {
368 response = JSON.parse(response);
373 if (info && response.success) {
374 writeText(info.filePath, response.result, function() {
375 readSizes(info, done);
378 // Site refused to process content
383 }).on('error', failed).end(querystring.stringify({
386 html_strip_quotes: 1,
392 html_optional_cdata: 1,
402 readSizes(original, done);
410 [fileName, '+ gzip', '+ lzma', '+ brotli'].join('\n'),
411 [redSize(original.size), redSize(original.gzSize), redSize(original.lzSize), redSize(original.brSize)].join('\n')
414 '[' + fileName + '](' + urls[fileName] + ')',
417 for (var name in infos) {
418 var info = infos[name];
419 display.push([greenSize(info.size), greenSize(info.gzSize), greenSize(info.lzSize), greenSize(info.brSize)].join('\n'));
420 report.push(info.size ? toKb(info.size) : 'n/a');
424 blueSavings(original.size, infos.minifier.size),
425 blueSavings(original.gzSize, infos.minifier.gzSize),
426 blueSavings(original.lzSize, infos.minifier.lzSize),
427 blueSavings(original.brSize, infos.minifier.brSize)
430 blueTime(infos.minifier.endTime - infos.minifier.startTime),
431 blueTime(original.gzTime - original.endTime),
432 blueTime(original.lzTime - original.gzTime),
433 blueTime(original.brTime - original.lzTime)
440 progress.tick({ fileName: '' });
445 function get(site, callback) {
446 var options = url.parse(site);
447 https.get(options, function(res) {
448 var status = res.statusCode;
449 if (status === 200) {
450 if (res.headers['content-encoding'] === 'gzip') {
451 res = res.pipe(zlib.createGunzip());
453 res.pipe(fs.createWriteStream(filePath)).on('finish', function() {
457 else if (status >= 300 && status < 400 && res.headers.location) {
458 get(url.resolve(site, res.headers.location), callback);
461 throw new Error('HTTP error ' + status + '\n' + site);
466 return function(done) {
467 progress.tick(0, { fileName: fileName });
468 get(urls[fileName], function(site) {
469 processFile(site, done);
474 var content = generateMarkdownTable();
475 var readme = './README.md';
476 readText(readme, function(data) {
477 var start = data.indexOf('## Minification comparison');
478 start = data.indexOf('|', start);
479 var end = data.indexOf('##', start);
480 end = data.lastIndexOf('|\n', end) + '|\n'.length;
481 data = data.slice(0, start) + content + data.slice(end);
482 writeText(readme, data);