marc walter

Code bundle splitting of Elm programs


Splitting the code bundle is not a topic that comes up often with Elm, because most times we are blessed with small bundles without the headache.
These improvements introduced in 2018 remove unused Elm code before the JavaScript code is generated. So no useless JS code makes it into the bundle when using the --optimize flag and dead code is effectively eliminated.

To summarize the first two links: Elm js bundles are small in comparison.
For instance the Elm implementation of TodoMVC weighs 9kb (if uglified), and the Elm realworld example weighs 29kb. For comparison the React runtime alone weighs 32kb (back in 2018).
From the list of maintained realworld examples, I looked at a few react versions and this one seems to be one of the smallest ones (in bundle size), and generates two files totaling 85kb (2.a9e8cf08.chunk.js 77kb and main.7edcaa81.chunk.js 9kb).

All numbers assume that the JS code was minified (e.g. with uglify-js or terser) and gzipped.

There is also a comparison from 2020 looking at bundle size, lighthouse performance score and time-to-interactive, where Elm performs very well.

Nevertheless, if I'm now assuming that my Elm program bundle is too big and I want to reduce the initial bundle size, I can split my single big Elm program into multiple smaller ones.

To skip ahead, in the end we can load each program from one js file and all common code (like the Elm runtime) is placed in a shared file that is imported by each program and downloaded only once.

read more

Testing the fixes that are distributed with elm-janitor


Because several core Elm packages have known bugs with proposed bugfixes, the elm-janitor github org forked the packages and applied some fix PRs.

To test that these patches actually work, I created a test suite repository with:

  1. Tests that only need an Elm test runner
  2. Tests that need a fake browser environment
  3. Tests that need an actual browser

The repository is also useful to develop new patches for the upstream core Elm packages.

First time using deno


For this script I used deno for the first time, and I liked the experience:

1. The permission system

I like the permission system, I can give very exact permissions what to access. I don't have to worry that a dependency might wipe my disk if I only expect it to left-pad a string.

ELM_HOME=$PWD/elm-home deno run --allow-env=ELM_HOME,HOME --allow-read=$PWD --allow-write=$PWD/elm-home,, --all

This example code can only read and write to the file system where I want it to, access two environment variables, and contact a few domains.

If I don't specify permissions, or github switches the domain to serve code, the runtime asks me. That way a long-running script does not have to start from the very beginning.

┌ ⚠  Deno requests net access to "".
├ Requested by `fetch()` API.
├ Run again with --allow-net to bypass this prompt.
└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all net permissions) >

2. No transpiling of TypeScript needed

3. Publishing to npm

It is not hard to publish a tool written in deno to npm thanks to the dnt (Deno to Node transform) package tool.

Using patched Elm core packages vetted by elm-janitor

2023-05-23 (Last updated on 2023-11-25)

Several core Elm packages have known bugs with proposed bugfixes as pull requests, which were not yet merged.
These fixes are merged in batches by Evan, so it could be that they will only be merged with the next official Elm release.
That might be a way off because Evan wants to finish his exploratory work for Elm on the backend.

Rupert and other Elm community members have started grouping and ranking PR fixes, and created an online spreadsheet.

There is also a series of posts on the Elm discourse where the process is explained.

Right now, there is a github organization elm-janitor that contains forks of the official Elm repositories with several bugfixes merged in.


elm-janitor is a group of Elm users that are interested in maintaining the Elm core packages by applying patches to them. The patch stack will always be applied on top of the latest official release of the Elm core packages, with patches removed from the stack if they become part of the official release.

To avoid manually downloading patches and placing theem in the correct places, I created a script, elm-janitor/apply-patches

read more

Optimizing bundle size of Elm programs

2021-09-09 (Last updated on 2022-10-18)

Elm is well known for generating small bundle sizes (screenshot), as explained on the official website.

But after seeing this post about minifying Elm code I also wanted to investigate if the same results hold true when using the compiled Elm bundle as an ES module instead of the default IIFE output.

I want to use ESM, because I also load other parts of my applications in that format and I want to avaoid a global Elm state.

