elm-pages

While elm-pages v2 was focused on static site generation, elm-pages v3 is a hybrid framework, giving you all the same static site generation features from v2, but with a whole new set of use cases opened up with server-rendered routes. page , github , site , docs , blog

Up to and including v2, elm-pages was a static site generator. There was only one mode, rendering your pages at build time. ⇒ elm-pages v3

~

We could provide an "index.html" style template in the server that would present a more familiar welcome page before easing visitors into the new world of Federated Wiki.

> It turns out many services they [⇒ Wikimedia Research] inherit from wiki aren't available in the static site generator they used. These include language translation as well as simply being able to correct punctuation.

~

Getting Started page

cd ~/workspace/elm

You can create a fresh elm-pages project with the init command.

npx elm-pages init my-project cd my-project npm install npm start # starts a local dev server using `elm-pages dev`

npx - Run a command from a local or remote npm package

npm - javascript package manager

elm-pages dev server running at <http://localhost:1234>

elm-pages scripts

https://cdn.simplecast.com/audio/6a206baa-9c8e-4c25-9037-2b674204ba84/episodes/7e71e134-e83b-48a6-877f-3267e5694dec/audio/c0b4f01a-da50-42d1-984a-5ecb83cbdd5e/default_tc.mp3 Elm Radio Episode#75: elm-pages scripts page

We discuss elm-pages BackendTasks and how to run them as scripts with a single command.

[00:02:31] […] static assets but you can also do server rendered pages. So the scope of Elm Pages V3 has changed

[00:02:39] […] But the heart of Elm Pages is still the same throughout all of its permutations. I've always thought of the heart of Elm Pages as being this sort of engine that's able to like execute things on a back end and give you back data. In Elm Pages V2 that was called data sources.

[00:03:49] In V3 because the scope of what Elm Pages does has changed the term data source has [00:03:58] been renamed and the concept has changed a tiny bit. And the reason for that is because [00:04:04] in V2 it was the model was much more you try to make an HTTP request. You try to read from [00:04:11] a file and if anything goes wrong you just stop the build and fail. And then the developer [00:04:17] can read the issue. They can read a nicely formatted error message and say oh this API [00:04:25] turned to 404. Let me fix that and then rerun the build and it succeeds.

[00:04:25] […] With V3 so for example if your server rendering pages maybe you get a 404 in an HTTP request. Maybe you're [00:04:39] doing a post and you need to update something and you need to handle that error in a graceful [00:04:46] way. So that's one of the reasons why this concept has changed and become a little more [00:04:52] powerful and part of the reason why the name has changed. So in V3 the term is no longer [00:04:58] data source is now called a BackendTask page . And in addition to that in V so back end task [00:05:05] it actually looks and feels a lot like the Elm core tasks.

~

elm-pages 3.0 uses the lamdera compiler, which is a superset of the Elm compiler with some extra functionality to automatically serialize Elm types to Bytes. That means there is no more OptimizedDecoder API, you can just use regular elm/json Decoders! And no more DataSource.distill, since types are now automatically serialized all those optimizations come for free.

~

[00:05:34] […] an Elm task is task with an error type variable and a data [00:05:43] type variable. So if you do an Elm HTTP task it's going to give you a task HTTP dot error [00:05:54] and then your decoded data as the data type. And then you do task dot attempt. You have [00:06:01] to do dot attempt if there is an error that could happen. And then you get a message where [00:06:09] you can deal with that result.

[00:07:01] And one of the things that I love about back end tasks as compared to the design in [00:07:09] V2 with data sources is with a back end task. If there is an error in that type variable [00:07:16] then it has a possibility of failing. If there is no error there. So if you have you know [00:07:21] back end task never my data then you know it will never fail. And that's that's something [00:07:28] that you couldn't just look at the types in a data source in V2 and know whether or not [00:07:33] it's going to fail because that possible failure gets sort of tucked under the hood. It's not [00:07:39] represented by the types.

[00:08:54] So that's BackendTask [00:08:57] back in tasks are the heart of Elm pages even more so in the in the V3 release that is coming [00:09:05] up soon. And and they are also the heart of Elm pages scripts. So let's talk about what [00:09:13] Elm pages scripts are.

[00:09:58] […] All you need is a folder called [00:10:06] script. So much like for an Elm review project you have a folder called review and it's a [00:10:13] regular Elm project. It has an Elm.JSON Elm pages script is the same thing. So you have [00:10:18] a script folder that script folder has to have an Elm.JSON. So it's a little Elm project. [00:10:23] It has to have Elm pages as a dependency in that Elm.JSON. And and then what you do is [00:10:29] you do Elm pages run hello. And now if you have something in your source directories [00:10:37] like source slash hello dot Elm it's going to go and execute that module.

