The universal filter for inline minified JavaScript (Quick Tip 002) no longer works with Terser 5.0.0 and Eleventy 0.11.0. Instead of minifying the supplied code, it outputs an empty file/string.
To reproduce, test the filter as it appears in the documentation with Terser 4.8.0 vs 5.0.0.
const Terser = require("terser");
eleventyConfig.addFilter("jsmin", function(code) {
let minified = Terser.minify(code);
if( minified.error ) {
console.log("Terser error: ", minified.error);
return code;
}
return minified.code;
});
This appears to be because the minify() function is now async, as of Terser 5.0.0 (release notes). Is there a simple way to adapt universal filters for asynchronous behaviors? Happy to help explore this some more / update docs as required.
Thanks!
Any workaround to this?
If you're using Nunjucks, you can probably use addNunjucksAsyncFilter():
const Terser = require("terser");
module.exports = function (eleventyConfig) {
eleventyConfig.addNunjucksAsyncFilter("jsmin", async (code, callback) => {
try {
const minified = await Terser.minify(code);
return callback(null, minified.code);
} catch (err) {
console.error("Error during terser minify:", err);
return callback(err, code);
}
});
return {
dir: {
input: "src",
output: "www",
},
};
};
Thanks @pdehaan , that works. I'll submit a PR to update the docs.
The obvious downside is that it doesn't work globally anymore, and is limited to Nunjucks only.
But I'm not sure if any other languages support async filters. /cc @zachleat
I found #831 for adding async filter support for LiquidJS. Not sure if there'd be an easy way of enabling async filter support globally in .addFilter(), or if that's a major headache.
Yeah, that is unfortunate! According to #518, Eleventy currently can鈥檛 have asynchronous global filters because not all templating languages support them at this time.
Awesome thanks, everyone!
I poked around with this a bit more and think I found a solution which uses global .addFilter() method, the sync-rpc module, and the new async terser@5 .minify() code.
It requires a few more steps, but is hopefully a bit more cross-engine, if you aren't using Nunjucks.
const path = require("path");
const rpc = require("sync-rpc");
const jsminPath = path.join(__dirname, "jsmin.filter.js");
const minify = rpc(jsminPath);
module.exports = function (eleventyConfig) {
eleventyConfig.addFilter("jsmin", (code) => minify(code));
return {
dir: {
input: "src",
output: "www",
},
};
};
And my "jsmin.filter.js" file which is in the same directory as my .eleventy.js file is:
const Terser = require("terser");
module.exports = () => {
return async (code, opts={}) => {
try {
const minified = await Terser.minify(code, opts);
return minified.code;
} catch (err) {
console.error(err);
// Unexpected minify error. Return unminified code.
return code;
}
}
};
This works for me too, thanks @pdehaan.
@zachleat, I'm happy to update https://github.com/11ty/11ty-website/pull/707 if you think it's worth changing the docs to reflect one of these solutions?
Before you try a PR to update the docs, I found https://github.com/sindresorhus/make-synchronous yesterday that might be a bit cleaner. I鈥檒l try rewriting the terser minify wrapper with that which might let us use one less file.
I like this version with make-synchronous a bit better, if anybody else wants to try it out:
const makeSynchronous = require("make-synchronous");
const jsmin = makeSynchronous(async (code="", opts={}) => {
const Terser = require("terser");
try {
const minified = await Terser.minify(code, opts);
return minified.code;
} catch (err) {
console.error(err);
// Unexpected minify error. Return unminified code.
return code;
}
});
module.exports = function (eleventyConfig) {
eleventyConfig.addFilter("jsmin", jsmin);
return {
dir: {
input: "src",
output: "www",
},
};
};
Most helpful comment
I like this version with make-synchronous a bit better, if anybody else wants to try it out: