Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Language Atlas (mini-courses for more languages)  Module 12 – Elm and The Elm Architecture

Elm 0.19.3: install, build and run headlessly

Install Elm 0.19.3 and check the download, start a project with elm init, compile it to JavaScript and run it under Node.js through Platform.worker.

  • Beginner
  • 25 minutes
  • Examples run with Python 3.14.8, Node.js 24.21.0, quickjs 0.32.0, Elm 0.19.3 and elm-test 0.19.3-0
  • By MySmartCoPilot
Free lesson, Pro answers: 25 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Install Elm 0.19.3 from its release, check the file against its published SHA-256 and read the version
  • Create a project with elm init and explain what elm.json and the ~/.elm package cache each hold
  • Compile a module to JavaScript with elm make, with and without --optimize, and compare the two files
  • Run an Elm program without a browser, through Platform.worker and a port under Node.js
  • Decide whether Elm fits a front end from what its compiler checks and what that costs

Before you start

What you’ll learn · Before you start

What you will learn

  • Install Elm 0.19.3 from its release, check the file against its published SHA-256 and read the version
  • Create a project with elm init and explain what elm.json and the ~/.elm package cache each hold
  • Compile a module to JavaScript with elm make, with and without --optimize, and compare the two files
  • Run an Elm program without a browser, through Platform.worker and a port under Node.js
  • Decide whether Elm fits a front end from what its compiler checks and what that costs

Before you start

On this page

A download of 15,126,132 bytes holds the whole Elm compiler. In this lesson’s recorded sessions it became one program of 61,507,360 bytes, which turned a 58-line program into a JavaScript file of 65,875 bytes, and Node.js ran that file with no browser at all and printed a receipt for 6,750 paise.

Here you install Elm 0.19.3 and check the download, start a project and read where its package versions are kept, compile it with and without --optimize, run it headlessly through a port, and then weigh what Elm’s compiler checks for you against what that costs, so you can say where Elm fits and where it does not.

Elm is one program, and you check it before you run it

The 0.19.3 release on GitHub offers five binaries and two installers. For a Mac with Apple silicon the file is elm-0.19.3-mac-arm.gz, 15,126,132 bytes, and the release page lists its SHA-256 digest beside it. The guide points Mac and Windows users to an installer; the compiler’s own install notes for macOS do the same job in four commands: download with curl, gunzip, chmod +x, and a move into a folder on PATH. Follow them, with the two checks that send you back a step:

Interactive · stage by stage From the release page to a first project Step through the install, with the two checks that send you back a step when they fail.
From the release page to a first project, step 8 of 8. Showing Download elm.gz, Check the SHA-256, Unpack and mark it, Put elm on PATH, Read the versions, Start a project. In focus: Start a project.digits differanother elm answersDownload elm.gzelm-0.19.3-mac-arm.gzCheck the SHA-256shasum -a 256 -cUnpack and mark itgunzip, then chmod +xPut elm on PATH~/.local/bin, firstRead the versionselm, node and elm-testStart a projectelm init

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. Download. The 0.19.3 release offers elm-0.19.3-mac-arm.gz for Apple silicon, 15,126,132 bytes, and lists its SHA-256 digest beside it. The install notes save it as elm.gz.
  2. Compare the digest. Put the published digest and the file name on one line and give it to shasum -a 256 -c. It works out the digest of the file you have and prints OK when the two are equal.
  3. Digits differ. A different digest means a different file: a download cut short, or not the file the release published. Delete it and download it again before you run anything.
  4. Unpack. gunzip replaces elm.gz with elm, one program of 61,507,360 bytes, and chmod +x lets the shell run it. The compiler, the package manager, the REPL and the reactor are all in that file.
  5. PATH. Move elm into a folder that comes first on PATH. The install notes use /usr/local/bin with sudo; a folder of your own, such as ~/.local/bin, needs no sudo. command -v elm names the file the shell will run.
  6. The versions. elm --version printed 0.19.3, node --version printed v24.21.0 and elm-test --version printed 0.19.3-0: the pins of every session in this module.
  7. Another elm answers. If elm --version prints an older number, an earlier install comes first on PATH, from npm or an installer. command -v elm says which file that is; put the new folder in front of it.
  8. A first project. elm init writes elm.json and an empty src folder, after asking first. The next section runs it twice, with and without the package cache.

The recorded session starts on a computer with Node.js and no Elm, with the file that curl -L -o elm.gz … saved. Each $ line is a command you would type, and below it is what the command printed:

Check the download, unpack it, put elm on PATH, read the versions Python · install_session.py
"""Installs Elm 0.19.3 from its GitHub release on a computer that has Node.js and no Elm, checks the download against
the SHA-256 digest that the release page lists beside the file, and reads the versions (session.py plays it).

Downloading needs the network, which the session keeps switched off, so it starts with the file that
curl -L -o elm.gz https://github.com/elm/compiler/releases/download/0.19.3/elm-0.19.3-mac-arm.gz saved. elm-test comes
from npm (npm install --global elm-test@0.19.3-0 on a computer with a network)."""

from session import download, run

download("elm-0.19.3-mac-arm.gz", saved_as="elm.gz")
run("""
! command -v elm
wc -c < elm.gz
echo "a426e47d14cba0588aa81475adc32ba24bbf045209d0c560d0a89a5c4f3c34d0  elm.gz" | shasum -a 256 -c
gunzip elm.gz
chmod +x elm
mkdir -p $HOME/.local/bin
mv elm $HOME/.local/bin/
export PATH=$HOME/.local/bin:$PATH
command -v elm
elm --version
wc -c < $HOME/.local/bin/elm
node --version
elm-test --version
""", elm_on_path=False)

Output

$ command -v elm
[exit status 1]
$ wc -c < elm.gz
 15126132
$ echo "a426e47d14cba0588aa81475adc32ba24bbf045209d0c560d0a89a5c4f3c34d0  elm.gz" | shasum -a 256 -c
elm.gz: OK
$ gunzip elm.gz
$ chmod +x elm
$ mkdir -p $HOME/.local/bin
$ mv elm $HOME/.local/bin/
$ export PATH=$HOME/.local/bin:$PATH
$ command -v elm
~/.local/bin/elm
$ elm --version
0.19.3
$ wc -c < $HOME/.local/bin/elm
 61507360
