Cloud Run functions × TypeScript 開発をしよう！【開発環境：Functions Framework環境】

# はじめに
この章では以下の内容について説明します。
- ローカルマシンで Cloud Run functions を動かす方法
- ES module で Function Frameworkを動かす方法

---

# 結論
基本は以下のドキュメントの「**ローカルでの開発 / 概要〜ローカル関数を呼び出す**」の章を参考にしたらできます。

https://cloud.google.com/functions/docs/running/overview?hl=ja

...ではこの記事の意味がないので、実際にローカルPCで動かせるようにするまでを解説します。

### 余談
GCPの公式ドキュメントは場所によって違うことを書いていたり情報が散らかっていたりしてみづらいので、できるだけ文献もセットで提示していくようにします。

# ローカルで Cloud Run functionsを実行する方法
2通りあります。
1. Functions Frameworkを利用する
1. Functions エミュレータを利用する

本記事では Functions Framework を利用したやり方を中心に紹介します。
後者のFunctions エミュレータは、ローカルにCloud Run functionsインスタンスをデプロイするやり方です。


# Functions Frameworkとは？
GCPから提供されている、Cloud Run functions上で動く関数をローカルで起動できるライブラリです。
ランタイムごとに（Node.jsだったりPython, Goだったり）ライブラリは提供されており、今回はNode.js用のFunctions Frameworkを使用します。

# Functions Framework を使ってNode.js関数を実行する方法
Functions Framework で実行した関数は以下の方法でトリガーすることができます。
1. HTTP（curlやブラウザのアドレスバーなど）
1. バックグラウンドで動かして、Cloud StorageやPub/Subなどのイベントを受ける
1. GCP上でのイベントを元に起動

本記事では、**Functions Framework を使って関数をHTTPで待ち受けるように起動させ、それを curl で呼び出す**方法を解説します（というよりCloud Run functionsのユースケースってほぼこれでは？）。

# ES module × Functions Frameworkの環境構築
## package.jsonの作成
まずは `npm init` でpackage.jsonを作成しましよう。
基本Enterの連打でOKです。
```bash
$ npm init
---
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.

### 以下、Enter連打
package name: (advent-calendar-2024) 
version: (1.0.0) 
description: 
entry point: (index.js) 
test command: 
git repository: 
keywords: 
author: 
license: (ISC) 
About to write to /Users/xxx/advent-calendar-2024/package.json:
```
すると以下のようなpackage.jsonが作成されます。

```json:package.json
{
  "name": "advent-calendar-2024",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}
```

## ES moduleを使えるようにする
`import`構文を使えるように、`package.json` に以下を追記します。

```json
"type": "module"
```

追記後は以下のようになります。
```json:package.json（改）
{
  "name": "advent-calendar-2024",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module", // <--------------- 追記する
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}
```
### 参考
https://cloud.google.com/functions/docs/concepts/nodejs-runtime?hl=ja#use_es_modules

https://github.com/GoogleCloudPlatform/functions-framework-nodejs/blob/main/docs/esm/README.md


## Functions Framework ライブラリをインストール
```bash:
npm install @google-cloud/functions-framework
```
:::note warn
**`--save-dev` はつけないでください。** 後の章でお話しするビルドのプロセスの際にエラーが起こります。
:::

ここで`node_modules`も生成されるので、`.gitignore`を作成してGit管理下から外しておく。
```bash:.gitignore
node_modules
```

### 余談: npm, yarn, pnpm, bunとか色々あるけど結局どれを使えばいいの？
結論: **`npm` 使っとけば問題ない。** わかんないなら尚更`npm`以外手を出す必要なし。一応やり方は公式ドキュメントには書いてある。

https://cloud.google.com/functions/docs/writing/specifying-dependencies-nodejs?hl=ja#configuring_deployment_dependencies

## `package.json` の `start` スクリプトに Functions Frameworkの実行コマンドを書く
function-frameworkを実行するコマンドを追記します。
```json
"scripts": {
  "start": "functions-framework --target=helloGET"
}
```

これで`package.json`は（一旦）完成です。

```json:package.json（一旦完成系）
{
  "name": "advent-calendar-2024",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "functions-framework --target=helloGET",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@google-cloud/functions-framework": "^3.4.2"
  }
}
```

# Node.js関数の実行
以下のような構成で考えます。
```bash
.
├── .gitignore
├── index.js
└── package.json
```
## 関数の作成
超簡単に関数を作ります。
```js:index.js
import functions from '@google-cloud/functions-framework'

export const helloGET = (req, res) => {
    res.send('Hello World!');
});
```

そして先ほど作成したコマンドを実行します。

```
npm run start
```

すると以下のような標準出力が出て、関数の起動が完了します。

```bash
$ npm run start

> advent-calendar-2024@1.0.0 start
> functions-framework --target=helloGET

Serving function...
Function: helloGET
Signature type: http
URL: http://localhost:8080/
```

curlで呼ぶと「Hello, World!」と標準出力に表示されます。

```bash
$ curl http://localhost:8080/
Hello World!% 
```

ブラウザでアクセスしても表示されます。

![スクリーンショット 2024-12-05 1.37.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/374872/3f57dad9-6f6d-42f9-0536-2be6011993c5.png)

ちなみに直接起動は以下のコマンドでできます。
```bash
npx functions-framework --target=helloGET
```

## 終了する
上記で起動した関数はプロセスとしてずっと動き続けています。
```bash
$ ps -ax | grep "functions"
---
29025 ttys025    0:00.18 node /Users/xxx/advent-calendar-2024/node_modules/.bin/functions-framework --target=helloGET
```

終了するには `Ctrl + C` を入力しましょう。

# おわりに

これで超基本的なCloud Run functionsのローカル実行環境を構築することができました。
次回はこれをTypeScriptで実行できるようにしていきます。