Cloudflare Workers の Get started を試してみる.

TypeScript プロジェクトの場合

C3 による Worker プロジェクトの作成

まず、 C3 (create-cloudflare-cli) でプロジェクトを生成する. これは pnpm create cloudflare@latest コマンドで行うことができる.

$ pnpm create cloudflare@latest hello-cloudflare-workers hello-cloudflare-workers

──────────────────────────────────────────────────────────────────────────────────────────────────────────
👋 Welcome to create-cloudflare v2.31.1!
🧡 Let's get started.
📊 Cloudflare collects telemetry about your usage of Create-Cloudflare.

Learn more at: https://github.com/cloudflare/workers-sdk/blob/main/packages/create-cloudflare/telemetry.md
──────────────────────────────────────────────────────────────────────────────────────────────────────────

╭ Create an application with Cloudflare Step 1 of 3
│
├ In which directory do you want to create your application?
│ dir ./hello-cloudflare-workers
│
├ What would you like to start with?
│ category Hello World example
│
├ Which template would you like to use?
│ type Hello World Worker
│
├ Which language do you want to use?
│ lang TypeScript
│
├ Copying template files
│ files copied to project directory
│
├ Updating name in `package.json`
│ updated `package.json`
│
├ Installing dependencies
│ installed via `pnpm install`
│
╰ Application created

╭ Configuring your application for Cloudflare Step 2 of 3
│
├ Installing @cloudflare/workers-types
│ installed via pnpm
│
├ Adding latest types to `tsconfig.json`
│ added @cloudflare/workers-types/2023-07-01
│
├ Retrieving current workerd compatibility date
│ compatibility date 2024-10-22
│
├ Do you want to use git for version control?
│ yes git
│
├ Initializing git repo
│ initialized git
│
├ Committing new files
│ git commit
│
╰ Application configured

╭ Deploy with Cloudflare Step 3 of 3
│
├ Do you want to deploy your application?
│ no deploy via `pnpm run deploy`
│
╰ Done

────────────────────────────────────────────────────────────
🎉  SUCCESS  Application created successfully!

💻 Continue Developing
Change directories: cd hello-cloudflare-workers
Start dev server: pnpm run start
Deploy: pnpm run deploy

📖 Explore Documentation
https://developers.cloudflare.com/workers

🐛 Report an Issue
https://github.com/cloudflare/workers-sdk/issues/new/choose

💬 Join our Community
https://discord.cloudflare.com
────────────────────────────────────────────────────────────

作成したプロジェクトに移動しておく.

$ cd hello-cloudflare-workers

C3 で作成されたプロジェクトの構造は以下の通り.

$ tree .
.
├── node_modules
│   ├── @cloudflare
│   │   ├── vitest-pool-workers -> ../.pnpm/@[email protected]_@[email protected]_@vitest+runner@_spseiqobqihm4dnc75a3rcyibi/node_modules/@cloudflare/vitest-pool-workers
│   │   └── workers-types -> ../.pnpm/@[email protected]/node_modules/@cloudflare/workers-types
│   ├── typescript -> .pnpm/[email protected]/node_modules/typescript
│   ├── vitest -> .pnpm/[email protected]_@[email protected]/node_modules/vitest
│   └── wrangler -> .pnpm/[email protected]_@[email protected]/node_modules/wrangler
├── package.json
├── pnpm-lock.yaml
├── src
│   └── index.ts
├── test
│   ├── index.spec.ts
│   └── tsconfig.json
├── tsconfig.json
├── vitest.config.mts
├── worker-configuration.d.ts
└── wrangler.toml

10 directories, 11 files

package.json の中身はこんな感じ. Cloudflare の Worker を開発する上でキモとなるのは CLI である wrangler である.

{
  "name": "hello-cloudflare-workers",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "deploy": "wrangler deploy",
    "dev": "wrangler dev",
    "start": "wrangler dev",
    "test": "vitest",
    "cf-typegen": "wrangler types"
  },
  "devDependencies": {
    "@cloudflare/vitest-pool-workers": "^0.5.2",
    "@cloudflare/workers-types": "^4.20241022.0",
    "typescript": "^5.5.2",
    "vitest": "2.0.5",
    "wrangler": "^3.60.3"
  }
}

Wrangler CLI を使った開発

Wrangler は Worker のコマンドラインインタフェース (CLI) である. これを使うことで Worker プロジェクトの作成、テスト、デプロイがすべて行える.

pnpm run dev を介して wrangler dev を実行することで、とりあえず開発用サーバーを起動して http://localhost:8787 にアクセスできるようになる.

