DEV Community

Camilo
Camilo

Posted on

Using Gleam in your Phoenix Hooks

Phoenix uses Elixir, but when dealing with LiveView Hooks it requires JavaScript. But how about using another functional language in that area?.

Presenting Gleam

Gleam Language

The Gleam programming language

favicon gleam.run

The power of a type system, the expressiveness of functional programming, with a familiar and modern syntax.

Gleam comes with compiler, build tool, formatter, editor integrations, and package manager all built in, so creating a Gleam project is just running gleam new.

In my humble opinion, Gleam is the perfect alternative to Typescript! if you want all the goodies of a functional language and a type system for your Phoenix Hooks.

Installation

Be sure the gleam binary is in your $PATH. You can refer to the Installation Guide for more details.

$ gleam --version
gleam 0.26.1
Enter fullscreen mode Exit fullscreen mode

GleamPhx Project

This will be an small and simple project (Phoenix 1.6) with no ecto. Just a LiveView with a simple hook for demostration.

Creating our Project

First let's start with a project named gleamphx with no database requirement (just to be slim).

$ mix phx.new . --app gleamphx --no-ecto
Enter fullscreen mode Exit fullscreen mode

Creating our Gleam Project

Let's go to assets/ directory and create a new Gleam Project named hooks.

$ cd assets
$ gleam new hooks
$ cd hooks
Enter fullscreen mode Exit fullscreen mode

Now we edit gleam.toml so we can setup the Javascript target.

name = "hooks"
version = "0.1.0"
description = "A Gleam project"
# ...
target = "javascript"

[javascript]
# Generate TypeScript .d.ts files
typescript_declarations = true

# Which JavaScript runtime to use with `gleam run`, `gleam test` etc.
runtime = "node" # or "deno"
Enter fullscreen mode Exit fullscreen mode

JavaScript Code

This will be the main Javascript file that will export all of our hooks. This file will be used in app.js later.

$ touch assets/hooks/index.js
Enter fullscreen mode Exit fullscreen mode
import * as Hello from "./build/dev/javascript/hooks/hello.mjs";

export default { Hello };
Enter fullscreen mode Exit fullscreen mode

Gleam Code

Inside the src/ directory we will create our hooks.
Lets create hello.gleam hook.

import gleam/io

pub fn mounted() {
  io.println("Hello from Gleam!")
}
Enter fullscreen mode Exit fullscreen mode

Phoenix Config

Ok we are ready with our hook. Lets configure Phoenix!.

assets/app.js

First lets edit assets/app.js to import our hooks.

// ...
import Hooks from "../hooks"
// ...

let liveSocket = new LiveSocket("/live", Socket, {hooks: Hooks, params: {_csrf_token: csrfToken}})

Enter fullscreen mode Exit fullscreen mode

mix.exs

Let's add a new build step in assets.deploy task.

"gleam.build": [
   "cmd cd assets/hooks && rm -rf build && gleam build"
 ]
Enter fullscreen mode Exit fullscreen mode

This task only builds the gleam code to the target javascript files.

defp aliases do
    [
      "gleam.build": [
        "cmd cd assets/hooks && rm -rf build && gleam build"
      ],
      setup: ["deps.get"],
      "assets.deploy": [
        "gleam.build",
        "esbuild default --minify",
        "phx.digest"]
    ]
  end
Enter fullscreen mode Exit fullscreen mode

We add the task to the assets.deploy pipeline.

lib/gleamphx_web/router.ex

Ok now we just have to test. Let's create a simple live view with a div that is Hooked to the Hello function in Gleam.

First we configure our router

scope "/", GleamphxWeb do
    pipe_through :browser

    live "/", Live.Example, :index
  end
Enter fullscreen mode Exit fullscreen mode

lib/gleamphx_web/live/example.ex

And then create our module

defmodule GleamphxWeb.Live.Example do
  use GleamphxWeb, :live_view

  @impl true
  def render(assigns) do
    ~H"""
      <div id="ExampleGleamHook" phx-hook="Hello">Example Hooked Component</div>
    """
  end
end
Enter fullscreen mode Exit fullscreen mode

If everything went OK then after mix phx.server you will see in the browser console a message similar to this.

GleamPHX

What's Missing?

There are some small caveats in using Gleam instead of Typescript. For example, because Gleam is a functional language, everything must be a function. So if you want to access some APIs inside a JS object, you just need to create a wrapper around it to make it functional. Nevertheless working with ffi is nice and painless, but it would take some time if you want to wrap something big, thankfully it would be a one time only task.

Automated tool for ffi bindings

Currently if you want to use a library that is JS only, you would need to create the ffi bindings for it. A DOM ffi for the standard browser apis (document, window, etc) would be awesome.

It would be really cool if some tool existed that make conversion from TS to Gleam, and ease the bridge part.

More documentation about the JS target

There are some examples:

Example working with JS promises.

pub fn stream(stream: Promise(Stream), element) {
  use stream <- promise.await(stream)

  let video = dom.create_video_element()
  dom.append_child(video)
  dom.video.play(video)
  video
}
Enter fullscreen mode Exit fullscreen mode

See more details here:

https://discord.com/channels/768594524158427167/768594524158427170/1070179914432643122

Next Steps?

TODO: Maybe configure autoreload on change of gleam files.

You can check out some example projects here:

Top comments (4)

Collapse
 
juliolinarez profile image
Julio Linarez

Good work

Collapse
 
bigardone profile image
Ricardo García Vega

Very cool, thanks for sharing. I definitely have to try this out 😍

Collapse
 
matejsarlija profile image
matej

Honestly, how do you get to a level where you understand all this interop and build level stuff?

Collapse
 
clsource profile image
Camilo

By drinking lots of Mate en.wikipedia.org/wiki/Mate_(drink) :D