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:
|
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).
|
- 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.
|
- `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
|
## Running
|
||||||
|
|
|
@ -1,43 +1,94 @@
|
||||||
const htmlMinifier = require("html-minifier");
|
const esbuild = require('esbuild');
|
||||||
const minifyHtml = require("@minify-html/js");
|
const htmlMinifier = require('html-minifier');
|
||||||
const minimize = require("minimize");
|
const minifyHtml = require('@minify-html/js');
|
||||||
const terser = require('terser');
|
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 = {
|
module.exports = {
|
||||||
'minify-html-nodejs': (_, buffer) => minifyHtml.minifyInPlace(Buffer.from(buffer), {minifyJs: true}),
|
'minify-html-nodejs': (_, buffer) => minifyHtml.minifyInPlace(Buffer.from(buffer), minifyHtmlCfg),
|
||||||
'html-minifier': content => htmlMinifier.minify(content, {
|
'html-minifier': async (content) => {
|
||||||
collapseBooleanAttributes: true,
|
const js = new EsbuildAsync();
|
||||||
collapseInlineTagWhitespace: true,
|
const res = htmlMinifier.minify(content, {
|
||||||
collapseWhitespace: true,
|
collapseBooleanAttributes: true,
|
||||||
// minify-html can do context-aware whitespace removal, which is safe when configured correctly to match how whitespace is used in the document.
|
collapseInlineTagWhitespace: true,
|
||||||
// html-minifier cannot, so whitespace must be collapsed conservatively.
|
collapseWhitespace: true,
|
||||||
// Alternatively, minify-html can also be made to remove whitespace regardless of context.
|
// minify-html can do context-aware whitespace removal, which is safe when configured correctly to match how whitespace is used in the document.
|
||||||
conservativeCollapse: true,
|
// html-minifier cannot, so whitespace must be collapsed conservatively.
|
||||||
customEventAttributes: [],
|
// Alternatively, minify-html can also be made to remove whitespace regardless of context.
|
||||||
decodeEntities: true,
|
conservativeCollapse: true,
|
||||||
ignoreCustomComments: [],
|
customEventAttributes: [],
|
||||||
ignoreCustomFragments: [/<\?[\s\S]*?\?>/],
|
decodeEntities: true,
|
||||||
minifyJS: true,
|
ignoreCustomComments: [],
|
||||||
processConditionalComments: true,
|
ignoreCustomFragments: [/<\?[\s\S]*?\?>/],
|
||||||
removeAttributeQuotes: true,
|
minifyJS: code => js.queue(code),
|
||||||
removeComments: true,
|
processConditionalComments: true,
|
||||||
removeEmptyAttributes: true,
|
removeAttributeQuotes: true,
|
||||||
removeOptionalTags: true,
|
removeComments: true,
|
||||||
removeRedundantAttributes: true,
|
removeEmptyAttributes: true,
|
||||||
removeScriptTypeAttributes: true,
|
removeOptionalTags: true,
|
||||||
removeStyleLinkTypeAttributes: true,
|
removeRedundantAttributes: true,
|
||||||
removeTagWhitespace: true,
|
removeScriptTypeAttributes: true,
|
||||||
useShortDoctype: true,
|
removeStyleLinkTypeAttributes: true,
|
||||||
}),
|
removeTagWhitespace: true,
|
||||||
'minimize': content => new minimize({
|
useShortDoctype: true,
|
||||||
plugins: [{
|
});
|
||||||
id: 'terser',
|
return js.finalise(res);
|
||||||
element: (node, next) => {
|
},
|
||||||
if (node.type === 'text' && node.parent && node.parent.type === 'script') {
|
'minimize': async (content) => {
|
||||||
node.data = terser.minify(node.data).code || node.data;
|
const js = new EsbuildAsync();
|
||||||
}
|
const res = new minimize({
|
||||||
next();
|
plugins: [{
|
||||||
},
|
id: 'esbuild',
|
||||||
}]
|
element: (node, next) => {
|
||||||
}).parse(content),
|
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",
|
"benchmark": "2.1.4",
|
||||||
"chart.js": "^2.9.3",
|
"chart.js": "^2.9.3",
|
||||||
"chartjs-node": "^1.7.1",
|
"chartjs-node": "^1.7.1",
|
||||||
|
"esbuild": "^0.6.5",
|
||||||
"html-minifier": "4.0.0",
|
"html-minifier": "4.0.0",
|
||||||
"minimize": "2.2.0",
|
"minimize": "2.2.0",
|
||||||
"minimist": "^1.2.0",
|
"minimist": "^1.2.0",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"request": "^2.88.0",
|
"request": "^2.88.0",
|
||||||
"request-promise-native": "^1.0.8",
|
"request-promise-native": "^1.0.8"
|
||||||
"terser": "^4.8.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "10.x"
|
"node": "10.x"
|
||||||
|
|
|
@ -22,20 +22,22 @@ const setSize = (program, test, result) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const t of tests) {
|
(async () => {
|
||||||
for (const m of Object.keys(minifiers)) {
|
for (const t of tests) {
|
||||||
try {
|
for (const m of Object.keys(minifiers)) {
|
||||||
const min = minifiers[m](t.contentAsString, t.contentAsBuffer);
|
try {
|
||||||
// If `min` is a Buffer, convert to string (interpret as UTF-8) to get canonical length.
|
const min = await minifiers[m](t.contentAsString, t.contentAsBuffer);
|
||||||
setSize(m, t.name, min.toString().length);
|
// If `min` is a Buffer, convert to string (interpret as UTF-8) to get canonical length.
|
||||||
const minPath = path.join(__dirname, 'min', m, `${t.name}.html`);
|
setSize(m, t.name, min.toString().length);
|
||||||
mkdirp.sync(path.dirname(minPath));
|
const minPath = path.join(__dirname, 'min', m, `${t.name}.html`);
|
||||||
fs.writeFileSync(minPath, min);
|
mkdirp.sync(path.dirname(minPath));
|
||||||
} catch (err) {
|
fs.writeFileSync(minPath, min);
|
||||||
console.error(`Failed to run ${m} on test ${t.name}:`);
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(`Failed to run ${m} on test ${t.name}:`);
|
||||||
process.exit(1);
|
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.
|
// Run JS libraries.
|
||||||
const suite = new benchmark.Suite();
|
const suite = new benchmark.Suite();
|
||||||
for (const m of Object.keys(minifiers)) {
|
for (const m of Object.keys(minifiers)) {
|
||||||
suite.add(m, () => {
|
suite.add(m, {
|
||||||
minifiers[m](test.contentAsString, test.contentAsBuffer);
|
defer: true,
|
||||||
|
fn (deferred) {
|
||||||
|
Promise.resolve(minifiers[m](test.contentAsString, test.contentAsBuffer)).then(() => deferred.resolve());
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
suite
|
suite
|
||||||
|
|
Loading…
Reference in New Issue