v0.16 - Gleam compiles to JavaScript!

Gleam is a type safe and scalable language for the Erlang virtual machine, and as of today’s v0.16.0 release Gleam compiles to JavaScript as well!

Show me!

What’s the introduction of a new way to write front end web code without some cliché examples?

Here’s a collection of interactive widgets you’ve probably seen countless times before, along with their Gleam source code and the JavaScript it compiles into.

In the interest of brevity we skip error handling in these examples and use rather quick-and-dirty type definitions of the JavaScript functions they import. In real Gleam programs we would expect these JavaScript bindings to live within shared libraries, but these examples should be enough to give you a taste of what Gleam can do with JavaScript.


Hello, world!
Display Gleam source
pub fn main() {
  let input = query_selector("[data-hello-input]")
  let display = query_selector("[data-hello-display]")
  let sync = fn() {
    let text = get(from: input, property: "value")
    set("innerText", on: display, to: text)
  }
  set("oninput", on: input, to: sync)
}

external type Element

external fn query_selector(String) -> Element =
  "" "document.querySelector"

external fn get(from: Element, property: String) -> String =
  "" "Reflect.get"

external fn set(on: Element, property: String, to: anything) -> Bool =
  "" "Reflect.set"
Display compiled JavaScript
"use strict";

export function main() {
  let input = query_selector("[data-hello-input]");
  let display = query_selector("[data-hello-display]");
  let sync = () => {
    let text = get(input, "value");
    return set(display, "innerText", text);
  };
  return set(input, "oninput", sync);
}

function query_selector(arg0) {
  return document.querySelector(arg0)
}

function get(from, property) {
  return Reflect.get(from, property)
}

function set(on, property, to) {
  return Reflect.set(on, property, to)
}

0
Display Gleam source
pub fn main() {
  let increment = query_selector("[data-counter-increment]")
  let decrement = query_selector("[data-counter-decrement]")
  let display = query_selector("[data-counter-display]")
  let add = fn(diff) {
    set("innerText", on: display, to: get_value(display) + diff)
  }
  set("onclick", on: increment, to: fn() { add(1) })
  set("onclick", on: decrement, to: fn() { add(-1) })
}

fn get_value(element) {
  element
  |> get(property: "innerText")
  |> parse_int
}

external type Element

external fn query_selector(String) -> Element =
  "" "document.querySelector"

external fn get(from: Element, property: String) -> String =
  "" "Reflect.get"

external fn set(on: Element, property: String, to: anything) -> Bool =
  "" "Reflect.set"

external fn parse_int(String) -> Int =
  "" "parseInt"
Display compiled JavaScript
"use strict";

export function main() {
  let increment = query_selector("[data-counter-increment]");
  let decrement = query_selector("[data-counter-decrement]");
  let display = query_selector("[data-counter-display]");
  let add = (diff) => {
    return set(display, "innerText", get_value(display) + diff);
  };
  set(increment, "onclick", () => { return add(1); });
  return set(decrement, "onclick", () => { return add(-1); });
}

function get_value(element) {
  return parse_int(get(element, "innerText"));
}

function query_selector(arg0) {
  return document.querySelector(arg0)
}

function get(from, property) {
  return Reflect.get(from, property)
}

function set(on, property, to) {
  return Reflect.set(on, property, to)
}

function parse_int(arg0) {
  return parseInt(arg0)
}

A dog
Display Gleam source
const url = "https://dog.ceo/api/breeds/image/random"

pub fn main() {
  let button = query_selector("[data-dogs-button]")
  let img = query_selector("[data-dogs-img]")
  new_dog(img)
  set(on: button, property: "onclick", to: fn() { new_dog(img) })
}

fn new_dog(img) {
  fetch(url)
  |> then(get_json_body)
  |> then(fn(json) {
    set(on: img, property: "src", to: get(json, "message"))
    resolve_promise(Nil)
  })
}

external type Element

external type Response

external type Json

external type Promise(value)

external fn query_selector(String) -> Element =
  "" "document.querySelector"

external fn get(from: Json, property: String) -> Json =
  "" "Reflect.get"

external fn set(on: Element, property: String, to: anything) -> Bool =
  "" "Reflect.set"

external fn fetch(String) -> Promise(Response) =
  "" "fetch"

external fn get_json_body(Response) -> Promise(Json) =
  "" "Response.prototype.json.call"

external fn then(Promise(a), fn(a) -> Promise(b)) -> Promise(b) =
  "" "Promise.prototype.then.call"

external fn resolve_promise(value) -> Promise(value) =
  "" "Promise.resolve"
Display compiled JavaScript
"use strict";

const url = "https://dog.ceo/api/breeds/image/random";

export function main() {
  let button = query_selector("[data-dogs-button]");
  let img = query_selector("[data-dogs-img]");
  new_dog(img);
  return set(button, "onclick", () => { return new_dog(img); });
}

function new_dog(img) {
  return then(
    then(fetch(url), get_json_body),
    (json) => {
      set(img, "src", get(json, "message"));
      return resolve_promise(undefined);
    },
  );
}