I have reached pretty much the same conclusion as Simon Lydell already published on the Elm discourse

  • For the smallest file size using a single tool, you can either pick UglifyJS or Google's closure compiler. Both take about the same time (9.5-10s in my case).
  • But you can achieve the same size result by running just parts of UglifyJS and then esbuild in about 40% of the time (~3.5s).
  • Esbuild alone produces about 10% more code than UglifyJS, but it needs only ~2% of the time (0.1s) when directly writing to a file.
  • SWC is comparable to Esbuild in compilation time, but not yet in compression ratio (16% bigger). They are working on reaching parity to terser, so re-visiting that in the future will be interesting.
  • Terser produces ~4% more code than UglifyJS but only needs 60% of the time.

Full results are documented on this page.

Debugging in Elm


Programming in Elm is pretty awesome, but sometimes you still have to do some debugging.
Like most languages, the language tooling can help you.


Elm has the Debug.log function that allows you to print any Elm data structure to the browser console. This works well for small structures, but it only prints it as one line and that is hard to read if it contains more data.
Fortunately, the Elm community member kraklin has created an Elm debug helper browser extension for that, so your code printed with Debug.log will become an expandable tree and might look like this:

Example output

Image source: Nicer Debug.log console output by @kraklin

Even if you can't or don't want to install it as an extension, you can still use it directly as an npm package or by adding a minimal snippet to your HTML in any browser.

<script src=""></script>

Time-Traveling Debugger (in elm/browser)

debugger button Debugging in Elm is quite nice with the time-traveling debugger.

You don't need to install a browser extension like the react developer tools or something like that, you just need to compile the code with the debug flag, e.g. elm make --debug src/Main.elm.

Then you will see the debugger in the bottom right corner of your page, can see the internal state and all the commands that were executed, and can go forward and backward in time to reproduce for instance a bug.

read more

Installing all Elm dependencies on a CI system


When configuring a CI pipeline for an Elm project, we ran into a couple of issues: The first problem was that the build step (which executes elm make) sometimes failed because downloading the Elm packages failed.
And the second one was that we were not able to easily cache the dependencies between runs in ~/.elm.

A failing pipeline is definitely annoying, and even more so if it looks like invalid code was pushed, if in fact only downloading dependencies failed because the package server is not reachable.

How we solved it:

First we changed the folder where all dependencies are downloaded to using the ELM_HOME environment variable. This allows the CI system to persist the cache easily, without needing access to ~/.elm. So the dependencies only need to get downloaded once and are then cached.

Then we faked a command to install all Elm dependencies. For that we created a minimal Elm file, compiled it (which downloads missing dependencies even though they are not used), and then removed it again.

As a simple script:

export ELM_HOME=elm-stuff/home

# create an Elm source file that definitely will compile
printf 'module InstallElmDeps exposing (main)\nimport Html exposing (text)\n\nmain = text "install"\n' > src/InstallElmDeps.elm
# run the compiler without output
elm make src/InstallElmDeps.elm --output /dev/null
# remove the file
rm src/InstallElmDeps.elm

Elm with custom elements (cheat sheet)


When exploring custom elements (often called Web Components) in combination with Elm, the following techniques seemed to be very important to me, and I want to have them easily available for future reference:


  1. Pass stringified data from Elm to the custom element as an HTML attribute (the custom element can watch on changes for these).
  2. Pass arbitrary JSON (also opaque objects like RTCPeerConnection) from Elm to the custom element
  3. Pass arbitrary JS data from the custom element to elm using DOM events

I use them also for the elm-conf example, but this post is easier to use as a cheat sheet.

read more

ELM-CONF: Using custom elements for WebRTC conferencing

2020-06-30 (Last updated on 2020-09-26)

This post shows how custom elements (often called Web Components) can be used to create a simple WebRTC conference solution with Elm.

The source code is available on github and the behavior is similar to the simple example from the W3C WebRTC spec.

My main goal was to NOT have two applications that need to synchronize state, but instead keep all state inside the Elm app and use ports to mutate it, and display it using custom elements.

read more