When working with Elm in GitHub Actions, caching dependencies efficiently can significantly speed up CI runs and improve reliability, as it prevents downloading dependencies every time, reducing exposure to outages and network failures.
Where does Elm store its dependencies?
Elm stores downloaded dependencies inside a hidden .elm
directory in the user’s home folder by default. On most systems, this directory is located at:
~/.elm
We can change the location where dependencies are stored by setting the ELM_HOME
environment variable in your shell. This allows us to relocate the .elm
directory to a location we control.
For example, we can move it to follow the XDG Base Directory Specification:
export ELM_HOME="$HOME/.cache/elm"
How can we only install Elm dependencies?
Unlike other package managers, Elm does not have a separate install command to fetch dependencies without compiling a project. Dependencies are only downloaded when Elm compiles a file in the project. Compiling the entire project could work, but it may be slow. However, there is a trick to circumvent this.
Using a temporary file
A workaround is to create a minimal valid Elm file that triggers dependency installation without requiring the compilation of the entire project. Below is an example:
module A exposing (a)
a = 0
Then, we create this file and compile it to download dependencies without affecting the main project. The compilation result is directed to /dev/null
, so it is discarded automatically without leaving any artifacts:
elm make Temp.elm --output=/dev/null
No Lockfile?
Elm does not have the concept of a “lockfile.” Instead, the elm.json
file stores exact package versions. This means we can base the cache on the elm.json
file. However, one downside is that any modification to elm.json—such as changing the Elm package version—will trigger a cache invalidation, even if dependencies remain unchanged.
Adding caching to GitHub Actions
Now, let’s put everything together in a GitHub Actions pipeline.
1. Set the ELM_HOME
Environment Variable
Modify your GitHub Actions workflow to define the ELM_HOME
path at the root level for consistency across runs. Here, we set it to the project’s root:
env:
ELM_HOME: ".elm"
2. Add a cache rule for the .elm
directory
Use the GitHub cache action to persist dependencies between workflow runs. This should go in the steps:
section of the workflow file.
- name: Cache Elm dependencies
id: elm_cache
uses: actions/cache@v3
with:
path: .elm
key: elm-${{ runner.os }}-${{ hashFiles('elm.json') }}
restore-keys: |
elm-${{ runner.os }}-
This ensures that dependencies are only redownloaded when elm.json
changes, as its content is used as the cache key. We set an id
so that we can reference it to check for cache hits.
3. Install dependencies using the temporary file trick
To ensure all dependencies are installed before the main build step, use the following command to create a temporary file and compile it, forcing dependency installation. This should also be included in the steps:
section of the workflow file.
- name: Install Elm dependencies
if: steps.elm_cache.outputs.cache-hit != 'true'
run: |
echo "module A exposing (a)\na=0" > Temp.elm
elm make Temp.elm --output=/dev/null
This step will be skipped if the cache is hit, meaning elm.json
has not changed.