$ node --version
v24.21.0
$ elm-test --version
0.19.3-0

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 install_session.py

command -v elm failed first: nothing called elm was on PATH. shasum -a 256 -c read one line, the published digest and a file name, worked out the digest of elm.gz and printed OK because the two were equal. gunzip replaced elm.gz with elm, which chmod +x made runnable. The install notes move it into /usr/local/bin with sudo; the session uses ~/.local/bin, a folder of your own, and puts it first on PATH, so command -v elm names that file. Unpacked, the compiler is 61,507,360 bytes, about four times the download, and it holds elm make, elm install, elm repl and elm reactor in one file.

The last three lines are this module’s toolchain pin: The exact version of the compiler, interpreter or SDK that an example was built and run with, such as Tcl 9.0.4 or the .NET SDK 10.0.401. Another version may print something else for the same file. More in “How the Language Atlas works”: 0.19.3, Node.js v24.21.0, which runs the compiled programs and elm repl, and 0.19.3-0 for elm-test, the test runner, which is not part of the compiler and comes from npm.

Hash Generator Work out the SHA-256 of a downloaded file in your browser and compare it with the digest on the release page.

Releases after Elm 0.19.3

The 0.19.3 release notes say there are no language changes, so a project moves to it by changing elm-version in elm.json, and the post that announced it plans more small releases on the way to 1.0. A later release can print other messages and file sizes for the same code, so compare your output with these sessions only when elm --version prints 0.19.3.

Checkpoint

Check: Installing Elm and checking the download

  1. Question 1 of 2 shasum -a 256 -c printed "elm.gz: OK". What has that shown?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The file has the same SHA-256 digest as the one the release page lists, so it is the file that was published

    Check the download before you unpack or run it. Equal digests mean the file arrived complete and is the one the release published; a different digest means delete it and download it again.

  2. Question 2 of 2 You have just put Elm 0.19.3 in ~/.local/bin, yet elm --version prints 0.19.1. What do you check first?

    Choose one answer.

    A hint

    The shell runs the first elm it finds on PATH.

    Show the answer: Question 2 of 2

    Answer: command -v elm, to see which elm the shell runs, and whether ~/.local/bin comes first on PATH

    command -v elm names the file the shell runs. If it is not the one you unpacked, put its folder in front on PATH, then ask elm --version again.

elm init writes elm.json, and only the first build needs the package site

elm init writes the one file every Elm project has, elm.json, and a src folder. To fill in the versions it needs the list of every published package, which comes from package.elm-lang.org. The session runs it twice with the network switched off: first with an empty package cache in ~/.elm, as on a computer that has never built Elm, then with the cache a computer has after its first builds. Both times elm init asks before it writes anything, and the session answers y:

elm init with an empty cache, then with a full one, offline Python · project_session.py
"""Creates a project with elm init twice: first on a computer whose package cache, ~/.elm, is empty, then with the cache
that a computer has after it has built a project once, both with the network switched off (session.py plays it).
elm init asks before it writes elm.json, and the session answers y."""

from session import cache, run

run("""
mkdir first
cd first
! elm init
""", stdin="y\n")
cache()
run("""
# ~/.elm now holds the packages that the first builds downloaded
elm init
ls
cat elm.json
ls $HOME/.elm/0.19.3/packages/elm
""", stdin="y\n")

Output

$ mkdir first
$ cd first
$ elm init
Hello! Elm projects always start with an elm.json file. I can create them!

Now you may be wondering, what will be in this file? How do I add Elm files to
my project? How do I see it in the browser? How will my code grow? Do I need
more directories? What about tests? Etc.

Check out <https://elm-lang.org/0.19.3/init> for all the answers!

Knowing all that, would you like me to create an elm.json file now? [Y/n]: -- PROBLEM LOADING PACKAGE LIST ------------------------------------------------

I need the list of published packages before I can start initializing projects,
so I tried to fetch:

    https://package.elm-lang.org/all-packages

But my HTTP library is giving me the following error message:

    ConnectionFailure Network.Socket.getAddrInfo (called with preferred socket type/protocol: AddrInfo {addrFlags = [AI_ADDRCONFIG], addrFamily = AF_UNSPEC, addrSocketType = Stream, addrProtocol = 0, addrAddress = 0.0.0.0:0, addrCanonName = Nothing}, host name: "package.elm-lang.org", service name: "443"): does not exist (nodename nor servname provided, or not known)

Are you somewhere with a slow internet connection? Or no internet? Does the link
I am trying to fetch work in your browser? Maybe the site is down? Does your
internet connection have a firewall that blocks certain domains? It is usually
something like that!
[exit status 1]
# ~/.elm now holds the packages that the first builds downloaded
$ elm init
Hello! Elm projects always start with an elm.json file. I can create them!

Now you may be wondering, what will be in this file? How do I add Elm files to
my project? How do I see it in the browser? How will my code grow? Do I need
more directories? What about tests? Etc.

Check out <https://elm-lang.org/0.19.3/init> for all the answers!

Knowing all that, would you like me to create an elm.json file now? [Y/n]: Okay, I created it. Now read that link!
$ ls
elm.json
src
$ cat elm.json
{
    "type": "application",
    "source-directories": [
        "src"
    ],
    "elm-version": "0.19.3",
    "dependencies": {
        "direct": {
            "elm/browser": "1.0.2",
            "elm/core": "1.0.5",
            "elm/html": "1.0.1"
        },
        "indirect": {
            "elm/json": "1.1.4",
            "elm/time": "1.0.0",
            "elm/url": "1.0.0",
            "elm/virtual-dom": "1.0.5"
        }
    },
    "test-dependencies": {
        "direct": {},
        "indirect": {}
    }
}
$ ls $HOME/.elm/0.19.3/packages/elm
browser
bytes
core
html
json
random
time
url
virtual-dom

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 project_session.py

The first try stopped with PROBLEM LOADING PACKAGE LIST and exit status 1: it could not fetch all-packages, and it wrote nothing. The second worked offline, because ~/.elm already held the list and the packages. The compiler keeps that cache in ~/.elm, or in the folder that the ELM_HOME variable names, and every project on the computer shares it: here it also holds bytes and random, packages that only a later lesson’s tests use.

