Cloud FunctionsとSql, Firebase hosting構成でのSSR Webアプリ作成は避けたほうがいい

メモ代わりの記事です
2021/11/09時点

# 要約
現状個人開発中で感じたもの
 - コードしか書かなくていいからCloud Functionsを選択は間違い
 - Firebase Hostingは公開「は」楽、Cloud Functionsを絡めるとregionが問題になる


## 環境
mac (imac 2019)

angular 10

```
Angular CLI: 10.2.3
Node: 14.17.3
OS: darwin x64

Angular: 10.2.5
... animations, common, compiler, compiler-cli, core, forms
... platform-browser, platform-browser-dynamic, platform-server
... router
Ivy Workspace: Yes

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1002.3
@angular-devkit/build-angular   0.1002.3
@angular-devkit/core            10.2.3
@angular-devkit/schematics      10.2.3
@angular/cdk                    10.2.7
@angular/cli                    10.2.3
@angular/material               10.2.7
@nguniversal/builders           11.2.1
@nguniversal/express-engine     11.1.2
@schematics/angular             10.2.3
@schematics/update              0.1002.3
rxjs                            6.6.7
typescript                      4.0.8
```

nestjs

```
    "@nestjs/common": "^7.0.0",
    "@nestjs/core": "^7.0.0",
    "@nestjs/ng-universal": "^4.3.0",
    "@nestjs/platform-express": "^7.0.0",
```

### コードしか書かなくていいからCloud Functionsを選択は間違い
 - App Engineでも同じことができる 使用歴はかなり浅いが簡単な差は以下
    - Functions: `コードベース&firebase.json`で、Cloud環境指定
    - App Engine: `app.yaml`で、Cloud環境指定、[package.jsonのstartがアプリ起動フック](https://cloud.google.com/appengine/docs/flexible/nodejs/runtime?hl=ja#application_startup)

 - 選択基準    
    - ファイルのアップロードや、他のアプリへのログインなど他の`イベントベースで動く`ならCloud Functions
    - `普通のWebサービス`ならApp Engineのほうが無難
 

 - deploy時にエラーが出たとき何が原因か見つけるのが大変
    - エラーログなどから推測しづらいためコードに集中しづらい上に時間がかなりかかる
    - <details>
  <summary> 一例</summary>
  <div>
Window や Document などのグローバルオブジェクトのMockとしてdominoが`server.ts`でimportされていて<br />
windowのMock作成時に何故か `distのindex.htmlが絶対パスでreadFileSync().toString()`していたためローカル環境では問題なし。<br />
`firebase deploy` 時に`cleaning up`で毎回失敗、ライブラリの更新等しても治らない、`--debug`オプションでも有用な情報がなし<br />
不要そうなものを消したりを繰り返して原因突き止めやっと解決といった流れ<br />
sqlファイルとかをdistにコピーするScriptで削除ディレクトリ間違えてコピーできなかったときにも出て結構困った<br />
初めて出たときには2日くらい無駄にした
  </div>
</details>


 
 - この環境での問題だがAngular Universal & Nestjsの構成だとネットにあまり情報がない＆自分の知識が浅すぎて公開が大変だった
    - SSR構成でもexportしているmoduleを理解していればすぐできる、素のExpressを利用しているならsampleので十分
    - 記載環境で以下のディレクトリ構成のときのcloud functionsへの公開方法 (やり方が正しいかはわかりません）

```
project-root
 ├── functions
　　│　　　　　　　　├── dist
 │    └──  src
 │         └── index.ts
 ├── node_modules
 ├── public
 ├── server
 └──  src
      ├──app
      ├──index.html
      ├──main.server.ts
      └──main.ts
```

```typescript
// main.server.ts

import { enableProdMode } from '@angular/core';
import * as express from 'express';
import { ExpressAdapter } from '@nestjs/platform-express';
import { environment } from './environments/environment';
import { NestFactory } from '@nestjs/core';
import { AppModule } from '../server/app.module';
import { Logger } from '@nestjs/common';

if (environment.production) {
  enableProdMode();
}

const server = express();
export const initNestServer = ( async (): Promise<any> => {
  const apiPrefix = 'api';
  Logger.log(`[Nest]initNestServer ${apiPrefix}`);

  const nest = await NestFactory.create(
    AppModule,
    new ExpressAdapter(server),
  );
  nest.setGlobalPrefix(apiPrefix);
  return nest.init();
});

export const expressServer = server;
export { AppServerModule } from './app/app.server.module';
export { renderModule, renderModuleFactory } from '@angular/platform-server';

```


```typescript
// functions/src/index.ts
// App Engine乗り換え検討し始めた時期なので少し適当

import * as functions from 'firebase-functions';
import * as path from 'path';
// @ts-ignore
import { initNestServer } from '../dist/project/server/main.js';

const mainDir = path.resolve(__dirname, '../dist/project/server/main.js');

initNestServer();
const server = require(mainDir).expressServer;
export const app =
  functions
    .region('us-central1')
    .runWith({ memory: '256MB' })
    .https
    .onRequest(server);
```



### Firebase Hostingは公開「は」楽、Cloud Functionsを絡めるとregionが問題になる
 - Firebase Hostingを利用してhosting側でrewritesを設定後、Functionsを利用しようとするとregionがus-central1に固定される
    - [重要: Firebase Hosting は、us-central1 でのみ Cloud Functions をサポートします。](https://firebase.google.com/docs/hosting/full-config?hl=ja)
    - HostingもFunctionsもus-central1でレスポンスに問題無いのであれば、Cloud Sql側もus-central1にしておいたほうがいい
    - TTFBのことを考えるならApp Engineでasia-northeastにしたほうが圧倒的にいい