2022.08.06

Firebase Functionsで絶対パスでimportする

FirebaseでCloud Functionsを使うときに相対パスでimportさせられる問題をBabelで解決しました<br> Webpackで解決している人は見かけた(*1, *2)のですが、Babelでやっている人は見かけなかったので書いてみました

ゴール

メンヘラせんぱいはFirebaseで動いており、サーバー側で行いたい処理はFirebase Functionsを使っています<br> 最近Functionsのディレクトリ構成を見直したくなったのですが、すべて相対パスでimportしていたため、簡単にディレクトリ間を移動できませんでした。階層が深いと ../ がかなり多くなって見栄えも悪いです

import { triggerOnce } from '../../utils/triggerOnce'

aliasをはり、↓のようにルートから @ で絶対‪パスでimportできるようにすることがゴールです

import { triggerOnce } from '@/utils/triggerOnce'

相対importのとき

FirebaseでFunctionsを使うときにプロジェクトを作成するとまず次のようなディレクトリ構成になると思います

functions
├── node_modules
├── src
├── index.js
├── package.json
└── tsconfig.json

そして型定義やリポジトリ層を用意すると、弊社ではこのようなディレクトリ構成になりました

functions
├── node_modules
├── src
│   ├── batches
│   ├── entities
│   ├── plugins
│   ├── repositories
│   ├── triggers
│   └── utils
├── package.json
└── tsconfig.json

そしてこれらをimportしようとした結果、次のようなコードになります

import * as admin from 'firebase-admin'
import * as functions from 'firebase-functions'

import { db, serverTimestamp } from '../../firestore'
import HogeRepository from '../../repositories/HogeRepository'
import { triggerOnce } from '../../utils/triggerOnce'

この大量の ../ を消します

原因

ということで絶対パスを使いたいのですが、ただtsconfig.jsonにpathsを設定するだけではうまくいきません<br> Firebase Functionsではビルドにtscを使っているのですが、tscがaliasを解釈してくれず、ビルド時にパスが自動で変換されないことが原因です

絶対pathを使えるようにする

解決策としては、tscをやめてWebpackやBabelで解決します<br> 今回はBabelを使うことにしました。WebpackではなくBabelを選ぶ理由としては、弊社のテックリード(@remew)の言葉を借りると、
・webpackはバンドラーで、babelはASTレベルの変換のために使うというイメージ
・webpackで解決できるのはバンドルする仮定でimportのパスを解決する必要があるから結果的に問題が解決できる
・今回はバンドルしたいわけじゃないのにwebpack入れるのもびみょい
からとのことです

作業

それでは実装に入ります<br> まず必要なライブラリをnpm installしていきます<br>

$ npm install --save-dev @babel/cli @babel/core @babel/preset-env @babel/preset-typescript babel-plugin-module-resolver

babel.config.jsで ./src@ というaliasをはります

// babel.config.js
module.exports = {
  presets: [
    [
      '@babel/env',
      {
        targets: {
          node: '10.0'
        }
      }
    ],
    '@babel/typescript'
  ],
  plugins: [
    [
      'module-resolver',
      {
        root: ['.'],
        alias: {
          '@': './src'
        }
      }
    ]
  ]
}

次にtsconfig.json側にもaliasを設定します

// tsconfig.json
{
  ...,
  "paths": {
    "@/*": ["./src/*"],
    "@": ["./src"],
  },
}

もしtslintでno-implicit-dependenciesで怒られていたら @ からのimportではoffにするように設定しておきましょう<br> 最後にデプロイコマンドを書き換えます

// package.json
{
  ...,
  "build": "tsc --noEmit && babel src -d lib --extensions .ts",
}

これでデプロイできました🎉

さいごに

今回はBabelを使ってFirebase Functionsで絶対パスでimportできるようにしました<br>