[00:12:17] […] So the hello world for Elm [00:12:24] pages script is you have your script folder you have an Elm.JSON you have source slash [00:12:32] hello dot Elm and in your hello dot Elm you expose a function called run. Okay. That is [00:12:39] the main function in a way. Exactly. Run is like the main function for an Elm pages script. [00:12:45] […] run has the type script and and then you do script dot without CLI options scripts [00:12:56] dot log hello. That's hello world.

[00:14:25] […] if you wanted to make an HTTP request you just do you know back end task dot HTTP [00:14:31] dot get JSON give it a URL give it a JSON decoder and then back end task dot and then [00:14:39] and then you can log some data that you decoded. So right. It's designed to be more like the [00:14:46] abstraction of a back end task lets you do things in a more lightweight way especially [00:14:51] for this sort of mental model where it's just like just execute this thing. So it back end [00:14:56] task maps very nicely to the idea of a script where it's just execute this thing or fail.

[00:17:15] The thing that's that I find really fun about back end tasks is that like it is [00:17:22] this it's a type it's data. It's a description of an effect or of something to achieve and [00:17:29] exactly get out of it.

[00:18:09] […] And I think that sometimes people underestimate what you can model for [00:18:16] frameworks to be able to do like effectful things using this pattern of describing effects [00:18:24] as data. I think it's like it's actually a very powerful tool that we can do a lot with [00:18:29] and as framework designers we can put guardrails so it's very very clear what what it's possible [00:18:37] to do using those data types and where they can be used and where they cannot be used. [00:18:42] So you know it's essentially the idea of a managed effect where like calling a back end [00:18:49] creating a back end task in Elm pages doesn't do anything. You can create a back end task [00:18:55] just like you can create a command but when you give it to Elm pages in a place where [00:19:01] it accepts that type then it lets the framework do something with it. So the sky's the limit [00:19:06] with how you build things with that.

[00:20:14] […] at the end of the day Elm pages is creating, [00:20:21] it's scaffolding up an application around your application. That's sort of what a framework [00:20:26] is. And so it's at the end of the day compiling an Elm application and executing it in this [00:20:35] case in Node.js. But it could be executing it in other contexts. It could be executing [00:20:39] it with Deno or Cloudflare workers or with Bun with different run times. But at the end [00:20:48] of the day it is using Elm which its way of communicating is through ports.

[00:21:25] […] But it creates a set of abstractions for that that makes it easier for the user [00:21:31] to basically execute things in a back end and run a script in a back end context which [00:21:38] turns out is a very useful thing to do if you're you know making a static site because [00:21:46] you want to read some files and then you want to pull that data in your front end. But that's [00:21:51] also scripting right. So it does bring up the question like is Elm a good tool for this [00:21:58] type of task like this kind of back end task.

[00:21:58] […] Is Elm a good tool for writing a script? Is that a good idea?

[00:23:40] […] What are the gains what are the benefits that you have when you [00:23:46] do it through Elm pages compared to just running a Node.js script for instance? [00:23:52] Exactly. Yeah. Great question. And that's that's exactly the right question I think. [00:23:57] So first of all a little bit of background. The motivation for Elm pages scripts and people [00:24:02] might be asking like Elm pages scripts like why what does Elm pages have to do with scripts? [00:24:09] Yeah the name don't match. Right. At the moment. So the Elm pages script was born out of this [00:24:18] use case of generating like the scaffolding for a new route.

[00:24:40] […] Ryan has created a nice feature in Elm SPA where you [00:24:48] can do some templating and create custom commands for for scaffolding new pages. I was really [00:24:56] keen on on using Matt's Elm code gen tool for that. And so as I was starting to build [00:25:03] that I'm like well it would be really nice if if I could use Elm code gen to create scaffolding [00:25:11] for new routes. But I also want to be able to read an environment variable read some [00:25:17] configuration from a JSON file maybe get some like JSON data from an API to figure out how [00:25:25] I'm going to generate my my new routes. And so well that's kind of what back end tasks [00:25:32] let you do.

[00:26:12] And now it's just on pages scripts. So it's really like Ruby on Rails generators where [00:26:18] it's just like that was the main motivation was Ruby on Rails generators are used for [00:26:25] if you want to create a new page with a form and then you just it's a tool for very quickly [00:26:31] building up boilerplate. So it's like you know you create a new controller in Rails [00:26:37] and your template and your template is defining a form and your form has these fields and [00:26:42] you also want to create you know some some stuff for for working with active record to [00:26:50] define this new user model or whatever. And so people are very productive using Rails [00:26:56] generators where they'll say like Rails generate whatever and and you can build custom workflows.

[00:27:20] […] if you want to create a new page and be super productive where you [00:27:25] can say hey I'm going to make a new form and it has these fields. Why not be able to write [00:27:30] a custom generator a custom Elm pages script that lets you just template that. And if you [00:27:36] want to read some configuration from something or whatever you want to do why not let users [00:27:41] do that. So that was the motivation.