Read elm.json from the top. "type": "application" says this is a program, not a package. "source-directories" says where the modules are. "elm-version" is the exact compiler, 0.19.3. Then come the dependencies: three direct ones, which your code may import, and four indirect ones, which those three need. Every one has a single exact version: elm/core is 1.0.5, not a range. The compiler’s documentation for this file says that the exact versions make elm.json double as a A file that records the exact version of every package a build uses, so that every later build gets the same ones. An Elm application's elm.json is one; npm keeps a separate package-lock.json., and that the indirect ones are listed so that builds are reproducible and you can see everything you depend on. A package’s own elm.json is different: elm/browser’s, in the cache, asks for ranges such as "1.0.0 <= v < 2.0.0", which an application then resolves to one version each. Step through what happened, and what a computer with a network does:

Interactive · message by message elm init and the first build, with and without the cache Step through elm init on a computer that has never built Elm, then on one that has.
elm init and the first build, with and without the cache, step 7 of 7. Messages so far: elm init to Package site: GET /all-packages; Package site to ~/.elm: all packages and versions; Package site to ~/.elm: seven packages; ~/.elm to elm init: list and packages.elm initin the folder first/Package sitepackage.elm-lang.org~/.elmthe package cacheGET /all-packagesall packages and versionsseven packageslist and packages

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. A question first. elm init explains what elm.json is and asks before it writes anything. The session answers y, as you would by typing it.
  2. The list of packages. To choose versions of elm/core, elm/browser and elm/html, elm init needs the list of every published package and version, so it asks the package site for it.
  3. Offline, empty cache. With the network off and nothing in ~/.elm, the request fails and elm init stops with PROBLEM LOADING PACKAGE LIST and exit status 1. No elm.json is written.
  4. Online. On a computer with a network the site answers, and elm keeps the list in ~/.elm, so that later commands can choose versions without asking again.
  5. Exact versions. elm.json lists three direct dependencies and four indirect ones, each with one exact version: elm/core 1.0.5, elm/json 1.1.4 and so on. The file is its own lock file.
  6. The first build. The first elm make downloads the source of each package into ~/.elm/0.19.3/packages and compiles it once. The cache is shared by every project on the computer.
  7. Every later init and build. With the cache in place, elm init works offline, as the session's second try did, and elm make prints Dependencies ready! without asking the site anything.

Keep elm.json, leave out elm-stuff

Commit elm.json, since it fixes every version of the build. elm make writes compiled modules into an elm-stuff folder next to it, a cache it rebuilds whenever it is missing, so it stays out of version control, as the build folders of other languages do.

Checkpoint

Check: elm.json and the package cache

  1. Question 1 of 2 The application's elm.json says "elm/core": "1.0.5". What does that mean?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Every build of this application uses elm/core 1.0.5 exactly

    An application's elm.json has one exact version for every package, direct and indirect, so the file itself fixes the build. Only packages publish ranges, which an application then resolves.

  2. Question 2 of 2 Both elm init runs of the session had the network switched off. Why did the second one work?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: ~/.elm already held the list of published packages and the packages themselves, so nothing had to be fetched

    The package cache in ~/.elm (or ELM_HOME) is shared by every project. A computer needs the network until the cache holds the package list and the packages, and from then on init and builds work offline.

elm make writes one JavaScript file, and the optimize flag changes little on a small one

orders/ is a project with the elm.json that elm init wrote and one module, src/Orders.elm: two lines of an order priced in paise, added up and sent out as a receipt. The session builds it twice, plainly and with --optimize, measures each file and runs it:

Build the Orders worker twice, measure it and run it under Node.js Python · build_session.py
"""Builds the Orders worker without and with --optimize, runs each build under Node.js through the port, measures both
files and shows the two lines that end the compiled file; late.cjs then subscribes too late (session.py plays it)."""

from session import cache, run

cache()
run("""
cd orders
elm make src/Orders.elm --output=build/orders.js
wc -c < build/orders.js
node run.cjs
elm make src/Orders.elm --optimize --output=build/orders.js
wc -c < build/orders.js
gzip -9 < build/orders.js | wc -c
tail -n 2 build/orders.js
node run.cjs
node late.cjs
""")

Output

$ cd orders
$ elm make src/Orders.elm --output=build/orders.js
Dependencies ready!
Success! Compiled 1 module.

    Orders ───> build/orders.js
$ wc -c < build/orders.js
   65875
$ node run.cjs
Compiled in DEV mode. Follow the advice at https://elm-lang.org/0.19.3/optimize for better performance and smaller assets.
chai    2 x ₹12.00  ₹24.00
samosa  3 x ₹14.50  ₹43.50
total               ₹67.50
$ elm make src/Orders.elm --optimize --output=build/orders.js
Success!

    Orders ───> build/orders.js
$ wc -c < build/orders.js
   65308
$ gzip -9 < build/orders.js | wc -c
   15746
$ tail -n 2 build/orders.js
_Platform_export({'Orders':{'init':$author$project$Orders$main(
	$elm$json$Json$Decode$succeed(0))(0)}});}(this));
$ node run.cjs
chai    2 x ₹12.00  ₹24.00
samosa  3 x ₹14.50  ₹43.50
total               ₹67.50
$ node late.cjs
messages that reached a subscriber added in a timer: 0

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 build_session.py

The plain build printed Dependencies ready! and Success! Compiled 1 module. and wrote build/orders.js, 65,875 bytes. Running it printed a warning first: Compiled in DEV mode, with a link to the compiler’s advice on optimising. The second build printed only Success!: the module was already compiled in elm-stuff, so there was nothing new to compile. Its file is 65,308 bytes, 567 bytes or 0.9 % smaller, 15,746 bytes after gzip -9, and it printed the same receipt without the warning.

