Use esbuild for minifiers in bench
This commit is contained in:
parent
3f963c99ca
commit
4ff2e14b04
|
@ -42,6 +42,7 @@ Since speed depends on the input, speed charts show performance relative to the
|
|||
The settings used for each minifier can be found in [minifiers.js](./minifiers.js). Some settings to note:
|
||||
|
||||
- CSS minification is disabled for all, as minify-html currently does not support CSS minification (coming soon).
|
||||
- To increase fairness, all minifiers use esbuild for JS minification, and do so asynchronously and in parallel, similar to how minify-html works.
|
||||
- `conservativeCollapse` is enabled for html-minifier as otherwise some whitespace would be unsafely removed with side affects. minify-html can safely remove whitespace with context if configured properly.
|
||||
|
||||
## Running
|
||||
|
|
|
@ -1,43 +1,94 @@
|
|||
const htmlMinifier = require("html-minifier");
|
||||
const minifyHtml = require("@minify-html/js");
|
||||
const minimize = require("minimize");
|
||||
const terser = require('terser');
|
||||
const esbuild = require('esbuild');
|
||||
const htmlMinifier = require('html-minifier');
|
||||
const minifyHtml = require('@minify-html/js');
|
||||
const minimize = require('minimize');
|
||||
|
||||
const minifyHtmlCfg = minifyHtml.createConfiguration({minifyJs: true});
|
||||
|
||||
const jsMime = new Set([
|
||||
undefined,
|
||||
'application/ecmascript',
|
||||
'application/javascript',
|
||||
'application/x-ecmascript',
|
||||
'application/x-javascript',
|
||||
'text/ecmascript',
|
||||
'text/javascript',
|
||||
'text/javascript1.0',
|
||||
'text/javascript1.1',
|
||||
'text/javascript1.2',
|
||||
'text/javascript1.3',
|
||||
'text/javascript1.4',
|
||||
'text/javascript1.5',
|
||||
'text/jscript',
|
||||
'text/livescript',
|
||||
'text/x-ecmascript',
|
||||
'text/x-javascript',
|
||||
]);
|
||||
|
||||
class EsbuildAsync {
|
||||
constructor () {
|
||||
this.promises = [];
|
||||
}
|
||||
|
||||
queue (code) {
|
||||
const id = this.promises.push(esbuild.transform(code, {
|
||||
minify: true,
|
||||
minifyWhitespace: true,
|
||||
minifyIdentifiers: true,
|
||||
minifySyntax: true,
|
||||
})) - 1;
|
||||
return `_____ESBUILD_ASYNC_PLACEHOLDER_${id}_____`;
|
||||
}
|
||||
|
||||
async finalise (html) {
|
||||
const jsTransformResults = await Promise.all(this.promises);
|
||||
return html.replace(/_____ESBUILD_ASYNC_PLACEHOLDER_([0-9]+)_____/g, (_, id) => jsTransformResults[id].js.replace(/<\/script/g, '<\\/script'));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
'minify-html-nodejs': (_, buffer) => minifyHtml.minifyInPlace(Buffer.from(buffer), {minifyJs: true}),
|
||||
'html-minifier': content => htmlMinifier.minify(content, {
|
||||
collapseBooleanAttributes: true,
|
||||
collapseInlineTagWhitespace: true,
|
||||
collapseWhitespace: true,
|
||||
// minify-html can do context-aware whitespace removal, which is safe when configured correctly to match how whitespace is used in the document.
|
||||
// html-minifier cannot, so whitespace must be collapsed conservatively.
|
||||
// Alternatively, minify-html can also be made to remove whitespace regardless of context.
|
||||
conservativeCollapse: true,
|
||||
customEventAttributes: [],
|
||||
decodeEntities: true,
|
||||
ignoreCustomComments: [],
|
||||
ignoreCustomFragments: [/<\?[\s\S]*?\?>/],
|
||||
minifyJS: true,
|
||||
processConditionalComments: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeComments: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeOptionalTags: true,
|
||||
removeRedundantAttributes: true,
|
||||
removeScriptTypeAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
removeTagWhitespace: true,
|
||||
useShortDoctype: true,
|
||||
}),
|
||||
'minimize': content => new minimize({
|
||||
plugins: [{
|
||||
id: 'terser',
|
||||
element: (node, next) => {
|
||||
if (node.type === 'text' && node.parent && node.parent.type === 'script') {
|
||||
node.data = terser.minify(node.data).code || node.data;
|
||||
}
|
||||
next();
|
||||
},
|
||||
}]
|
||||
}).parse(content),
|
||||
'minify-html-nodejs': (_, buffer) => minifyHtml.minifyInPlace(Buffer.from(buffer), minifyHtmlCfg),
|
||||
'html-minifier': async (content) => {
|
||||
const js = new EsbuildAsync();
|
||||
const res = htmlMinifier.minify(content, {
|
||||
collapseBooleanAttributes: true,
|
||||
collapseInlineTagWhitespace: true,
|
||||
collapseWhitespace: true,
|
||||
// minify-html can do context-aware whitespace removal, which is safe when configured correctly to match how whitespace is used in the document.
|
||||
// html-minifier cannot, so whitespace must be collapsed conservatively.
|
||||
// Alternatively, minify-html can also be made to remove whitespace regardless of context.
|
||||
conservativeCollapse: true,
|
||||
customEventAttributes: [],
|
||||
decodeEntities: true,
|
||||
ignoreCustomComments: [],
|
||||
ignoreCustomFragments: [/<\?[\s\S]*?\?>/],
|
||||
minifyJS: code => js.queue(code),
|
||||
processConditionalComments: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeComments: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeOptionalTags: true,
|
||||
removeRedundantAttributes: true,
|
||||
removeScriptTypeAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
removeTagWhitespace: true,
|
||||
useShortDoctype: true,
|
||||
});
|
||||
return js.finalise(res);
|
||||
},
|
||||
'minimize': async (content) => {
|
||||
const js = new EsbuildAsync();
|
||||
const res = new minimize({
|
||||
plugins: [{
|
||||
id: 'esbuild',
|
||||
element: (node, next) => {
|
||||
if (node.type === 'text' && node.parent && node.parent.type === 'script' && jsMime.has(node.parent.attribs.type)) {
|
||||
node.data = js.queue(node.data);
|
||||
}
|
||||
next();
|
||||
},
|
||||
}],
|
||||
}).parse(content);
|
||||
return js.finalise(res);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -5,13 +5,13 @@
|
|||
"benchmark": "2.1.4",
|
||||
"chart.js": "^2.9.3",
|
||||
"chartjs-node": "^1.7.1",
|
||||
"esbuild": "^0.6.5",
|
||||
"html-minifier": "4.0.0",
|
||||
"minimize": "2.2.0",
|
||||
"minimist": "^1.2.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"request": "^2.88.0",
|
||||
"request-promise-native": "^1.0.8",
|
||||
"terser": "^4.8.0"
|
||||
"request-promise-native": "^1.0.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": "10.x"
|
||||
|
|
|
@ -22,20 +22,22 @@ const setSize = (program, test, result) => {
|
|||
};
|
||||
};
|
||||
|
||||
for (const t of tests) {
|
||||
for (const m of Object.keys(minifiers)) {
|
||||
try {
|
||||
const min = minifiers[m](t.contentAsString, t.contentAsBuffer);
|
||||
// If `min` is a Buffer, convert to string (interpret as UTF-8) to get canonical length.
|
||||
setSize(m, t.name, min.toString().length);
|
||||
const minPath = path.join(__dirname, 'min', m, `${t.name}.html`);
|
||||
mkdirp.sync(path.dirname(minPath));
|
||||
fs.writeFileSync(minPath, min);
|
||||
} catch (err) {
|
||||
console.error(`Failed to run ${m} on test ${t.name}:`);
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
(async () => {
|
||||
for (const t of tests) {
|
||||
for (const m of Object.keys(minifiers)) {
|
||||
try {
|
||||
const min = await minifiers[m](t.contentAsString, t.contentAsBuffer);
|
||||
// If `min` is a Buffer, convert to string (interpret as UTF-8) to get canonical length.
|
||||
setSize(m, t.name, min.toString().length);
|
||||
const minPath = path.join(__dirname, 'min', m, `${t.name}.html`);
|
||||
mkdirp.sync(path.dirname(minPath));
|
||||
fs.writeFileSync(minPath, min);
|
||||
} catch (err) {
|
||||
console.error(`Failed to run ${m} on test ${t.name}:`);
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
results.writeSizeResults(sizes);
|
||||
results.writeSizeResults(sizes);
|
||||
})();
|
||||
|
|
|
@ -44,8 +44,11 @@ const runTest = test => new Promise((resolve, reject) => {
|
|||
// Run JS libraries.
|
||||
const suite = new benchmark.Suite();
|
||||
for (const m of Object.keys(minifiers)) {
|
||||
suite.add(m, () => {
|
||||
minifiers[m](test.contentAsString, test.contentAsBuffer);
|
||||
suite.add(m, {
|
||||
defer: true,
|
||||
fn (deferred) {
|
||||
Promise.resolve(minifiers[m](test.contentAsString, test.contentAsBuffer)).then(() => deferred.resolve());
|
||||
},
|
||||
});
|
||||
}
|
||||
suite
|
||||
|
|
Loading…
Reference in New Issue