Nxを使ってFirebase Functionsのアプリケーションを設定する方法その2です。
その1では yarn run serve
を実行出来るようになったので、
この記事ではFirebase Functionsにデプロイするところまで記載していきます。
その1の記事内容を実装している前提で記載をしていきます
検証した環境
1 | nx | 14.6.3 |
2 | firebase-functions | 3.23.0 |
3 | depcheck | 1.4.3 |
Firebase Functionsにデプロイ出来るようにする
firease init
で作成されるpackage.jsonは以下のようになっています
{
"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",
・・・
}
この中のdeploy
コマンドをNxのアプリケーションで実行出来るようにしていきます。
deployは
firebase deploy --only functions
を実行出来れば良い事が分かります。
が、1つ落とし穴があり
デプロイを実行する際にpredeployでbuildも実行しています。
{
"functions": {
"predeploy": "npm --prefix \"$RESOURCE_DIR\" run build"
}
}
そのため
yarn run build
firebase deploy --only functions
を実行出来れば良いはず。
nx run backend:build・serveを変更する
デプロイをする際もビルドを実行する必要がある、ということが分かりました。
そのため nx run backend:build
を共通化します。
やる事としては nx run backend:serve
で実行していた内容の一部をnx run backend:build
で行えるようにします。
変更前
"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
}
},
変更後
"targets": {
"build-node": { //変更前のbuildをリネームしてbuild-nodeに
・・・
},
"build": {
"builder": "@nrwl/workspace:run-commands",
"options": {
// build-nodeを実行後、serveで実行していたpackage.jsonの書き出しを行う
"commands": [
{
"command":"nx run backend:build-node"
},
{
"command":"ts-node ./tools/scripts/generate-api-package-json.ts"
}
],
"parallel": false
}
},
"serve": {
"builder": "@nrwl/workspace:run-commands",
"options": {
"commands": [
{
"command":"nx run backend:build"
},
{
"command":"firebase emulators:start --only functions --inspect-functions"
}
],
"parallel": false
}
},
ポイントは
- 既存のbuild → build-node に変更
- 新たに追加した build に serve で実行していたpackage.jsonをコピーするスクリプトを移動
記事を記載する上でjsonファイルにコメントをしてますが
jsonはコメントが出来ないファイル形式のため注意です
deployコマンドを追加
nx run backend:build
は共通化出来たので、
deployコマンドをpjoject.jsonに追加します
"targets": {
・・・
"build": {
・・・
},
・・・
"deploy": {
"builder": "@nrwl/workspace:run-commands",
"options": {
"commands": [
{
"command":"nx run backend:build"
},
{
"command":"firebase deploy --only functions"
}
],
"parallel": false
}
},
これで npx nx run backend:deploy
を実行してみると
$ npx nx run backend:deploy
> nx run backend:deploy
・・・
Error: There was an error deploying functions
> NX ERROR: Something went wrong in run-commands - Command failed: firebase deploy --only functions
エラーが起きます🤔
今回私がFirebase Functionsを導入しようとしているmonorepoプロジェクトは、
フロント側にNext.jsやMaterial-UIを使用しています。
エラー内容を見るとそれらのパッケージが悪さをしているように見えます。
この一覧の流れを見る限り、
Nx + Firebase Functionsのデプロイの動きとしては
- buildをする
(dist/apps/backend/にmain.jsを書き出す) - ルートのpackage.jsonを元に dist/apps/backend/package.json を作成
- dist/apps/backend/package.json 内の記載されているパッケージを install
- Firebaseにデプロイ
という手順を踏んでいて、
今回のエラーは「3.」の部分でエラーになったように見受けられます。
コピーする際のpackage.jsonを調整する
そこで上記手順「2.」のdist/apps/backend/package.json を作成する際に、
記載するパッケージの内容を調整します。
最終的に「apps/backend」で利用しているパッケージさえ読み込めればよいはず。
まず depcheck
というパッケージをインストール
$ yarn add -D depcheck
これは指定したフォルダやファイルで使用しているパッケージのみを選別してくれるライブラリです。
次にこのライブラリを利用しつつ
「tools/scripts/generate-api-package-json.ts」を変更していきます。
変更時の内容は以下の記事を参考にさせていただきましたmm
Nx で Fireabase Functions を扱う方法 | ティーポットは珈琲を淹れられない
import { readFileSync } from 'fs'
import { join } from 'path'
//@ts-ignore
import depcheck from 'depcheck'
import * as fs from 'fs'
import * as path from 'path'
const PACKAGE_JSON_TEMPLATE = {
engines: { node: '16' },
main: 'main.js',
}
async function main(): Promise<void> {
const APPLICATION_NAME = 'backend'
console.log(`Application name: ${APPLICATION_NAME}`)
/*****************************************************************************
* package.json
* - Filter unused dependencies.
* - Write custom package.json to the dist directory.
****************************************************************************/
const ROOT_PATH = path.resolve(__dirname + '/../..')
const DIST_PROJECT_PATH = `${ROOT_PATH}/dist/apps/${APPLICATION_NAME}`
console.log('Creating cloud functions package.json file...')
const src = readFileSync(join(ROOT_PATH, 'package.json')).toString()
const packageJson = JSON.parse(src)
// Get unused dependencies
const { dependencies: unusedDependencies } = await depcheck(DIST_PROJECT_PATH, {
package: {
dependencies: packageJson.dependencies,
},
})
// Filter dependencies
const requiredDependencies = Object.entries(packageJson.dependencies as { [key: string]: string })
?.filter(([key, _value]) => !unusedDependencies?.includes(key))
?.reduce<{ [key: string]: string }>((previousValue, [key, value]) => {
previousValue[key] = value
return previousValue
}, {})
console.log(`Unused dependencies count: ${unusedDependencies?.length}`)
console.log(`Required dependencies count: ${Object.values(requiredDependencies)?.length}`)
// Write custom package.json to the dist directory
await fs.promises.mkdir(path.dirname(DIST_PROJECT_PATH), { recursive: true })
await fs.promises.writeFile(
`${DIST_PROJECT_PATH}/package.json`,
JSON.stringify(
{
...PACKAGE_JSON_TEMPLATE,
dependencies: requiredDependencies,
},
undefined,
2
)
)
console.log(`Written successfully: ${DIST_PROJECT_PATH}/package.json`)
}
main().then()
3,4行目の@ts-ignore
はimport時に以下のようなtsエラーが発生するため、
サッと回避するために使用
can only be default-imported using the 'esModuleInterop' flag
これで改めてデプロイを実行してみると
$ npx nx run backend:deploy
> nx run backend:deploy
・・・
i functions: updating Node.js 16 function helloWorld(us-central1)...
✔ functions[helloWorld(us-central1)] Successful update operation.
Function URL (helloWorld(us-central1)): https://us-central1-<プロジェクトID>.cloudfunctions.net/helloWorld
i functions: cleaning up build files...
✔ Deploy complete!
デプロイが成功しました!
Firebaseのページを見てもデプロイされている事が確認出来ます。
余談: 書き出されたpackage.jsonについて
tools/scripts/generate-api-package-json.ts変更後の書き出されたpackage.jsonは
変更前
{
"version": "0.1.0",
"license": "MIT",
"private": true,
"dependencies": {
"@apollo/client": "^3.6.9",
"@emotion/react": "^11.10.4",
・・・
},
"main": "main.js",
"engines": {
"node": "16"
}
}
変更後
{
"engines": {
"node": "16"
},
"main": "main.js",
"dependencies": {
"firebase-functions": "^3.23.0"
}
}
とてもシンプルでスッキリしました!