Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
34
Help us understand the problem. What is going on with this article?

More than 1 year has passed since last update.

@masatomix

Cloud Functions for Firebase をつかってみる。ついでにBabelとかTypeScriptのトランスパイルも。

Cloud Functions for Firebaseはいわゆる AWS Lambdaの Firebase 版です。サーバレスで、サーバ側にロジックを構築できるわけですが、FirebaseやFirestoreが便利すぎる件にくわえて、さらにそれらの処理をトリガーにロジックがうごかせるわけなので、、ちゃんと勉強しておいた方がよさそうです。。

ちなみに、サイトにあるユースケースを見ると

  • Realtime Database の書き込みをトリガーに、ユーザに通知を投げるとか、
  • 画像のアップロードをトリガーに、画像の加工を行うとか
  • GitHub webhook APIをつかって、commitのpushをトリガーにSlackに通知を投げる

なんて事ができそうです。HTTPSの受信をトリガーに出来るので、簡単なRESTサーバとかも構築できますね。。

という事で作業メモ。

やってみる

Hello World

firebase-toolsのインストール

$ npm install -g firebase-tools
$ source ~/.bash_profile
$ firebase --version
6.3.0

プロジェクトのディレクトリ作成

$ mkdir fb_function_samples  && cd $_

Firebaseにログインしておく

$ firebase login
? Allow Firebase to collect anonymous CLI usage and error reporting information?
 No

Visit this URL on any device to log in:
https://accounts.google.com/o/oauth2/auth?client_id=...   // OAuthでアクセスの認可まち

Waiting for authentication...

✔  Success! Logged in as masatomix@example.com

プロジェクト作成

$  firebase init functions

     ######## #### ########  ######## ########     ###     ######  ########
     ##        ##  ##     ## ##       ##     ##  ##   ##  ##       ##
     ######    ##  ########  ######   ########  #########  ######  ######
     ##        ##  ##    ##  ##       ##     ## ##     ##       ## ##
     ##       #### ##     ## ######## ########  ##     ##  ######  ########

You're about to initialize a Firebase project in this directory:

質問には基本デフォルトでよさげ

? Select a default Firebase project for this directory: [create a new project]
? What language would you like to use to write Cloud Functions? JavaScript
? Do you want to use ESLint to catch probable bugs and enforce style? No
? Do you want to install dependencies with npm now? Yes

✔  Firebase initialization complete!

Project creation is only available from the Firebase Console
Please visit https://console.firebase.google.com to create a new project, then run firebase use --add

いわれるままに、コマンドを実行(どのFirebase上のプロジェクトにデプロイする?とかを指定してるぽい)

$ firebase use --add
? Which project do you want to add? sandbox-xxxxxxx
? What alias do you want to use for this project? (e.g. staging) test_env

Created alias test_env for sandbox-xxxxxxx.
Now using alias test_env (sandbox-xxxxxxx)

結果、プロジェクト構成はこんな感じ

$ pwd
/..../fb_function_samples
$ tree -a
.
├── .firebaserc
├── .gitignore
├── firebase.json
└── functions
    ├── .gitignore
    ├── index.js
    ├── node_modules/.... 割愛
    ├── package-lock.json
    └── package.json

さあ index.jsをいじってみます。

functions/index.js(以下のように修正)
$ cat functions/index.js
const functions = require('firebase-functions');

// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
exports.helloWorld = functions.https.onRequest((request, response) => {
 response.send("Hello from Firebase!");
});

exports.echo = functions.https.onRequest((request, response) => {
    const task = request.body
    console.log(JSON.stringify(task))
    // const firestore = admin.firestore()
    // const ref = firestore.collection("todos")
     response.send(JSON.stringify(task));
});

HTTPのリクエストの受信をトリガーに動く、メソッド helloworldとechoを作成しました。

サーバへデプロイ

$ firebase deploy --only functions

=== Deploying to 'sandbox-xxxxxxx'...

i  deploying functions
i  functions: ensuring necessary APIs are enabled...
✔  functions: all necessary APIs are enabled
i  functions: preparing functions directory for uploading...
i  functions: packaged functions (42.39 KB) for uploading
✔  functions: functions folder uploaded successfully
i  functions: creating Node.js 6 function helloWorld(us-central1)...
i  functions: creating Node.js 6 function echo(us-central1)...
✔  functions[echo(us-central1)]: Successful create operation.
Function URL (echo): https://us-central1-sandbox-xxxxxxx.cloudfunctions.net/echo
✔  functions[helloWorld(us-central1)]: Successful create operation.
Function URL (helloWorld): https://us-central1-sandbox-xxxxxxx.cloudfunctions.net/helloWorld

