Firebase + Express + Pugで動的ページをlocalhostでホスティングする


 この記事の前提

  • node, npm, yarnのインストールは終わっているものとします。 まだの人はこちらのページをどうぞ(yarnをインストールする)
  • Firebaseアカウント、およびホスティングプロジェクトの作成が完了しているものとします。 まだのひとはこちらのページをどうぞ(Firebaseのホームページ)

この記事の目的 

  • 既存のExpressプロジェクトを手間をかけずにFirebaseで動かしたい
  • ローカルPCでFirebaseをテストする環境がほしい
  • テンプレートエンジンにはPugを使いたい

Firebaseプロジェクトの準備

$ npm install --save firebase-functions@latest firebase-admin
$ npm install -g firebase-tools
$
$ mkdir 'あなたのプロジェクト用フォルダ名'
$ cd 'あなたのプロジェクト用フォルダ名'
$ firebase init

「firebase init」の後の最初の質問で「Functions」と「Hosting」にチェックを入れておけば、後の質問はすべてディフォルトのままスキップしてよい。
するとこのようなフォルダ構成ができる。

├── firebase.json
├── functions
│   ├── index.js
│   ├── node_modules
│   └── package.json
└── public
    ├── 404.html
    └── index.html

public直下のindex.htmlは不要なので削除し、pugファイルから参照するjavascriptやimage、stylesheetを格納するためのフォルダを作成した。既存のExpressプロジェクトを移植するのであれば、既存のpublicの構成をそのままコピーすればよい。
ただし、pugファイルを格納するviewsフォルダはfunctionsフォルダ内におくので、そこだけ注意。こちらも移植目的であれば、viewsフォルダ構成をそのままコピーすればよい。

$ rm public/index.html
$
$ mkdir public/javascript
$ mkdir public/image
$ mkdir public/stylesheet
$
$ mkdir functions/views

仕上がったフォルダ構成はこんな感じ。

├── firebase.json
├── functions
│   ├── index.js
│   ├── node_modules [408 entries exceeds filelimit, not opening dir]
│   ├── package.json
│   └── views
└── public
    ├── 404.html
    ├── image
    ├── javascript
    └── stylesheet

ExpressとPugサポートの追加

$ cd functions
$
$ yarn add express
$ yarn add pug

npmパッケージの追加は、Functionsフォルダ内で「yarn add <追加したいパッケージ名>」でOK。

Expressルーティングコードの記述

functions/index.js
const functions = require("firebase-functions")
const express = require("express")
const path = require("path")

// Express Appを準備
const app = express()

// ExpressのView EngineにPUGを指定
// pugファイルを格納するフォルダ「views」を宣言
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

// Expressルーティングルールの設定
// サイトルートへのリクエスト時にはindex.pugをレンダリングするように指定
app.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});


// CloudFunctionへのリクエストをExpressに引き継ぐためのコード
const api = functions.https.onRequest(app)
module.exports = {
  api
}

functions直下のindex.jsを上記のように書き換える。
最下部でCloudFunctionへのリクエストをExpressに引き継いでいるコード以外は、見慣れたExpressのスタイルだと思う。
ちなみにmodule.exportしている「api」はCloudFunction名前なので、好きな名前をつけてもらってよい。

とりあえずのPUGファイル

functions/views/index.pug
doctype html
html
  head
    title=  title
  body
    | Express on Firebase with PUG view engineの世界へようこそ

index.pugを作成し、先ほど作成したviewsフォルダに放り込んでおく。

静的ホスティングへのリクエストをCloudFunctionに送る

firebase.json
{
  "hosting": {
    "public": "public",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    //rewriteプロパティを追加する
    "rewrites": [ {
      "source": "**", "function": "api"
    } ]
  }
}

自動作成されたfirebase.jsonにrewriteのプロパティを追加する。
rewriteは静的ホスティングに向けられたリクエストをindex.jsで作成したCloudFunctionに引き渡すために記述している。

Firebaseサーバーをローカルで起動する

$ firebase login
$
$ firebase use --add
$
$ firebase serve

firebase loginでログインページに飛ばされるので、そこでログインしておく。
firebase use --addとすると、事前に作成しておいたホスティングプロジェクト名が表示されるので、それを選択する。(ここで選択した内容は、.firebasercに自動的に書き込まれるので興味がある人はご確認を)
最後のfirebase serveでローカルサーバーが起動する

$ === Serving from '<プロジェクトのローカルパス>'...
$ 
$ i  functions: Preparing to emulate functions.
$ i  hosting[<あなたのプロジェクト名>]: Serving hosting files from: public
$ ✔  hosting[<あなたのプロジェクト名>]: Local server: http://localhost:5000
$ Warning: You're using Node.js v7.2.0 but Google Cloud Functions only supports v6.11.5.
$ ✔  functions: api: http://localhost:5001/<あなたのプロジェクト名>/us-central1/api

Firebaseがサポートするバージョンと異なるnodejsがローカルで動作しているとwarningがでるので、気になる人はFirebase用に古いnodejsをインストールして、切り替えて使えばよいと思う。

ブラウザからアクセス

ブラウザからアクセスするとこんな感じだ。

まとめ

Firebaseでは、ExpressやPUGを含む動的コンテンツのルートはfunctionsフォルダとなり、静的コンテンツのルートはpublicとなる。
つまりサーバ側はfunctionsフォルダ内、クライアント側のスタイルシートやJavaScript、イメージファイルなどはpublicフォルダ内で賄う作りとなっている。そこさえ抑えてしまえば、今回の組み合わせ以外でも簡単に組み込めてしまうようだ。