function query_selector(arg0) {
  return document.querySelector(arg0)
}

function get(from, property) {
  return Reflect.get(from, property)
}

function set(on, property, to) {
  return Reflect.set(on, property, to)
}

function fetch(arg0) {
  return globalThis.fetch(arg0)
}

function get_json_body(arg0) {
  return Response.prototype.json.call(arg0)
}

function then(arg0, arg1) {
  return Promise.prototype.then.call(arg0, arg1)
}

function resolve_promise(arg0) {
  return Promise.resolve(arg0)
}

Thank you to dog.ceo for their Dog API


Why JavaScript?

The Erlang virtual machine is second-to-none for long-running services that run on servers, but outside of this space it may not always be the best tool for the job. It is not supported on all platforms, and factors such as compiled application size, boot time, and ease of installation may be an issue.

JavaScript is one of the most widely used and widely supported languages in the world. It can be used for website front ends, desktop applications, command line applications, mobile phones app, on IoT devices, on cloud serverless platforms, and more.

While JavaScript lacks the multi-core and distribute computing capabilities of the Erlang virtual machine it does have robust concurrency features and respectable single threaded performance thanks to highly optimised runtimes such Google’s V8 engine, which is used in NodeJS and the Chrome web browser.

By compiling to JavaScript as well as Erlang Gleam can be used for a much wider range of problem spaces and domains, and be accessible to a wider range of people. A team writing a backend web API in Gleam can now choose to also write their website frontend in Gleam, sharing code between both platforms and enjoying the friendly and productive type-based programming style of Gleam throughout their application stack.

Personally I’m excited to run Gleam code using serverless function platforms, and to make fun little Processing style sketches that run in the browser.

How does it work?

Much like the Erlang compiler backend this new JavaScript backend outputs human readable and pretty printed source code. It is now included with the compiler and does not require any extra components to be installed to use it.

Rather than attempting to replicate a subset of Erlang’s actor model Gleam uses the standard promise based concurrency model when targeting JavaScript. While this may be disappointing for some, it means that there is no additional runtime code added. This keeps bundle size small and makes it so code written in Gleam can be called like normal from languages such as JavaScript and TypeScript.

What’s next?

Full language support

The JavaScript backend supports almost all of the Gleam language, but there’s a few features still to be implemented, most notably the bit string syntax. Support for the remaining features will be added in following releases.

Tooling

When compiling to Erlang we typically use Erlang’s rebar3 build tool or Elixir’s mix build tool. With JavaScript we do not yet any build tool integration and instead have to use the lower level gleam compile-package --target=javascript command line API. This API could be called by an npm preprocess script, a makefile, or by more sophisticated integrations with tools such as Parcel or Webpack.

Currently a Gleam build tool is being worked on, one that will be suitable for use with Erlang and JavaScript, and handle the resolution and compilation of dependancies from the Hex package manager.

Concurrency exploration

In JavaScript code yield points have to be manually inserted using Promise.prototype.then, or with the await keyword, while in languages like Erlang and Go this is handled automatically by the compiler.

The pervasive Promise type and the split between synchronous and asynchronous functions in JavaScript can be more difficult to learn and use than languages such as Erlang and Go that make no such distinction. While there are no firm plans at present I would like to explore using the Gleam compiler’s static analysis to automatically insert await statements into the generated JavaScript code.

If this proves to be successful and useful we could swap promise based Gleam code that looks like this:

pub fn main() -> Promise(Int) {
  async_function()
  |> promise.then(fn(x) {
    let y = sync_function()
    async_function()
    |> promise.then(fn(z) {
      promise.resolve(x + y + z)
    })
  })
}

For automatically yielding Gleam code that looks like this:

pub fn main() -> Int {
  let x = async_function()
  let y = sync_function()
  let z = async_function()
  x + y + z
}

Both of these examples would have the same runtime behaviour and performance characteristics.

This would also solve the problem of JavaScript’s promise type automatically flattening Promise(Promise(value)) into Promise(value), which makes it not possible to soundly type without adding a special case to Gleam’s type system.

How can I try it?

Instructions on how to install the latest version of Gleam can be found on the getting started page of the website.

Once installed this Gleam JavaScript template can be used for writing and running Gleam code on JavaScript.

For all the details of this release check out the changelog files:

Supporting Gleam

Gleam sponsorship is my primary source of income. If you would like to support me in making Gleam please consider sponsoring Gleam or asking your employer to sponsor Gleam. Every donation makes a difference, no matter how small, so thank you for your help.

⭐ Or alternatively give us a star on GitHub! ⭐

Thank you

The JavaScript backend for the Gleam compiler was initially created by Peter Saxton, so a big thank you to him. I also want to thank Andy Thompson, her knowledge of other functional languages that compile to JavaScript has been invaluable in getting to this first release.

Gleam is made possible by the support of all the people who have sponsored and contributed to the project. Thank you all!

Thanks for reading! Have fun! 💜