marcin

marcin

A demo project using Popcorn (Elixir in the browser)

I did a fun/experimental project using Popcorn, a library that lets you run Elixir in the browser!
It bundles your Elixir app with AtomVM, a minimal BEAM implementation, and runs it on a WASM runtime.

It’s a portfolio page (that will perhaps help me getting a job in Elixir which is not gambling or user tracking tech :sleepy_face:): https://marcin-koziej.cahoots.pl/
Source on Github

Thanks @mat-hek and the SoftwareMansion team for this impressive work!

Some thoughts and learnings:

  1. Early stage tooling: Popcorn is in active development, which meant I had to do some debugging, trial and error, as well as reading the Popcorn source code. The documentation could use contributions, but the examples provided with the source code are a great starting point.

  2. WASM vs non-WASM context: The bundled Elixir app runs on the WASM runtime, but running IEx or tests runs on a standard BEAM VM. In the BEAM, the NIF module required by JS interop isn’t available, so your code will raise :nif_not_loaded when using Popcorn.Wasm APIs. This means the interop code is best isolated, making it easy to mock out/disable for testing Elixir code or for interactive exploration in the IEx REPL (I do this a lot).

    Perhaps I could run tests and IEx on a WASM runtime in the shell? I didn’t explore this direction.

  3. Elegant JS interop: You can do GenServer.call and GenServer.cast from the JavaScript side to the Elixir side, and you can execute JS functions with arguments from the Elixir side. Popcorn provides an object proxy called Popcorn.TrackedValue, which is a reference to a value on the JS side (DOM node, object, string, etc.). When it’s garbage collected on the Elixir side (e.g., when the process holding it in its state stops), it will be released on the JS side. There’s a special cleanup callback you can use if cleanup is more complex. Basic Elixir types are converted to JS and vice versa (atoms are one-way converted to strings). Some types (like Pid or Reference) don’t work.

  4. Use cases: I have a feeling that for UI-heavy apps, there’s a lot of JS interop, which results in lots of small JS snippets scattered around your codebase (which is a liability because of #2). It probably makes more sense to put a “backend” Elixir app in the browser and use a more typical JS frontend framework to drive the UI. Migrating a web app into an offline-first desktop app comes to mind.

  5. AtomVM limitations: AtomVM doesn’t implement all BEAM modules, which generates non-obvious errors. For example, the timer_manager module doesn’t work, which means Process.send_after will crash your app. Same for Logger, which has a timestamp in its default formatting, which in turn breaks DynamicSupervisor (which does some logging by default).

    Generally, this work required a lot of printf-style debugging and trying out what works versus what will silently crash the app.

  6. Deployment: You can deploy to any static hosting which allows you to set COOP and COEP headers (latest security requirement to run WASM). These are not supported in Github Pages, so I am deploying to Netlify. The WASM assets sizes:
    31K static/wasm/AtomVM.mjs.gz
    190K static/wasm/AtomVM.wasm.gz
    3.4M static/wasm/bundle.avm.gz
    1.5K static/wasm/popcorn_iframe.js.gz
    2.9K static/wasm/popcorn.js.gz

Related:

Popcorn announcement thread

Most Liked

mat-hek

mat-hek

Membrane Core Team

Hi @marcin, thanks for this write-up and the shout-out!

the BEAM, the NIF module required by JS interop isn’t available, so your code will raise :nif_not_loaded when using Popcorn.Wasm APIs.

I suppose you could mock Popcorn.Wasm with any mocking tool :thinking: We could also make it so Popcorn.Wasm isn’t available when compiling for the Beam so you can just provide an alternative implementation, if that would help. We use Playwright to run tests, and it works quite well. I think you could create an interactive shell backed with Popcorn running via Playwright too.

Perhaps I could run tests and IEx on a WASM runtime in the shell?

Using Popcorn.Wasm usually involves interacting with browser APIs, so I’m not sure how that would work in a pure WASM runtime :thinking: Anyway, AtomVM WASM only works with Emscripten currently. You can compile and run it for Unix though, if that helps.

It probably makes more sense to put a “backend” Elixir app in the browser and use a more typical JS frontend framework to drive the UI

Agreed, to make frontend development feasible, we’d need a framework on top of Popcorn. We’re currently experimenting with client-side LiveView.

AtomVM doesn’t implement all BEAM modules, which generates non-obvious errors.

The documentation could use contributions

Noted the Process.send_after, Logger should already work on Popcorn master. If you have more concrete examples what’s missing / not working, please share :wink: It’ll probably take time to fix, but eventually we’ll get there.

P.S. Hoping these efforts help you find a nice Elixir job :smiley: :crossed_fingers:

marcin

marcin

I know about this!
This is actually one small caveat I have not figured out yet properly.
I am using ExTTY module which will create a child process with IEx.

To make help() and other functions available, you give IEx a path to .iex.exs file. In WASM, there is no access to the filesystem, so what I do is evaluate import Portfolio.Helpers just after starting the REPL. However, I am not informed when IEx is restarted by ExTTY (on syntax error for example), and Helpers should be re-imported.

I am looking into doing Process.monitor() on one of the IEx processes, but still need to figure out which exactly process I should monitor.

edit
After a deep dive into ExTTY and IEx, I was not able to figure out how to monitor for IEx.Evaluator errors..What make it harder is that in WASM, some process introspection calls (Process.info) crash.
As for now I’ve added a hacky workaround to check for “Interactive Elixir” output that IEx is showing on startup, and import then :anguished_face:

Perhaps there is another way to do this?

Where Next?

Popular in Guides/Tuts Top

tfwright
I thought I’d share a small project I’m working on to gain some familiarty with LiveView in a Phoenix app. Github Repo Deployment It’s...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
drapermd
So here is the code I came up with to generically generate an array param that will be stored on a jsonb property in ecto. It only handl...
New
9mm
So I’m really loving elixir. BY FAR the most excruciating piece of learning a functional language for me is having to “transform” all my ...
New
GenericJam
Just leaving some breadcrumbs for future me and future others like me. Connect with TCP (not secured) - most servers will reject but use...
New
emilsoman
Hello, While answering a StackOverflow question on how to debug an elixir node running remotely, I thought it may be helpful to write a...
New
dogweather
I just finished a long process of configuring and debugging a Docker Compose-based dev environment. Several late-night hours! I think I’v...
New
georgeguimaraes
Another cool plugin for Neovim, GitHub - jmbuhr/otter.nvim: Just ask an otter! 🦦 makes it possible to run linters for embedded code, like...
New
AstonJ
With a few questions relating to this recently, I wonder if it might help having a thread where we share how we’ve set up Elixir/Erlang/P...
New
niku
I have published an elixir project with using Travis CI. I would like to share some tips & thoughts that I was getting through this ...
New

Other popular topics Top

senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 45766 226
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New

We're in Beta

About us Mission Statement