Vue.js とCloud Functions for Firebase をFirebase Hostingへデプロイするための環境構築手順

## 前置き

Qiitaに [Vue.js](https://qiita.com/masatomix/items/24142b084b745ce816a9) や [Firebase](https://qiita.com/masatomix/items/7be14b8eb433edcfbfc7) や [Cloud Functions for Firebase](https://qiita.com/masatomix/items/ee1d7d162c8f1c25df7a) などの記事を書いてて、ツールのセットアップや環境構築を都度継ぎ足してきたのですが、一度に構築する方法が整理できてなかったので、メモっておきます。

本記事で説明が足りないところは、適宜下記の記事を見ていただくとよいかもしれません。

- [FirebaseとGoogle Cloud Platform をさわれる環境を構築する](https://qiita.com/masatomix/items/0ddbfacb9bc1797044a1) おもに firebase-tools まわり
- [Vue.js の主要な機能をざっくりとつかってみたときのメモ](https://qiita.com/masatomix/items/24142b084b745ce816a9) Vue.jsのとっかかりはこちら
- [Cloud Functions for Firebase をつかってみる。](https://qiita.com/masatomix/items/ee1d7d162c8f1c25df7a) Functionsまわり


## コレをやったらできること

- Vue.jsのコマンドラインツールとFirebaseのコマンドラインツールがインストールできます
- Vue.jsのプロジェクトをローカルのWEBサーバで動かせます(npm run dev 上で)
- Vue.jsのプロジェクトをFirebaseのローカルWEBサーバで動かせます
- Cloud FunctionsをFirebaseのローカルWEBサーバで動かせます
- Vue.jsのプロジェクトをFirebase Hostingにデプロイできます
- Cloud FunctionsをFirebaseへデプロイできます
- Cloud FunctionsをHostingのドメインで呼び出す事ができます

> 2020/07/18 追記
> [改訂前の記事](https://qiita.com/masatomix/items/bd1c3a6b31e8d2cb7aef/revisions/1?type=preview) では vue/cli のバージョンが 2.9.6 向けだったのですが、新しいバージョン向け(2020/07/18時点の最新 4.4.6) に多少書き直しました


## 前提環境

```bash
%  sw_vers
ProductName:	Mac OS X
ProductVersion:	10.15.5
BuildVersion:	19F101

% node --version
v10.19.0    <-ホントはFunctionsとかのバージョンに合わせるべきなんだけどいったん気にしない :-)

```

## 準備

コマンドラインツール達のインストールと、Firebaseへのログインなど。あ、Firebaseはサインアップ済みってことで、、、。

### vue コマンドラインツールのインストール

vue-cli というVue.jsのテンプレを生成してくれるコマンドラインツールをインストールします。
公式: https://cli.vuejs.org/

```bash
% npm install -g @vue/cli
% source ~/.zshrc
% vue --version
@vue/cli 4.4.6
```

よさそうですね。

### firebase-toolsのインストール

firebaseのコマンドラインツール。

```bash
% npm install -g firebase-tools
% source ~/.zshrc
% firebase --version
8.6.0
```

### Firebaseにログイン

```bash
% 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
```


## やってみる

### まずは プロジェクト全体の作成

メインとなるプロジェクトは Vue.jsのコマンドラインツールで作成します。

```bash 
% vue create fb_vue_template

↓ とりあえず下記の通りManuallyでやってみた

Vue CLI v4.4.6
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Linter
? Pick a linter / formatter config: Prettier
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No

Vue CLI v4.4.6
✨  Creating project in /Users/masatomix/git/fb_vue_template.
🗃  Initializing git repository...
⚙️  Installing CLI plugins. This might take a while...

....

41 packages are looking for funding
  run `npm fund` for details

🚀  Invoking generators...
📦  Installing additional dependencies...

...

📄  Generating README.md...

🎉  Successfully created project fb_vue_template.
👉  Get started with the following commands:
 $ cd fb_vue_template
 $ npm run serve

%
```

完了しました。

### Vue.jsの Hello World を表示

最後に`` cd fb_vue_template``,``npm run serve`` とでてましたので、その通りに。。

```bash
% cd fb_vue_template
% npm run serve

> fb_vue_template@0.1.0 serve /Users/masatomix/git/fb_vue_template
> vue-cli-service serve

 INFO  Starting development server...
98% after emitting CopyPlugin

 DONE  Compiled successfully in 7310ms                       10:00:05 AM


  App running at:
  - Local:   http://localhost:8080/
  - Network: http://192.168.xx.xx:8080/

  Note that the development build is not optimized.
  To create a production build, run npm run build.
```

さて、メッセージ通りにブラウザで http://localhost:8080 をひらいてみます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73777/6f92ac9e-97d9-14af-c221-99d3b5ce15a6.png)

よさそうですね。。問題なければ、プロンプトは Ctrl-c で終了ておきましょう。

## Firebase関連情報を設定

さて Firebase関連です。

### Firebase Hosting設定

まずはこの Vue.js プロジェクトをサーバへデプロイするためのHosting設定を行います。

```bash
$ pwd
/xxx/xxxx/fb_vue_template
$ firebase init hosting

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

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

  /xxx/xxxx/fb_vue_template


=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add,
but for now we'll just set up a default project.

? Select a default Firebase project for this directory: hoge-xxxx (hoge)
i  Using project hoge-xxxx (hoge)

↑ デプロイしたい先のプロジェクトを選びます


=== Hosting Setup

Your public directory is the folder (relative to your project directory) that
will contain Hosting assets to be uploaded with firebase deploy. If you
have a build process for your assets, use your build's output directory.

? What do you want to use as your public directory? dist

↑ public？って聞いてきますが、vue.jsのプロジェクトが出力する先「dist」を選択します。


? Configure as a single-page app (rewrite all urls to /index.html)? Yes
✔  Wrote dist/index.html

SPAで作成するので、Yesを選択します。
サーバにデプロイした際、どのURLを叩いてもindex.html を表示するよっていう設定になります。

i  Writing configuration info to firebase.json...
i  Writing project information to .firebaserc...

✔  Firebase initialization complete!
$

$ cat firebase.json
{
  "hosting": {
    "public": "dist",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"  
        # サーバにデプロイした際、どのURLを叩いてもindex.html を
        # 表示するよっていう設定。
      }
    ]
  }
}
$
```

サーバにデプロイするための設定が追加されました。

### Cloud Functions 設定

つづいてバックエンド機能となる、Cloud Functions の設定を行います。

```bash
$ pwd
/xxx/xxxx/fb_vue_template
$ firebase init functions

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

You're about to initialize a Firebase project in this directory:
  /xxx/xxxx/fb_vue_template
=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add,
but for now we'll just set up a default project.


? Select a default Firebase project for this directory: hoge-xxxx (hoge) ← 使用するプロジェクト選択
? 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? Yes    ← Yesをえらぶ
? Do you want to install dependencies with npm now? Yes

✔  Firebase initialization complete!
$
```

``` bash
$ cat firebase.json
{
  "hosting": {
    "public": "dist",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  },
  "functions": {
    "predeploy": [
      "npm --prefix \"$RESOURCE_DIR\" run lint",
      "npm --prefix \"$RESOURCE_DIR\" run build"
    ]
  }
}
```

Functionsの設定が追加されましたね。よさそうです。


最終的に、ディレクトリ構成は以下のようになりました。

```bash
% tree -a
.
├── .browserslistrc
├── .eslintrc.js
├── .firebaserc
├── .gitignore
├── README.md
├── babel.config.js
├── dist
│   └── index.html
├── firebase.json
├── functions
│   ├── .gitignore
│   ├── package-lock.json
│   ├── package.json
│   ├── src
│   │   └── index.ts
│   ├── tsconfig.json
│   └── tslint.json
├── package-lock.json
├── package.json
├── public
│   ├── favicon.ico
│   └── index.html
└── src
    ├── App.vue
    ├── assets
    │   └── logo.png
    ├── components
    │   └── HelloWorld.vue
    └── main.js

36 directories, 80 files
%
(適度に整形)
```

## ビルド

初回のセットアップである程度ビルドや 外部ライブラリのインストールなどは済んでるんですが、一応 ライブラリのインストールとビルドまでの手順を書きます。


### Firebase Hosting 側

```bash
% pwd
/xxx/xxxx/fb_vue_template
% npm install    ← こちらは外部ライブラリを再度入れるときだけ実施
% npm run build  ← こちらはVue.js側のコードをいじったら毎回実施
% ls -lrt dist
total 24
drwxr-xr-x  3 masatomix  staff    96  7 18 10:12 img
-rw-r--r--  1 masatomix  staff   738  7 18 10:12 index.html
drwxr-xr-x  3 masatomix  staff    96  7 18 10:12 css
-rw-r--r--  1 masatomix  staff  4286  7 18 10:12 favicon.ico
drwxr-xr-x  6 masatomix  staff   192  7 18 10:12 js
```

distディレクトリに ビルドされたファイル群ができました。

### Cloud Functions 側

```bash
$ pwd
/xxx/xxxx/fb_vue_template
$ cd functions
```

ビルドするとき、プロジェクト初期状態の index.ts だと、関数がないためにエラーとなってしまうので、下記の通りコメントを外しておきます。


```bash
$ cat src/index.ts
import * as functions from 'firebase-functions';

// // Start writing Firebase Functions
// // https://firebase.google.com/docs/functions/typescript
//
export const helloWorld = functions.https.onRequest((request, response) => {
 response.send("Hello from Firebase!");
});

$ npm install    ← こちらは外部ライブラリを再度入れるときだけ実施
$ npm run build  ← こちらはfunctionsのtsコードをいじったら毎回実施
$ ls -lrt lib/
total 16
-rw-r--r--  1 xxx  staff  296  3  5 13:39 index.js.map
-rw-r--r--  1 xxx  staff  380  3  5 13:39 index.js
$
$ cd ../
```

こちらは「lib」のディレクトリに ビルドされたファイル群ができました。


## ローカルのWEBサーバ起動

以下のコマンドで、ローカルのWEBサーバ上で起動します。

```bash
$ pwd
/xxx/xxxx/fb_vue_template
$ firebase serve --only hosting,functions

=== Serving from '/xxx/xxxx/fb_vue_template'...

i  functions: Preparing to emulate functions.
i  hosting: Serving hosting files from: dist
✔  hosting: Local server: http://localhost:5000
Warning: You're using Node.js v10.14.2 but Google Cloud Functions only supports v6.11.5.
✔  functions: helloWorld: http://localhost:5001/hoge-xxxx/us-central1/helloWorld

```

ブラウザで、http://localhost:5000 へアクセスすると、先ほどのVue.jsの画面が表示されると思います。またcurlで ``curl http://localhost:5001/hoge-xxxx/us-central1/helloWorld `` とアクセスしてfunctionsを呼び出してみましょう。

```bash
$ curl http://localhost:5001/hoge-xxxx/us-central1/helloWorld
Hello from Firebase!
$
```

OKそうですね。。Ctrl-c で終了しておきましょう

### Functionsの関数達を Hosting のドメイン経由で処理できるようにする

サーバにデプロイしたときのFirebase Hostingのドメイン(URL)と、Cloud Functions が使用するドメインは異なっています。具体的には、

Firebase Hosting: https://hoge-xxxx.firebaseapp.com/
Cloud Functions: https://us-central1-hoge-xxxx.cloudfunctions.net/

とかになったりします。HostingしたWEBアプリからCloud Functions の関数を呼び出すときにドメインが異なっていると、CORSの考慮やURLをフルパス指定しないといけないなど何かとメンドウなので、HostingのURLでアクセス出来るようHostingのリバースプロキシを有効にします。

具体的には以下の通り firebase.json に設定を追加します。


```json:firebase.json
{
  "hosting": {
    "public": "dist",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "/hello",   # /hello にきたら、
        "function": "helloWorld"   #functionsの helloWorld 関数へ転送
      }, 
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  },
  "functions": {
    "predeploy": [
      "npm --prefix \"$RESOURCE_DIR\" run lint",
      "npm --prefix \"$RESOURCE_DIR\" run build"
    ]
  }
}
```

rewrites にリバースプロキシの設定を追加しました。 ``"source":"**"`` の設定よりも先に書いてるのがポイントです(後に書くと、**が優先されちゃう)。

さて、ローカルのWEBサーバをもいちど起動します。

```bash
$ firebase serve --only hosting,functions
```

疎通します。

```bash
$ curl  http://localhost:5000/hello
Hello from Firebase
$
```

これでHostingとCloud Functionsがおなじドメインとなりました。

``"source": "/hello"`` の箇所は ``"source": "/hello/**"`` などとかけるので、必要なパラメタに応じて適宜工夫するのがよさそうです。



## サーバにデプロイする

ココまでできていれば、サーバへのデプロイも簡単です

**--2020/12/16追記--**
いまのバージョンのFirebaseは下記のコマンド``firebase serve`` は非推奨になっているようです。
自分はまだ未確認ですが、いまは ``firebase emulators:start`` になっている模様。

@Uhucream さん
ご指摘ありがとうございました！
**--2020/12/16追記以上--**

```bash
$ pwd
/xxx/xxxx/fb_vue_template
$ firebase deploy --only hosting,functions

... 割愛

✔  functions[helloWorld(us-central1)]: Successful create operation.
Function URL (helloWorld): https://us-central1-hoge-xxxx.cloudfunctions.net/helloWorld
i  hosting[hoge-xxxx]: finalizing version...
✔  hosting[hoge-xxxx]: version finalized
i  hosting[hoge-xxxx]: releasing new version...
✔  hosting[hoge-xxxx]: release complete

✔  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/hoge-xxxx/overview
Hosting URL: https://hoge-xxxx.firebaseapp.com
$
```

表示されたサーバに アクセスしてみましょう。

ブラウザで: https://hoge-xxxx.firebaseapp.com
curlで: https://us-central1-hoge-xxxx.cloudfunctions.net/helloWorld
curlで: https://hoge-xxxx.firebaseapp.com/hello

いままでおなじ結果が得られればOKです。



## ビルド手順まとめ

```bash
$ pwd
/xxx/xxxx/fb_vue_template
```

プロジェクトがココに展開されている想定で、コマンドを整理しておきます。


### Hosting側(Vue.jsのWEBアプリ)

ビルド

```bash
$ cd /xxx/xxxx/fb_vue_template
$ npm run build
```

ローカルWEBサーバ起動(事前にビルド要)

```bash
$ cd /xxx/xxxx/fb_vue_template
$ firebase serve --only hosting
カンマ区切りで、functionsもならべられる
```

デプロイ(事前にビルド要)

```bash
$ cd /xxx/xxxx/fb_vue_template
$ firebase deploy --only hosting
カンマ区切りで、functionsもならべられる
```

### Cloud Functions

ビルド

```bash
$ cd /xxx/xxxx/fb_vue_template
$ cd functions && npm run build && cd ../
```

ローカルWEBサーバ起動(事前にビルド要)

```bash
$ cd /xxx/xxxx/fb_vue_template
$ firebase serve --only functions
```

デプロイ

```bash
$ cd /xxx/xxxx/fb_vue_template
$ firebase deploy --only functions
ちなみにfunctionsは、deploy時に自動で再ビルドが走るので、事前にビルドは不要
```


おつかれさまでした。


## 関連リンク

公式

- [Vue.js](https://jp.vuejs.org/index.html)
- [Firebase Hosting](https://firebase.google.com/docs/hosting/?hl=ja)
- [Cloud Functions for Firebase](https://firebase.google.com/docs/functions/?hl=ja)


いままで書いたVue.js/Firebase関連記事

- [FirebaseとGoogle Cloud Platform をさわれる環境を構築する](https://qiita.com/masatomix/items/0ddbfacb9bc1797044a1) おもに firebase-tools まわり
- [Vue.js の主要な機能をざっくりとつかってみたときのメモ](https://qiita.com/masatomix/items/24142b084b745ce816a9) Vue.jsのとっかかりはこちら
- [Cloud Functions for Firebase をつかってみる。](https://qiita.com/masatomix/items/ee1d7d162c8f1c25df7a) Functionsまわり

- [Vue.js の主要な機能をざっくりとつかってみたときのメモ(Firebase編)](https://qiita.com/masatomix/items/7be14b8eb433edcfbfc7)
- [Vue.js の主要な機能をざっくりとつかってみたときのメモ(Firebase認証・認可編)](https://qiita.com/masatomix/items/5316acdb5980289e5cc2)
- [Cloud Functions で構築したREST APIをFirebase認証で保護する。そして自前RESTサーバのAPIにもFirebase認証を適用する。](https://qiita.com/masatomix/items/b446a07a641f97686a97)
- [Cloud Functions for Firebase の関数をスケジューラから定期的に呼び出す](https://qiita.com/masatomix/items/b40a2c604319e52df4bf)