Cloud Run functions × TypeScript 開発をしよう！【開発環境：TSの直接実行 × ホットリロード環境】

# はじめに
この章では以下の内容について説明します。
- TypeScriptの実行方法
- `tsx`を利用して簡単に Cloud Run functionsの TS直接実行 × ホットリロード環境を構築する
---

TypeScriptを実行する方法として以下の2つがあります。
1. TS → JSにトランスパイルしてから実行
1. Node.js上でTypeScriptを直接実行できる `ts-node` や `tsx` を使用する

それぞれのやり方を紹介します。

# 0. Node.js関数のTS化
事前準備として、前章で作成した `index.js` を `index.ts` に名称変更し、TS用に中身を書き換えます。
```ts: index.ts
import * as ff from '@google-cloud/functions-framework';
import type { HttpFunction } from "@google-cloud/functions-framework";

export const helloGET: HttpFunction =  (req: ff.Request, res: ff.Response) => {
    res.send(`Hello World!`);
};
```

方法1, 2の最初のディレクトリ構成は以下の状態から始めます。
```bash:ディレクトリ構成
.
├── node_modules
├── package-lock.json
├── package.json
├── src
│   └── index.ts
└── tsconfig.json
```

## 方法1. TS → JSにトランスパイル
TypeScriptはそのままで実行できないため、JavaScriptにトランスパイルする必要があります。
トランスパイルするには `tsc` をコマンドラインで実行します。

```bash
npx tsc
```

すると `tsconfig.json` の内容に従って `dist/index.js` ファイルが出力されます。

```js:dist/index.js
import * as ff from '@google-cloud/functions-framework';
export const helloGET = (req, res) => {
    res.send(`Hello World!`);
};
//# sourceMappingURL=index.js.map
```

```bash:tsc後のディレクトリ構成
├── dist
│   ├── index.js
│   └── index.js.map
├── node_modules
├── package-lock.json
├── package.json
├── src
│   └── index.ts
└── tsconfig.json
```

`package.json` の `main` は実行されるJSファイルのパスにします。

```js:package.json / main
  "main": "dist/index.js",
  "type": "module",
  "scripts": {
    "start": "functions-framework --target=helloGET"
  }
```

これで `npm run start` を実行すると、Node.js関数が実行されHTTPで待ち受けるようになります。

お分かりだと思いますが、このやり方には以下2つの欠点があります。

- 変更のたびJSファイルも更新するために毎回 `tsc` を打たないといけない
- `tsc`に結構時間がかかる

トランスパイルの時間を省略できて、HTTPで待ち受けているプロセスの再起動もファイルの変更のたびによしなに行って欲しい（Hot reload）ですよね。
この2つを解消するのが次に紹介する`tsx` を使ったFunctions Frameworkの実行方法です。

## 方法2. `tsx` を使ってTSのまま実行
`tsx` とは簡単にいうと、Node.js上でTypescriptファイルをそのまま実行できるライブラリです。
`node` コマンドを使うところを `tsx` に置き換えたらそのまま使えます。超便利。
```js:nodeでの書き方
node index.js // node JSモジュール
```
```js:tsxでの書き方
tsx index.ts  //  tsx TSモジュール
```

型チェックが走らないという特徴がありますが、型チェックはビルド時にまとめて行うようにする（つもり）なので、無問題です。

まず `devDependencies` に `tsx` をインスコしましょう。

```bash:tsxのインストール
npm install --save-dev tsx
```

`package.json` の `start` を以下のように書き換えましょう。
1. `node` → `tsx`
1. `functions-framework` → `node_modules/.bin/functions-framework`
1. `--source=./src/index.ts` オプションの追加

```js:package.json / start
  "scripts": {
    "start": "tsx node_modules/.bin/functions-framework --target=helloGET --source=./src/index.ts"
  }
```

`tsx` コマンドの引数は TS/JS モジュールである必要がありますが、**ライブラリの実行ファイルももちろんTS/JSモジュールです。** 中身をチラ見してみますと、

```js:node_modules/.bin/functions-framework
#!/usr/bin/env node
"use strict";
// ... 省略
const main = async () => {
  // ... 省略
};
exports.main = main;
// Call the main method to load the user code and start the http server.
(0, exports.main)();
//# sourceMappingURL=main.js.map
```

なので `tsx` の引数に指定して使うことができます。
そして Functions Framework には公式ドキュメントに書いていない隠しオプションがあります。**`--source`** オプションです。

https://github.com/GoogleCloudPlatform/functions-framework-nodejs/blob/0dc849225d18da323dd8f8b2de198a87a784def7/src/options.ts#L100-L105

このオプションを指定すると、そのファイル内の `--target` オプションで指定した関数をエントリーポイントとして実行するようになります。

`npm run start` を実行してみましょう。

```bash
$ npm run start

> advent-calendar-2024@1.0.0 start
> tsx node_modules/.bin/functions-framework --target=helloGET --source=./src/index.ts

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

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

無事、TSファイルのまま実行することができました。

### hot reload
先ほどの`start`コマンドの内容に `---watch` オプションをつけるだけです。

```js:package.json / watch
  "scripts": {
    "watch": "tsx --watch node_modules/.bin/functions-framework --target=helloGET --source=./src/index.ts"
  }
```

巷に溢れている Cloud Run functions のホットリロードの記事では `nodemon` を使うだの `concurrently` を使うだの `npm-wacth` を使うだのしているものがほとんどですが、間違いなくこれが一番簡単な方法です。

### inspector（デバッグ機能）
`tsx` オプションに `---inspect` オプションをつけるだけです。

```js:package.json / start
  "debug": {
    "start": "tsx --inspect node_modules/.bin/functions-framework --target=helloGET --source=./src/index.ts"
  }
```

# おわりに
Node.js上でTSを直接実行する方法、特にFunctions frameworkでTS関数を実行する方法を紹介しました。
次回は `--inspect` を使ってデバッグを行う方法を紹介したいと思います。