まえがき
"テイキョウドットコム"の裏側のCMSをFirebaseで作った話.
しばらくフレームワークとしてNuxt.jsを使っていたものの,lang="ts"
にしようとしていろいろ躓いたりして面倒だったので,流行り(?)の@vue/cli
に全部任せることにした.もうSSRはいいかな.
でもクライアントもサーバ(Firebase Functions)もTypeScriptで書けたらいいじゃん.良いじゃん(断定)
そのうちNuxt.js+TypeScriptちゃんと研究しよ.
Nuxt.jsすき.
Firebase
まずfirebase-toolsで
$ firebase init
今回はHosting, Functions, Firestore, Authenticationを使う
ディレクトリ構造はこう
~/
|- functions/ --- Functionsのコード
| |- src/ --- .tsファイル群
| |- lib/ --- コンパイル済み.jsファイル群
| |- package.json
| ...
|- src/ --- クライアント側のコード
| |- common/
| |- site_name/ --- vue cliで作るディレクトリ
| | |- package.json
| | ...
| `- site_name_2/ --- vue cliで作るディレクトリ
| |- package.json
| ...
|- public/ --- クライアント側ビルド済みファイル
| |- site_name/
| `- site_name_2/
|- .firebaserc
|- firebase.json
|- package.json
...
Hosting
表側と裏側の2サイトを用意するので従量プランにアップグレード.
その上で"複数のサイトでプロジェクトのリソースを共有する"の通りに,Hostingサイトと~/public/site_name
を関連付ける.
また,SPAの場合すべてのURLリクエストをindex.htmlに飛ばす必要がある."Hosting 動作をカスタマイズする"の"リライト"の項など参考に各target毎に設定を行う.
こんな感じ.
"hosting": [ {
"target": "target_name",
"public": "public/site_name",
"rewrites": [ {
"source": "**",
"destination": "/index.html"
} ]
} ],
vue cliの項に続く.
Authentication
方式はとりあえずメール+パスワード認証にした.あとでGoogleアカウントにしよ.
Firebase Consoleからユーザを作成(CMSを利用するのは限定人数なので)
実際に使えるメールアドレスと,仮の適当なパスワードで作る.
パスワード再設定用メール送信で実際のパスワードを設定させると同時にメール認証が完了する.
Firestore
Firebase Consoleから作成しておく.ロックモードでよい.
Firestoreのreadは全員許可,writeを前項で作ったユーザのみに限定する.
ルールを以下のようにする.uidはユーザを作った時に発行されるやつ.
複数許可したり,emailで絞ることもできる.詳しくはドキュメント参照されたし.
...
allow read;
allow write:
if request.auth.token.email_verified
&& request.auth.uid in [
'XXXXXXXXXXXXXXXXXXXXXXXXXXXX',
'YYYYYYYYYYYYYYYYYYYYYYYYYYYY'
];
...
複数fieldを使ったソートのクエリを使う必要があったので,indexを作成しておく.
Functions
functionいっぱい作ってREST APIみたいにするとそれぞれのfunctionの起動に時間がかかるっぽいので、1つのfunctionにexpressを載せる構成にした。
expressのようなFunctionsだけの依存ライブラリは~/functions/package.json
にインストールしよう.
Vue
$ vue create site_name
TypeScriptを使う設定にした.その他お好み.
これをサイト数ぶん作る.
pugを使う場合は追加で
$ npm i -D pug pug-plain-loader
Firebase Consoleのoverviewにある"アプリにFirebaseを追加して利用を開始しましょう"から,initializaAppするために必要な値を確認し,.env
を以下のように設定.
.env
の仕様の詳細はドキュメント参照されたし
VUE_APP_FIREBASE_FUNCTIONS_URL=https://region-name-project-id.cloudfunctions.net/
VUE_APP_FIREBASE_API_KEY=xxx
VUE_APP_FIREBASE_AUTH_DOMAIN=project-id.firebaseapp.com
VUE_APP_FIREBASE_DATABASE_URL=https://project-id.firebaseio.com
VUE_APP_FIREBASE_PROJECT_ID=project-id
VUE_APP_FIREBASE_STORAGE_BUCKET=project-id.appspot.com
VUE_APP_FIREBASE_MESSAGING_SENDER_ID=xxx
...
VUE_APP_FIREBASE_FUNCTIONS_URL=http://localhost:5001/project-id/us-central1/
おなじ
...
$ firebase serve
でローカルで動かすとき,Firestoreだけはローカルで立たないので,.env.development
では予め用意したstaging用のFirebaseプロジェクトを使うようにすると◎.(その場合Firestoreだけ使うのでstaging用プロジェクトの料金プランアップグレードは不要)
.env
で設定したもののうちVUE_APP_
で始まるものは以下のようにクライアントサイドのコードで呼び出せるのでそうなっている.
headのmetaでcanonical urlが欲しいときはVUE_APP_PUBLIC_PATH
など設定しておくと◎.
そして最後にこう
const config = {
apiKey: process.env.VUE_APP_FIREBASE_API_KEY,
authDomain: process.env.VUE_APP_FIREBASE_AUTH_DOMAIN,
databaseURL: process.env.VUE_APP_FIREBASE_DATABASE_URL,
projectId: process.env.VUE_APP_FIREBASE_PROJECT_ID,
storageBucket: process.env.VUE_APP_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.VUE_APP_FIREBASE_MESSAGING_SENDER_ID,
};
firebase.initializeApp(config);
import axios from 'axios';
export default axios.create({
baseURL: process.env.VUE_APP_FIREBASE_FUNCTIONS_URL,
});
Build, Serve, Deploy
npm scriptだけ列挙する
"build": "tsc",
"watch": "tsc -w",
"build": "vue-cli-service build --dest=../../public/site_name --mode=development",
"watch": "vue-cli-service build --dest=../../public/site_name --mode=development --watch",
"prod": "vue-cli-service build --dest=../../public/site_name --mode=production",
"build": "run-s build:*",
"build:functions": "npm --prefix=functions run build",
"build:site_name": "npm --prefix=src/site_name run build",
"build:site_name_2": "npm --prefix=src/site_name_2 run build",
"serve": "firebase serve --project project-id-staging",
"prod:site_name": "npm --prefix=src/site_name run prod",
"prod:site_name_2": "npm --prefix=src/site_name_2 run prod",
"predeploy": "run-s clean prod:*",
"deploy": "firebase deploy --project project-id",
"clean": "rm -r public || true",
開発中はnpm run watch
を各package.jsonの位置で実行する.またはルートでnpm run build
.
その後ルートでnpm run serve
でFirebase Hostingがローカルで動く.
npm run deploy
で全部productionビルドしてデプロイする.Functionsはfirebase-toolsが勝手にビルドします.
npm run deploy -- --only functions
とかも可能.詳しくはfirebase-toolsのドキュメント参照されたし
run-s
はnpm-run-all
パッケージ
蛇足
alias vs symlink
複数サイト間やVue・Functions間でinterfaceの定義を共有したかったりする.
tsconfig.jsonのpathsで"@common/*": ["../../comon/*"]
とするか,
シンボリックリンクln -s ../../common ./common
とするか,
どっちがいいんだろうか?
どちらにせよ実体がtsconfig.jsonやtslint.json/eslint.jsonの外側ディレクトリにあるので,
lintはかからないしwatchしてくれない・・・?
VSCode
プロジェクトルートから見てtsconfigが複数あるので,paths解決やインテリセンス等々を上手にやってくれない.
tsconfigの位置毎にVSCodeを複窓して書いてるけどめんどくさいぞ!
いい方法教えてください.
type safeなstore
Vuex+TypeScript問題に足突っ込む