LoginSignup
41
22

More than 5 years have passed since last update.

Firebase Functionsを関数ごとにファイル分割 with TypeScript

Last updated at Posted at 2019-01-22

正解っぽい方法を見つけたので追記しました。(2019/01/25)


Udemyの正月セールであれもこれもと買っていたらクレジットカードにとんでもねえ請求1が来たので憂さ晴らしに書きます。

目的

Firebase Functionsで関数ごとにファイルを分割し高速化とメンテナンス性向上も目指す

これがやりたい。
しかしながら動的型付け言語イヤなので静的型付け言語でやりたい。つまりTypeScriptでやりたい。

コード

サンプルをコピペして愚直に書いてみます。全部同じディレクトリに置いておきます。
ランタイムはNode.js 6です。

index.ts
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');

fromDb.ts
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');
fromHttps.ts
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

死んだ!
スクリーンショット 2019-01-22 23.43.01.png
でもなんかいる……

関数の横のハンバーガーメニューから「使用状況の詳細な統計情報」を押すことでGoogle Cloud PlatformのCloud Functionに移動することができます。
スクリーンショット 2019-01-22 23.48.20.png
やはり死んでいる

関数テスト・その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

スクリーンショット 2019-01-23 0.23.50.png
しばらく待ってもなにも起こらず。死んでる。

中間考察

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

スクリーンショット 2019-01-23 0.08.37.png

デプロイその2

とりあえず前方一致にしてみる……。

index.ts
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

スクリーンショット 2019-01-23 0.24.16.png
変化中のスクリーンショットが取れなかったんですが、生きてます。

結論

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で関数ごとにファイルを分割し高速化とメンテナンス性向上も目指す
GincoにおけるCloud Functionsの利用とその高速化
TypeScriptで複数ファイル構成する2つの方法

追記

なぜ関数名が上記のようになってしまっているかというと、require(funcObj["fromHttps"]) で { fromHttps: } のようなオブジェクトが返るので、
exports.fromHttps = { fromHttps: }
のようにexportされてしまうためです。

いただいたコメントを読んで

exports.fromHttps = { fromHttps: <fromHttpsの関数本体> }

ということはつまりその関数本体をexportsとかいうやつにそのままぶちこめばなんかうまくいくのでは?
と思って以下の変更を加えたら期待通りの動作になりました。3

exportmodule.exports

fromHttps.ts
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');

スクリーンショット 2019-01-25 1.37.29.png
関数名も繰り返してない。よろしい。

追記の参考

Node.jsのexportsとmodule.exportsの違いについてのメモ


  1. やばい。 

  2. 「わからないことがあったらstackoverflowに聞いてくれ! あそこは素晴らしいサイトだよ!」とか朗らかに言っててつらい 

  3. なんでかはよくわからん。 

41
22
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
41
22