So what does --optimize do? elm make --help says it shortens record field names and unboxes values, and the post that introduced it in 0.19 says the renaming tends to make files 5 % to 10 % smaller. Orders is too small to show that: most of its file is the runtime and the core functions it calls, and it has three record fields of its own. Two things happen in every build, optimised or not. The types are checked before anything is written, and dead code elimination: A compiler step that leaves out every function a program can never call. Elm does it in every build, function by function, across the application and all its packages, because Elm functions cannot be redefined while the program runs. drops every function that main cannot reach, which the same post says works across all packages because each one on the package site is written entirely in Elm. The big saving is a minifier, which the compiler’s notes on optimising add as a second step; the project lesson of this module measures it. Step through one build:

Interactive · step by step What one elm make reads and writes Step through the two builds of the Orders worker, from the source file to the program Node.js runs.
What one elm make reads and writes, step 8 of 8. Showing src/Orders.elm, ~/.elm packages, elm make (keeps what main uses), elm-stuff/, build/orders.js (65,308 bytes optimised), node run.cjs. In focus: node run.cjs.src/Orders.elmyour module, with main~/.elm packageselm/core 1.0.5 and otherselm makekeeps what main useselm-stuff/compiled modulesbuild/orders.js65,308 bytes optimisednode run.cjsprints the receiptElm.Orders

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. Your module. elm make reads src/Orders.elm, the module named on the command line, and every module it imports, here only modules of elm/core.
  2. Packages from the cache. The packages come from ~/.elm, already compiled, so the build needs no network: Dependencies ready!
  3. Types first. Nothing is written until every type fits. A mismatch, such as a number added to a string, stops the build with a message and exit status 1; the next lesson shows one.
  4. Only what main reaches. Elm drops every function that main cannot reach, in your code and in the packages, so the file holds the runtime and the functions the receipt uses, not all of elm/core.
  5. One file. The build writes build/orders.js, 65,875 bytes: the runtime, the used functions and your module, wrapped so that it sets Elm.Orders on whatever loads it.
  6. Kept for next time. The compiled module also goes into elm-stuff/. The --optimize build found it there and printed only Success!, without Compiled 1 module.
  7. With --optimize. The optimised file is 65,308 bytes: record fields such as init and update get short names, and the warning about DEV mode is gone. On a program this small that saves under 1 %.
  8. Node.js runs it. node run.cjs loads the file, starts the worker and prints the three lines of the receipt, the same from both builds.

JavaScript Minifier Paste a compiled file into a minifier in your browser and compare its size before and after.

Key idea

elm make checks every type, keeps only what main reaches and writes one JavaScript file; --optimize renames record fields on top of that, which saves little on a small program and is a step before minifying, not instead of it.

Checkpoint

Check: What elm make and --optimize write

  1. Question 1 of 2 The plain build of Orders was 65,875 bytes and the --optimize build 65,308 bytes. How many bytes did --optimize save?

    Type a number.

    Show the answer: Question 1 of 2

    Answer: 567 bytes

    65,875 − 65,308 = 567 bytes, under 1 % of the file. Most of the file is the runtime and the core functions the receipt calls, and the program has only three record fields to rename; a minifier is the step that shrinks it.

  2. Question 2 of 2 The --optimize build printed only "Success!", without "Compiled 1 module." Why?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Orders was already compiled in elm-stuff, so there was nothing new to compile

    elm make keeps compiled modules in elm-stuff and recompiles only what changed. The output file is still written each time, in the mode you asked for.

Platform.worker runs Elm without a page, and a port carries the result out

A browser program draws a view. Orders has none: its main is a Platform.worker: The kind of Elm program that has no view, only init, update and subscriptions. It runs under Node.js or in a page without drawing anything and talks to JavaScript through ports, so Elm code can run headlessly., which the core library’s documentation calls a program with no user interface. It takes three functions: init starts it, update handles messages and subscriptions says which outside events it listens to. Its only way to show anything is a port: A typed channel between Elm and JavaScript, declared with the port keyword in a port module. A port that returns Cmd sends values out to JavaScript subscribers; one that returns Sub brings values sent from JavaScript in as messages.: port report : String -> Cmd msg declares a command that sends a string to JavaScript, and the first line, port module Orders, is what allows a module to declare one:

Orders.elm: an order in paise, a receipt sent through a port ELM · orders/src/Orders.elm
port module Orders exposing (main)

{-| Adds up a small order priced in paise and sends a receipt out through the
port `report`. A Platform.worker has no view, so Node.js can run it.
-}


port report : String -> Cmd msg


type alias Line =
    { item : String
    , paise : Int
    , quantity : Int
    }


order : List Line
order =
    [ { item = "chai", paise = 1200, quantity = 2 }
    , { item = "samosa", paise = 1450, quantity = 3 }
    ]


lineTotal : Line -> Int
lineTotal line =
    line.paise * line.quantity