$ pnpm run dev

> [email protected] dev
> wrangler dev


 ⛅️ wrangler 3.84.1
-------------------

╭────────────────────────────────────────────────────────────────────────────────────────⎔ Starting local server...
[wrangler:inf] Ready on http://localhost:8787
[wrangler:inf] GET / 200 OK (7ms)
[wrangler:inf] GET /favicon.ico 200 OK (2ms)
╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
│  [b] open a browser, [d] open devtools, [l] turn off local mode, [c] clear console, [x] to exit  │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯

ブラウザ上で確認してみると、 Hello World! という文字が表示されることがわかる.

コードを書く

実行されるプログラムの実装は src/index.ts 内に存在する.

/**
 * Welcome to Cloudflare Workers! This is your first worker.
 *
 * - Run `npm run dev` in your terminal to start a development server
 * - Open a browser tab at http://localhost:8787/ to see your worker in action
 * - Run `npm run deploy` to publish your worker
 *
 * Bind resources to your worker in `wrangler.toml`. After adding bindings, a type definition for the
 * `Env` object can be regenerated with `npm run cf-typegen`.
 *
 * Learn more at https://developers.cloudflare.com/workers/
 */

export default {
  async fetch(request, env, ctx): Promise<Response> {
    return new Response('Hello World!');
  },
} satisfies ExportedHandler<Env>;

出力を適当に書き換え、 http://localhost:8787 の出力が変化することを確認する.

Cloudflare Worker としてデプロイする

作成した Worker は Wrangler を用いることで *.workers.dev サブドメインかカスタムドメインへとデプロイできる. このためには pnpm run deploywrangler deploy を呼び出せば良い.

$ pnpm run deploy

> [email protected] deploy /Users/y-takahs/src/gitlab.com/AnnPin/hello-cloudflare-workers
> wrangler deploy


 ⛅️ wrangler 3.84.1
-------------------

Attempting to login via OAuth...
Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&s
cope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%
20ai%3Awrite%20queues%3Awrite%20pipelines%3Awrite%20offline_access&state=zvyMjb-AX__tAVqlTUiiLffSfXzXrX1p&code_challenge=4SGTMCriSQH21ptsssOVrux-Wg8FEvpmx8yrftHCoow&code_challenge_method=S256

すると、コマンド実行中にブラウザ上で https://dash.cloudflare.com/oauth/consent-form?consent_challenge=…​; が開き、 Wrangler による Cloudflare のアカウントの状態を確認させて良いかが聞かれるので、 Allow をクリック

...
Successfully logged in.
✔ Select an account › [email protected]'s Account
Total Upload: 27.06 KiB / gzip: 6.26 KiB
✔ Would you like to help improve Wrangler by sending usage metrics to Cloudflare? … yes
Your choice has been saved in the following file: ../../../../Library/Preferences/.wrangler/metrics.json.

  You can override the user level setting for a project in `wrangler.toml`:

   - to disable sending metrics for a project: `send_metrics = false`
   - to enable sending metrics for a project: `send_metrics = true`
Worker Startup Time: 13 ms
Uploaded hello-cloudflare-workers (16.55 sec)
Deployed hello-cloudflare-workers triggers (4.16 sec)
  https://hello-cloudflare-workers.e0956224.workers.dev
Current Version ID: 4651e39b-dc7a-498b-bc78-5f071eb4c2bf

デプロイが完了した. 試しに curl から呼び出してみる.

$ curl https://hello-cloudflare-workers.e0956224.workers.dev
Hello Worker!

問題なく実行できているようだ.

Cloudflare のウェブサイト上では Workers & Pages から現在実行中の Worker と Pages の一覧を確認できるようになっており、 今回作成した Worker も hello-cloudflare-workers という名前で登録されていることがわかる.

各 Worker の詳細画面では Settings タブから個別にカスタムドメインやルーティングの設定を行うことができるようになっており、自分の所持しているドメインでアクセスすることができるようになっている.

Rust プロジェクトの場合

プロジェクトの生成

Cloudflare Workers では Rust の開発をサポートするために workers-rs クレートが提供されており、 Runtime API や Workers KV 、 R2 、 Queues などが Rust から直接アクセスできるようにバインディングが提供されている.

まず、 Rust のビルドターゲットとして WASM を追加し、プロジェクトの雛形を生成するサブコマンド (npm create のようなもの) を追加するための Cargo のプラグインを導入する.

$ rustup target add wasm32-unknown-unknown
#  info: component 'rust-std' for target 'wasm32-unknown-unknown' is up to date

