Skip to content

lydell/elm-minification-benchmarks

Repository files navigation

Elm Minification Benchmarks

A tool to help you evaluate what the best minifier for your Elm program is. The metrics are:

  • How small is the code after minification?
  • How small is the code after minification and brotli compression?
  • How fast is the minifier?
  • How much disk space does the minifier use, and how many dependencies does it have?
  • Does the minifier break the program?

The output table is sorted by brotli size, but feel free to make your own trade-offs and conclusions.

Inspired by the excellent JavaScript minification benchmarks repo.

The winner?

If you use SWC with the settings from the Elm Guide, you seem to get about the smallest size in very little time. It’s not the fastest, but still not even close to a second even on a large Elm app (depending on the computer of course). It has just two dependencies (which are other @swc/ packages). The executable size is roughly comparable to Elm’s executables.

import * as swc from "@swc/core";

const pureFuncs = [ "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9"]; // prettier-ignore

async function minify(code) {
  return (
    await swc.minify(code, {
      compress: {
        pure_funcs: pureFuncs,
        pure_getters: true,
        unsafe_comps: true,
        unsafe: true,
      },
      mangle: {
        reserved: pureFuncs,
      },
    })
  ).code;
}

How to run

  1. Clone this repo.
  2. Install dependencies: npm install
  3. Compile your Elm app: elm make --optimize --output elm.js path/to/your/Main.elm
  4. Run this tool on the compiled JS: node bench.js ../path/to/elm.js

How to add a new minifier

  1. Install it: npm install --save-exact my-minifier
  2. Copy minifiers/(none).js to minifiers/my-minifier.js.
  3. Edit the (code) => code function to run your minifier. The function takes a string and returns a string or a promise of a string.
  4. Try it with node bench.js example-azimutt.js.

How to add a new configuration for a minifier

  1. Copy minifiers/my-minifier.js to minifiers/my-minifier_variation.js.
  2. Edit it as you like.
  3. Try it with node bench.js example-azimutt.js.

Example

The winner in each column is marked with .

❯ node bench.js example-azimutt.js
┌─────────┬───────────────────────┬───────────┬────────────┬───────┬──────────────┬─────────┬──────────────┬─────────┬───────────────────────────────────────────────────────┐
│ (index) │ name                  │ version   │ time       │ x     │ size         │ %       │ brotli ↓     │ %       │ installation size and dependencies                    │
├─────────┼───────────────────────┼───────────┼────────────┼───────┼──────────────┼─────────┼──────────────┼─────────┼───────────────────────────────────────────────────────┤
│ 0       │ '(none)'              │ ''        │ ''         │ ''    │ '  1.35 MiB' │ ''      │ '   167 KiB' │ ''      │ ''                                                    │
│ 1       │ '@swc/core_elm-guide' │ '1.15.3'  │ '  108 ms' │ 'x2'  │ '   411 KiB' │ '-70 %' │ '❱  101 KiB' │ '-40 %' │ 'https://packagephobia.com/result?p=@swc/core'        │
│ 2       │ 'uglify-js_elm-guide' │ '3.19.3'  │ '  2.26 s' │ 'x50' │ '❱  402 KiB' │ '-71 %' │ '   102 KiB' │ '-39 %' │ 'https://packagephobia.com/result?p=uglify-js'        │
│ 3       │ '@swc/core'           │ '1.15.3'  │ '  124 ms' │ 'x3'  │ '   410 KiB' │ '-70 %' │ '   102 KiB' │ '-39 %' │ 'https://packagephobia.com/result?p=@swc/core'        │
│ 4       │ 'terser_elm-guide'    │ '5.44.1'  │ '  1.57 s' │ 'x35' │ '   412 KiB' │ '-70 %' │ '   102 KiB' │ '-39 %' │ 'https://packagephobia.com/result?p=terser'           │
│ 5       │ 'uglify-js'           │ '3.19.3'  │ '  2.13 s' │ 'x47' │ '   405 KiB' │ '-71 %' │ '   103 KiB' │ '-38 %' │ 'https://packagephobia.com/result?p=uglify-js'        │
│ 6       │ 'uglify-js+esbuild'   │ ''        │ '  1.60 s' │ 'x36' │ '   403 KiB' │ '-71 %' │ '   104 KiB' │ '-38 %' │ ''                                                    │
│ 7       │ 'terser'              │ '5.44.1'  │ '  1.30 s' │ 'x29' │ '   415 KiB' │ '-70 %' │ '   104 KiB' │ '-38 %' │ 'https://packagephobia.com/result?p=terser'           │
│ 8       │ 'oxc-minify'          │ '0.101.0' │ '❱  45 ms' │ 'x1'  │ '   421 KiB' │ '-69 %' │ '   105 KiB' │ '-37 %' │ 'https://packagephobia.com/result?p=oxc-minify'       │
│ 9       │ 'esbuild_tweaked'     │ '0.27.1'  │ '   56 ms' │ 'x1'  │ '   423 KiB' │ '-69 %' │ '   107 KiB' │ '-36 %' │ 'https://packagephobia.com/result?p=esbuild'          │
│ 10      │ 'esbuild'             │ '0.27.1'  │ '   70 ms' │ 'x2'  │ '   430 KiB' │ '-69 %' │ '   109 KiB' │ '-35 %' │ 'https://packagephobia.com/result?p=esbuild'          │
│ 11      │ 'bun'                 │ '1.3.3'   │ '❱  45 ms' │ 'x1'  │ '   432 KiB' │ '-69 %' │ '   110 KiB' │ '-34 %' │ 'https://packagephobia.com/result?p=bun'              │
│ 12      │ '@tdewolff/minify'    │ '2.24.7'  │ '   50 ms' │ 'x1'  │ '   434 KiB' │ '-69 %' │ '   114 KiB' │ '-32 %' │ 'https://packagephobia.com/result?p=@tdewolff/minify' │
└─────────┴───────────────────────┴───────────┴────────────┴───────┴──────────────┴─────────┴──────────────┴─────────┴───────────────────────────────────────────────────────┘