rupees : Int -> String
rupees paise =
    "₹" ++ String.fromInt (paise // 100) ++ "." ++ String.padLeft 2 '0' (String.fromInt (modBy 100 paise))


receipt : List Line -> String
receipt lines =
    let
        row line =
            String.padRight 8 ' ' line.item
                ++ String.fromInt line.quantity
                ++ " x "
                ++ rupees line.paise
                ++ "  "
                ++ rupees (lineTotal line)

        total =
            List.sum (List.map lineTotal lines)
    in
    String.join "\n" (List.map row lines ++ [ "total" ++ String.repeat 15 " " ++ rupees total ])


main : Program () () Never
main =
    Platform.worker
        { init = \_ -> ( (), report (receipt order) )
        , update = \_ model -> ( model, Cmd.none )
        , subscriptions = \_ -> Sub.none
        }

init ignores its flags and returns two things: the model, (), which holds nothing, and the command report (receipt order). update and subscriptions do nothing, since nothing ever sends this program a message. The JavaScript side is three lines of code:

run.cjs: start the worker and print what the port sends JavaScript · orders/run.cjs
// Starts the Orders worker of build/orders.js and prints what it sends through its port `report`.
const { Elm } = require('./build/orders.js');

const app = Elm.Orders.init();
app.ports.report.subscribe((text) => console.log(text));

Step through one run, the one the session recorded twice:

Interactive · message by message One run of the Orders worker under Node.js Step through node run.cjs, from loading the compiled file to the receipt it prints.
One run of the Orders worker under Node.js, step 7 of 7. Messages so far: run.cjs to Elm runtime: init(); Elm runtime to Orders: init (); Orders to Elm runtime: report receipt; Elm runtime to run.cjs: app.ports.report; Elm runtime to run.cjs: the receipt text.run.cjsNode.js 24.21.0Elm runtimein build/orders.jsOrdersyour codeinit()init ()report receiptapp.ports.reportthe receipt textthree lines printed

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. Load the file. require('./build/orders.js') runs the compiled file. It hands its programs to the this of the module that loads it, so run.cjs gets an object Elm with one key, Orders.
  2. Start the program. run.cjs calls Elm.Orders.init(). No node of a page is given, because a worker has no view to draw.
  3. Your init runs. The runtime calls the init of Platform.worker with the flags, here the unit value ().
  4. A model and a command. init returns the pair ((), report (receipt order)): a model with nothing in it, and a command that asks the runtime to send one string out through the port report.
  5. Subscribe in time. init() has returned app, and run.cjs subscribes to app.ports.report on the next line, in the same turn of the event loop, before the runtime delivers the command.
  6. The string arrives. The runtime converts the Elm String to a JavaScript string and calls every function subscribed to report with it.
  7. Printed. console.log prints the three lines: chai, samosa and a total of 67.50 rupees. The program sends nothing else, so Node.js exits when nothing is left to do.

run.cjs uses CommonJS on purpose. The compiled file is one function called with this, and it puts Elm on that object, as the two lines that end build/orders.js show in the session: _Platform_export({'Orders': …}) and }(this));. Under require, this is the module’s exports, and in a browser <script> it is the window. Loaded as an ES module, from a folder whose package.json says "type": "module", the same file finds no this to put Elm on.

Subscribing to a port after a delay

The receipt comes from init, and the runtime delivers it once the code that called init() has finished its turn. run.cjs subscribes on the very next line and gets it; the session’s last command, node late.cjs, subscribes from a timer and reports 0 messages. Subscribe to every port right after init() returns.

Checkpoint

Check: Platform.worker and an outgoing port

  1. Question 1 of 2 What lets Node.js run the Orders program, which has no browser to draw in?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Its main is a Platform.worker, a program with no view, and it sends its result through a port

    Platform.worker is the headless kind of Elm program. With a port it can hand results to JavaScript, which is how this module runs and checks Elm code without a browser.

  2. Question 2 of 2 Orders declares port report : String -> Cmd msg. Which way does the data go, and what does JavaScript do with it?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Out of Elm: init returns a report command, and JavaScript receives the string through app.ports.report.subscribe

    A port with a Cmd type carries values out, and JavaScript subscribes to it. A port with a Sub type carries values in, and JavaScript sends to it. The next lessons use both.

What the compiler checks for you, and what that costs

Elm’s homepage leads with no runtime exceptions, friendly error messages and great performance, and lists small assets and enforced semantic versioning among its features. Two of those you have now seen at work: a compiler that checks every type before it writes a line, and a build that keeps only what main reaches. The third is about packages. elm diff compares the types of two versions of a package, and the homepage says Elm uses that check to make every package follow semantic versioning, so a patch release cannot remove a function you call.

These checks come at a price, and the price is the border with JavaScript. Elm code reaches JavaScript only through flags at start-up, ports and custom elements, and every package on the package site is written in Elm, so an npm library is used by writing a port for it. The language also moves slowly and with care: the four releases of 0.19 changed the language once, at 0.19.0, and each later release says there are no language changes. Walk through them:

Timeline · 4 events in order The four releases of Elm 0.19 Walk through the releases in order, and note which ones changed what your code may say.
  1. 0.19.0

    Small assets by default language changes

    Every build drops the functions that main cannot reach, and --optimize also shortens record field names. Code written for 0.18 had to be upgraded: the last release that changed the language.

  2. 0.19.1

    Clearer errors, faster rebuilds no language changes

    No language changes: better parse error messages, faster incremental compiles and file locks on the cache. Three bug fixes can stop old code compiling, such as a port module with no ports.

  3. 0.19.2

    Faster builds no language changes

    Less allocation while parsing: on 850,000 lines, 20 % less copying in garbage collection, 10 % lower peak memory and 7 % faster overall. No language changes.

  4. 0.19.3

    Correct by construction this course

    More precise types inside the compiler, and better primitives for concurrency, binary data and file locking, as a base for later releases. No language changes. This module runs it.

The timeline has 4 events, from 0.19.0 to 0.19.3.

“No runtime exceptions” also has edges, and the next two lessons record them: modBy 0 stops a program with an error, and so does a value of the wrong type sent through a port. The rule holds inside your Elm code; at the border, you check.

So Elm fits front ends where wrong states and crashes cost more than a small ecosystem does: forms, flows with many steps, dashboards a team keeps for years. It can start small, because Browser.element takes over one node of an existing page, and the element documentation says many companies that use Elm began that way. Three questions about the work decide most of the choice; answer them in the table:

Interactive · answer and compare Which way fits the next part of your front end? Answer three questions about the work. The table marks the ways that fit and says why the others do not.
Is there a running JavaScript app that this work goes into?
Does the work lean on npm UI libraries, such as a chart or a rich text editor?
Does the logic have many states that must never mix, such as a checkout or a booking flow?

Answer the questions above: the table then marks what fits your case and says why the rest does not.

A whole app in Elm, one Elm element inside a page, or plain JavaScript or TypeScript, against four needs
Way to build it Errors in UI logic npm UI libraries Fits an existing app Package updates
A whole app in Elm Looks like: Browser.document or application Good no null and every case handled; a few crashes remain, such as modBy 0 Poor reached only through ports or custom elements Poor a rewrite of the pages it replaces Good the package site checks that each release follows semantic versioning
One Elm element Looks like: Browser.element in one node Good the same checks, inside the element Mixed the page keeps its libraries; the element talks to them through ports Good takes over one node of a page that stays as it is Good the same enforced versioning for the Elm part
JavaScript or TypeScript Looks like: the framework you use now Mixed TypeScript catches null with strictNullChecks; a missed case needs your own check Good any npm package, imported directly Good no change to the build or the team Mixed semantic versioning is a promise of each author
How the answers decide
  • “Is there a running JavaScript app that this work goes into?” Yes, an existing app: rules out A whole app in Elm. Replacing a running app is a rewrite; Browser.element lets Elm take over one node and grow from there.
  • “Does the work lean on npm UI libraries, such as a chart or a rich text editor?” Yes, several: adds a caveat to A whole app in Elm and One Elm element. Elm packages are written entirely in Elm, so each npm library sits behind a port or a custom element that you write and test.
  • “Does the logic have many states that must never mix, such as a checkout or a booking flow?” Yes: adds a caveat to JavaScript or TypeScript. Custom types and case make an impossible mix of states a compile error in Elm; in TypeScript it takes discipline and settings such as strictNullChecks.

