Eleventy: Minify Javascript - But as a separate file

Created on 21 Sep 2020  Β·  8Comments  Β·  Source: 11ty/eleventy

Hello,

I have seen Eleventy uses "Terser" plugin for minification. However the docs only explain how to inline it.

Suppose I have a file js/main.js then I want to minify it using Terser as js/main.min.js and then run addPassthroughCopy to copy to _site.

src/js/main.js --> src/js/main.min.js --> _site/js/main.min.js

is it something possible with Eleventy? if yes, Can share the sample code?

Thanks a lot.

education

Most helpful comment

Sorry, me again... OK I think I found _a_ solution. By putting the JavaScript file in /src/_includes/, and then including the file elsewhere and wrapping it in a {% terser %}...{% endterser %} shortcode, I think I got your use case working (without the need for extra build steps).

https://github.com/pdehaan/11ty-terser-test

tree . -aI "node_modules|.git"
.
β”œβ”€β”€ .eleventy.js
β”œβ”€β”€ .gitignore
β”œβ”€β”€ package-lock.json
β”œβ”€β”€ package.json
β”‚
β”œβ”€β”€ src # <<<<< INPUT FOLDER
β”‚Β Β  β”œβ”€β”€ _includes
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ layouts
β”‚Β Β  β”‚Β Β  β”‚Β Β  └── base.njk
β”‚Β Β  β”‚Β Β  └── site.js
β”‚Β Β  β”œβ”€β”€ index.njk
β”‚Β Β  └── scripts.njk
β”‚
└── www # <<<<< OUTPUT FOLDER
    β”œβ”€β”€ index.html
    └── site.min.js

4 directories, 10 files

So, src/_includes/site.js contains some truly horrible JavaScript.
In src/scripts.njk we set our output file permalink (so it writes a JavaScript file), and include our site.js file from src/_includes/, and wrap all the included output in our {% terser %}...{% endterser %} paired shortcode:

---
permalink: /site.min.js
---

{% terser %}
{%- include "site.js" -%}
{% endterser %}

This should let you still keep your JavaScript in a .js file so it can easily be formatted by Prettier or linted by ESLint, and your minified file gets written out to a separate file instead of inlined.

And if we open www/site.min.js in our code editor, we can see that it is beautifully minified.

function hello(l){l||(l="World"),alert("Hello "+l)}


Unminified src/_includes/site.js

// COPY 2020 BY PEOPLE

function hello(name) {
  if (!name) {
    name = "World";
  }
  alert("Hello" + " " +
  name)

  return
    true;
}

Finally, my terser paired shortcode is just the same example I posted over in https://github.com/11ty/eleventy/issues/1344#issuecomment-676497888 since I couldn't remember how to do the async terser in a sync paired shortcode off the top of my head.

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.addPairedShortcode("terser", jsmin);

  return {
    dir: {
      input: "src",
      output: "www"
    }
  };
};

All 8 comments

Wondering if you could just create src/js/main.js.njk (or liquid or whatever template you want), wrap the whole template it in some custom {% jsmin %}...{% endjsmin %} Terser paired tags, and then set a custom permalink to "/js/main.min.js" in your frontmatter.
Possibly not ideal as you'd probably lose the ability to lint your JavaScript via ESLint in the process though. Not sure if it'd be better to do minification via a separate post-Eleventy-build hook in your package.json file's "scritps":

"build": "eleventy build",
"postbuild": "npm run minify:js",
"minify:js": "terser src/js/main.js --compress --mangle -o _site/js/main.min.js",
"start": "....",
"test": "...."

Hello @pdehaan

Writing JS inside njk doesn’t seems a good idea. I was thinking Eleventy provides some option for this like Gulp.

The second option seems a good idea, but how can we make sure it always run together? In your example, I think I needed to run both command separately.

Still wondering how do others do it..

Npm scripts with β€œpre” and β€œpost” prefixes are a bit special. https://www.yld.io/blog/using-npm-pre-and-post-hooks

So if you type prebuild, build, then postbuild script. So you’d only need to type one command, but it wouldnt run postbuild if you typed eleventy directly from your Terminal.

Sorry, me again... OK I think I found _a_ solution. By putting the JavaScript file in /src/_includes/, and then including the file elsewhere and wrapping it in a {% terser %}...{% endterser %} shortcode, I think I got your use case working (without the need for extra build steps).

https://github.com/pdehaan/11ty-terser-test

tree . -aI "node_modules|.git"
.
β”œβ”€β”€ .eleventy.js
β”œβ”€β”€ .gitignore
β”œβ”€β”€ package-lock.json
β”œβ”€β”€ package.json
β”‚
β”œβ”€β”€ src # <<<<< INPUT FOLDER
β”‚Β Β  β”œβ”€β”€ _includes
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ layouts
β”‚Β Β  β”‚Β Β  β”‚Β Β  └── base.njk
β”‚Β Β  β”‚Β Β  └── site.js
β”‚Β Β  β”œβ”€β”€ index.njk
β”‚Β Β  └── scripts.njk
β”‚
└── www # <<<<< OUTPUT FOLDER
    β”œβ”€β”€ index.html
    └── site.min.js

4 directories, 10 files

So, src/_includes/site.js contains some truly horrible JavaScript.
In src/scripts.njk we set our output file permalink (so it writes a JavaScript file), and include our site.js file from src/_includes/, and wrap all the included output in our {% terser %}...{% endterser %} paired shortcode:

---
permalink: /site.min.js
---

{% terser %}
{%- include "site.js" -%}
{% endterser %}

This should let you still keep your JavaScript in a .js file so it can easily be formatted by Prettier or linted by ESLint, and your minified file gets written out to a separate file instead of inlined.

And if we open www/site.min.js in our code editor, we can see that it is beautifully minified.

function hello(l){l||(l="World"),alert("Hello "+l)}


Unminified src/_includes/site.js

// COPY 2020 BY PEOPLE

function hello(name) {
  if (!name) {
    name = "World";
  }
  alert("Hello" + " " +
  name)

  return
    true;
}

Finally, my terser paired shortcode is just the same example I posted over in https://github.com/11ty/eleventy/issues/1344#issuecomment-676497888 since I couldn't remember how to do the async terser in a sync paired shortcode off the top of my head.

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.addPairedShortcode("terser", jsmin);

  return {
    dir: {
      input: "src",
      output: "www"
    }
  };
};

Thanks a lot. Let me try it.

Actually, another (probably better) approach is to use Eleventy transforms.

Example repo at https://github.com/pdehaan/11ty-transform-async (does both HTML prettification/minification and JavaScript minification).

Oh Cool...

However don’t you think there should be a simple way in eleventy to handle this? Something similar to Gulp?

I think #117 would make this easier, so feel free to upvote that for a better workflow.

I don't know that adding a bunch of extra complexity to make Eleventy behave like Gulp is the best solution. Pretty sure you can use Gulp+Eleventy together now if you want. I've seen several 11ty templates out there that have full build systems like webpack or parcel or babel which do all sorts of clever file processing and that seems to work fairly well if you need a more robust solution.

Was this page helpful?
0 / 5 - 0 ratings