I have an express app that, among other things, delivers some html code based on a single handlebars template. It would be great for the use-case to deliver it with short latency. So I read and compile the template when the app starts and for now I simply call
res.send(
compiledTemplateIframeContent({ data: iframeData })
)
All of that works flawlessly.
The problem
Now I would like to minify the html and uglify the js code. I tried with html-minifier but the embedded javascript is not uglified (renaming variables), not even minified (eg remove whitespaces). I guess uglify-js is not called in the background because of some miss-configuration. I can't find my mistake by reading the docs.
I wrote some test html code and tried it in this online tool. It is just a simple gui infront of html-minifier and therefor potentially a good way to test my problem.
In the online version I can at least make it compress the javascript code when I add type="text/javascript"
to the tag and text/javascript
to the Process scripts
option (even this does not work in my express app).
But also in the online tool the Minify JavaScript
checkbox does not change anything.
I don't know the possibilities of uglifyjs yet (a dependency of html-minifier) but with that name I would assume it should not only compress but also uglify the js code.
Edit / Update
I did some more research. I extracted the js code by regex match and just called uglify-js directly with the code. It had some problems with the handlebar fragments {{ }}. After removing them uglif-js works on the embedded js part of the template.
As I have set the option ignoreCustomFragments: [/{{.*}}/g]
for the html-minifier I guess this should not be the problem?!
Here is the part that directly uses uglify-js
function minifyWithUglifyJs(orig: string): string {
const re = /<script\b[^>]*>([\s\S]*?)<\/script>/gm;
const match = re.exec(orig);
if(match == null) {
throw new Error(`Can't minify js in iframe template as found no match for js script!`);
}
const origJs = match[1] as string;
console.log(origJs);
const minifyResult = uglifyjs.minify(origJs);
if(minifyResult.warnings != null) {
minifyResult.warnings.forEach(logger.warn);
}
if(minifyResult.error) {
throw new Error(`Can't minify the iframe template!`, {cause:minifyResult.error});
} else {
// replace orig js with minified js in code
return orig.replace(origJs, minifyResult.code);
}
}
Summing it up:
Here is my attempt to minify in my node app (using html-minifier@4.0.0)
import htmlMinifier from 'html-minifier';
function minifyWithHtmlMinifier(orig: string):string {
return htmlMinifier.minify(orig, {
minifyJS: true,
processScripts: ["text/javascript"],
ignoreCustomFragments: [/{{.*}}/g]
});
}
And here is some test code one can use in the online tool
<!DOCTYPE html>
<html lang="en">
<head>
<title>Test ad</title>
</head>
<body onload="onLoad()">
<div id="img-container" style="width:100%;height:100%">test text in page</div>
<script type="text/javascript">
const longNameThatShouldBeUglified= 12;
console.log("test");
//{{another test}}
function onLoad() {
console.log("longNameThatShouldBeUglified (Not this time as it's a string) "+longNameThatShouldBeUglified);
}
</script>
</body>
</html>
The problem is, that html-minifier does not give you any warnings when uglify-js throws any errors. I thought I had misconfigured html-minifier becaue it did not minify.
After splitting my code to get only the content between the tags and using uglify-js directly I saw the problems and could fix the to-be-minified code. As soon as it worked I switched back to using html-minifier and it worked.
I now use the following code where I tell html-minifier to use uglify-js wrapped in some code to recognize any problems.
function minifyWithHtmlMinifier(orig: string):string {
return htmlMinifier.minify(orig, {
// I have replaced the default minifyJS usage of the html-minifier
// as this silently ignores any uglifyjs errors and just displays
// the non-minified code. Has cost me half a day to figure out
// that it was not a configuration problem but problem with the
// to-be-minified-code.
// By setting the call to uglifyjs myself I can catch the errors and
// rethrow them, failing fast, as this is much better than recognizing
// a not-minified code much later when I don't remember what I changed
minifyJS: (text, _inline) => {
const minifyResult = uglifyjs.minify(text, {
compress: {
drop_console: true // remove all console log entries
}
});
if(minifyResult.warnings != null) {
minifyResult.warnings.forEach(logger.warn);
}
if(minifyResult.error) {
// TODO maybe use terser. I think it supports
// newer ES versions than uglifyjs
throw new Error(`Can't minify the iframe template!`, {cause:minifyResult.error});
}
return minifyResult.code;
},
minifyCSS: true,
ignoreCustomFragments: [/{{.*}}/g], // handlebars fragments
collapseBooleanAttributes: true,
collapseInlineTagWhitespace: true,
collapseWhitespace: true
});
}
Important to note: there is also html-minifier-terser that did not come up during my upfront research on what minifier to use with nodejs. Maybe it does better reporting.