Gleam is a new-ish functional programming language that compiles to Erlang and JavaScript. It features a familiar Rust like syntax while being similar to Elm in complexity and in general is a lot of fun to work with.
The problem however is that Gleam does not natively support creating executables.
This note/guide explains how to create a Gleam executable in various ways, along with their advantages and caveats.
I will be using my WIP project to demonstrate the process of creating an executable throughout the guide.
gleam new <project_name>gleam build --target=erlang|javascriptThe target is important as it determines the generated output of the build command and hence, the way of packaging the code into an executable.
Gleescript is a tool that allows you to create a single executable from a Gleam project. It uses the Erlang escript stdlib module to create the escript which can then be run on the Erlang VM.
The caveat is that it requires the Erlang VM to be installed on the target machine.
As quoted by the official docs -
The escript can run on any computer that has the Erlang VM installed. Older versions of the virtual machine may not support the newer bytecode contained in the escript. Typically being within a couple major versions of the version used to build the escript is safe.
gleam add gleescriptgleam build --target erlanggleam run -m gleescript./your_projectBurrito is a tool to wrap Elixir applications in a BEAM burrito so as to speak. Unlike gleescript, it does not require the host machine to have the Erlang VM installed. As quoted from the official docs -
Builds a self-extracting archive for a Mix project, targeting Windows, MacOS, and Linux, containing:
- Your compiled BEAM code
- The required ERTS for your project
- Compilation artifacts for any elixir-make based NIFs used by the project
I have not experimented enough with Burrito to get it working with Gleam but it definitely is worth a try considering it supports Elixir and Erlang projects. Perhaps a project can be converted into an escript and then wrapped with Burrito to create a self-contained executable.
Deno compile is a command built into Deno that allows you to compile a JavaScript file into a single executable. It bundles a lightweight Deno runtime into the executable, so it can run on any system without requiring Deno to be installed.
You still have to bundle the generated Gleam code into a single file using a bundler like Webpack/Parcel/Rollup/Esbuild. Deno used to support bundling applications with deno bundle but it has been deprecated in favor of other bundlers stated before.
gleam build --target=javascriptesbuild build/dev/javascript/<project_name>/<project_name>.mjs --platform=node --minify-whitespace --minify-syntax --bundle --outfile=bundle.cjs --format=cjs --footer:js=\"main();\"node as my specific project is using Node.js API’s internally (Gleam simplifile package)bundle.cjs and the format as cjs (CommonJS). This was a result of me trying out Node SEA first (the next method) but ESM probably works here too as Deno does not have any explicit declaration that it only supports CommonJS.main method in the generated file as we are not using the bundled file as a module. Usually the footer is used for adding comments and there might be a better way of doing this that I am not aware of. This however serves the purpose very well.deno compile --target=<target_architecture> --output <executable_name> bundle.cjs
Node Single Executable Applications (SEA) is an experimental Node v23+ feature that allows the distribution of a Node.js application to a system that does not have Node.js installed.
The caveat with this method is that it supports only CommonJS files and you will have to bundle the generated Gleam JavaScript files into a single file using a bundler like Webpack/Parcel/Rollup/Esbuild.
gleam build --target=javascriptesbuild build/dev/javascript/<project_name>/<project_name>.mjs --platform=node --minify-whitespace --minify-syntax --bundle --outfile=bundle.cjs --format=cjs --footer:js=\"main();\"node as my specific project is using Node.js API’s internally (Gleam simplifile package)bundle.cjs and the format as cjs (CommonJS). This is required for Node SEA.main method in the generated file as we are not using the bundled file as a module. Usually the footer is used for adding comments and there might be a better way of doing this that I am not aware of. This however serves the purpose very well.The next steps are a lot more complicated that Deno and you can find them here. For my project however, they are -
sea-config.json and populate as per instructions in the docs. The main and output are the important fields here.node --experimental-sea-config sea-config.jsoncp $(command -v node) executable_namenpx postject executable_name NODE_SEA_BLOB <output>.blob --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2./executable_nameFollowing the steps above resulted in a segfault for me and I am not sure why. I will update this section once I study SEA in more detail. It is clear however that Deno is way simpler to use compared to Node SEA.
Bun build with the compile flag is a Bun feature to bundle a bunch of JS files and then compile them into a single executable. It is similar to Deno compile but does not require a separate bundler.
All imported files and packages are bundled into the executable, along with a copy of the Bun runtime. All built-in Bun and Node.js APIs are supported.
gleam build --target=javascriptbun build --compile --outfile=bundle build/dev/javascript/<project_name>/<project_name>.mjs --footer="main();"That’s it. Bun is incredibly convinient and crazy fast compared to Deno or Node or any of the methods mentioned so far.
Nexe is a command-line utility that compiles your Node.js application into a single executable file. Like Burrito, I have not played around with Nexe enough to get it working with my project but it should be way more straightforward than making Burrito work with Gleam.
Out of all the tools/libraries that I used, I found Bun to be incredibly fast and also very easy to use. The only problem with Bun and Deno is that due to them bundling their own runtimes, the executables are large, usually exceeding 100MB. I personally do not mind this as I am not using the executables in production but it is something to keep in mind.