正解っぽい方法を見つけたので追記しました。(2019/01/25)
Udemyの正月セールであれもこれもと買っていたらクレジットカードにとんでもねえ請求1が来たので憂さ晴らしに書きます。
目的
[Firebase Functionsで関数ごとにファイルを分割し高速化とメンテナンス性向上も目指す]
(https://uyamazak.hatenablog.com/entry/2018/10/22/113000)
これがやりたい。
しかしながら動的型付け言語イヤなので静的型付け言語でやりたい。つまりTypeScriptでやりたい。
コード
サンプルをコピペして愚直に書いてみます。全部同じディレクトリに置いておきます。
ランタイムはNode.js 6
です。
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp();
const funcs = {
fromHttps: './fromHttps',
fromDb: './fromDb'
};
const loadFunctions = (funcsObj) => {
console.log('loadFunctions ' + process.env.FUNCTION_NAME);
for (const name in funcsObj) {
if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === name) {
exports[name] = require(funcsObj[name])
}
}
};
loadFunctions(funcs);
export const inIndex = functions.https.onRequest((request, response) => {
console.log('inIndex');
response.status(200).send('inIndex');
});
console.log('index loaded');
import * as functions from 'firebase-functions';
export const fromDb = functions.database.ref('/onamae/{name}/name')
.onCreate((snapshot: functions.database.DataSnapshot, context: functions.EventContext) => {
const name: string = snapshot.val();
console.log('fromDb' + name);
return snapshot.ref.parent.child('uppercase').set(name.toUpperCase());
});
console.log('fromDb loaded');
import * as functions from 'firebase-functions';
export const fromHttps = functions.https.onRequest((request, response) => {
const name: string = request.body.name;
console.log('fromHttps' + name);
response.status(200).send(name + '!');
});
console.log('fromHttps loaded');
各ファイルに計3つの関数を定義しました。
function | file | trigger |
---|---|---|
inIndex | index.ts | https |
fromHttps | fromHttps.ts | https |
fromDb | fromDb.ts | RealtimeDatabase |
デプロイその1
とりあえずデプロイ。
npm run deploy
> functions@ deploy /Users/●●●/Firebase/testBed/functions
> firebase deploy --only functions
=== Deploying to '●●●'...
i deploying functions
Running command: npm --prefix "$RESOURCE_DIR" run lint
> functions@ lint /Users/●●●/Firebase/testBed/functions
> tslint --project tsconfig.json
no-unused-variable is deprecated. Since TypeScript 2.9. Please use the built-in compiler checks instead.
Running command: npm --prefix "$RESOURCE_DIR" run build
> functions@ build /Users/●●●/Firebase/testBed/functions
> tsc
✔ functions: Finished running predeploy script.
i functions: ensuring necessary APIs are enabled...
✔ functions: all necessary APIs are enabled
i functions: preparing functions directory for uploading...
i functions: packaged functions (15.37 KB) for uploading
✔ functions: functions folder uploaded successfully
i functions: creating Node.js 6 function fromHttps-fromHttps(us-central1)...
i functions: creating Node.js 6 function fromDb-fromDb(us-central1)...
i functions: creating Node.js 6 function inIndex(us-central1)...
⚠ functions[fromHttps-fromHttps(us-central1)]: Deployment error.
Function failed on loading user code. Error message: Node.js module defined by file lib/index.js is expected to export function named fromHttps.fromHttps
⚠ functions[fromDb-fromDb(us-central1)]: Deployment error.
Function failed on loading user code. Error message: Node.js module defined by file lib/index.js is expected to export function named fromDb.fromDb
✔ functions[inIndex(us-central1)]: Successful create operation.
Function URL (inIndex): https://[trigger url]/inIndex
Functions deploy had errors with the following functions:
fromDb-fromDb
fromHttps-fromHttps
To try redeploying those functions, run:
firebase deploy --only functions:fromDb-fromDb,functions:fromHttps-fromHttps
To continue deploying other features (such as database), run:
firebase deploy --except functions
Error: Functions did not deploy properly.
npm ERR! Darwin 18.2.0
npm ERR! argv "/Users/●●●/.nodebrew/node/v6.11.5/bin/node" "/Users/●●●/.nodebrew/current/bin/npm" "run" "deploy"
npm ERR! node v6.11.5
npm ERR! npm v3.10.10
npm ERR! code ELIFECYCLE
npm ERR! functions@ deploy: `firebase deploy --only functions`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the functions@ deploy script 'firebase deploy --only functions'.
npm ERR! Make sure you have the latest version of node.js and npm installed.
npm ERR! If you do, this is most likely a problem with the functions package,
npm ERR! not with npm itself.
npm ERR! Tell the author that this fails on your system:
npm ERR! firebase deploy --only functions
npm ERR! You can get information on how to open an issue for this project with:
npm ERR! npm bugs functions
npm ERR! Or if that isn't available, you can get their info via:
npm ERR! npm owner ls functions
npm ERR! There is likely additional logging output above.
npm ERR! Please include the following file with any support request:
npm ERR! /Users/●●●/Firebase/testBed/functions/npm-debug.log
関数の横のハンバーガーメニューから「使用状況の詳細な統計情報」を押すことでGoogle Cloud PlatformのCloud Functionに移動することができます。
やはり死んでいる
関数テスト・その1
そうはいうものの生きてるかもしれないので関数をテストしてみます。
inIndex
curl https://[trigger url]/inIndex
---
inIndex
生きてる。
fromHttps
curl https://[trigger url]/fromHttps-fromHttps -X POST -H "Content-Type: application/json" -d '{"name": "whoseyards"}'
---
Error: could not handle the request
死んでる。
fromDb

