|
| 1 | ++++ |
| 2 | +title = "Compiler-Driven Development: Building an Elm Playground That Compiles in the Browser" |
| 3 | +description = "How I built an interactive Elm workshop environment using Guida's in-browser compiler after server-side compilation hit memory limits" |
| 4 | +tags = ["elm", "compiler", "guida", "workshop", "frontend", "functional programming", "in-browser compilation"] |
| 5 | +date = "2025-08-31" |
| 6 | +draft = false |
| 7 | ++++ |
| 8 | + |
| 9 | +Sometimes the best solutions emerge from the ashes of failed approaches. This is the story of how I built [elm-playground](https://elm-playground.render.com) – an interactive Elm environment for teaching "compiler-driven development" – and how hitting memory limits forced me to discover something even better than my original plan. |
| 10 | + |
| 11 | +## The Mission: Teaching Compiler-Driven Development |
| 12 | + |
| 13 | +I've been planning to host an Elm workshop at [Ensō](https://enso.no) with a specific theme: "Compiler-driven development." The idea is to showcase how Elm's famously friendly compiler can guide your development process, catching errors before they become runtime surprises and helping you write better code through its helpful error messages. |
| 14 | + |
| 15 | +But explaining the magic of compiler-driven development requires more than slides – it needs hands-on experience. I wanted workshop participants to feel the difference between "debugging by guessing" and "being guided by the compiler." That meant building an interactive playground where people could write Elm code and immediately see compilation results. |
| 16 | + |
| 17 | +Granted, setting up Elm locally is not hard by any stretch of the word. But no setup is better than little setup! |
| 18 | + |
| 19 | +## The Obvious Initial Approach |
| 20 | + |
| 21 | +My initial plan was textbook simple: |
| 22 | + |
| 23 | +1. Build a frontend with a code editor |
| 24 | +2. Set up a backend that receives code via POST requests |
| 25 | +3. Run `elm make` on the server |
| 26 | +4. Return compilation results to the frontend |
| 27 | + |
| 28 | +I even had it working! The Go backend would create temporary directories, write the submitted code to `Main.elm`, run the compiler, and return either success (with the compiled JavaScript) or failure (with error messages). |
| 29 | + |
| 30 | +```go |
| 31 | +// The original backend approach (simplified) |
| 32 | +func compileHandler(w http.ResponseWriter, r *http.Request) { |
| 33 | + tmpDir := createTempDir() |
| 34 | + writeCodeToFile(tmpDir, requestBody.Code) |
| 35 | + defer removeTmpDir() |
| 36 | + |
| 37 | + cmd := exec.Command("elm", "make", "src/Main.elm", "--output=main.js") |
| 38 | + cmd.Dir = tmpDir |
| 39 | + |
| 40 | + output, err := cmd.Run() |
| 41 | + // Handle success/error cases... |
| 42 | +} |
| 43 | +``` |
| 44 | + |
| 45 | +It worked perfectly on my local machine. It worked perfectly in development. But the moment I deployed to production on Render.com's free tier... |
| 46 | + |
| 47 | +``` |
| 48 | +failed |
| 49 | +43b72b0 |
| 50 | +Ran out of memory (used over 512MB) while running your code. |
| 51 | +``` |
| 52 | + |
| 53 | +## The Memory Problem |
| 54 | + |
| 55 | +No matter what I tried, `elm make` consistently exceeded the 512MB memory limit. I attempted several optimizations: |
| 56 | + |
| 57 | +- **Pre-warming `elm-stuff`**: Reusing compiled dependencies between requests |
| 58 | +- **Symlinking instead of copying**: Reducing disk I/O overhead |
| 59 | +- **Switching from Go to Node.js**, using the existing node-elm-compiler package: Using lighter runtime abstractions |
| 60 | +- **Constraining `elm make`**: Looking for memory limit flags (spoiler: they don't exist) |
| 61 | + |
| 62 | +Nothing worked. The Elm compiler, while excellent at its job, simply requires more memory than a free hosting tier provides for even simple programs. (_Allegedly_, that is – locally I could never reproduce this high memory consumption! But how and why that is is a different post.) |
| 63 | + |
| 64 | +## Discovery: The Elm Community Has Solutions |
| 65 | + |
| 66 | +Frustrated but not defeated, I turned to the Elm community Slack. The response was immediate and enlightening – several people had already solved this exact problem in different ways: |
| 67 | + |
| 68 | +- One suggested [Ellie](https://ellie-app.com/new), the established Elm playground |
| 69 | +- Others mentioned [elmrepl.de](https://elmrepl.de) and [elm-repl-worker](https://pithub.github.io/elm-repl-worker/eager-tea.html) |
| 70 | +- Not least: [@deciojf](https://github.com/deciojf) introduced me to the [Guida/Try](https://guida-lang.org/try) PoC – a similar project using [Guida](https://guida-lang.org/) a port of the Elm compiler written in Elm itself |
| 71 | + |
| 72 | +While Ellie would have worked, I wanted something I could customize and brand for our workshop. And Guida? That was intriguing. |
| 73 | + |
| 74 | +## Enter Guida: Elm Compiling Elm |
| 75 | + |
| 76 | +[Guida](https://guida-lang.org/) is a remarkable project – it's literally the Elm compiler ported to Elm, which means it can run in the browser via JavaScript. No server required, no memory limits, no deployment complexity. |
| 77 | + |
| 78 | +The architecture is beautifully simple: |
| 79 | + |
| 80 | +```elm |
| 81 | +-- (The actual code has more to it, but the following is a simplified illustration) |
| 82 | + |
| 83 | +-- Instead of HTTP requests to a backend: |
| 84 | +type Msg |
| 85 | + = CompileCode String |
| 86 | + | CompilationComplete (Result Error String) |
| 87 | + |
| 88 | +-- Guida handles compilation directly in the browser: |
| 89 | +update : Msg -> Model -> ( Model, Cmd Msg ) |
| 90 | +update msg model = |
| 91 | + case msg of |
| 92 | + CompileCode sourceCode -> |
| 93 | + ( { model | status = Compiling } |
| 94 | + , Guida.compile sourceCode |
| 95 | + ) |
| 96 | + |
| 97 | + CompilationComplete result -> |
| 98 | + case result of |
| 99 | + Ok compiledJs -> |
| 100 | + ( { model | status = Success compiledJs }, Cmd.none ) |
| 101 | + |
| 102 | + Err error -> |
| 103 | + ( { model | status = Error error }, Cmd.none ) |
| 104 | +``` |
| 105 | + |
| 106 | +## The Journey to Working Solution |
| 107 | + |
| 108 | +Getting Guida integrated wasn't without challenges. Initially, I hit CORS issues even with identical configuration to the Guida demo. And the errors I got from the compiler once I'd solved the CORS/Proxy issue were not pretty-printed text but rather JSON that needed massaging. |
| 109 | + |
| 110 | +But the Elm community came through again. [@deciojf](https://github.com/deciojf), Guida's creator, provided crucial guidance: |
| 111 | + |
| 112 | +- **Error formatting**: Pointed me to [`elm/project-metadata-utils`](https://package.elm-lang.org/packages/elm/project-metadata-utils/latest/) for proper error message decoding |
| 113 | +- **Configuration tips**: Shared how the default `elm.json` is [hardcoded in browser.js](https://github.com/guida-lang/compiler/blob/master/lib/browser.js#L326C7-L349) |
| 114 | + |
| 115 | +With these insights, I finally had a working solution. The final architecture is refreshingly simple: |
| 116 | + |
| 117 | +1. **Pure frontend application** built with Elm |
| 118 | +2. **Guida integration** for in-browser compilation |
| 119 | +3. **No backend** required beyond static file hosting, and [a proxy](https://github.com/ensolabs/elm-playground/blob/master/index.cjs#L13) to Make Things Work™ when fetching Elm dependencies. |
| 120 | +4. **Proper error formatting** using Elm's own error decoders |
| 121 | + |
| 122 | +## The Final Result |
| 123 | + |
| 124 | +[Elm Playground](https://elm-playground.render.com) now provides exactly what I wanted for the workshop: |
| 125 | + |
| 126 | +- **Interactive code editing** with syntax highlighting |
| 127 | +- **Real-time compilation** without server round-trips |
| 128 | +- **Friendly error messages** formatted just like the CLI compiler |
| 129 | +- **Zero infrastructure overhead** – just static files |
| 130 | + |
| 131 | +More importantly, it perfectly demonstrates compiler-driven development. Workshop participants can: |
| 132 | + |
| 133 | +1. Write invalid Elm code and see helpful error messages |
| 134 | +2. Fix errors guided by compiler suggestions |
| 135 | +3. Experience the confidence that comes with "if it compiles, it works" |
| 136 | +4. Understand why Elm developers love their compiler |
| 137 | + |
| 138 | +The final two exercises don't work yet, because they have additional package dependencies not found in the deault `elm.json` file in Guida. But I'm quite confident I'll be able to solve that quite quickly 🤓 |
| 139 | + |
| 140 | +## Why This Solution is Better |
| 141 | + |
| 142 | +The journey from server-side to client-side compilation turned out to be more than just a workaround – it's actually superior in several ways: |
| 143 | + |
| 144 | +### 1. **Instant Feedback** |
| 145 | + |
| 146 | +No network latency means compilation results appear immediately. This makes the compiler-driven development cycle feel natural and responsive. |
| 147 | + |
| 148 | +### 2. **Scalable by Default** |
| 149 | + |
| 150 | +Since compilation happens on the user's machine, the playground can handle unlimited concurrent users without additional infrastructure. |
| 151 | + |
| 152 | +### 3. **Offline Capable** |
| 153 | + |
| 154 | +Once loaded, the playground works without internet connectivity – perfect for workshop environments with unreliable WiFi. |
| 155 | + |
| 156 | +### 4. **Lower Operating Costs** |
| 157 | + |
| 158 | +Static file hosting is essentially free compared to maintaining server-side compilation infrastructure. |
| 159 | + |
| 160 | +### 5. **Better Privacy** |
| 161 | + |
| 162 | +Code never leaves the user's browser, addressing any potential concerns about code privacy. |
| 163 | + |
| 164 | +## The Meta-Experience |
| 165 | + |
| 166 | +There's something delightfully meta about this solution. The workshop is about compiler-driven development, and the tool we're using to teach it exemplifies the principle perfectly: |
| 167 | + |
| 168 | +- **Elm code compiling Elm code** in the browser |
| 169 | +- **Type safety** throughout the compilation pipeline |
| 170 | +- **Impossible states** made impossible by Guida's architecture |
| 171 | +- **Helpful errors** at every layer |
| 172 | + |
| 173 | +It's Elm all the way down, and it works beautifully. |
| 174 | + |
| 175 | +## Lessons Learned |
| 176 | + |
| 177 | +Building elm-playground taught me several valuable lessons: |
| 178 | + |
| 179 | +### 1. **Constraints Drive Innovation** |
| 180 | + |
| 181 | +The 512MB memory limit felt like a showstopper, but it forced me to discover a fundamentally better solution. |
| 182 | + |
| 183 | +### 2. **Community Knowledge is Invaluable** |
| 184 | + |
| 185 | +The Elm community's willingness to share experiences and solutions was crucial to success. (Good luck having GPT-whatever giving you advice is these scenarios...) |
| 186 | + |
| 187 | +### 3. **In-Browser Compilation is Powerful** |
| 188 | + |
| 189 | +Tools like Guida open up new possibilities for educational and development environments. |
| 190 | + |
| 191 | +### 4. **Simple Solutions Scale** |
| 192 | + |
| 193 | +The final architecture is much simpler than the original server-side approach, yet more capable. |
| 194 | + |
| 195 | +## Looking Forward |
| 196 | + |
| 197 | +Elm Playground is just the beginning. The success of in-browser compilation opens up interesting possibilities: |
| 198 | + |
| 199 | +- **Multi-file projects** with proper module structure |
| 200 | +- **Package installation** and dependency management |
| 201 | +- **Interactive tutorials** with progressive skill building |
| 202 | +- **Code sharing** and collaborative editing |
| 203 | +- **Integration with other tools** in the Elm ecosystem |
| 204 | + |
| 205 | +The foundation is solid, and the community is active. I'm excited to see where this goes. |
| 206 | + |
| 207 | +Bonus: I was also invited to collaborate on [Guida](https://github.com/guida-lang/compiler), and I think I just might 🤓 |
| 208 | + |
| 209 | +## Try It Yourself |
| 210 | + |
| 211 | +[Elm Playground](https://elm-playground.render.com) is live and ready for exploration. Whether you're teaching Elm, learning compiler-driven development, or just curious about in-browser compilation, I encourage you to give it a try. |
| 212 | + |
| 213 | +The source code is available on [GitHub](https://github.com/ensolabs/elm-playground), and contributions are welcome. Special thanks to [@deciojf](https://github.com/deciojf) and the Guida project for making this possible. |
| 214 | + |
| 215 | +## Conclusion |
| 216 | + |
| 217 | +Sometimes the best engineering solutions come from embracing constraints rather than fighting them. What started as a simple backend compilation service evolved into something much more interesting: a demonstration of Elm's potential for meta-programming and self-hosting. |
| 218 | + |
| 219 | +The elm-playground project proves that compiler-driven development isn't just a teaching concept – it's a practical approach that can lead to elegant, efficient solutions. And when your playground for teaching compiler-driven development is itself an example of compiler-driven development working beautifully... well, that's just good teaching. |
| 220 | + |
| 221 | +At the time of writing this project has already been forked by my betters, as it turns out to be quite interesting in more ways than expected. |
| 222 | + |
| 223 | +I'm glad I didn't give up! |
0 commit comments