$ cargo install cargo-generate
#    Updating crates.io index
#  Downloaded cargo-generate v0.22.0
#  Downloaded 1 crate (102.2 KB) in 0.17s
#  Installing cargo-generate v0.22.0
#    Updating crates.io index
#     Locking 218 packages to latest compatible versions
#      Adding fs-err v2.11.0 (available: v3.0.0)
#      Adding gix-config v0.40.0 (available: v0.41.0)
#      Adding regex v1.10.6 (available: v1.11.1)
#      Adding rhai v1.19.0 (available: v1.20.0)
#      Adding tempfile v3.10.1 (available: v3.13.0)
#  Downloaded anstyle v1.0.10
#  ...
#   Compiling cargo-generate v0.22.0
#    Finished `release` profile [optimized] target(s) in 1m 33s
#  Installing /Users/y-takahs/.cargo/bin/cargo-generate
#   Installed package `cargo-generate v0.22.0` (executable `cargo-generate`)

プロジェクトを生成してみる.

$ cargo generate cloudflare/workers-rs --name hello-cloudflare-workers-rs
⚠️    Favorite `cloudflare/workers-rs` not found in config, using it as a git repository: https://github.com/cloudflare/workers-rs.git
✔ 🤷   Which template should be expanded? · templates/hello-world
🔧   Destination: /Users/y-takahs/src/gitlab.com/AnnPin/hello-cloudflare-workers-rs ...
🔧   project-name: hello-cloudflare-workers-rs ...
🔧   Generating template ...
🔧   Moving generated files into: `/Users/y-takahs/src/gitlab.com/AnnPin/hello-cloudflare-workers-rs`...
🔧   Initializing a fresh Git repository
✨   Done! New project created /Users/y-takahs/src/gitlab.com/AnnPin/hello-cloudflare-workers-rs

$ cd hello-cloudflare-workers-rs

生成されたプロジェクトの中身は以下の通り.

$ tree .
.
├── Cargo.toml
├── src
│   └── lib.rs
└── wrangler.toml

2 directories, 5 files

Cargo.toml はこんな感じ.

[package]
name = "hello-cloudflare-workers-rs"
version = "0.1.0"
edition = "2021"
authors = [ "Yushi Takahashi <[email protected]>" ]

[package.metadata.release]
release = false

# https://github.com/rustwasm/wasm-pack/issues/1247
[package.metadata.wasm-pack.profile.release]
wasm-opt = false

[lib]
crate-type = ["cdylib"]

[dependencies]
worker = { version="0.4.2" }
worker-macros = { version="0.4.2" }
console_error_panic_hook = { version = "0.1.1" }

wrangler.toml は以下の通り.

name = "hello-cloudflare-workers-rs"
main = "build/worker/shim.mjs"
compatibility_date = "2024-11-04"

[build]
command = "cargo install -q worker-build && worker-build --release"

ローカルで開発してみる

ローカルでの開発を開始するには wrangler コマンドを利用する. システムグローバルに wrangler がインストールされていない場合には、 npx コマンドを介して使用する.

$ npx wrangler dev