中間考察
見事に他ファイルに分割した関数だけ死んで、 index.ts
に記載した関数だけが生き残りました。
やはりJavaScript
でやるしかない……?
ここでfirebase cloud functionsのログを見ます。別ファイルに分割した関数名だけが繰り返されているのが目に入ります。

デプロイその2
とりあえず前方一致にしてみる……。
const loadFunctions = (funcsObj) => {
console.log('loadFunctions ' + process.env.FUNCTION_NAME);
for (const name in funcsObj) {
// 全文じゃなくて前方一致にする
// if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === name) {
if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME.startsWith(name)) {
exports[name] = require(funcsObj[name])
}
}
};
npm run deploy
> functions@ deploy /Users/●●●/Firebase/testBed/functions
> firebase deploy --only functions
=== Deploying to '●●●'...
i deploying functions
Running command: npm --prefix "$RESOURCE_DIR" run lint
> functions@ lint /Users/●●●/Firebase/testBed/functions
> tslint --project tsconfig.json
no-unused-variable is deprecated. Since TypeScript 2.9. Please use the built-in compiler checks instead.
Running command: npm --prefix "$RESOURCE_DIR" run build
> functions@ build /Users/●●●/Firebase/testBed/functions
> tsc
✔ functions: Finished running predeploy script.
i functions: ensuring necessary APIs are enabled...
✔ functions: all necessary APIs are enabled
i functions: preparing functions directory for uploading...
i functions: packaged functions (15.89 KB) for uploading
✔ functions: functions folder uploaded successfully
i functions: updating Node.js 6 function fromHttps-fromHttps(us-central1)...
i functions: updating Node.js 6 function fromDb-fromDb(us-central1)...
i functions: updating Node.js 6 function inIndex(us-central1)...
✔ functions[fromDb-fromDb(us-central1)]: Successful update operation.
✔ functions[inIndex(us-central1)]: Successful update operation.
✔ functions[fromHttps-fromHttps(us-central1)]: Successful update operation.
✔ Deploy complete!
デプロイ成功。
関数テスト・その2
inIndex
curl https://[trigger url]/inIndex
---
inIndex
引き続き生きてる。
fromHttps
curl https://[trigger url]/fromHttps-fromHttps -X POST -H "Content-Type: application/json" -d '{"name": "whoseyards"}'
---
whoseyards!
生きてる。
fromDb

結論
process.env.FUNCTION_NAME
で実行される関数名が取得できるのでそれを利用して読み込むファイルを限定する、というのが元記事と元々記事の要点なわけですが、なんか-増えてるのでうまくいかなかったようです。
process.env.FUNCTION_NAME = 関数名-関数名
前方一致だけだと関数名の前方が同じ場合に死ぬので、後方一致も条件に含めておいたほうがより安全です。
ちなみに別ファイルに分割した関数に対しての個別関数デプロイも通ります。
firebase deploy --only functions:fromHttps
感想
で、なんでふえてるの……?
Firebase・GCP・Node.js・TypeScript・JavaScriptのどれもよくわからんのでどこに原因があるかよくわからない! 詳しい人書いといて!
TypeScriptで書かれた実戦に投入できるレベルのCloudFunctionのサンプルが見つからないのですが、やっぱり主流はJavaScriptなんでしょうか。
Firebaseを重点的に勉強中なのですが、詳しいことを学ぼうとするとFirebase公式Youtubeチャンネルを見る羽目になります。重要なこと喋ってるのに再生数少ないのはそういうこと2なんだなって……。
個人的な願望なのですが、動的型付け言語だとIDEとかコンパイラが面倒見てくれなくてめんどくさいので、静的型付け言語でCloudFunction書きたいです。Gradual Typingじゃなくてガチガチの静的型付け言語のやつ。Kotlinとか。PythonとGoがβらしいので、βが取れたら頑張ってGoを覚えようかと思っています。
このまま進めばfirebase-admin
とかfirebase-function
をTypeScriptでglobalに格納する話……になる予定です。正直namespace
がよくわからないすぎるので、いつになるかわかりませんが。
おしまい。
参考
[Firebase Functionsで関数ごとにファイルを分割し高速化とメンテナンス性向上も目指す]
(https://uyamazak.hatenablog.com/entry/2018/10/22/113000)
GincoにおけるCloud Functionsの利用とその高速化
TypeScriptで複数ファイル構成する2つの方法
追記
なぜ関数名が上記のようになってしまっているかというと、require(funcObj["fromHttps"]) で { fromHttps: } のようなオブジェクトが返るので、
exports.fromHttps = { fromHttps: }
のようにexportされてしまうためです。
いただいたコメントを読んで
exports.fromHttps = { fromHttps: <fromHttpsの関数本体> }
ということはつまりその関数本体をexportsとかいうやつにそのままぶちこめばなんかうまくいくのでは?
と思って以下の変更を加えたら期待通りの動作になりました。3
export
→ module.exports
import * as functions from 'firebase-functions';
// before
// export const fromHttps = functions.https.onRequest((request, response) => {
// after
module.exports = functions.https.onRequest((request, response) => {
const name: string = request.body.name;
console.log('fromHttps' + name);
response.status(200).send(name + '!');
});
// module.exportsから公開されるのは1ファイル内で1つだけ
// = 1関数1ファイルが強制される! はず!
console.log('fromHttps loaded');

追記の参考
Node.jsのexportsとmodule.exportsの違いについてのメモ