Cloud Functions 用のローカル開発環境
以前に試したこれをもう少し試すために、ローカルで Cloud Functions + Pub/Sub 用の開発環境を作っているメモ。
メモは下記のリポジトリで試している。
このメモではとりあえず Functions の部分について。VSCode と Dev Containers(docker-compose) を使うが、Dev Container についてはあまり触れない(Pub/Sub 用のscrap を作ったらそちらに書くかも)。
ローカルの開発環境を作成する方針
ローカル側での実行環境について、Cloud Functions の場合はドキュメントに記述がある。
独自の関数ホスティング環境を設定する前に、次の 2 つの重要な選択を行う必要があります。
- 使用する抽象化レイヤ
- 実行する関数のタイプ
抽象化レイヤは下記の2つから選択。
- Function Frameworks - トリガーを関数に渡すフレームワーク、HTTP トリガーなら(おそらく内部は express の)サーバーを起動できる
- Buildpack - コンテナで実行環境を作る、後述の Pub/Sub エミュレーターからの push はこちらを使う記述がある(push のトリガーを扱うため?)
関数のタイプはイベントタイプを選択。
今回は Functions Framworks で HTTP 関数を使う。ということで、この後ローカルで環境を作るときは上記にあわせて実施することになる。
利用する言語は TypeScript(JavaScript)。
Functions Frameworks の利用
これは NPM のパッケージなので npm init
などでパッケージを作った後にインストールすれば利用できる。
利用方法は基本的には上記のドキュメント通り。デプロイまで考慮すると gcloud CLI が必要そうなどいろいろ違いそうだが、コードを記述するだけなら express(http server)などを使うのとあまり変わらない、と思う(Dev Container を作ったりするときもとくに変わったところはなさそうだった)。
下記は少し気になった点など。
- ドキュメントには
—save-dev
でインストールするように記載されている - Function のソースは
index.js
に記述、正確にはpackage.json
のmain
で指定したファイルが利用される - ESM でも利用できる
- 使える機能はデプロイ時のランタイムで決まる、のかな?
- フレームワーク側の
package.json
でexports
が使われているので TypeScript では少し注意が必要
- ホットリロードには対応していないようなので自前で対応することになる。
参考
--save-dev
について。
注: このライブラリは、関数の依存関係内で指定しない場合、関数をデプロイする際に自動的に追加されます。
ESM について。
ホットリロード
フレームワークの機能には含まれていないようなので npm-check などで対応することになる。とくに引っかかる点はなかったが、リロードはデバッグ用に使いたくなると思うので、 start
スクリプトなどとは別に debug
スクリプトを用意してそちらに設定しておくのがよいかと思う。
また、ホットリロードの設定についてではないが、 tsc
でビルドしているとやはりちょっと遅い。この辺は swc の CLI などを検討している。
TypeScrtipt
Firebase の Functions のドキュメントは出てくるのだが Cloud Functions はなかった(Firebase Functions と何が違う?)。
フレームワーク側で型の情報(.d.ts
)もエクスポートされているので「使えるかな?」と試したら使えた。
少し気になる点としては main
を dist/index.js
にしたのだが、プロジェクトのルートから移動しているとデプロイ時に面倒なことなるようなことを見かけた(ページのアドレスをメモっておくのを忘れた)。この辺は「そのときはそのとき」ということでとりあえず進める。
Source map
Frerbase のドキュメントではログ出力時に .map
ファイルがあった方がよいとある。Cloud Functions の方ではわからないが、後述のデバッグでも必要になってくるので、出力するようにしておいた方がよいと思う。
moduleResolution
フレームワークのテスト用の関数 getFunction
などは package.json
の exports
の指定でエクスポートされている。TypeScript で使う場合は tsconfig.json
などで moduleResolution
を NodeNext
にする必要がある(たぶん node16
でもいける)。
デバッグ
フレームワークで関数を実行しているとき、VSCode でブレークポイントなどを使えるようにする場合。
基本的にはホットリロードを使ったデバッグになるので、下記の --inspect
と attach が基本的な方針となる。
ただし、上記の方法でフレームワークを利用しても HTTP サーバーが起動されない(すぐに終了してしまう)。これは下記を参考に .bin
の実行用ファイルを使うことで解決された。
node --inspect node_modules/.bin/functions-framework --target=<FUNCTION NAME> --signature-type=http
また、Auto Attach の設定は新しい JavaScript デバッガーではフィールド名が変更されている。これでソースを変更してリロードされても自動的に attach される。
"debug.javascript.autoAttachFilter": "smart",
ただし、これを設定すると Jest のテストでも反応するのだが、Jest の --watch
ではブレークポイントで停止しない。devcontainer.json
の settings
などに含めるかは悩み中。
フレームワークでデバッグしているときは下図のような感じ。ソースを変更するとリロードされる。
テストのとき。上記のように --watch
だと停止しないので、デバッグするときはオプションなしで実行することになる(少しめんどう)。
テスト
ドキュメントにテストについての記述がある。
下記の 3 通りのテスト方法が記載されている
- 単体テスト - フレームワークのテスト用ユーティリティ(
getFunction
)で関数を取得、テストコードの中で実行する - 統合テスト - フレームワークのテスト用ユーティリティ(
getTestServer
)でテスト用サーバーを取得して supertest でテストする - システムテスト - Cloud Functions の環境にデプロイしてテストする(たぶん)
TypeScript(ESM)
上記のドキュメントは CJS で sinon と mocha を使っている前提。ESM の場合は下記がわかりやすい。
少し注意点。
ドキュメントに書いてあるようにユーティリティ(getFunction
など)を実行する前に関数をロード(importして関数登録のコードを実行させる)しておく必要がある。
beforeAll(async () => {
// load the module that defines `chk1`
await import('../src/index.js')
})
前述のように TypeScript では moduleResolution
を NodeNext
などにしておかないと tsserver では下記がエラーになる(VSCode のエディターでエラーが表示される)。
import {getFunction} from "@google-cloud/functions-framework/testing";
単体テスト
getFunction
で取得した関数の型は HandlerFunction
で各種関数の union になっている。そのままでは HttpFunction
として扱えない。しかたないので、暫定的に型ガード関数を使っている(この辺の定番な記述方法は不明)。
const isHttpFunction = (
func: HandlerFunction | undefined
): func is HttpFunction => {
// TODO: HttpFunction であるかの判定を確実にできるか調べる.
if (typeof func === 'function' && func.length === 2) {
return true
}
return false
}
統合テスト
いまのところ、とくに引っかかるところはなかった。
システムテスト
まだ、試していない。