Nxを使っているプロジェクトでFirebase Functionsを使ってバックエンドを構築する事になりました。
NxにはFirebase Functionsを簡単に作成するためのテンプレートが用意されていないため、
自分で設定する必要があります。
この記事ではNxを使ってFirebase Functionsのアプリケーションを設定する方法を記載します。
ちなみに、理解するのが難しかったため細かめに記載してます…!!
(ドキュメントあんまりないし難しいし、半べそかきそうになってました😂)
この記事でFirebaseへのデプロイまで記載しようとしたのですが、
長くなるため前後編になっております。
後編はこちら
検証した環境
1 | nx | 14.6.3 |
2 | firebase-admin | 11.0.1 |
3 | firebase-functions | 3.23.0 |
Nxで実行するための下準備
firebase init 実行
monorepoのrootでfirebase init
$ firebase init
- ESLintはNxのものがあるため
n
を選択 - install dependenciesは後ほどNxワークスペース機能を利用するため
n
を選択
ここまでで以下のようなファイルが作成されます
.
├── .firebaserc
├── firebase.json
├── functions
│ ├── package.json
│ ├── src
│ │ └── index.ts
│ └── tsconfig.json
Nxのアプリケーションを作成
Nxにはfirebaseを作るようなテンプレートの用意がないため、
かわりにNxでnodeのアプリケーションを作成出来るようにします。
$ yarn add -D @nrwl/node
Nxでnodeのアプリケーションを作成します。
作成時に名前を聞かれるので私は backend
としました。
$ npx nx generate @nrwl/node:application
> NX Generating @nrwl/node:application
? What name would you like to use for the node application? ›
・・・
apps/backend/ 内に様々なファイルが作成されます
functionsを実行するためのファイルを用意
Nxのコマンドを使ってfirebase functionsの処理を行いたいので、
最終的に実行される apps/backend/src/main.ts を編集します
import * as functions from 'firebase-functions'
// // Start writing Firebase Functions
// // https://firebase.google.com/docs/functions/typescript
//
export const helloWorld = functions.https.onRequest((request, response) => {
functions.logger.info('Hello logs!', { structuredData: true })
response.send('Hello from Firebase!')
})
↑は firebase initで functions/src/index.ts に書き出された内容をまるっとコピーしたものです
firebase関係のパッケージを追加
下準備の最後に firebase init
時に install dependenciesでn
を選択したため、
firebase関連のパッケージが入っていないのでインストールします。
$ yarn add firebase-admin firebase-functions
Nxでfirebaseの処理を実行出来るようにする
ここからいよいよNxのアプリケーションでFirebase Functionsを実行出来るようにしていきます!
前提
Firebase Functionsを実行する上で確実に必要なnpm-scriptsは以下の2つ
- 開発時に必要
serve
- デプロイ時に必要
deploy
{
"scripts": {
"build": "tsc",
"build:watch": "tsc --watch",
"serve": "npm run build && firebase emulators:start --only functions",
"shell": "npm run build && firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"engines": {
"node": "16"
},
"main": "lib/index.js",
・・・
}
まずserve
コマンドを使えるようにします。
(この記事では長くなるのでserveコマンドのみの紹介です)
serveを実行出来るようにする
最終的にnx run backend:serve
コマンドを実行した際にFirebase Functionsのサーバがローカルで立ち上がるようにします。
npm-scriptsのserveについて
Firebase Functionsが自動で作成したnpm-scriptsのserveコマンドを見てみると
"scripts": {
・・・
"build": "tsc",
"serve": "npm run build && firebase emulators:start --only functions",
・・・
- tscでビルドする(デフォルトだと functions/lib/ 配下に書き出される)
firebase emulators:start --only functions
を実行する
という流れになっているのが分かります。
nxで再現する
nx run backend:serve
で上記の流れを再現していきます。
nxのコマンドは、それぞれのアプリケーション内のproject.jsonファイルで管理します
{
・・・
"targets": {
"build": {
"executor": "@nrwl/node:webpack",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/apps/backend",
・・・
},
"serve": {
"executor": "@nrwl/node:node",
"options": {
"buildTarget": "backend:build"
},
・・・
targets内のキー名がコマンド名になるため、
buildコマンド・serveコマンドが用意されている事が分かります!
試しにnpx nx run backend:build
を実行すると
- apps/backend/src/main.ts → dist/apps/backend/main.js
に変換してくれる(ビルドしてくれる)事を確認出来ます。
つまり
npx nx run backend:build
を実行(dist/apps/backend/main.js を作成)firebase emulators:start --only functions
でビルドされたmain.jsを読み込む
という流れが組めれば、
Firebase Functionsをローカルで実行出来そうです。
project.jsonを編集します
{
・・・
"targets": {
"build": {
・・・
},
"serve": {
"builder": "@nrwl/workspace:run-commands",
"options": {
"commands": [
{
"command":"nx run backend:build"
},
{
"command":"firebase emulators:start --only functions --inspect-functions"
}
],
"parallel": false
}
},
この記述は下記と同じ意味合いになります
$ nx run backend:build && firebase emulators:start --only functions --inspect-functions
これでnx run backend:serve
を実行してみます。
$ npx nx run backend:serve
> nx run backend:serve
・・・
i functions: Watching "/Users/yuu/src/github.com/yuki-takara/<project-name>/functions" for Cloud Functions...
i emulators: Shutting down emulators.
・・・
私の環境だとまずfunctionsのエミュレータ起動で失敗しますが、
それ以上に重要なのが上記画像の赤枠で囲った箇所の通り
Firebase Functionsのエミュレータが監視しているフォルダが 「<プロジェクトroot>/functions/」 という事ですね。
今回はビルド先が「<プロジェクトroot>/dist/apps/backend/」なのでここを読み込むようにします。
functionsの読み込み先を変更する
functionsの読み込み先変更はシンプルで、
firebase init
時に作成された 「firebase.json」のsourceを指定する事で実現します。
{
"functions": {
"source": "dist/apps/backend"
}
}
sourceを設定する際、
firebase.jsonに最初から記述されていた predeploy も削除して問題なしです。
ただし、ここまでだとまだエミュレータの起動が出来ません。
先ほどまでとは違うエラーメッセージなので、
挙動が変わったのは分かりつつも--verbose
しても何しても詳細が分からない。。😭
ここでかなりつまったものの、最終的に 「package.json」が足りない、という事が分かりました!
package.jsonを動的に作成する
ごにょごにょやっているうちに
firebase emulators:start --only functions
は対象ディレクトリのpackage.jsonのmainに設定したjsファイルを実行している事が分かりました!
{
"functions": {
"source": "dist/apps/backend"
}
}
{
・・・
"main": "main.js",
・・・
と記載すればエミュレータが「dist/apps/backend/main.js」を参照してくれる、という事です。
dist/apps/backend/にpackage.jsonを用意するために、
以下の記事を参考にscriptファイルを用意します。
【Nx+Angular+NestJS+Firebase】15分でフルスタックPWA環境を構築しデプロイする - Qiita
scriptファイルの作成先はNxが最初から用意してくれる tools
フォルダに作成していきます
import { readFileSync, writeFileSync } from 'fs'
import { join } from 'path'
import * as fs from 'fs'
async function main() {
const BASE_PATH = join(__dirname, '..', '..')
const srcDir = BASE_PATH
const distDir = join(BASE_PATH, 'dist/apps/backend')
const src = readFileSync(join(srcDir, 'package.json')).toString()
const dist = convertPackageJson(src)
if (!fs.existsSync(distDir)) fs.mkdirSync(distDir, { recursive: true })
writeFileSync(join(distDir, 'package.json'), dist)
}
function convertPackageJson(src: string): string {
const packageJson = JSON.parse(src)
delete packageJson['scripts']
delete packageJson['devDependencies']
packageJson['main'] = 'main.js'
packageJson['engines'] = { node: '16' }
return JSON.stringify(packageJson, null, '\t')
}
main().then()
このscriptは以下の事を実行します
- ./package.json を元に dist/apps/backend/package.json を作成する
- その際に元となるpackage.jsonで不要な項目は削除、必要な項目を改めて追加
後はこれをnx run backend:serve
で実行すればよいのでproject.jsonを編集します
{
・・・
"targets": {
"build": {
・・・
},
"serve": {
"builder": "@nrwl/workspace:run-commands",
"options": {
"commands": [
{
"command":"nx run backend:build"
},
{
"command":"ts-node ./tools/scripts/generate-api-package-json.ts"
},
{
"command":"firebase emulators:start --only functions --inspect-functions"
}
],
"parallel": false
}
},
- ビルドする(dist/apps/backend/ 作成)
- dist/apps/backend/package.json 作成
- functions起動
の流れになります。
これでnx run backend:serve
を実行すると
Firebase Functionsが起動出来ました!
そして赤枠で囲ったURLにアクセスしてみると
無事 apps/backend/src/main.ts に記載した内容が表示されています。
これで開発が進められますね☺️
Firebase Functionsにデプロイする
記事が長くなったので次の記事で記載していきます!