No side effects

Compiled Elm JS files start with this section:

function F2(fun) {
  return F(2, fun, function(a) { return function(b) { return fun(a,b); }; })
}
// …
function F9(fun) {
  return F(9, fun, function(a) { return function(b) { return function(c) {
    return function(d) { return function(e) { return function(f) {
    return function(g) { return function(h) { return function(i) {
    return fun(a, b, c, d, e, f, g, h, i); }; }; }; }; }; }; }; };
  });
}

function A2(fun, a, b) {
  return fun.a === 2 ? fun.f(a, b) : fun(a)(b);
}
// …
function A9(fun, a, b, c, d, e, f, g, h, i) {
  return fun.a === 9 ? fun.f(a, b, c, d, e, f, g, h, i) : fun(a)(b)(c)(d)(e)(f)(g)(h)(i);
}

Those are used with basically all function definitions (the F2F9 functions) and all function calls (the A2A9 functions), and are needed for currying and partial application.

Unfortunately, they can cause minifiers to skip optimizations, because the minifier can’t know if the F2F9 and A2–A9 functions cause side effects or not (spoiler: they don’t). For this reason, the Elm Guide configures the minifier to treat those as pure.

There are generally two ways a minifier knows which functions are pure:

  1. Through configuration.
  2. Through /* @__NO_SIDE_EFFECTS__ */ comments in the JS code.

Some minifiers support both ways, some support just one, and some support none.

It’s pretty easy to add the /* @__NO_SIDE_EFFECTS__ */ comments via string replacement:

