Gleamという静的型付け・関数型言語がある.
Erlang VMやJavaScriptランタイムで動くようコンパイルされるのが特徴.
今回は,「JavaScriptにトランスパイルできる」というところに注目し,Cloudflare WorkersでGleamを動かしてみた.
環境構築
依存関係にgleam_javascriptを追加する.
また,ローカルでの動作確認・デプロイ用にwranglerをインストールする.
gleam new workers_gleamcd workers_gleamgleam add gleam_javascriptpnpm initpnpm add -D wranglergleam.tomlを編集.targetにJavaScriptを指定した.
name = "workers_gleam"version = "1.0.0"target = "javascript"
[dependencies]gleam_stdlib = ">=0.34.0 and < 2.0.0"gleam_javascript = ">=0.12.0 and < 1.0.0"
[dev-dependencies]gleeunit = ">= 1.0.0 and < 2.0.0"また,wrangler.tomlを以下のように記述する.
name = "workers-gleam"main = "build/dev/javascript/workers_gleam/index.js"compatibility_date = "2023-09-22"
[build]command = "gleam build"GleamとJavaScriptをFFIで繋ぐ
GleamはJavaScriptやErlangと相互に運用できるため,FFIの仕組みが備わっている.
ここでは,JavaScriptで定義されているResponseやRequestといったオブジェクトをGleamでも使えるようにしていく.
まずはsrc/ffi.jsにresponse, method, url, bodyを追加する.
export function response(status, headersList, body) { let headers = new Headers() for (let [k, v] of headersList) headers.append(k, v) return new Response(body, { status, headers })}
export function method(request) { return request.method}
export function url(request) { return request.url}
export function body(request) { return request.body}src/cloudflare/workers.gleamでffi.jsで書いた関数とGleamの関数を結びつける.
pub type Request
pub type Response
pub type Environment
pub type ExecutionContext
@external(javascript, "../ffi.js", "response")pub fn response( status: Int, headers: List(#(String, String)), body: String,) -> Response
@external(javascript, "../ffi.js", "method")pub fn method(req: Request) -> String
@external(javascript, "../ffi.js", "url")pub fn url(req: Request) -> String
@external(javascript, "../ffi.js", "body")pub fn body(req: Request) -> StringこれでGleam側でresponseやmethodを実行したときには,JavaScript側の関数を呼び出すようになった.
ハンドラーを作る
これらの型や関数を使ってハンドラーを書く.
handle_requestはJavaScriptのPromise<Response>を返さなければならないため,返り値をPromise(Response)としている.
また,promise.resolveに渡すことでresponseをPromiseでラップしている.
import cloudflare/workers.{type Request, type Response}import gleam/javascript/promise.{type Promise}
pub fn handle_request( _request: Request,) -> Promise(Response) { workers.response(200, [], "Cloudflare Workers上でGleamが動いてるよ") |> promise.resolve}エントリポイントを作る
最後にCf Workers用のエントリポイントを作る.src/index.jsの中に書いていく.
import { handle_request } from "./workers_gleam.mjs"
export default { async fetch(request) { return handle_request(request) }}一行目でimportしているworkers_gleam.mjsはビルド時にsrc/workers_gleam.gleamから生成されるJavaScriptモジュールで,Gleamで定義したhandle_requestがそのままJavaScriptの関数としてexportされている.
import * as $promise from "../gleam_javascript/gleam/javascript/promise.mjs";import * as $workers from "./cloudflare/workers.mjs";import { toList } from "./gleam.mjs";
export function handle_request(_) { let _pipe = $workers.response(200, toList([]), "Cloudflare Workers上でGleamが動いてるよ"); return $promise.resolve(_pipe);}Envを使う
Cf Workersではfetch関数の第二引数にenvを受け取る.Cf Workersに設定した環境変数がKey-Value形式で格納されたオブジェクトになっている.
.dev.varsに環境変数を追加する.
SECRET_KEY=5U93R_53CR37_K3Yこの値をGleamから取り出して使う.
ffi.jsに新たにread_environment(env, key)関数を用意するが,この関数はGleamのResult型を返すようにする.
値が存在すればOk,無ければErrorを返すようにしておけば,Gleamからは安全にEnvの値にアクセスすることが出来るためである.
JavaScriptからGleamの機能にアクセスするには,./gleam.mjsをインポートする.gleam.mjsもビルド時に生成され,Gleamの一部機能をJavaScriptから使えるようになっている.
import { Ok, Error as Err } from "./gleam.mjs"
export function read_environment(env, key) { const value = env[key] return value ? new Ok(value) : new Err(undefined)}src/cloudflare/workers.gleamにread_environmentを追加する.
@external(javascript, "../ffi.js", "read_environment")pub fn read_environment(env: Environment, key: String) -> Result(String, Nil)ハンドラーで呼び出す.
import cloudflare/workers.{type Environment, type Request, type Response}import gleam/ioimport gleam/javascript/promise.{type Promise}
pub fn handle_request(_request: Request, env: Environment) -> Promise(Response) { let _ = workers.read_environment(env, "SECRET_KEY") |> io.debug
workers.response(200, [], "Cloudflare Workers上でGleamが動いてるよ") |> promise.resolve}エントリポイントもEnvを受け取るように変更する.
import { handle_request } from "./workers_gleam.mjs"
export default { async fetch(request, env) { return handle_request(request, env) }}実行すると,ログに設定した環境変数が表示される.

FFIコードを書かなければならないものの,GleamをCf Workersで動かせることが分かった.
KVやR2などのBindingsが使えないかどうか,今後も調査を進めていきたい.
この記事を書いている最中に,Deno DeployやCloudflare Workersでも動かせるGlenというGleam製フレームワークを発見した.
実際にCf Workersに載せるなら,自力でFFIを頑張るのではなく,このようなフレームワークを使うのが良いだろう.