Gleam calling JavaScript and v.v.

Mar 20, 2023   Kero van Gelder

This post is part of a series of four, Gleam calling Erlang and v.v., A Rebar3 project using Gleam, this post, and Gleam in the browser.

In this post, we look at running Gleam with node, and deno. We also call JavaScript code from Gleam and vice versa.

Compile Gleam to JavaScript

To let the gleam compiler output JavaScript, you need to call it with the argument --target=javascript, e.g. when you build the project, or run the tests. If you want to do this all the time, add a line target = "javascript" to the gleam.toml configuration file.

You may notice that compiling to Erlang takes longer, that is because the Erlang compiler is called in addition to the Gleam compiler.

Using Deno

When the target is JavaScript, Gleam can choose between node and deno when running, either on the command line:
gleam run --runtime=deno
or via the configuration file:
[javascript]
runtime = "deno"
Deno claims better security, and you'll notice that when you run your tests this way.

Let Gleam call JavaScript code

In order to call JavaScript code from Gleam, we need to use the @external(javascript, "module-path", "method" annotation, just as we did for Erlang. The path is to a JavaScript module, i.e. it works with import and export, and it is best to use the .mjs extension.

Let us create a file src/ffi.mjs that contains:

export const random = Math.random

Then we can use it from Gleam as follows:

import gleam/io

pub fn main() {
  random()
  |> io.debug
}

@external(javascript, "./ffi.mjs", "random")
pub fn random() -> Float

JavaScript knows no List

The JavaScript target has been added to Gleam later; Gleam is designed for the Erlang/BEAM target. There is no nice one-on-one mapping from Gleam types to JavaScript types. Specifically, a Gleam list is not mapped to a JavaScript array. Because it is a singly linked list, not an array. The JavaScript implementation of Gleam thus comes with its own list that will be used.

If you want to use JavaScript constructs such as an array, promises, and 'typeof', there is a library for that:

gleam add gleam_javascript

JavaScript calling Gleam

Like in our Erlang post, let's have a look at the generated code, now JavaScript, namely build/dev/javascript/happy/happy.mjs:
import * as $io from "../gleam_stdlib/gleam/io.mjs";
import { random } from "./ffi.mjs";

export { random };

export function main() {
  let _pipe = random();
  return $io.debug(_pipe);
}
Pretty straightforward, files map to modules, with the path providing a nested scope. Note that this is different from Erlang, which has no nested scope.

Calling an exported Gleam function from JavaScript is, hence, trivial:

import * as $happy from "./happy.mjs"

$happy.random()