A Solution to the Elm - Config Conundrum

When writing elm apps one often encounters the problem of how to get different configurations for different environments in the app. Often this is a base url for an API.

We are looking for a solution that supports all three different environments:

Previous solutions

This problem is known and was discussed before. Solutions include:

Adding a config-files outside of git, using a reverse proxy, serving the config as json file, passing it as flag or using string replacements. Link to Discussion

Another proposal is to use elm-app and environment variables. Link

All these solutions either lack the simplicity of just having a constant string in your code, or miss the point that configurations should be part of the git repository.

Looking at elm-make for a solution

elm-make --help responds with:

Usage: elm-make [FILES...] [--output FILE] [--yes] [--report FORMAT] [--debug]
                [--warn] [--docs FILE] [--prepublish] [--prepublish-core]

So, how about building the app with

elm-make src/Main.elm env/test/Env.elm --output target/test/elm.js

Env.elm could look like this:

module Env exposing (apiUrl)

apiUrl : String -> String
apiUrl str =
    "https://test.api-example.com/api" ++ str

Inside the app we can refer to it like this:

import Env exposing (apiUrl)

This allows us to compile Main.elm against different Env.elm-files.

What about elm-reactor

Elm-reactor has no arguments for extra files so the previous solution will not work.

It will tell us:

I cannot find module 'Env'.
Module 'Request is trying to import it.

Potential problems could be:
    * Misspelled the module name
    * Need to add a source directory or new dependency to elm-package.json

This can be solved by putting the Env.elm which should be used in local development into the src/ folder. It acts as a fallback as elm-make seems to prefer input files over files in the source directory.

Putting it all together

We have an Env.elm for every environment we want to build against:

.
├── env
│   ├── prod
│   │   └── Env.elm
│   └── test
│       └── Env.elm
└── src
    ├── Env.elm
    └── Main.elm


We also have a Env.elm lying in the src/ folder to be used by elm-reactor.

elm-package.json contains the src/ directory but not the env/ directory:

"source-directories": [
        "src"
    ],

And now, to make things more convenient: Let’s create a good old Makefile!

TARGET = target/js

all: test live

test:
	rm -f elm-stuff/build-artifacts/0.18.0/user/project/1.0.0/Env.elm*
	elm-make src/Main.elm env/test/Env.elm --output $(TARGET)/test/elm.min.js
	elm-make src/Main.elm env/test/Env.elm --output $(TARGET)/test/elm.debug.min.js --debug
	rm -f elm-stuff/build-artifacts/0.18.0/user/project/1.0.0/Env.elm*

live:
	rm -f elm-stuff/build-artifacts/0.18.0/user/project/1.0.0/Env.elm*
	elm-make src/Main.elm env/prod/Env.elm --output $(TARGET)/prod/elm.min.js
	elm-make src/Main.elm env/prod/Env.elm --output $(TARGET)/prod/elm.debug.min.js --debug
	rm -f elm-stuff/build-artifacts/0.18.0/user/project/1.0.0/Env.elm*

Why the rm -f call to elm-stuff you ask? It seems elm-make tries to use a cached and previously compiled artefact of Env.elm if it is available. So before and after every build we delete it.

Conclusion

This solution works for all environments without manual adaptions. The Makefile can be used by your build scripts. Local development works as good as ever and changing and distributing configurations is trivial.