Firebase functionsは現在標準でTypeScriptに対応している。
しかしHostingなど他の機能も使いたい場合や、それらとモジュールを共有したい場合、tsconfigやpackage.jsonが複数のディレクトリに割れたりしてちょっとしんどくなる。
この場合lernaなどでmonorepo構成を取るのが良いのだろうが、TypeScriptが絡んだりするとなかなかまだハマりどころが少なく無く面倒になりがちだ。
そこで以前AWS Lambda向けのファイルをparcelでビルドしたのと似たようなことをやってみたら思ったより悪くなかったのでまとめておく。
1. 準備
まずは適当にディレクトリを作るとこから
$ mkdir some-firebase-project
$ cd some-firebase-project
$ yarn init -y
$ touch firebase.json
parcelとtypescriptも入れてしまおう
$ yarn add parcel typescript
$ yarn tsc --init
あと今回はfirebase init
は使わないのでそこは自前で行う
$ mkdir functions
$ touch functions/package.json
2. package install
次にプロジェクト直下でパッケージインストール。トップディレクトリでfunctionsに必要なパッケージを入れる。最終的にbundleしたものをアップするのでpeerDependencyも入れる
$ yarn add firebase-admin @firebase/database @firebase/app @firebase/app-types @firebase/database-types firebase-tools
3. functionファイルの用意
function本体となるファイルを配置する。
$ mkdir src
$ mkdir src/functions
// src/functions/index.ts
import * as functions from "firebase-functions" // 全インポートにしないとひっかかるっぽい
exports.helloWorld = functions.https.onRequest((request, response) => {
response.send("Hello from Firebase!")
})
4. スクリプト設定(本題)
package.jsonをこんな感じで設定
{
"scripts": {
"build:functions": "parcel build src/functions/index.ts --target=node -d functions -o index.js --bundle-node-modules --no-source-maps --no-minify",
だいたいAWS Lamdaの場合と一緒で、--target=node
、--bundle-node-modules
にしている。
またfirebase-admin関連がsourcemap関連のwarningを吐き出してくるので--no-source-maps
を付けている。
また吐き出し先を-d functions
でfunctions下にしている。
--no-minify
をしてるのはビルド速度を上げたい&サーバーサイドのスクリプトなのでminifyする意味あんまり感じない&デバッグ楽そう ぐらいな気持ちなので好みで決めていい
これでyarn build:functions
が出来た。
4.5. Cannot resolve dependency 'http2' の対策をする
本当はここまでで通るはずなのだが、そのまま実行するとCannot resolve dependency 'http2'
のエラーが出る事がある。
どうもparcelの問題として、http2モジュールをうまく読み込めずビルドが失敗する状態にあるようだ
ということでその場合は下記のようにhttp2パッケージをインストールしてしまえば解決する
$ yarn add http2
5. firebase.jsonの準備
次にfirebase.jsonを記述する。だいたいこんな感じ。predeployで指定を与える部分が重要
{
"functions": {
"source": "functions",
"predeploy": "yarn build:functions"
},
"hosting": {
...
}
}
6. functions/package.jsonをいじる
AWS Lambdaと違ってpackage.jsonが無いとdeploy時に失敗してしまうので、これを回避するためにpackage.jsonを置いとく。
バージョンは10にしておく。今回はparcel側で--bundle-node-modules
するやり方をとっているのでdependencies
は不要だ。
{
"name": "functions",
"engines": {
"node": "10"
},
"main": "index.js",
"private": true
}
7. gitignoreする(オプション)
細かいがgitignoreとしてfunctions/index.jsを追加しておくと良い。
functions/index.js
dist
好みでなければ吐き出し先をfunctions/lib/
などディレクトリ先にしてそこごとignoreするのも良いだろう。
8. deploy
レッツデプロイ。
$ firebase deploy --only functions
おまけ: hostingがここに入った場合
hositngが入るとこんな具合のディレクトリ構成になる。
.
├── dist # hostingの出力先
│ ├── hosting.09bd9689.js
│ └── index.html
├── functions # functionsの出力先
│ ├── index.js
│ └── package.json
├── src
│ ├── functions # functionsのソース
│ │ └── index.ts
│ ├── hosting # hostingのソース
│ │ ├── index.html
│ │ └── index.tsx
│ └── lib # 共通部分
│ └── database-client.ts
├── firebase.json
├── package.json
├── renovate.json
├── tsconfig.json
└── yarn.lock
firebase.jsonとpackage.jsonはこんな感じになる
"script": {
"build": "parcel build src/index.html",
...
"hosting": {
"public": "dist",
"predeploy": "yarn build",
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}