In short: a new front end with many states suits a whole Elm app; an existing app suits one Elm element that grows; and work that leans on many npm UI libraries stays easiest in JavaScript or TypeScript, or keeps those libraries behind ports.

Checkpoint

Check: Where Elm fits, and what it costs

  1. Question 1 of 2 Which of these are ways for Elm code to work with JavaScript?

    Choose every answer that is right.

    Show the answer: Question 1 of 2

    Answer:

    • Custom elements that the view places on the page
    • Flags given to init when the program starts
    • Ports, for messages in both directions

    Elm's border with JavaScript has three doors: flags, ports and custom elements. That border is the price of the checks inside, and the next lessons show where it can still fail.

  2. Question 2 of 2 A team has a large JavaScript app and wants to rebuild only its booking flow, which has many states that must never mix, without rewriting the rest. Which way fits?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: One Elm element: Browser.element takes over one node of the existing page, and the flow talks to the rest through ports

    Start small where the checks matter most: one element in an existing page, growing if it works, as the Browser documentation recommends.

Practise: print paise as rupees, then read elm.json

Two steps, in JavaScript, because the page runs JavaScript tests in your browser. The first writes the worker’s rupees function in JavaScript, digit for digit, so that you can check any amount the receipt might print without building Elm at all. The second reads the elm.json files of this lesson the way the compiler reads them: where each package is listed, which versions an application pins, and why a package’s own file, with its ranges, is not a lock file. Each step has sample tests and a first hint:

Code lab · 2 steps

Format money, then read a project file

Turn whole paise into the text the Orders worker printed, then say where an elm.json lists each package and which versions it pins.

  1. Print paise as rupees, the way Orders.elm does To do
  2. Read an application's elm.json To do

Exercise · Easy · JavaScript

Print paise as rupees, the way Orders.elm does

Orders.elm keeps every amount as a whole number of paise and only turns it into text at the end: paise // 100 rupees, a dot, then modBy 100 paise padded with zeros to two digits. Write the same function in JavaScript: rupees(paise) in rupees.mjs returns the text the worker printed, such as "₹67.50" for 6750.

  • paise is a whole number of 0 or more; the amount has no thousands separators: 123456 paise is "₹1234.56".
  • Always two digits after the dot: 5 paise is "₹0.05" and 1200 paise is "₹12.00".
  • A negative amount or one that is not a whole number throws a RangeError, because the worker never prints one.

The starter code divides by 100 and prints whatever JavaScript makes of the result, which is the mistake the paise avoid.

Starter code · rupees.mjs

/** The amount `paise` as rupees with two digits after the dot: rupees(6750) is "₹67.50". */
export function rupees(paise) {
  // Replace this: dividing first gives "₹67.5" for 6750 and "₹0.05" only by luck.
  return '₹' + paise / 100;
}
The sample tests · rupees.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { rupees } from './rupees.mjs';

test('the three amounts of the receipt that Orders.elm printed', () => {
  assert.equal(rupees(1200), '₹12.00');
  assert.equal(rupees(4350), '₹43.50');
  assert.equal(rupees(6750), '₹67.50');
});

test('always two digits after the dot', () => {
  assert.equal(rupees(0), '₹0.00');
  assert.equal(rupees(5), '₹0.05');
  assert.equal(rupees(99), '₹0.99');
  assert.equal(rupees(100), '₹1.00');
  assert.equal(rupees(101), '₹1.01');
});

test('large amounts have no separators', () => {
  assert.equal(rupees(123456), '₹1234.56');
  assert.equal(rupees(1000000), '₹10000.00');
});

test('negative amounts and fractions of a paisa are refused', () => {
  assert.throws(() => rupees(-5), RangeError);
  assert.throws(() => rupees(12.5), RangeError);
});
A hint

Split the amount into two whole numbers before you make any text: Math.trunc(paise / 100) rupees and paise % 100 paise, which for amounts of 0 or more are what Elm's // and modBy give. Then pad the paise with String(rest).padStart(2, '0'), as String.padLeft 2 '0' does in the worker.

The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Exercise · Easy · JavaScript

Read an application's elm.json

elm init wrote an elm.json whose two dependency blocks name every package with one exact version, and elm-test init adds a third and a fourth list, the test dependencies. Write two functions in deps.mjs that read the text of such a file:

  • whereListed(text, name) returns where the file lists the package name: "direct", "indirect", "test-direct" or "test-indirect", or null when it lists it nowhere.
  • pinned(text) returns every package of the file with its version as [name, version] pairs, test dependencies included, sorted by name. It throws an Error when the file is a package's elm.json ("type": "package"), whose dependencies are ranges such as "1.0.0 <= v < 2.0.0", not versions.

The sample tests use the elm.json of the lesson's session, the same file after elm-test init, and the elm.json of the elm/browser 1.0.2 package from the package cache.

Starter code · deps.mjs

/** Where the elm.json text `text` lists the package `name`: "direct", "indirect", "test-direct", "test-indirect" or null. */
export function whereListed(text, name) {
  // Replace this: it only looks at the direct dependencies.
  const json = JSON.parse(text);
  return name in json.dependencies.direct ? 'direct' : null;
}

/** Every package of an application's elm.json with its exact version, as [name, version] pairs sorted by name. */
export function pinned(text) {
  // Replace this: it forgets the indirect and test dependencies, and accepts a package's elm.json.
  const json = JSON.parse(text);
  return Object.entries(json.dependencies.direct);
}
The sample tests · deps.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { whereListed, pinned } from './deps.mjs';

