Node用Cloud Function用環境構築

この記事は [ここのえ Advent Calendar 2023](https://qiita.com/advent-calendar/2023/99no_exit) Day 6の記事です。

# 要約

これです

https://github.com/nonuplet/cloud-functions-template

# Introduction

最近Cloud Functionsを使い始めたのですが、Nodeでちょっとしたスクリプトを回すのに結構便利でした。
頻繁に環境構築するのが面倒なので、備忘録も兼ねて手順を残しておきます。

# 環境

WSL + Ubuntu環境でテストしていますが、Linuxであれば問題ないと思います。
前提として`Node.js`, `Yarn` は導入済みとします。

- Functions Framework
- Typescript
- ESLint
- Prettier
- esbuild

# 手順

- 初期化

```bash
yarn init -y
```

デバッグ用に [**Functions Framework**](https://cloud.google.com/functions/docs/functions-framework) を導入します。Cloud Functionsのデバッグをローカルで出来るので便利です。

```bash
yarn add @google-cloud/functions-framework
```

- Typescriptの導入

```bash
yarn add -D typescript ts-node @types/node
```

- `tsconfig.json`

```json
{
  "compilerOptions": {
    "target": "es2020",
    "module": "commonjs",
    "esModuleInterop": true,
    "strict": true,
    "outDir": "dist"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}
```

- ESLintの導入

```bash
❯ npx eslint --init
Need to install the following packages:
eslint@8.53.0
Ok to proceed? (y) y
You can also run this command directly using 'npm init @eslint/config'.

? How would you like to use ESLint? …
  To check syntax only
  To check syntax and find problems
▸ To check syntax, find problems, and enforce code style

? What type of modules does your project use? …
▸ JavaScript modules (import/export)
  CommonJS (require/exports)
  None of these

? Which framework does your project use? …
  React
  Vue.js
▸ None of these

? Does your project use TypeScript?  No / Yes <- Yesを選択

? Where does your code run? …  (Press <space> to select, <a> to toggle all, <i> to invert selection)
   Browser
✔ Node

? How would you like to define a style for your project? …
▸ Use a popular style guide
  Answer questions about your style

? Which style guide do you want to follow? …
▸ Standard: https://github.com/standard/eslint-config-standard-with-typescript
  XO: https://github.com/xojs/eslint-config-xo-typescript

? What format do you want your config file to be in? …
▸ JavaScript
  YAML
  JSON

eslint-config-standard-with-typescript@latest @typescript-eslint/eslint-plugin@^6.4.0 eslint@^8.0.1 eslint-plugin-import@^2.25.2 eslint-plugin-n@^15.0.0 || ^16.0.0  eslint-plugin-promise@^6.0.0 typescript@*
? Would you like to install them now?  No / Yes <- Yesを選択

? Which package manager do you want to use? …
  npm
▸ yarn
  pnpm
```

- Prettierの導入

```bash
yarn add -D prettier eslint-config-prettier
```

- `.eslintrc.js` にPrettierのconfigを追加
```js
"extends": [
  "standard-with-typescript",
  "prettier"
],
```

:::note info
VSCode, WebStorm等へのESLint, Prettierの設定は適宜行ってください。
:::

- ビルドツールとして[**esbuild**](https://esbuild.github.io/)の導入

```bash
yarn add -D esbuild
```

`<project root>/script/build.ts` にビルド用スクリプトを書きます。

```ts
import { buildSync } from "esbuild";

buildSync({
  entryPoints: ["./src/index.ts"],
  outdir: "./dist",
  platform: "node",
  format: "cjs",
  bundle: true,
});
```

`package.json`を修正します。

```json
{
  ...
  
  "main": "dist/index.js",
  "scripts": {
    "build": "tsc --noEmit && ts-node scripts/build.ts",
    "start": "functions-framework --target=TestFunction",
    "prestart": "npm run build"
  },
  
  ...
}
```

# 実装サンプル

こんな感じで実装できます。

```ts
import type { HttpFunction } from "@google-cloud/functions-framework";

export const TestFunction: HttpFunction = (req, res) => {
  const sample: string = "hello world";
  console.log(sample);
};

```

:::note alert
公式ドキュメントにある [この書き方](https://github.com/GoogleCloudPlatform/functions-framework-nodejs/blob/main/docs/typescript.md) をすると、esbuildでビルド後にFunctions Frameworkが対象の関数を見つけられず、エラーになります。
前述のように `export const 関数名` で関数を公開するようなコードにすれば、問題なく動作します。

```ts
import * as ff from '@google-cloud/functions-framework';

ff.http('TestFunction', (req: ff.Request, res: ff.Response) => {
  // いろいろ
});
```
```bash
$ functions-framework --target=TestFunction
Function 'TestFunction' is not defined in the provided module.
Did you specify the correct target function to execute?
Could not load the function, shutting down.
```

Parcelでbundleした際は同様の問題は起こらなかったため、一部のビルドツールでは気を付けた方が良いと思います。
:::

# オマケのあとがき

ESLint + Prettier構成は無難ではありますが、のちのAdvent Calendarの記事で[**Biome**](https://biomejs.dev/)を触ってたらこっちの方が良いような気がしてきました。[Prettierの報奨金を勝ち取ったニュース](https://zenn.dev/sosukesuzuki/articles/e1e47e2a760e9d)とかもあったので、近いうちに切り替えたいですね。

# 参考資料

https://cloud.google.com/functions/docs/functions-framework?hl=ja

https://esbuild.github.io/api/

https://zenn.dev/yuichkun/articles/b514808e182a8e

https://qiita.com/mokichi/items/c2dd9db3d093dbbdc1be