ESM builds with Vite - Tree Shaking?

Happy New Year to everyone, I hope you have a wonderful and healthy 2026!

Thought I'd spend an hour investigating ZIM ES6 modules. I've installed Vite and put together a simple boilerplate test which uses dynamic imports to try to get the benefits of tree shaking, as I always loved using DISTILL with traditional ZIM.

Running in dev mode this code works, but when I build the output is 1487kB (compared with 327kB for CreateJs + distilled ZIM with some of my traditional ZIM games). Vite suggests that I use dynamic import() to code-split the application. I notice shader functions for instance are included in the output which I wouldn't have expected with tree-shaking.

I guess my question is, are CreateJs and ZIM set up to support tree-shaking, and if so how can I optimise it in the simple example below?

HTML:

<script type="module">
import { initZim } from "/js/bootZim.js";
initZim();
</script>

/js/bootZim.js

import { Frame, Circle } from "zimjs";

export function initZim({scaling="fit", width=1660, height=1080, color="#000"}) {
    new Frame({
      scaling: scaling,
      width: width,
      height: height,
      color: color,
      ready: ready
    });

    function ready() {
        new Circle(100, purple).center().drag();
        S.update();
    }
};

From what I understand, import {Frame, Circle} in theory should request from the server only that code and what that code uses - but I don't see that happening. I really don't. Until you run everything, there is no way it could figure out what code it needs. So I think it just downloads the whole of ZIM. Possibly, once it is downloaded the import can load only those things in memory - but then how is that different than just loading what is called into memory.

If anyone else knows... pipe in. Thanks for asking this question and doing some digging. It is a bit of a mystery to us - so a proper answer would be good to have.

I guess on the bundling side, you could ready the code that gets used and make files with it - before the user downloads. But how is the bundler figuring out what gets used if the user has not run things... I guess, maybe anything that could get called gets included. In that case - indeed, why is the shader stuff for a Perspective or Glitch showing up if those are local variables in classes not being used?

I know distill works. I just never bother using it as I think getting ZIM from cache is better. Load it once, keep using it. Why load different smaller versions of ZIM, unless, I suppose, someone is only going to see one ZIM feature. Even then, with a gzip being 300k - that is the size of a couple images. But that is just me.

I've looked into this a bit more. Bundlers tend to tree shake based upon exactly what each module imports, which should be limited only to what is used.

I believe that Vite can’t safely remove unused exports because importing anything might depend on everything. This is because ZIM has side effects on internal global variables throughout it's code, so it's a limitation due to the design of ZIM, which obviously predated modules.

With respect to using the cached version of ZIM, that makes a lot of sense. Most of my resources use a shared minimal distilled version of ZIM which gets cached for returning users and between our different resources.

1 Like

Okay - good to know. Thanks for the research and concise answer.