【Firebase】Firebase Cloud Functions を TypeScriptで実装するための環境準備

# 目的
　Firebase Cloud Functions を TypeScriptで実装するための環境設定の手順を纏める。

# 手順
Reference: [Firebase 公式ドキュメント](https://firebase.google.com/docs/functions/get-started?authuser=0#create-a-firebase-project)
## 1. Firebaseのプロジェクト作成　・・・　委細省略

## 2.1. nvm(Node Version Manager)をインストールしてNode.jsのバージョンを管理（クライアントPC毎にワンタイム）
nvmは、Node.jsのインストール及びバージョンアップ管理ツール。
＊この作業を実施する場合、2.2項の 1 と 2 項は省略可能。バージョン管理が必要ない場合は、2.2だけでOK。

(1) インストール済みのNode.jsのバージョンを確認する。```$ node --version```

(2) ```nvm``` のインストール状況を確認する

``` :インストールされている場合
$ command -v nvm
nvm // インストールされていない場合、ここに何も表示されない
```

(3) ```nvm```をインストール
[GitHub](https://github.com/nvm-sh/nvm.git)からもインストールできる。以下はcurlでのインストール。
```curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash```

```
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 13527  100 13527    0     0   137k      0 --:--:-- --:--:-- --:--:--  137k
=> Downloading nvm from git to '/Users/{ユーザ名}/.nvm'
=> Cloning into '/Users/{ユーザ名}/.nvm'...
remote: Enumerating objects: 333, done.
remote: Counting objects: 100% (333/333), done.
remote: Compressing objects: 100% (283/283), done.
remote: Total 333 (delta 38), reused 150 (delta 25), pack-reused 0
Receiving objects: 100% (333/333), 177.15 KiB | 9.84 MiB/s, done.
Resolving deltas: 100% (38/38), done.
=> Compressing and cleaning up git repository

=> Profile not found. Tried ~/.bashrc, ~/.bash_profile, ~/.zshrc, and ~/.profile.
=> Create one of them and run this script again
   OR
=> Append the following lines to the correct file yourself:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm

npm ERR! peer dep missing: bufferutil@^4.0.1, required by ws@7.4.3
npm ERR! peer dep missing: utf-8-validate@^5.0.2, required by ws@7.4.3
=> You currently have modules installed globally with `npm`. These will no
=> longer be linked to the active version of Node when you install a new node
=> with `nvm`; and they may (depending on how you construct your `$PATH`)
=> override the binaries of modules installed with `nvm`:

/usr/local/lib
├── eslint@6.0.1
├── firebase-tools@9.5.0
└── typescript@4.0.2
=> If you wish to uninstall them at a later point (or re-install them under your
=> `nvm` Nodes), you can remove them from the system Node as follows:

     $ nvm use system
     $ npm uninstall -g a_module

=> Close and reopen your terminal to start using nvm or run the following to use it now:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm

```

``` :インストールが成功したか確認
// 例えば以下のようなコマンドを実行
$ nvm ls-remote --lts
-bash: nvm: command not found  // インストールできていない場合
```

ここで、Read Meにあるように、Macへのインストールが失敗している場合は、以下のコマンドを実行（必ず必要かは不明だが、筆者のインストール時には必要だった）。

```
// Since macOS 10.15, the default shell is zsh and nvm will look for .zshrc to update, none is installed by default. Create one with touch ~/.zshrc and run the install script again.
$ touch ~/.zshrc

// インストールを再実行
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash

// You might need to restart your terminal instance or run . ~/.nvm/nvm.sh. Restarting your terminal/opening a new tab/window, or running the source command will load the command and the new configuration.
$ . ~/.nvm/nvm.sh

// nvmのバージョンを確認
$ nvm --version
0.37.2
```

(4) Node.jsのバージョンを確認
LTS（Long Term Support）とは長期間に渡って、安全なメンテナンスリリースのみが実施されるバージョン。最新バージョンを使う理由が特に無ければ、安定しているLTSを使う。

``` :LTSバージョンを確認
$ nvm ls-remote --lts
```

(5) Node.jsのインストール（既に旧Versionをインストールしている場合の更新も同じ）

``` :v12.21.0の"Erbium"をインストールする例（LTSの名称は(4)で確認）
$ nvm install --lts=Erbium
Installing with latest version of LTS line: Erbium
Downloading and installing node v12.21.0...
Downloading https://nodejs.org/dist/v12.21.0/node-v12.21.0-darwin-x64.tar.xz...
############################################################################################################################################################### 100.0%
Computing checksum with shasum -a 256
Checksums matched!
Now using node v12.21.0 (npm v6.14.11)
Creating default alias: default -> lts/Erbium (-> v12.21.0)

// バージョンの確認
$ node --version
v12.21.0

// Defaultの設定（１つのPCでプロジェクトごとにNode.jsのバージョンを使い分けるため？？）
$ nvm alias default "12.21.0"
default -> 12.21.0 (-> v12.21.0)

// npmを最新に更新
$ nvm install-latest-npm
Attempting to upgrade to the latest working version of npm...
* Installing latest `npm`; if this does not work on your node version, please report a bug!
（中略）
+ npm@7.6.1
added 59 packages from 24 contributors, removed 241 packages and updated 194 packages in 7.601s
* npm upgraded to: v7.6.1
```

(6) nvm の環境パスを通す
　```~/.zprofile``` に下記を書き込む。```~/.zprofile``` が存在しない場合は、 ```$ touch ~/.zprofile``` で作成。パスを通した後は、ターミナルを再起動する（先起動しないとパスが反映されない）

```
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
```

（参考リンク）
[nvm(Node Version Manager)を使ってNode.jsをインストールする手順](https://qiita.com/ffggss/items/94f1c4c5d311db2ec71a)
[nvmの環境パスを通す](https://photo-tea.com/p/17/hexo-start-kit-tools-4/#nvm%E3%81%AE%E7%92%B0%E5%A2%83%E3%83%91%E3%82%B9%E3%82%92%E9%80%9A%E3%81%99)


## 2.2. Node.js と Firebase CLI を設定（クライアントPC毎にワンタイム）
1. Node.jsをインストール（[公式サイト](https://nodejs.org/ja/)）　＊JavaScript実行環境
2. npmをインストール　＊Node.jsのパッケージ管理システム
3. Firebase CLI をインストール

```
// npmを使用してインストールする場合
npm install -g firebase-tools
```

## 3. ```firebase-tools```を最新に更新（頻繁に実施）
　Firebase プロジェクトの functions フォルダ内で次のコマンドを使用し、Firebase CLI と SDK の両方を更新する。

```
// Firebase プロジェクトの functions フォルダ内で次のコマンドを使用
npm install firebase-functions@latest firebase-admin@latest --save
npm install -g firebase-tools
```

## 4. プロジェクトの初期化
　Firebase SDK for Cloud Functions を初期化するときに、依存関係と最小限のサンプルコードを含む空のプロジェクトを作成する。
　　a. firebase login を実行してwebブラウザ（自動で起動する）からログインし、firebase ツールを認証
　　　（既に別のアカウントでログインしている場合は、firebase logout）
　　b. Firebase プロジェクトのディレクトリに移動
　　c. firebase init functions を実行。以下、実行例

```:（ターミナルの実行結果例）
hogeMBP:$ firebase login
（略）

hogeMBP:Cloud_Functions $ firebase init functions

     ######## #### ########  ######## ########     ###     ######  ########
     ##        ##  ##     ## ##       ##     ##  ##   ##  ##       ##
     ######    ##  ########  ######   ########  #########  ######  ######
     ##        ##  ##    ##  ##       ##     ## ##     ##       ## ##
     ##       #### ##     ## ######## ########  ##     ##  ######  ########

You're about to initialize a Firebase project in this directory:

  /Users/{ 作業ディレクトリ }/


=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add, 
but for now we'll just set up a default project.

? Please select an option: Use an existing project
? Select a default Firebase project for this directory: プロジェクトID (プロジェクト名)
i  Using project プロジェクトID (プロジェクト名)

=== Functions Setup

A functions directory will be created in your project with a Node.js
package pre-configured. Functions can be deployed with firebase deploy.

? What language would you like to use to write Cloud Functions? TypeScript
? Do you want to use ESLint to catch probable bugs and enforce style? Yes
✔  Wrote functions/package.json
✔  Wrote functions/.eslintrc.js
✔  Wrote functions/tsconfig.json
✔  Wrote functions/tsconfig.dev.json
✔  Wrote functions/src/index.ts
✔  Wrote functions/.gitignore
? Do you want to install dependencies with npm now? Yes

> protobufjs@6.10.2 postinstall /Users/{ 作業ディレクトリ }/functions/node_modules/protobufjs
> node scripts/postinstall

npm notice created a lockfile as package-lock.json. You should commit this file.
added 385 packages from 272 contributors and audited 385 packages in 12.613s
found 0 vulnerabilities


i  Writing configuration info to firebase.json...
i  Writing project information to .firebaserc...
i  Writing gitignore file to .gitignore...

✔  Firebase initialization complete!

```

なお、Authエラーがあると、下記のようなエラーが発生する。
＊＊プロジェクトを追加しないで先に進めると、デプロイの前に、「firebase use --add」を実行する必要がある。
関連：[Cloud Functions for Firebase で deploy 先を切り替える](https://qiita.com/star__hoshi/items/30cb78fe25b8b0fb279e)

```:（エラーの例１）
? Please select an option: Use an existing project  // 「Use an existing project」を選択
Error: Failed to list Firebase projects. See firebase-debug.log for more info.
```

```:（エラーの例２）
? Please select an option: Create a new project  // 「Create a new project」を選択
i  If you want to create a project in a Google Cloud organization or folder, please use "firebase projects:create" instead, and return to this command when you've created the project.
? Please specify a unique project id (warning: cannot be modified afterward) [6-30 characters]:
 hogehoge  // FirebaseのプロジェクトIDを入力
? What would you like to call your project? (defaults to your project ID) hogehoge  // FirebaseのプロジェクトIDを入力
✖ Creating Google Cloud Platform project

Error: Failed to create project. See firebase-debug.log for more info.
```


この処理で、クライアントPCのプロジェクトフォルダ内に、以下の構成が生成される。
TypeScriptを利用する上で必要となる「package.json」や「tsconfig.json」はここで自動生成される。

[Firebase 公式ドキュメント](https://firebase.google.com/docs/functions/typescript?authuser=0)

```:（ファイル構成）
myproject
 +- functions/     # Directory containing all your functions code
      |
      +- package.json  # npm package file describing your Cloud Functions code
      |
      +- tsconfig.json
      |
      +- tslint.json # Optional file
      |
      +- src/     # Directory containing TypeScript source
      |   |
      |   +- index.ts  # main source file for your Cloud Functions code
      |
      +- lib/
          |
          +- index.js  # Built/transpiled JavaScript code
          |
          +- index.js.map # Source map for debugging
```

## 5. TypeScriptコンパイラをインストール（クライアントPC毎にワンタイム）
a. （Node.jsをインストール）　＊上記で既にインストール済み
b. TypeScriptコンパイラをインストール

```
// npmを使用してインストールする場合
npm install -g typescript
```

c. インストールの確認　・・・　ターミナルで「tsc -v」を実行し、バージョンが表示されたらOK

 [TypeScriptチュートリアル① -環境構築編-](https://qiita.com/ochiochi/items/efdaa0ae7d8c972c8103)

## 6. VS Codeをインストール・初期設定（クライアントPC毎にワンタイム）
[公式サイト](https://code.visualstudio.com/)
[MacOSでVisual Studio Codeをインストールする手順](https://qiita.com/watamura/items/51c70fbb848e5f956fd6)

## 7. VS Codeのビルド設定を作成　＊プロジェクトファイル毎に実施

a. VS Codeでプロジェクトフォルダを開く。上記のフォルダ構成の場合、「functions」の位置
b. ビルドタスクの設定ファイル（tasks.json）を作成
　デバック時に事前に、TypeScriptコードをコンパイルするためのタスクを設定する。

```
Visual Studio Code のメニュー 「ターミナル」 
> 「既定のビルドタスクの構成」を選択
> 「tsc: ビルド - tsconfig.json」 を選択
```
または

```
shift + command + p
> 「Tasks: Configure Default Build Task」を開く
> 「tsc: ビルド - tsconfig.json」 を選択
```

作成した「tasks.json」は以下のような内容。

```:tasks.json
{
	"version": "2.0.0",
	"tasks": [
		{
			"type": "typescript",
			"tsconfig": "tsconfig.json",
			"problemMatcher": [
				"$tsc"
			],
			"group": {
				"kind": "build",
				"isDefault": true
			},
			"label": "tsc: ビルド - tsconfig.json"
		}
	]
}
```

c. 構成ファイルの作成（launch.json）
　デバッグセッションの開始前に起動するタスクを指定する。```preLaunchTask```を追記し、tasks.json の```label```と同じ値を定義する

```
「実行」 
> 「構成の追加」を選択
> 「Node.js」を選択
```

作成した「launch.json」は以下のような内容。
ここで、「preLaunchTask」の値は、「tasks.json」の"label"の値に変更。

```:launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "プログラムの起動",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "${workspaceFolder}/lib/index.js",
            "preLaunchTask": "tsc: ビルド - tsconfig.json" 
        }
    ]
}
```

ここまでの設定で、VS Codeでデバッグ、コンパイルを実行できる（「実行」＞「デバッグの開始」）。

（参考リンク）: 
[Visual Studio Code で TypeScript 環境を設定してデバッグ実行する](https://knooto.info/vscode-typescript/)
[VSCode で TypeScript のデバック設定 ](https://blog1.erp2py.com/2018/09/vscode-typescript.html)

# エラー対策
## コンパイル時にAbortSignalのエラー
[この記事](https://qiita.com/mobilebiz/items/3b290c29284088640233)に従って対策した。
```:対策内容
// ~~ /node_modules/@types/node/globals.d.ts の73行目あたりの下記の変数をコメントアウト
/*
declare var AbortSignal: {
    prototype: AbortSignal;
    new(): AbortSignal;
    // TODO: Add abort() static
    timeout(milliseconds: number): AbortSignal;
};*/

```

## コード127で終了（コンパイルエラー）
[記事](https://www.exceedsystem.net/2020/11/27/how-to-solve-exit-code-127-error-on-building-typescript-on-vscode/)を参照し、typescriptをインストールし直したらコンパイルできるようになった。
npmのアップデートなどを実施した際に、インストール済みの環境が失われたのかもしれない（node.jsなどもインストールはされているが、コマンドが通らない事象が起きた）