✔  Deploy complete!

Please note that it can take up to 30 seconds for your updated functions to propagate.
Project Console: https://console.firebase.google.com/project/sandbox-xxxxxxx/overview

さあ、デプロイが完了したので、 firebase loginしたコンソールで、curlで呼び出してみます

$ curl https://us-central1-sandbox-xxxxxxx.cloudfunctions.net/helloWorld
Hello from Firebase!

$ cat request.json
{
  "id": "001",
  "name": "こんにちは",
  "isDone": true
}

$ curl -X POST -H "Content-Type:application/json" \
  --data-binary  @request.json \
  https://us-central1-sandbox-xxxxxxx.cloudfunctions.net/echo | jq
{
  "id": "001",
  "name": "こんにちは",
  "isDone": true
}
$

よさそうです。

requireでなくてimportをつかいたい

vue-cliで vue.jsのアプリを書いていると、ES2015形式(?)でコードを書いてますよね。外部モジュールをconst functions = require('firebase-functions'); ではなく import * as functions from 'firebase-functions' って書きたい、ってヤツです。

ってことで、babelを入れてトランスパイルすることで、ES2015形式でかけるようにしてみます。

babel インストール他

$ cd functions
$ npm install --save-dev babel-cli babel-preset-es2015
$ cd ../

firebase.json にpredeployを追加(ココに書いておくと、deploy前に実行されます)

$ cat firebase.json
{
    "functions": {
      "predeploy": "npm --prefix \"$RESOURCE_DIR\" run build" // buildはこのあと定義します
    }
}

package.json にbuildのスクリプトの定義を追加

