この記事は「【マイスター・ギルド】本物の Advent Calendar 2020」15日目の記事です。
ちょっと前ですがWindows System for Linux2(WSL2)がリリースされました。これでWindows上でLinuxを動かすことができます。
それともう一つ、WSL2にすることでHomeなWindowsでもDocker Desktop for Windowsを使うことができます。案外これは知らない人もいるかもしれません。
Homeな私はこれまでDocker ToolBoxを使ってましたが、あまり良い体験とは言えず、Dockerに対してのモチベーションがあまりありませんでした。
しかしDocker Desktop for Windowsを使ったところ、非常に使いやすかったので、DockerでFirebase Cloud FunctionsをバックエンドにしたTSアプリを開発する環境を構築してみました。フロントはVue.jsにしてみます。これはそれの結果をまとめた記事になります。
↓はこの記事で説明する環境構築に関係のあるファイルのディレクトリ構成図です。もちろんこれ以外にソースファイルやアプリケーションの設定ファイルがあります。
.
├──app
│ ├──functions
│ │ ├──lib
│ │ ├──node_modules
│ │ ├──.dockerignore
│ │ ├──copy-node_modules.sh
│ │ ├──Dockerfile
│ │ └──package.json
│ │
│ ├──vuejs
│ │ ├──dist
│ │ ├──node_modules
│ │ ├──.dockerignore
│ │ ├──copy-node_module.sh
│ │ ├──Dockerfile
│ │ └──vue.vonfig.js
│ │
│ └──firebase.json
│
├──swagger
│ └──index.yml
│
└──docker-compose.yml
ちなみに今回はたまたまDBを使わないアプリの開発でした。DBを使う場合、FirebaseのCloud Firestoreなんかを使う場合はおそらくこの構成のままですが、もしMySQLなんかが必要になる場合はdb
のコンテナが追加されると思います。
TSアプリの開発がほとんど今回初めてだったのですが、Dockerで開発環境を構築する場合、次の問題に当たりました。
- TSのビルド先をWindowsと共有するとホットリロードが効かない
- node_modulesをWindowsと共有するとビルドがめっちゃ遅い
- *.d.tsがエディタ側に無いと開発時に型の恩恵が受けられない
またFirebaseの次の仕様も頭を悩ませました。
- エミュレーターを立ち上げるとAPIに
プロジェクト名/リージョン名/
のプレフィックスが付く
では、それぞれのコンテナを見ていきます。
firebaseコンテナ
"scripts": {
...
"emulators": "tsc-watch --onFirstSuccess \"firebase emulators:start --token ${FIREBASE_TOKEN}\""
...
},
...
"dependencies": {
...
"firebase-admin": "^9.2.0",
"firebase-functions": "^3.6.1",
...
}
...
"devDependencies": {
...
"tsc-watch": "^4.2.9"
...
}
Firebase CLIのCloud Functions Emulatorsを利用します。tsc-watch
を導入してホットリロードで開発できるようにします。
$ npm run emulators
でホットリロードしつつ開発が行えるエミュレーターが立ち上がります。
...
"hosting": {
"public": "vuejs/dist",
...
"rewrites": [
{
"source": "/get-users",
"function": "get-users"
}
]
},
"emulators": {
"functions": {
"host": "0.0.0.0",
"port": 5001
},
"hosting": {
"host": "0.0.0.0",
"port": 5000
},
"ui": {
"enabled": true,
"host": "0.0.0.0",
"port": 4000
}
}
...
Firebaseの設定です。
hosting
ではまず公開ディレクトリをVue.jsのデフォルトのビルド先に指定しています(public
)。rewrites
では/~
にアクセスすることでCloud Functionsの各関数を呼び出せるようにしています。これはCloud Functions Emulatorsで用意されるAPIのエンドポイントにはプレフィックスにプロジェクト名、リージョン名が付くのですが、SwaggerからAPIを叩くときにこのプレフィックス部分を無視したいからです(Swaggerのymlファイルにプロジェクト名、リージョン名を書きたくない)。APIが増えればここに追記する必要が出てきます。
emulators
ではDocker外からアクセスできるようにhost
を指定しているのと、Cloud Functions EmulatorsのUI画面を有効にし、一番小さいポート番号が割り当たるようにしています。あまり使わないかもですが、これでDocker Desktop for Windowsの「OPEN IN BROWSER」でエミュレーターのダッシュボード的な画面が開くようになります。
volumes:
firebase-node_modules:
firebase-lib:
services:
firebase:
build: ./app/functions
image: firebase
container_name: firebase
ports:
- 4000:4000
- 5000:5000
- 5001:5001
volumes:
- ./app:/app
- firebase-lib:/app/functions/lib
- firebase-node_modules:/app/functions/node_modules
environment:
- FIREBASE_PROJECT
- FIREBASE_TOKEN
node_modulesはホストと共有したくないので、別にvolumesを作ってそこに保持するようにしています。TSのトランスパイル後のコードもホスト側と共有するとホットリロードを妨げるので、同様にしています。
FIREBASE_TOKEN
はログイン不要でFirebase CLIを叩くためのものです。取得方法は https://firebase.google.com/docs/cli?hl=ja#cli-ci-systems にあります。
FROM node:10
RUN apt-get update && \
npm install -g firebase-tools
WORKDIR /app/functions
COPY package*.json ./
CMD npm install \
&& firebase use ${FIREBASE_PROJECT} --token ${FIREBASE_TOKEN} \
&& npm run emulators
EXPOSE 4000 5000 5001
-
firebase-tools
のインストール - npmパッケージのインストール
- Firebaseプロジェクトの設定
- エミュレーター(ホットリロード有)の起動
を行っています。ここでpackage*.json
をコピーしているのは、docker-compose.yml
で書いたvolumesのマウントがDockerfileの内容の処理の後になるので、まだDocker内に無いからです。
node_modules
ホストのnode_modulesは不要なので、.dockerignoreに書いておきます。
#! /bin/bash
SCRIPT_DIR=$(cd $(dirname $0); pwd)
docker cp -a -L firebase:/app/functions/node_modules $SCRIPT_DIR
ホストのVSCodeで開発をするために、Dockerコンテナ内でインストールした.d.ts
ファイルをホスト側にコピーしてくるスクリプトです。docker cp
コマンドで拡張子指定とかできると良いんですが、出来ないのでnode_modulesをまるっと持ってきてます。時間かかります。さらにシンボリックリンク関係と思いますが、管理者権限での実行が必要です。
本当はVSCodeのRemote WSLプラグインでDockerコンテナ内に接続して開発したいんですが、まだPreview版で動作が安定しないのか、私の環境では頻繁にフリーズしてしまいます…。正式版になったら、またトライしたいと思います。これがうまく使えれば、このスクリプトは不要になりますね。
firebaseコンテナを立ち上げると、OPEN IN BROWSERからエミュレーターのUIへアクセスできます。
swaggerコンテナ
services:
# 他の設定...
swagger:
image: swaggerapi/swagger-ui
container_name: swagger
ports:
- 8000:8080
volumes:
- ./swagger:/var/www
environment:
SWAGGER_JSON: /var/www/index.yml
./swagger/index.ymlファイルを、ホスト側8000ポートで見れるようにします。
swaggerコンテナを立ち上げると、OPEN IN BROWSERからSwagger UIへアクセスできます。
このコンテナは単純にindex.ymlを公開しているだけなんで、シンプルですね。
vuejsコンテナ
volumes:
# 他の設定...
vuejs-node_modules:
services:
# 他の設定...
vuejs:
build: ./app/vuejs
image: vuejs
container_name: vuejs
ports:
- 3000:3000
- 3001:3001
volumes:
- ./app:/app
- vuejs-node_modules:/app/vuejs/node_modules
environment:
- CHOKIDAR_USEPOLLING=true
3000ポートをVue CLI UI用、3001ポートをserve用にします。
こちらでもはやりnode_modulesはホストと共有したくないので別volumesで回避します。
環境変数のCHOKIDAR_USEPOLLING
はserveでホットリロードするために必要になるもののようです。
FROM node:10
RUN apt-get update && \
npm install -g @vue/cli
WORKDIR /app/vuejs
COPY package*.json ./
CMD npm install \
&& vue ui --host 0.0.0.0 --port 3000
EXPOSE 3000 3001
Vue CLIをインストールし、vue ui
コマンドのオプションで以下を指定しています
-
--host 0.0.0.0
Dockerコンテナの外からでもWebUIにアクセスできるようにする -
--port 3000
3000ポートでWebUIを立ち上げる
node_modules
こちらもホストのnode_modulesは不要なので、.dockerignoreに書いておきます。
module.exports = {
devServer: {
port: 3001,
host: '0.0.0.0'
}
}
serveで立ち上げるサーバーのオプションをvue ui
と同様な感じで指定します。
#! /bin/bash
SCRIPT_DIR=$(cd $(dirname $0); pwd)
docker cp -a -L vuejs:/app/vuejs/node_modules $SCRIPT_DIR
firebaseコンテナと同じくホストのVSCodeで開発をするために、Dockerコンテナ内でインストールした.d.ts
ファイルをホスト側にコピーしてくるスクリプトです。時間かかります。管理者権限での実行が必要です。
vuejsコンテナを立ち上げると、OPEN IN BROWSER
からVue CLI UIへアクセスできます。
以上、Dockerを使ったFirebase+Vue.js+TSアプリの開発環境構築でした。今後の各ツールのバージョンアップによってここの内容は変わりそうですが、2020/12時点ではこんな感じになりましたという記事でした。Docker便利ですね。