// The elm.json that elm init wrote in the lesson's session.
const app = JSON.stringify({
  type: 'application',
  'source-directories': ['src'],
  'elm-version': '0.19.3',
  dependencies: {
    direct: { 'elm/browser': '1.0.2', 'elm/core': '1.0.5', 'elm/html': '1.0.1' },
    indirect: { 'elm/json': '1.1.4', 'elm/time': '1.0.0', 'elm/url': '1.0.0', 'elm/virtual-dom': '1.0.5' },
  },
  'test-dependencies': { direct: {}, indirect: {} },
}, null, 4);

// The same file after elm-test init added the test runner's packages.
const withTests = JSON.stringify({
  ...JSON.parse(app),
  'test-dependencies': {
    direct: { 'elm-explorations/test': '2.2.1' },
    indirect: { 'elm/bytes': '1.0.8', 'elm/random': '1.0.0' },
  },
}, null, 4);

// elm/browser 1.0.2's own elm.json, from ~/.elm/0.19.3/packages/elm/browser/1.0.2/elm.json.
const browserPackage = JSON.stringify({
  type: 'package',
  name: 'elm/browser',
  version: '1.0.2',
  'elm-version': '0.19.0 <= v < 0.20.0',
  dependencies: {
    'elm/core': '1.0.0 <= v < 2.0.0',
    'elm/html': '1.0.0 <= v < 2.0.0',
    'elm/json': '1.0.0 <= v < 2.0.0',
    'elm/time': '1.0.0 <= v < 2.0.0',
    'elm/url': '1.0.0 <= v < 2.0.0',
    'elm/virtual-dom': '1.0.2 <= v < 2.0.0',
  },
  'test-dependencies': {},
}, null, 4);

test('direct and indirect dependencies of the session elm.json', () => {
  assert.equal(whereListed(app, 'elm/core'), 'direct');
  assert.equal(whereListed(app, 'elm/html'), 'direct');
  assert.equal(whereListed(app, 'elm/json'), 'indirect');
  assert.equal(whereListed(app, 'elm/virtual-dom'), 'indirect');
  assert.equal(whereListed(app, 'elm/http'), null);
});

test('test dependencies after elm-test init', () => {
  assert.equal(whereListed(withTests, 'elm-explorations/test'), 'test-direct');
  assert.equal(whereListed(withTests, 'elm/random'), 'test-indirect');
  assert.equal(whereListed(withTests, 'elm/core'), 'direct');
});

test('every package with its exact version, sorted by name', () => {
  assert.deepEqual(pinned(app), [
    ['elm/browser', '1.0.2'],
    ['elm/core', '1.0.5'],
    ['elm/html', '1.0.1'],
    ['elm/json', '1.1.4'],
    ['elm/time', '1.0.0'],
    ['elm/url', '1.0.0'],
    ['elm/virtual-dom', '1.0.5'],
  ]);
  assert.deepEqual(pinned(withTests).map(([name]) => name), [
    'elm-explorations/test',
    'elm/browser',
    'elm/bytes',
    'elm/core',
    'elm/html',
    'elm/json',
    'elm/random',
    'elm/time',
    'elm/url',
    'elm/virtual-dom',
  ]);
});

test('a package lists ranges, so pinned refuses it', () => {
  assert.throws(() => pinned(browserPackage), Error);
});
A hint

JSON.parse(text) gives an object whose dependencies and test-dependencies each hold direct and indirect objects, from package name to version. Look name up in those four objects in turn. For pinned, check type first, then collect Object.entries of all four and sort the pairs by their first item.

The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Then try the self-check on the five ideas of this lesson. It asks first about the idea you are least sure of, and explains every answer as soon as you choose it:

Practice that adapts to you

Practise this lesson

10 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 10 Put the steps of installing Elm from its release file in order.

    Give each item its position, from 1 (first).

    Show the answer: Question 1 of 10

    Answer:

    1. Download elm-0.19.3-mac-arm.gz as elm.gz
    2. Check its SHA-256 against the release page
    3. gunzip elm.gz
    4. chmod +x elm
    5. Move elm into a folder that comes first on PATH
    6. Read elm --version

    Check the file before you unpack it, make it runnable before you move it, and read the version last: it shows the elm that the shell will really run.

  2. Question 2 of 10 The download was 15,126,132 bytes and the unpacked elm is 61,507,360 bytes. What explains the difference?

    Choose one answer.

    Show the answer: Question 2 of 10

    Answer: elm.gz is gzip-compressed, and gunzip restores the full program

    A .gz file is compressed. One program of about 61.5 MB holds make, install, repl, reactor and the rest of the commands, so nothing else has to be installed for the compiler itself.

  3. Question 3 of 10 Why does an application's elm.json list indirect dependencies, packages its code never imports?

    Choose one answer.

    Show the answer: Question 3 of 10

    Answer: So that builds are reproducible and you can see every package you depend on

    Listing the indirect packages with exact versions makes the file a complete record of the build, which is why it needs no separate lock file.

  4. Question 4 of 10 A CI machine starts empty for every build, and builds fail whenever package.elm-lang.org is slow. What makes the build independent of the site?

    Choose one answer.

    A hint

    Which step of a fresh machine needs package.elm-lang.org?

    Show the answer: Question 4 of 10

    Answer: Keep the package cache between runs, ~/.elm or the folder ELM_HOME names, so that only a new package needs the site

    The versions are fixed by elm.json; the packages come from the cache. Restoring ~/.elm, or the folder ELM_HOME names, on each CI run removes the site from every build that adds no new package.

  5. Question 5 of 10 Orders imports nothing but elm/core, yet its file holds only some of elm/core's functions. Why?

    Choose one answer.

    Show the answer: Question 5 of 10

    Answer: Every build drops the functions that main cannot reach

    Because Elm functions cannot be redefined at run time and every package is written in Elm, the compiler can tell exactly what main needs and leave the rest out.

  6. Question 6 of 10 You must cut a 65 KB optimised Elm file down before serving it. Which step does the compiler's own advice add after --optimize?

    Choose one answer.

    Show the answer: Question 6 of 10

    Answer: A minifier, uglifyjs, with flags that tell it Elm's helper functions have no side effects

    --optimize prepares the file, a minifier shrinks it, and the server gzips what it sends. The project lesson of this module measures all three.

  7. Question 7 of 10 Why is the harness run.cjs, a CommonJS file, rather than an ES module in a package marked "type": "module"?

    Choose one answer.

    Show the answer: Question 7 of 10

    Answer: The compiled file puts Elm on the this it runs with, which under require is the module's exports

    The compiled file is a function called with this. A CommonJS module or a browser script gives it an object, so Elm.Orders appears there; that is why the harness is .cjs.

  8. Question 8 of 10 A harness calls Elm.Orders.init() and subscribes to app.ports.report inside a setTimeout. What does it print?

    Choose one answer.

    Show the answer: Question 8 of 10

    Answer: Nothing: the receipt from init was delivered before the subscriber existed

    Subscribe to every port right after init() returns, in the same turn. A command from init is delivered once, to the subscribers present then.

  9. Question 9 of 10 Elm promises no runtime exceptions. Where can an Elm program still stop with an error, as this module records?

    Choose one answer.

    Show the answer: Question 9 of 10

    Answer: At edges such as modBy 0, and when JavaScript sends a port a value of the wrong type

    The compiler rules out null and unhandled cases in your code. A handful of operations and every value that crosses from JavaScript are where you still check.

  10. Question 10 of 10 A new analytics page leans on three npm chart and grid libraries and has little state of its own. What does the lesson's table suggest?

    Choose one answer.

    Show the answer: Question 10 of 10

    Answer: JavaScript or TypeScript, or Elm only with each library behind a port or a custom element you write

    The border with JavaScript is Elm's main cost. Work made mostly of JavaScript libraries pays it many times and gains little, so it suits JavaScript or TypeScript, or Elm with wrappers you are ready to maintain.

