Eleventy: Universal filter `jsmin` not working with Terser 5.0.0

Created on 4 Aug 2020  路  10Comments  路  Source: 11ty/eleventy

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!

needs-triage

Most helpful comment

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",
    },
  };
};

All 10 comments

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",
    },
  };
};
Was this page helpful?
0 / 5 - 0 ratings