THINK

Cloudflare WorkersでDartを動かす

5/11/2024

環境構築

  1. Cloudflare Workersのプロジェクトを作る
    npm create cloudflare@latest example-dart-on-cloudflare
    
  2. Dartのプロジェクトとしても初期化する
    # すでにNode.jsのプロジェクトとして作っているので`--force`オプションをつける
    dart init --force
    

動かす

  1. Dartのコードを書く

    // bin/main.dart
    import 'dart:js_interop';
    
    @JS()
    external void responseMessage(JSString message);
    
    void main(List<String> arguments) {
      final message = 'Hello, Dart!';
      responseMessage(message.toJS);
    }
    
  2. Wasmにビルドする

    dart compile wasm ./bin/main.dart -o src/__dart/main.wasm
    
  3. JSのコードを書く(Cloudflare は import mod from "./module.wasm"でWasmモジュールを読み込むことができる).

    // src/index.ts
    import { instantiate, invoke } from "./__dart/index.mjs";
    import mod from "./__dart/index.wasm";
    
    export default {
      async fetch(
        request: Request,
        env: Env,
        ctx: ExecutionContext,
      ): Promise<Response> {
        let responseMessage: string;
        globalThis.responseMessage = (message: string) => {
          responseMessage = message;
        };
    
        invoke(
          await instantiate(
            mod,
            async () => ({}),
          ),
        );
    
        return new Response(responseMessage);
      },
    };
    
  4. 動かしてみる

    npm run dev
    

Deploy先: example-dart.askua.dev

Import Modules

WebAssembly.instantiateを使ってWasmモジュールを読み込むときに,モジュールを渡すことで,Wasmモジュール内で使う関数を定義することができる.

Dartでは,次のように設定することでうまくいく.

-@JS()
+@pragma("wasm:import", "env.responseMessage")
 external void responseMessage(JSString message);

ただ,引数と戻り値はdoubleJSAny以外使えないと思った方が良い. Stringに関してはコンパイルした時にできる index.mjsの中にあるstringFromDartStringstringToDartStringを良い感じにカスタムすることで使えなくはないが,この辺りのJSの型をDartで良い感じに扱うのであればglobalThisに置くのが無難なよう.

下記の書き方は現状推奨しないが,参考実装として置いておく.

// bin/main.dart
@pragma("wasm:export", "hello")
String hello(String foo) {
  print(foo);
  return 'Hello, Dart!';
}

// mainはビルドに必須
void main(List<String> arguments) {}
// src/index.ts
import { instantiate } from "./__dart/index.mjs";
import { stringFromDartString, stringToDartString } from "./dart_utils.ts";
import mod from "./__dart/index.wasm";

export default {
  async fetch(
    _request: Request,
    _env: Env,
    _ctx: ExecutionContext,
  ): Promise<Response> {
    const dartInstance = await instantiate(mod);
    const message = stringFromDartString(
      dartInstance,
      dartInstance.exports.hello(stringToDartString(dartInstance, "hoge")),
    );

    return new Response(message);
  },
};

参考