わりと wrangler dev を動かすのに苦労した. まず、 wrangler は内部的に wrangler.toml に書かれているコマンド build.command (cargo install -q worker-build && worker-build --release) を実行しようとする. このコマンドは ~/.cargo/bin 以下に worker-build コマンドをインストールし、これを実行するというものであるが、現在実行しているシェルに ~/.cargo/bin のパスが通っていないとコマンドが見つからないというエラーが出ることになってしまう (https://github.com/cloudflare/workers-rs/issues/242).

以下のコードを ~/.zshrc などに書き込み、 source ~/.zshrc などで設定を再読み込みしておく.

export PATH="/$HOME/.cargo/bin:$PATH"

これで npx wrangler dev を実行すると、コンパイルは走るようだ.

しかし、サーバーを起動していざ実行というタイミングでリンクエラーが発生する.

⎔ Starting local server...
✘ [ERROR] service core:user:hello-cloudflare-workers-rs: Uncaught LinkError: WebAssembly.Instance(): Import #9 "./index_bg.js" "__wbg_error_53abcd6a461f73d8": function import requires a callable

    at null.<anonymous> (shim.js:41:9)

✘ [ERROR] The Workers runtime failed to start. There is likely additional logging output above.

どうやら、 Rust のバージョンに依存する問題のようだ. GitHub の issue では Rust 1.81.0 ではうまくいくとのこと (https://github.com/briansmith/ring/issues/2155).

現在のバージョンを確認してみる.

$ rustc --version
rustc 1.83.0-nightly (9096f4faf 2024-10-05)

1.81.0 を使うように切り替えてみる.

$ rustup install 1.81.0
### info: syncing channel updates for '1.81.0-aarch64-apple-darwin'
### info: latest update on 2024-09-05, rust version 1.81.0 (eeb90cda1 2024-09-04)
### info: downloading component 'cargo'
### info: downloading component 'clippy'
### info: downloading component 'rust-docs'
### info: downloading component 'rust-std'
### info: downloading component 'rustc'
###  52.2 MiB /  52.2 MiB (100 %)  40.2 MiB/s in  1s ETA:  0s
### info: downloading component 'rustfmt'
### info: installing component 'cargo'
### info: installing component 'clippy'
### info: installing component 'rust-docs'
###  15.9 MiB /  15.9 MiB (100 %)   4.1 MiB/s in  2s ETA:  0s
### info: installing component 'rust-std'
###  25.2 MiB /  25.2 MiB (100 %)  19.1 MiB/s in  1s ETA:  0s
### info: installing component 'rustc'
###  52.2 MiB /  52.2 MiB (100 %)  20.5 MiB/s in  2s ETA:  0s
### info: installing component 'rustfmt'
###
###   1.81.0-aarch64-apple-darwin installed - rustc 1.81.0 (eeb90cda1 2024-09-04)
###
### info: self-update is disabled for this build of rustup
### info: any updates to rustup will need to be fetched with your system package manager

$ rustup default 1.81.0
### info: using existing install for '1.81.0-aarch64-apple-darwin'
### info: default toolchain set to '1.81.0-aarch64-apple-darwin'
###
###   1.81.0-aarch64-apple-darwin unchanged - rustc 1.81.0 (eeb90cda1 2024-09-04)

$ rm -rf target
$ rm -rf node_modules
$ rm Cargo.lock

再度 npx wrangler dev を実行すると、今度は問題なくコンパイル・実行がどちらもうまくいった. この状態で http://localhost:8787 にアクセスすると、問題なく Hello World! という文字列が返ってくることが確認できた.

Worker のコードを書く

ワーカーの処理は src/lib.rs に記述されている.

use worker::*;

#[event(fetch)]
async fn fetch(
    _req: Request,
    _env: Env,
    _ctx: Context,
) -> Result<Response> {
    console_error_panic_hook::set_once();
    Response::ok("Hello World!")
}

なお、少し直感的ではない振る舞いが含まれているらしい.

まず、 workers-rs クレートは event マクロを提供しており、これは JavaScript のワーカーを扱っている際に見たものと同じハンドラ関数シグネチャを期待するようになっているようだ.

次に、 async は一般的に Wasm でサポートされていないが、 workers-rs プロジェクト内では使うことができるようになっているらしい.

とりあえず、適当に Response の文字列を変更し、 http://localhost:8787 の結果が変わることを確認しておく.

デプロイする

JavaScript / TypeScript のプロジェクトと同様、 Rust でのワーカーの実装も Cloudflare Workers としてデプロイすることができる. デプロイも wrangler を用いて以下のように行うことができる.

$ npx wrangler deploy

 ⛅️ wrangler 3.84.1
-------------------

Running custom build: cargo install -q worker-build && worker-build --release
[INFO]: 🎯  Checking for the Wasm target...
[INFO]: 🌀  Compiling to Wasm...
    Finished `release` profile [optimized] target(s) in 0.13s
[INFO]: ⬇️  Installing wasm-bindgen...
[INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended
[INFO]: ✨   Done in 0.31s
[INFO]: 📦   Your wasm pkg is ready to publish at /Users/y-takahs/src/gitlab.com/AnnPin/hello-cloudflare-workers-rs/build.

  shim.mjs  15.8kb

⚡ Done in 7ms
✔ Select an account › [email protected]'s Account
Total Upload: 376.20 KiB / gzip: 129.87 KiB
Worker Startup Time: 1 ms
Uploaded hello-cloudflare-workers-rs (2.47 sec)
Deployed hello-cloudflare-workers-rs triggers (3.88 sec)
  https://hello-cloudflare-workers-rs.e0956224.workers.dev
Current Version ID: c0dd2b6e-0e59-4787-80cd-3b428d22cf1f

curl コマンドで動作確認すると、きちんと動いていることがわかる.

$ curl https://hello-cloudflare-workers-rs.e0956224.workers.dev
Hello Rust worker!

どうやら、 workers-rs を使っている場合、 JavaScript のエントリポイントファイルが自動生成されることでワーカーが動くようになっているようだ.