ロゴテキスト ロゴ

    NxでFirebase Functionsのアプリケーションを作成する その2

    NxでFirebase Functionsのアプリケーションを作成する その2

    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は以下のようになっています

    functions/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も実行しています。

    firebase.json
    {
      "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で行えるようにします。



    変更前

    apps/backend/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
        }
      },

    変更後

    apps/backend/project.json
    "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に追加します

    apps/backend/project.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のデプロイの動きとしては

    1. buildをする(dist/apps/backend/にmain.jsを書き出す)
    2. ルートのpackage.jsonを元に dist/apps/backend/package.json を作成
    3. dist/apps/backend/package.json 内の記載されているパッケージを install
    4. 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 を扱う方法 | ティーポットは珈琲を淹れられない

    tools/scripts/generate-api-package-json.ts
    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 Functionsへのデプロイが成功した

    デプロイが成功しました!



    Firebaseのページを見てもデプロイされている事が確認出来ます。

    Firebase上にデプロイされている事が確認出来る




    余談: 書き出されたpackage.jsonについて

    tools/scripts/generate-api-package-json.ts変更後の書き出されたpackage.jsonは



    変更前

    dist/apps/backend/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"
    	}
    }

    変更後

    dist/apps/backend/package.json
    {
      "engines": {
        "node": "16"
      },
      "main": "main.js",
      "dependencies": {
        "firebase-functions": "^3.23.0"
      }
    }

    とてもシンプルでスッキリしました!

    プロフィールの背景画像 プロフィール画像
    Yuki Takara
    都内でフリーランスのエンジニアをやってます。フロントとアプリ開発メインに幅広くやってます。