読み飛ばしてください
みなさまどうも。
限界派遣SESと言われて心が折れるnikawamikanです。
最近、学生さんと一緒になんやかんや開発することがあり、その中で使ってみてよかった技術の中にDev Cointanersと言われる技術があります。
VSCode限定ではありますが、開発環境の差異を可能な限り埋めてくれるスゴイやつです。
さらに言えば新たにチームに参加するメンバーに開発環境の構築を逐一説明する必要もなくなるので、入れ替わりの激しい限界派遣SESにこそ使う技術です。
本題
前提として以下の環境はインストールされているものとします。
- Docker
- docker compose (WindowsやMacの場合DockerDesktopをインストールしているのでインストール不要のはずです)
- VSCode
OSは上記がインストールできるのであればわりとなんでもOKだと思います(例外はどこにでもあるので断言はしません)
Dev Containersで環境構築を行うというのはどういうことか?
例えば私のgithubにあるdiscord BotのテンプレートではPycordというdiscord.pyのフォークされたライブラリを利用して開発を行うテンプレートを定義しているのですが、これを使うことでDockerとVSCodeがインストールされた環境であれば特定のLinuxディストリビューションにPythonの指定バージョンのインストールやライブラリの指定バージョンのインストールなどがDockerの仮想環境上に構築されPythonに必要なVSCodeの拡張機能のインストールまでが自動で行われます。
つまり、すでに存在しているrepositoryから環境構築にかかる時間はせいぜいgit clone
+ コンテナをビルドする時間程度で実際に行う作業はほとんどありません。
あとは数行のコードを記述すればBOTを実行することも可能ですし。第三者のコントリビューターが環境構築をする手間もかなり省けると言えます。
Dev Containersで環境構築をしていく
実際に環境構築をしていきましょう。
VSCodeにDev Containersをインストールします。
この拡張機能をインストールすることで前提となるRemote - SSHのインストールも行われるはずです。
次にDev Containerの設定フォルダを作成します。(この時のディレクトリは適宜プロジェクトごとに設定するなどしてください)
これはVSCode左下にあるリモート ウィンドウを開きます
と表示されているアイコンをクリックし、開発コンテナー構成ファイルを追加
をクリックすることで対話式のプロンプトが開始されます。
今回はサンプルとしてNode.js & TypeScript
の環境を構築していきます。
Apple SiliconのMacでは一部のバージョンでバグが存在しているようなので今回は18-bullseys
を選択します。
すると.devcontainer
というフォルダにdevcontainer.json
というファイルが作成されています。
また右下にコンテナーで再度開くという通知が出てることが確認できます。
通知は消えてしまうと分からなくなるので、とりあえず見逃してしまったら左下のリモートウィンドウのアイコンをクリックしコンテナーで再度開く
を選択します。
するとDockerのimageが展開され仮想環境にSSH接続された状態と同じ状態が再現されます。
これでひとまず、ローカル環境を汚さずにNodeの実行環境が用意できました。
Next.jsプロジェクトの作成
このNode環境に対してNext.jsのプロジェクトを作成してみます。
手順は一般的なNext.jsの構築と変わりません。
npx create-next-app@latest
のコマンドを実行し対話式のプロンプトに対し適宜入力をしてください。
http://localhost:3000
をブラウザで開くと実行されていることが確認できます。
これはRemote SSHの拡張機能により自動的にコンテナ内のPortとlocalhostのPortが紐付けられるためです。
これによりほぼローカルの環境と変わりなく作業ができることが確認できたかと思います。
拡張機能のインストール
このDev Containersを利用して開発環境を作成すると拡張機能が入っていない状態となります。
これはRemote SSHでサーバー機に接続した状態と一緒といえます。
つまり、コンテナ内に対してSSH接続を行い、VSCode Serverをインストールしている状態となっていると同じといえます。
そのため、拡張機能を新たにインストールする必要があります。
メンドクサイと思ったかもしれませんが、これは余計な拡張機能が入らずクリーンな状態で環境が分けられていると考えると喜ばしいとも言えます。
また、チーム内で共通の拡張機能を使って設定も共有することができます。
実際に拡張機能をインストールしていきます。
共通で利用する拡張機能のインストール
今回のプロジェクトではTailwind CSSを利用していくため、拡張機能としてTailwind CSS IntelliSenseをインストールします。
この時、ここにインストールしただけではdevcontainer.json
に反映されないため、歯車アイコンからdevcontainer.jsonに追加
を選択し、devcontainer.json
へ記述します。
するとextensions
の項目が追加されていることが確認できます。
devcontainer.json
など、.devcontainer
配下にあるファイルはDockerの環境の設定ファイルなので、変更時は再ビルドするのが好ましいです。(拡張機能のインストールではすでにインストールされている状態だと思うので必要ありませんが)
リビルドは左下のリモート ウィンドウアイコンをクリックしてコンテナーのリビルド
を実行します。
また、tailwindを利用する際は一部で警告が発生するため、これを解消するために.vscode
のsettings.json
に以下の記述を追記します。
{
"files.associations": {
"*.css": "tailwindcss"
}
}
個人でのみ利用する拡張機能のインストール
逆に個人でのみしか利用しない拡張機能をdevcontainer.json
に記述するのは好ましくありません。
なぜなら誰もがCopilotを使えるわけでもありませんし、nyan cat
プラグインは複数の種類があり好みがあるからです。
しかし、これらを毎回インストールするのはメンドクサイですし、コンテナをリビルドする度に行う必要があるので、軽く賽の河原の石積みを思わせる苦行を感じることになります。
これを回避するためにDefault Extentions
を設定します。
設定画面の検索欄にdevcontainer default
など入力すればすぐに出てきます。
拡張機能の歯車アイコンから拡張機能IDのコピー
をし、IDを貼り付けて追加します。
これにより、あなたの賽の河原に費やす時間を節約できます。
ここまでで同じ環境構築複数人で行う不毛な状況を回避することができるようになりました。(DockerとVSCodeのインストールは結局する必要がありますが)
Node.js特有の注意点など
これはNode.js環境特有の問題です。
ただし、docker composeを利用する方法なども掲載するため必要な場合はそれ以外でも参考にしてください。
この環境から開発をしようとすると以下の問題が発生します。
- サーバーの起動に時間がかかる
- ホットリロードができない
これらについて一つずつ解説します。
サーバーの起動に時間がかかる
これはnode_modules
の保存先がローカルにあり、それをDockerのコンテナに対してbindする形になっているのが原因です。
そのため、node_modules
のフォルダをDockerのVolumeを利用して管理するように変更します。
先ほど作った.devcontainer
に対してcompose.yaml
を作成し以下のようにします。
.devcontainer
┣━ compose.yaml
┗━ devcontainer.json
このcompose.yaml
ではdevcontainer.json
に記述されているimageを利用し、volumesの記述などをします。
具体的には以下のようなコードになります。
volumes:
node_modules:
services:
main:
# Docker imageの指定
image: mcr.microsoft.com/devcontainers/typescript-node:1-18-bullseye
volumes:
# ローカルのワークスペースをコンテナのワークスペースにマウント
- ..:/workspace/app
# volumeのnode_modulesをコンテナのnode_modulesにマウント
- node_modules:/workspace/app/node_modules
# ttyとstdin_openをtrueにするのはbuild時を失敗しないため(ないとすぐ閉じちゃう)
tty: true
stdin_open: true
次にこれを利用してDev Containerを構築するために.devcontainer
ファイルを編集します。
このjsonファイルはコメントが許容されているため必要であればコメントを記述するとわかりやすくなります。
{
"name": "my project",
// docker composeのファイル名
"dockerComposeFile": "compose.yaml",
// アタッチを行うdocker composeのサービス名
"service": "main",
// workspaceをバインドするディレクトリ
"workspaceFolder": "/workspace/app",
// node_modulesのディレクトリの所有者をnodeユーザーに変更してからyarnを実行する
"postCreateCommand": "sudo chown -R node:node ./node_modules && yarn",
// リモートユーザーをnodeユーザーに変更する
"remoteUser": "node",
// 拡張機能のインストール
"customizations": {
"vscode": {
"extensions": [
"bradlc.vscode-tailwindcss"
]
}
}
}
この状態でコンテナのリビルドを行い再度開発コンテナにアタッチします。
この時、プロジェクトのPathに2byte文字が含まれているとcompose.yaml
をbuildするときにエラーとなるため日本語などは含めないようにしてください。
以下のようにビルドに成功していればnode_modulesがDockerのVolume側に作成されている状態となります。
ホットリロードができない
これはNext.jsだけでなくvueでも同じような現象を確認しています。
不便極まりないのでそれぞれ解決策を提示します。
Next.js の場合
package.json
ファイルにあるscripts
の項目のdev
コマンドにWATCHPACK_POLLING=true
の変数を追加し対応します。
これによりホットリロードが行われるようになり、幸せになれます。
{
"name": "my-app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "WATCHPACK_POLLING=true next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
}
// 中略
}
vueの場合
vue.config.js
ファイルのdefineConfig
に以下の設定を追加することでホットリロードが可能になります。
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
configureWebpack: {
watchOptions :{
aggregateTimeout: 300,
poll: 1000
}
},
})
最後に
これにてDev Containers布教は終了です。
実際、私が現在新規でWebアプリやコンソールアプリケーションの環境構築を行っていく中でほとんどの場合Dev Containersを利用して作成しています。
これにより、開発環境の構築のような本質とは無関係な部分でのコンフリクトが起こりにくくなり、円滑なコミュニケーションをとることができ満足しています。
皆さんも開発環境をスキップして快適な開発生活をスタートしましょう!