How does elm-pages work?
elm-pages magic? Does it use native code or hacks to give me side effects or things like
BackendTask? Does my mental model of Elm and pure functions change?
The answer to all of these questions is no!
It might feel a bit like magic to be able to run full-stack Elm. And while it may be a bit of a mental model of how all these pieces fit together, it's all just Elm, plus some code generation, and generating two Elm applications. One backend version that runs at build-time, or on each server-request if it's a server-rendered route. And one frontend version that takes the initial HTML and
data provided by the backend app. So it gives you a seamless experience of writing in one context, but this is more a convention and an abstraction than magic.
elm-pages does also use the Lamdera compiler to get some automatic
Bytes Encoders and Decoders that it uses to communicate between the frontend and backend apps. This doesn't change your guarantees about using Elm, though it is a key piece that makes it seamless to build your
elm-pages routes. This is how the
data is resolved from the backend app, but is then accessible to the frontend app. Hopefully you'll find it very predictable once you understand this general architecture and set of abstractions.
elm-pages does not change Elm's pure functions. In fact, it heavily relies on this as a basis for its design! It does give you
BackendTask which allows you to execute data. It may not seem like it, but this is nothing more than a plain old Elm Custom Type! That Custom Type is handled with some special behavior by the framework to give you a lot of functionality, but it doesn't in any way break the guarantees you know and love from any other Elm application. Think of
BackendTask like a
Effect. It's a special type which you pass up to the runtime to ask it to do something for you. Since Elm code is pure, it can't perform side-effects.
BackendTasks will not do anything unless they're passed to the
elm-pages framework. Since the framework has its own
update, etc., it's able to go perform side-effects to resolve those
BackendTasks for you. You can think of it like a wrapper around The Elm Architecture.
The secret to elm-pages is that it creates an Elm app that calls the code you define (in your Route Modules, etc.), wires up routing for you, manages some state in its Model (it's just an Elm app, so it can have its own
Model and share some of the things it keeps in there with you - in fact, that's exactly what
RouteBuilder.App is, some state that is manged for you by the framework!).
So the main techniques elm-pages uses to give what feels like magic are:
elm-pagestakes the code you write in your Route Modules and it wires it up to give you routing, etc. with some generated code (also gives you a
Route.elmmodule, for example)
elm-pagescreates two Elm applications from your Route Modules. One that runs in the browser, one that runs on the server!
actionare never run in the Browser, they are only run from your backend (which could be your build step if it's a pre-rendered route)
- The app that
elm-pagesmakes to run on your server has a
portthat it uses. Since this runs in the backend, this runs in a NodeJS context! So that means this port is able to do things like read files, or even run arbitrary NodeJS functions! This is what
elm-pagesjust tries running
custom-backend-task.tsfile. It transpiles that file with esbuild, which is why you can write it in TypeScript.
elm-pageshas a few places that accept a
BackendTask(like your Route Modules'
actionfunctions). It takes a
BackendTaskand is able to use the
portin its generated code for the Server app to step through and resolve any data that's needed until the
BackendTaskis complete. Then it passes that resolved data through to the user code.
That's all it is really! It might feel magical, but it's really just a couple of abstractions that are used over and over again. It's the difference between you (the user) running a
port, vs. the framework (
elm-pages) using a port internally. But under the hood,
elm-pages is just using
BackendTask's are resolved in the Server app that
elm-pages generates just using these vanilla Elm things, so it's all just an abstraction that's built on regular Elm!