$ cat  functions/package.json
{
  "name": "functions",
  "description": "Cloud Functions for Firebase",
  "scripts": {
    "build": "mkdir -p dist && ./node_modules/.bin/babel ./*.js --out-file ./dist/index.js",    // buildの定義を追加
    "serve": "firebase serve --only functions",
    "shell": "firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "main": "dist/index.js",  // デプロイするファイルをココで指定
  "engines": {
    "node": "8" // ついでにnodeを8にしてみる
  },
  "dependencies": {
    "firebase-admin": "~6.0.0",
    "firebase-functions": "^2.1.0"
  },
  "private": true,
  "devDependencies": { // これらは自動で追加されたはず
    "babel-cli": "^6.26.0",
    "babel-preset-es2015": "^6.24.1"
  }
}

babel設定ファイル作成

$ cat functions/.babelrc
{
  "presets": [
    [
      "es2015",
      {
        "targets": {
          "node": "current"
        }
      }
    ]
  ]
}

ディレクトリ構成は以下のように。

$ tree -a
.
├── .firebaserc
├── .gitignore
├── firebase.json
└── functions
    ├── .babelrc
    ├── .gitignore
    ├── index.js
    ├── node_modules/.... 割愛
    ├── package-lock.json
    └── package.json

デプロイ

$ firebase deploy --only functions

=== Deploying to 'sandbox-xxxxxxx'...

i  deploying functions
Running command: npm --prefix "$RESOURCE_DIR" run build

> functions@ build /..../fb_function_samples/functions
> mkdir -p dist && ./node_modules/.bin/babel ./*.js --out-file ./dist/index.js

✔  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 (55.53 KB) for uploading
✔  functions: functions folder uploaded successfully
i  functions: updating Node.js 8 function helloWorld(us-central1)...
i  functions: updating Node.js 8 function echo(us-central1)...
✔  functions[helloWorld(us-central1)]: Successful update operation.
✔  functions[echo(us-central1)]: Successful update operation.

✔  Deploy complete!

Please note that it can take up to 30 seconds for your updated functions to propagate.
Project Console: https://console.firebase.google.com/project/sandbox-xxxxxxx/overview
$

ログを見ると、babelのトランスパイルが動いてからデプロイされていますね。。
curlで実行出来ることを確認してください。

ちゃんとES2015の文法に書き直して、再ラン

さっきはindex.jsをもとのファイルそのままつかいましたが、以下のようにトランスパイルが必要なコードにしてみます。

functions/index.js
import * as functions from 'firebase-functions'

// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
export const helloWorld = functions.https.onRequest((request, response) => {
  response.send("Hello from Firebase!")
})

export const echo = functions.https.onRequest((request, response) => {
  const task = request.body
  console.log(JSON.stringify(task))
  // const firestore = admin.firestore()
  // const ref = firestore.collection("todos")
  response.send(JSON.stringify(task))
})

も一度デプロイしてみましょう。ちなみに dist/index.js をみても、ちゃんと変換されてそうですね。。

っていうかTypeScriptつかえば?ってことかな

いまいち JavaScriptのES2015とTypeScriptの関係がよく分かってないのですが、ES2015でbabelで前提でコードを書くのと、Gradual Typing なTypeScript で書くのって、構文おなじっぽいですよね?ってことで、そもそも初めのプロジェクト作成で、JavaScriptでなくてTypeScriptを選べばいいんじゃんね?っておもいました。。

ということで、別のTypeScriptプロジェクトを作成します。index.jsは後でつかうのでどっかに取っておきましょう。

TypeScriptをつかってみる

プロジェクト作成(こんどはTypeScriptをえらびますよ)

$ mkdir fb_function_samples  && cd $_
$ firebase init functions


? Select a default Firebase project for this directory: [create a new project]
? What language would you like to use to write Cloud Functions? TypeScript    ← TypeScriptをえらぶ
? Do you want to use TSLint to catch probable bugs and enforce style? No      ← いったんNo
? Do you want to install dependencies with npm now? Yes

✔  Firebase initialization complete!

Project creation is only available from the Firebase Console
Please visit https://console.firebase.google.com to create a new project, then run firebase use --add
$ firebase use --add
? Which project do you want to add? sandbox-xxxxxxx
? What alias do you want to use for this project? (e.g. staging) test_env

Created alias test_env for sandbox-xxxxxxx.
Now using alias test_env (sandbox-xxxxxxx)
$

ディレクトリ構成

$ tree -a
.
├── .firebaserc
├── .gitignore
├── firebase.json
└── functions
    ├── .gitignore
    ├── node_modules/.... 割愛
    ├── package-lock.json
    ├── package.json
    ├── src
    │   └── index.ts
    └── tsconfig.json

さて、待避しておいた functions/index.js の内容を functions/src/index.ts へ上書きして、firebase deploy します

$ firebase deploy --only functions

=== Deploying to 'sandbox-xxxxxxx'...

i  deploying functions
Running command: npm --prefix "$RESOURCE_DIR" run build

> functions@ build /..../fb_function_samples_ts/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 (43.74 KB) for uploading
✔  functions: functions folder uploaded successfully
i  functions: updating Node.js 8 function helloWorld(us-central1)...
i  functions: updating Node.js 8 function echo(us-central1)...
✔  functions[helloWorld(us-central1)]: Successful update operation.
✔  functions[echo(us-central1)]: Successful update operation.

✔  Deploy complete!

Please note that it can take up to 30 seconds for your updated functions to propagate.
Project Console: https://console.firebase.google.com/project/sandbox-xxxxxxx/overview

さきほどのbabelではpredeployにbabelの処理を手動で追加しましたが、TypeScriptで作成したプロジェクトにはあらかじめ tsc(TypeScriptのトランスパイラ?)が事前に動くように設定されていますね。
(っていうかさっきbabelの処理を入れる際に、TypeScriptのプロジェクトを参考にしたので当たり前なのですが :-))

ちなみにトランスパイルするときのチェック処理などは、functions/tsconfig.json の定義に則って行われるみたいなので、必要に応じて設定ファイルを見直してください。

参考:
http://js.studio-kingdom.com/typescript/project_configuration/tsconfig_json

さて、curlでやってみて、おなじ結果が得られることを確認しましょう。

$ curl https://us-central1-sandbox-xxxxxxx.cloudfunctions.net/helloWorld
Hello from Firebase!

$ cat request.json
{
  "id": "001",
  "name": "こんにちは",
  "isDone": true
}

$ curl -X POST -H "Content-Type:application/json" \
  --data-binary  @request.json \
  https://us-central1-sandbox-xxxxxxx.cloudfunctions.net/echo | jq
{
  "id": "001",
  "name": "こんにちは",
  "isDone": true
}
$

なんだか遠回りした気がしますが、Vue.jsでつかってるJavaScriptとおなじ文法で、Functionsを記述することができるようになりました!

おつかれさまでした。

34
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
34
Help us understand the problem. What is going on with this article?