"use strict"; 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 colours = [ { backgroundColor: '#9ad0f5', borderColor: '#47aaec', }, { backgroundColor: '#ffb0c1', borderColor: '#ff87a1', }, { backgroundColor: '#a4dfdf', borderColor: '#4bc0c0', }, ]; const chartOptions = (title, displayLegend, yTick = t => t) => ({ options: { title: { display: true, text: title, }, scales: { xAxes: [{ barPercentage: 0.5, gridLines: { color: '#ccc', }, ticks: { fontColor: '#222', }, }], yAxes: [{ gridLines: { color: '#666', }, ticks: { callback: yTick, fontColor: '#222', }, }], }, legend: { display: displayLegend, labels: { fontFamily: 'Ubuntu, sans-serif', fontColor: '#000', }, }, }, }); const renderChart = async (file, cfg) => { const chart = new chartjs(435, 320); await chart.drawChart(cfg); await chart.writeImageToFile('image/png', path.join(__dirname, `${file}.png`)); }; const sizes = {}; const setSize = (program, test, result) => { if (!sizes[test]) { sizes[test] = { original: { absolute: tests.find(t => t.name === test).content.length, relative: 1, }, }; } const original = sizes[test].original.absolute; sizes[test][program] = { absolute: result, relative: result / original, }; }; // Run once to set sizes. for (const t of tests) { for (const p of Object.keys(programs)) { try { setSize(p, t.name, programs[p](t.content)); } catch (err) { console.error(`Failed to run ${p} on test ${t.name}:`); console.error(err); process.exit(1); } } } const suite = new benchmark.Suite(); for (const p of Object.keys(programs)) { suite.add(p, () => { for (const t of tests) { programs[p](t.content); } }); } suite .on('cycle', event => { console.info(event.target.toString()); }) .on('complete', async function () { const speedResults = this.map(b => ({ name: b.name, ops: b.hz, })); fs.writeFileSync(path.join(__dirname, "speed.json"), JSON.stringify(speedResults, null, 2)); await renderChart('speed', { type: 'bar', data: { labels: speedResults.map(r => r.name), datasets: [{ ...colours[0], data: speedResults.map(r => r.ops), }], }, ...chartOptions('Operations per second (higher is better)', false), }); const testNames = Object.keys(sizes); const programNames = Object.keys(programs); fs.writeFileSync(path.join(__dirname, "minification.json"), JSON.stringify(sizes, null, 2)); await renderChart('minification', { type: 'bar', scaleFontColor: 'red', data: { labels: testNames, datasets: programNames.map((program, i) => ({ label: program, ...colours[i], data: testNames.map(test => sizes[test][program].relative * 100), })), }, ...chartOptions('Relative minified HTML file size (lower is better)', true, tick => `${tick}%`), }); }) .run({'async': true});