[00:27:41] […] Now back to the question of like why what what benefit [00:27:46] do you gain by doing this compared to a bash script or a node script. If we look at the [00:27:52] pros and cons between like writing a script in in Elm and writing a script in bash or [00:27:58] Node.js we can see some pretty pretty obvious pros and cons on either side. So let's look [00:28:05] at like writing a vanilla Elm script.

[00:28:29] […] what if we just wanted to grab some HTTP [00:28:37] data. Right. If we have to create and update to do that that becomes pretty verbose and [00:28:42] tedious. So back end tasks make that less tedious because you just do back end tasks [00:28:48] dot HTTP dot get JSON URL JSON decoder and then you can do back end tasks dot and then [00:28:56] you don't have that boilerplate of init update subscriptions.

[00:29:30] […] The challenge is well what what things can fail. So it's very easy to [00:29:38] just run something and let it fail. Right. It just throws an exception. The problem is [00:29:44] knowing where it might fail and what implicit assumptions there are and what possible runtime [00:29:52] errors are lurking there. So if you want to write a quick and dirty script and you just [00:29:56] say I want to hit this API I want to grab this data I want to map the data a little [00:30:01] bit and I want to write some file or something like that. Right. Then writing a Node.js script [00:30:07] is is great for that because it doesn't get in your way with saying hey the errors might [00:30:13] be wrong. You just pull off JSON data. It doesn't get in your way with saying hey this [00:30:17] HTTP request might fail. So that if you're just writing a vanilla Elm file you do have [00:30:24] to deal with those cases and that becomes tedious. Elm Elm pages back end tasks try [00:30:30] to address that problem.

[00:30:54] […] So Elm pages v3 provides a new abstraction called a fatal error.

[00:33:38] […] So the at the end of the day the Elm pages expects when you say script [00:33:46] dot without CLI options and you give it a backend task the type of that backend task [00:33:52] needs to be the error type can be a fatal error and the data type needs to be unit. [00:33:59] So so at the end of the day you you need to give it either no possibility of an error [00:34:05] or a fatal error if anything. So doing allow fatal just throws away that recoverable error [00:34:14] data that has the nicely structured error whereas allow fit. Yeah allow fatal just grabs [00:34:19] that fatal error and passes it through. But if you do on error then you can continue with [00:34:26] something else.

[00:35:25] […] It's just that if you have the possibility of a failure you have to turn that error type into a fatal error at the end of the day.

[00:36:41] […] So that's so the core APIs and Elm pages like HTTP reading from files [00:36:48] writing to files things that can fail. They give you these two different bits of data [00:36:53] where you can choose I want to either recover or let the fatal exception through the fatal [00:36:59] error through.

[00:38:34] […] The only thing the only reason that [00:38:38] is going to fail is because some operation that's touched the external system like the [00:38:46] file system or made requests across HTTP failed for some reason. But it's never going to fail [00:38:53] because of how you wrote the code. So that is quite nice. So that is one of the plus [00:38:58] sides that I find in using Elm pages scripts. But do you see other ones compared to writing [00:39:06] because you compared it previously with writing a script in Elm without Elm pages which yeah [00:39:12] sounds painful. Some people have done it. It's actually not that bad in practice. I [00:39:17] have done so myself obviously. But how does it compare to writing something in JavaScript [00:39:22] or in Bash or Perl or Python or whatever. When would you do one of those or when would [00:39:28] you use Elm pages scripts?

[00:41:26] […] if you write a script in Node.js and then it succeeds you're like okay well [00:41:35] it's possible for this script to succeed but you're not necessarily convinced that it will [00:41:39] succeed for all cases. Whereas like if I if I write the script in Elm I would be much [00:41:45] more confident that like oh yeah it it's good now like it's it's handling the expected JSON [00:41:52] data I mean maybe the API sends slightly different data formats in different cases but I'm much [00:41:57] more confident that I'm done at that point.

[00:42:26] […] I mean you don't have anys in bash. Right. Oh man working with the API data responses in bash does not sound fun. I don't even know how you would do that. [00:42:42] Yeah. I would just curl it and yeah pray that it works. jq or something I don't know there [00:42:49] yeah there are tools but it's it's not fun you know so it's it's nice to use like a programming [00:42:54] language for that not just a bash script.

[00:46:15] […] the goal of this design is to give you a way to be [00:46:23] productive build things up with minimal boilerplate. You write your script hello.elm you expose [00:46:31] run its type of script you define a back end task and then you want a quick and dirty script [00:46:37] just the happy path you allow fatal. But as you want to deal with more error cases in [00:46:45] a graceful way it gives you the tools to do that and to really maintain it. So it's trying [00:46:51] to give a balance between convenience and maintainability.