function addNoSideEffectsComments(code) {
  return code.replace(/^function [FA]\d\(/gm, "/* @__NO_SIDE_EFFECTS__ */ $&");
}

The bench.js script supports doing that, by passing true as the second argument. It then duplicates all the benchmarks with _nos (No Side-effects) versions.

❯ node bench.js example-azimutt.js true
┌─────────┬───────────────────────────┬───────────┬────────────┬───────┬──────────────┬─────────┬──────────────┬─────────┬───────────────────────────────────────────────────────┐
│ (index) │ name                      │ version   │ time       │ x     │ size         │ %       │ brotli ↓     │ %       │ installation size and dependencies                    │
├─────────┼───────────────────────────┼───────────┼────────────┼───────┼──────────────┼─────────┼──────────────┼─────────┼───────────────────────────────────────────────────────┤
│ 0       │ '(none)'                  │ ''        │ ''         │ ''    │ '  1.35 MiB' │ ''      │ '   167 KiB' │ ''      │ ''                                                    │
│ 1       │ '@swc/core_elm-guide_nos' │ '1.15.3'  │ '  105 ms' │ 'x4'  │ '   411 KiB' │ '-70 %' │ '❱  101 KiB' │ '-40 %' │ 'https://packagephobia.com/result?p=@swc/core'        │
│ 2       │ '@swc/core_elm-guide'     │ '1.15.3'  │ '  107 ms' │ 'x4'  │ '   411 KiB' │ '-70 %' │ '❱  101 KiB' │ '-40 %' │ 'https://packagephobia.com/result?p=@swc/core'        │
│ 3       │ '@swc/core_nos'           │ '1.15.3'  │ '  106 ms' │ 'x4'  │ '   405 KiB' │ '-71 %' │ '   101 KiB' │ '-40 %' │ 'https://packagephobia.com/result?p=@swc/core'        │
│ 4       │ 'uglify-js_elm-guide'     │ '3.19.3'  │ '  2.27 s' │ 'x76' │ '❱  402 KiB' │ '-71 %' │ '   102 KiB' │ '-39 %' │ 'https://packagephobia.com/result?p=uglify-js'        │
│ 5       │ 'uglify-js_elm-guide_nos' │ '3.19.3'  │ '  2.29 s' │ 'x76' │ '❱  402 KiB' │ '-71 %' │ '   102 KiB' │ '-39 %' │ 'https://packagephobia.com/result?p=uglify-js'        │
│ 6       │ '@swc/core'               │ '1.15.3'  │ '  132 ms' │ 'x4'  │ '   410 KiB' │ '-70 %' │ '   102 KiB' │ '-39 %' │ 'https://packagephobia.com/result?p=@swc/core'        │
│ 7       │ 'terser_elm-guide_nos'    │ '5.44.1'  │ '  1.60 s' │ 'x53' │ '   412 KiB' │ '-70 %' │ '   102 KiB' │ '-39 %' │ 'https://packagephobia.com/result?p=terser'           │
│ 8       │ 'terser_elm-guide'        │ '5.44.1'  │ '  1.61 s' │ 'x54' │ '   412 KiB' │ '-70 %' │ '   102 KiB' │ '-39 %' │ 'https://packagephobia.com/result?p=terser'           │
│ 9       │ 'uglify-js_nos'           │ '3.19.3'  │ '  2.10 s' │ 'x70' │ '   405 KiB' │ '-71 %' │ '   103 KiB' │ '-38 %' │ 'https://packagephobia.com/result?p=uglify-js'        │
│ 10      │ 'uglify-js'               │ '3.19.3'  │ '  2.19 s' │ 'x73' │ '   405 KiB' │ '-71 %' │ '   103 KiB' │ '-38 %' │ 'https://packagephobia.com/result?p=uglify-js'        │
│ 11      │ 'uglify-js+esbuild_nos'   │ ''        │ '  1.62 s' │ 'x54' │ '   403 KiB' │ '-71 %' │ '   104 KiB' │ '-38 %' │ ''                                                    │
│ 12      │ 'uglify-js+esbuild'       │ ''        │ '  1.66 s' │ 'x55' │ '   403 KiB' │ '-71 %' │ '   104 KiB' │ '-38 %' │ ''                                                    │
│ 13      │ 'terser'                  │ '5.44.1'  │ '  1.33 s' │ 'x44' │ '   415 KiB' │ '-70 %' │ '   104 KiB' │ '-38 %' │ 'https://packagephobia.com/result?p=terser'           │
│ 14      │ 'terser_nos'              │ '5.44.1'  │ '  1.35 s' │ 'x45' │ '   415 KiB' │ '-70 %' │ '   104 KiB' │ '-38 %' │ 'https://packagephobia.com/result?p=terser'           │
│ 15      │ 'oxc-minify_nos'          │ '0.101.0' │ '   43 ms' │ 'x1'  │ '   417 KiB' │ '-70 %' │ '   104 KiB' │ '-38 %' │ 'https://packagephobia.com/result?p=oxc-minify'       │
│ 16      │ 'oxc-minify'              │ '0.101.0' │ '   46 ms' │ 'x2'  │ '   421 KiB' │ '-69 %' │ '   105 KiB' │ '-37 %' │ 'https://packagephobia.com/result?p=oxc-minify'       │
│ 17      │ 'esbuild_tweaked_nos'     │ '0.27.1'  │ '   57 ms' │ 'x2'  │ '   418 KiB' │ '-70 %' │ '   106 KiB' │ '-37 %' │ 'https://packagephobia.com/result?p=esbuild'          │
│ 18      │ 'esbuild_tweaked'         │ '0.27.1'  │ '   55 ms' │ 'x2'  │ '   423 KiB' │ '-69 %' │ '   107 KiB' │ '-36 %' │ 'https://packagephobia.com/result?p=esbuild'          │
│ 19      │ 'esbuild_nos'             │ '0.27.1'  │ '   56 ms' │ 'x2'  │ '   430 KiB' │ '-69 %' │ '   109 KiB' │ '-35 %' │ 'https://packagephobia.com/result?p=esbuild'          │
│ 20      │ 'esbuild'                 │ '0.27.1'  │ '   69 ms' │ 'x2'  │ '   430 KiB' │ '-69 %' │ '   109 KiB' │ '-35 %' │ 'https://packagephobia.com/result?p=esbuild'          │
│ 21      │ 'bun_nos'                 │ '1.3.3'   │ '❱  30 ms' │ 'x1'  │ '   432 KiB' │ '-69 %' │ '   110 KiB' │ '-34 %' │ 'https://packagephobia.com/result?p=bun'              │
│ 22      │ 'bun'                     │ '1.3.3'   │ '   46 ms' │ 'x2'  │ '   432 KiB' │ '-69 %' │ '   110 KiB' │ '-34 %' │ 'https://packagephobia.com/result?p=bun'              │
│ 23      │ '@tdewolff/minify_nos'    │ '2.24.7'  │ '   47 ms' │ 'x2'  │ '   434 KiB' │ '-69 %' │ '   114 KiB' │ '-32 %' │ 'https://packagephobia.com/result?p=@tdewolff/minify' │
│ 24      │ '@tdewolff/minify'        │ '2.24.7'  │ '   49 ms' │ 'x2'  │ '   434 KiB' │ '-69 %' │ '   114 KiB' │ '-32 %' │ 'https://packagephobia.com/result?p=@tdewolff/minify' │
└─────────┴───────────────────────────┴───────────┴────────────┴───────┴──────────────┴─────────┴──────────────┴─────────┴───────────────────────────────────────────────────────┘

About

A tool to help you evaluate what the best minifier for your Elm program is.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published