🍃このブログは移転しました。
3秒後、自動的に移動します・・・。

Cloudflare Pages Functionsを試す

Cloudflare Pagesは、今まで単なる静的サイトのホスティングサービスだった。

それがこの度のパワーアップで動的なAPIも一緒にデプロイできるようになった。しかもそれはCloudflare Workersで実行されるので、デフォルトで速いんですよ!っていう触れ込み。

Cloudflare Pages Goes Full Stack

そんな折、社の案件で人柱になれる機会もあったので、その仕組みやら使用感やらをメモっておく。

Functions自体のドキュメントはこちらのページ。

https://developers.cloudflare.com/pages/platform/functions

Overview

Pagesでサイトをデプロイする時、Functionsを一緒にデプロイさせるためには、以下のいずれかをやればよい。

  • `functions`ディレクトリを作り、そこにコードを置く
    • 特別なルールに則ったコード
    • ファイルベースのルーティングにできる
  • リポジトリルートに`_worker.js`というファイルを置く
    • 今までWorker単独でやってたのと同じコード

SSR系のフレームワークだと後者のフォーマットで勝手に出力してくれたりするけど、それ以外の用途は基本的に前者になるはず。

ハンドラのコード

パスはディレクトリとファイル名がそのまま使われて、そこで受けたいHTTPメソッド専用の関数を公開する。

// GET
export const onRequestGet = async (context) => {
  const {
    request, // same as existing Worker API
    env, // same as existing Worker API
    params, // if filename includes [id] or [[path]]
    waitUntil, // same as ctx.waitUntil in existing Worker API
    next, // used for middleware or to fetch assets
    data, // arbitrary space for passing data between middlewares
  } = context;

  return new Response("Hello ;D");
};

// POST
export const onRequestPost = async (context) => {
  return new Response("Created!", { status: 201 });
};

// ぜんぶ手動で
export const onRequest = async (context) => {
  context.request.method;
};

`onRequest(Xxx)`からなる関数があれば、それがルーティングとして登録され、それ以外は単なるモジュールとして使える。

なのでファイルベースルーティングではありつつ、ルートを定義しないただのファイルを置いても良いし、そこからただの関数をエクスポートしてもいい。わざわざ`_`からはじまるディレクトリ・ファイルにする必要もない。これはなかなかスマートやなって思った。

ただドキュメントに明示されてるわけではないので、そのうち変わるかもしれない・・?

基本的にコードとしてはこれだけ知ってれば、それだけでAPIが生える。

ミドルウェア

APIといえばやっぱミドルウェアですよね〜ってことで、ちゃんと対応してる。

これも使い方は2通りあって、

  • 特定のディレクトリに、`_middleware.js`を置く
    • それ以降の層で自動的に使われるようになる
  • 各ハンドラの`onRequestXxx`を、配列にする

という具合に、割と細かく挙動が調整できる。

ローカルで開発する

まだステータスとしてはベータな`wrangler2`を使う必要がある。現行のv1は`@cloudflare/wrangler`なので、npmのネームスペースが違うので注意。

npx wrangler@beta pages dev dist

ってすると、`dist`ディレクトリにある静的なコンテンツをプレビューしつつ、`functions`にコードがあればそれが叩けるようになる。

npx wrangler@beta pages dev -- npm run dev

ってすると、静的なコンテンツをプレビューする部分は既存のツールでやらせつつ、APIのルートとまとめてプロキシするサーバーを立ててくれる。

コマンドの仕組み

`pages dev`コマンドは、

  • 静的なコンテンツを返すサーバー
    • コマンドがあればそれで立てたやつ
    • なかったら`npx serve`
  • 動的なAPIのためのサーバー
    • Miniflareを使ってる

この2つを、`http-proxy-middleware`を使ってプロキシしてまとめて返す`express`のサーバーを立てるコマンド。

まあそうするしかないよなって感じではあるけど、割と力ずく・・w
ってことが、中のコードを読んでてわかった。

https://github.com/cloudflare/wrangler2/blob/main/packages/wrangler/src/pages.tsx

@cloudflare/pages-functions-compiler

これは、Functionsのルーティングの定義をパースしてビルドする`plinko`ってツールのパッケージ名。

ソースは公開されてないけど、ログやら`node_modules`のコードをがんばって読む限りは、

  • `functions`配下のコードをすべてASTにパースする
  • `onRequest`からはじまる関数を取得
  • ディレクトリ構成とあわせてルーティングのツリーにする
  • その定義をインポートしたWorkerのコードを生成して、`esbuild`でビルドする

ということをやってる。なので、適当にファイルを置いたとしても、いい感じにファイルベースのルーティングを実現してくれる。

このツールが`functions`配下に変更があったときに毎回リビルドして、それをMiniflareのサーバーが実行するという形になってる。

環境変数

Workersと違って、CLIから登録できない。
なのでダッシュボードから平文で登録するしかなく、ここはちょっと惜しい感じ。

現状の`wrangler@beta pages`コマンドは、さっきの`dev`しかサブコマンドが存在しないし。

ローカルでの開発時は、`-b KEY=VALUE`でコマンド引数で渡せるものの、`.env`などがサポートされてるわけでもない。(Miniflareは`.env`を読めるはずなので、あえてやってないのはおそらくデザイン的な問題っぽい)

まとめと感想

使えないことはないし、動作もするけど、なんだろうこの惜しい感じは・・。

特にCLIの`wrangler2`は絶賛ベータって感じで、大々的に告知されてる割に中身はぜんぜんWIPだったりするし。

Roadmap to v2 · Issue #12 · cloudflare/wrangler2 · GitHub

コード読んでても思ったけど割と荒削りなので、そこが不安を感じるポイントなのかもしれない・・。

そういう意味では、静的サイト+APIっていう本来の使い方よりも、SSRフレームワークも動かせるプラットフォームとしての使い方のほうが、気持ちよく開発できると思う。いまのところは。

ちなみに、PagesでFunctionsをデプロイしたとしても、Workersのダッシュボードには何も現れない。
というわけで、Workers側で見れてたメトリクスとか実行結果とかそういうのも一切みれない。

というのが2021年末での感想。