Interview questions

Warm-up (fresher to mid-level): Where does an Elm application record the versions of its packages, and what is ~/.elm for?

In elm.json. Its dependencies list every package the application uses, the direct ones that its code imports and the indirect ones that those need, each with one exact version, such as elm/core 1.0.5; test packages go in test-dependencies the same way. Because the versions are exact, the file is its own lock file: there is no second file to keep in step, and committing elm.json fixes the build. ~/.elm is the package cache, shared by every project on the computer: the list of published packages and the source and compiled form of each version downloaded so far. The ELM_HOME variable moves it. A fresh computer needs the network for its first elm init or build; once the cache holds the packages, both work offline, as the lesson’s second elm init did.

Two longer questions, one about a build that breaks in CI and one about choosing Elm:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to senior

    Your Elm app's CI starts every job on a fresh machine. On some days the build fails at its first step with PROBLEM LOADING PACKAGE LIST, while builds on laptops work. Nobody changed elm.json. What is going on, and how do you make the CI build reliable?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to staff

    Your admin dashboard is a large JavaScript app. A team wants to write a new multi-step refund flow in Elm, and the flow must use the dashboard's date picker and its signed-in session. Do you agree, and how do you introduce Elm so that the risk stays small?

    Model answer, Pro. First step and follow-ups

Flashcards · 8 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. Which three pins does the Elm mini-course check, and why does elm-test have its own?

    elm 0.19.3, Node.js v24.21.0, which runs the compiled programs and elm repl, and elm-test 0.19.3-0. The test runner is not part of the compiler: it comes from npm.

  2. What does an Elm application's elm.json record about its packages?

    Every direct and indirect dependency, each at one exact version such as elm/core 1.0.5, so the file doubles as a lock file. A package's own elm.json lists ranges instead.

  3. Why did elm init fail on a computer with an empty ~/.elm and no network?

    It needs the list of all published packages from package.elm-lang.org. Once ~/.elm holds that list and the packages, elm init and builds work offline.

  4. Which of elm.json and elm-stuff belong in version control?

    elm.json, which fixes every version of the build. elm-stuff is a cache of compiled modules that elm make rebuilds whenever it is missing.

  5. What does elm make --optimize do, and how much did it save on the Orders worker?

    It shortens record field names and unboxes values. On a 65,875-byte worker that saved 567 bytes, 0.9 %, since most of the file is runtime and core code; a minifier does the real shrinking.

  6. What does dead code elimination remove in every Elm build?

    Every function that main cannot reach, across all packages, which is possible because every package on the package site is written entirely in Elm.

  7. What is a Platform.worker program?

    An Elm program with no user interface, built from init, update and subscriptions. It shows results only through ports, so Node.js can run it with no browser.

  8. Why did late.cjs, which subscribed to the port from a timer, receive 0 messages?

    The receipt comes from init, and the runtime delivers it once the code that called init() finishes its turn. Subscribe to each port right after init() returns.

Key takeaways

  • Elm is one program: download the release file, check it against the digest the release page lists, unpack it and put it first on PATH; elm --version must print 0.19.3 before outputs can be compared with this module.
  • An application’s elm.json names every direct and indirect package with one exact version, so it is its own lock file, while ~/.elm caches the packages for every project and lets later inits and builds run offline.
  • elm make checks types, keeps only what main reaches and writes one JavaScript file; --optimize renamed record fields and saved 0.9 % of a 65,875-byte worker, and a minifier is the step that shrinks it.
  • Platform.worker runs Elm with no view, and a port carries values out to JavaScript, so Node.js can run and check an Elm program headlessly once it subscribes right after init().
  • Elm’s compiler rules out null and unhandled cases and enforces package versions, at the price of a border with JavaScript that you cross through flags, ports and custom elements, and check yourself.

Terms in this lesson

dead code elimination
A compiler step that leaves out every function a program can never call. Elm does it in every build, function by function, across the application and all its packages, because Elm functions cannot be redefined while the program runs.
Platform.worker
The kind of Elm program that has no view, only init, update and subscriptions. It runs under Node.js or in a page without drawing anything and talks to JavaScript through ports, so Elm code can run headlessly.
port
A typed channel between Elm and JavaScript, declared with the port keyword in a port module. A port that returns Cmd sends values out to JavaScript subscribers; one that returns Sub brings values sent from JavaScript in as messages.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.