GAミント至上主義

Web Monomaniacal Developer.

ぼくが考えた最強のFirebase Functionsのファイル構成を考えてる途中

まだ全然最強感がないけど、やり始めたら1日過ぎたのでまとめる。

% tree -L 2 -I node_modules functions 
functions
├── index.js # メインのやつ
├── package-lock.json
├── package.json
├── src
│   ├── blog-rss-to-json.js # 呼び出すやつ
│   ├── globals.js # グローバル変数のやつ
│   ├── libs.js # 共有で使う関数とか
│   ├── not-found.js # 呼び出すやつ
│   ├── reply-postcard-to-yagish.js # 呼び出すやつ
│   ├── send-postcard-to-chat.js # 呼び出すやつ
│   └── update-omikuji-statistics.js # 呼び出すやつ
└── yarn.lock

1関数1ファイルに分割

この記事に詳しく書いたけど1関数1ファイルに分割する。
一覧のオブジェクトを見れば、どんな名前でどの関数を読んでるか分かるようになる。

index.js

const funcs = {
  blogRssToJson: './src/blog-rss-to-json',
  sendPostCard2Chat: './src/send-postcard-to-chat',
  replyPostCard2Yagish: './src/reply-postcard-to-yagish',
  notFound: './src/not-found',
  updateOmikujiStatistics: './src/update-omikuji-statistics'
}
const loadFunctions = (funcsObj) => {
  for(let name in funcsObj){
    if(! process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === name) {
      exports[name] = require(funcsObj[name])
    }
  }
}

loadFunctions(funcs)

とりあえずindex.js以外のjsファイルはsrc以下にぶちこんでる。
関数のファイルと後述のglobalsとかlibs的なファイルは増えてきたら別に分けた方がいいかもしれない。

これでindex.jsがめっちゃ長くなったり、無駄なモジュールを読み込むことが減る・・はず。

環境変数は.env的なファイルにする

firebase functionの環境変数の扱いはちょっと変わってる。
process.envではなく、funtions.config()を使えとなってる。

環境の構成  |  Firebase

呼び出しは

funtions.config().foo.bar

みたいな感じでいいとしても、セットがめんどい。

デプロイする前に、以下の slack.url 環境構成変数を設定します。

firebase functions:config:set slack.url=https://hooks.slack.com/services/XXX

CLIのコマンドでセットしないといけない。

JSONファイルも読ませられるけど、トップレベルのキーは指定しなきゃいけないので、1ファイルにすべてとまとめるのができない。
qiita.com

そのため環境ごとに下記のようなファイルを作り、デプロイのシェルスクリプトに追加することにした。
よくある.env.*にしなかったのは、コマンドがconfigだから。

.config.production

chat.webhook_url=https://chat.googleapis.com/v1/spaces/YAGIYAGIYAGIYAGISH
site.id=yagish_production
blog.rss_url=https://blog.yagish.jp/rss
blog.access_control_allow_origin= https://rirekisho.yagish.jp

そのままcatしただけだと改行が値に入ってしまうので、 tr "\n\r" " "をいれて改行をスペースに変換する
Windowsで書いたから改行がCRLFになってたのが原因、LFだったら大丈夫)

値を""で囲いたかったけど、どうしてもエスケープされて入ってしまうので諦めた
(printfとかcat、echoのオプションとか2時間ぐらい闘った)
もっとbashを理解したい

firebase functions:config:set `cat ./app/functions/.config.production| tr "\n\r" " "`

セットしたらちゃんとできてるか確認した方がいい。

firebase functions:config:get

実際のdeploy.shはこんな感じ(一部抜粋)
全部Dockerコンテナ内でやっているのでこんなになってしまう。一つのexecで&で繋げてもいいかな

sudo docker exec  \
    ${CONTAINER_ID} \
    yarn build-production

sudo docker exec  \
    ${CONTAINER_ID} \
    firebase -P ${PROJECT_ID} functions:config:set `cat ./app/functions/.config.production| tr "\n\r" " "`

sudo docker exec  \
    ${CONTAINER_ID} \
    firebase -P ${PROJECT_ID} functions:config:get

sudo docker exec  \
    ${CONTAINER_ID} \
    firebase deploy -P ${PROJECT_ID} -p dist-production

よく使う大きいオブジェクトはglobal変数にぶち込んで再利用&遅延評価

詳しくはここを参照
ヒントとアドバイス  |  Firebase

とりえあずよく使う大きいオブジェクトは、firebase-admin、firestoreのオブジェクトなのでこれをglobal化しておく。
ほぼ絶対使うfirebase-functionsもglobalにいれるけど、絶対使うので遅延評価はしない。

./src/globals.js

global.functions = require('firebase-functions')

global.admin = null
global.adminInit = function () {
  console.log('adminInit')
  global.admin = require('firebase-admin')
  global.admin.initializeApp()
  return global.admin
}

global.firestore = null
global.firestoreInit =  function () {
  console.log('firestoreInit')
  const admin = global.admin || global.adminInit()
  global.firestore = admin.firestore()
  global.firestore.settings({
    timestampsInSnapshots: true
  })
  return global.firestore
}

とりえあず変数名+Initという命名規則で読み込む関数を作る。
冗長で最強感ない。

これをindex.jsの冒頭で読ませておく
index.js

require('./src/globals')
// 省略

あとそれぞれファイルの冒頭に使うものを書いておく。

久しぶりにグローバル変数使った(WordPressぶり?)けど、改めて「こいつどこから来たんだ?」感が半端ないので、明示するという意味もある。
グローバル変数なのでglobalをつけずにも呼べるんだけど、あえてglobalを付けて使ってる。

ちなみにnodeのglobalオブジェクトは今回知った。
globalsにglobalオブジェクトがあるのでglobalも当然globalなオブジェクトなんだね(不明)。

global.firestore = global.firestore || global.firestoreInit()
global.admin = global.admin || global.adminInit()

関数の数も実行回数も少ないので、圧倒的に早すぎる最適化感がある。
今後問題を感じたら都度改善して最強を目指すか、もっといいのを見つけたらそっちにしようと思う。