🍣

VSCode+Dev Containerの開発体験をNeovimで再現する

2024/11/25に公開

VSCodeでのDev Container開発体験について

みなさんはDev Containerというものをご存知でしょうか?

Dev Containerは、仕様に基づいたdevcontainer.jsonというJSONファイルを用意しておけば、記載された内容に従ったDockerコンテナを立ち上げてくれるツールやその関連仕様群です。docker-compose/docker-compose.ymlと似ていますが、こちらはより開発環境のセットアップに特化したものです。

これにより開発環境のセットアップ作業が省略でき、環境の再現性も向上するため、複数人での開発で有用です。またOSSプロジェクトでPull Requestを送る側やIssueを報告する側にとっても環境の再現性が上がるので良いですね。筆者は基本的にすべてのプロジェクトでDev Containerを利用しています。

ツールの代表的な実装としてはVSCodeのDev Containers拡張機能が挙げられます。devcontainer.jsonの仕様に基づいてコンテナをビルドし立ち上げてくれ、またVSCodeのリモート開発用のサーバーもコンテナ内に展開してくれるため、ホストマシンで立ち上げたVSCodeからシームレスにコンテナ内環境での開発に切り替えることができて非常に便利です。

Neovimでも使いたいっすよね?

さて、ここで「Dev ContainerをNeovimでも使いたい」と考える方も多いのではないでしょうか。しかし、devcontainer.jsonの成り立ちはVSCodeを前提としているため、基本的にVSCode以外のテキストエディタでの利用は想定されていません。そのため、NeovimからDev Containerを利用するためには少し工夫が必要です。

この記事では、Dev Container CLIを使って、devcontainer.jsonを変更せずにNeovimのセットアップを行う方法を紹介したいと思います。

まず、Dev Container機能の実装としてははVSCodeのDev Containers拡張機能が第一に挙げられますが、Dev Container CLIというCLIツールの実装も存在します。CLIツールを利用することで、ターミナルから直接コンテナのビルドや起動を行うことができます。

また、Dev Containerにはfeaturesという仕組みがあって、拡張機能のような形でコンテナに追加機能をインストールすることができます。ベースとなるDockerfileに手を入れることなく、開発ツールやユーティリティをコンテナに簡単に追加することができます。

利用可能なfeature一覧はAvailable Dev Container Featuresを参考にしてください。featureは誰でも自作し公開することができます。

さらに、Dev Container CLIの--additional-featuresオプションを使うと、devcontainer.jsonに変更を加えず動的にfeatureを追加したコンテナの立ち上げができます。これにより、devcontainer.jsonに特定の開発者のみが利用するfeatureを記述することなく自由にfeatureを追加していくことができます。

さて、感の良い読者であれば既にお気づきかもしれませんが、実は公開されているfeatureにNeovimをセットアップするというものが存在します。そうです、--additional-features
オプションでこのfeatureを指定することで、devcontainer.jsonにNeovimという特定の開発者に依存する設定を追加せずに、NeovimがセットアップされたDev Containerを起動することができます。

以下のようなコマンドを実行してみましょう。

devcontainer up \
  --additional-features='{"ghcr.io/duduribeiro/devcontainer-features/neovim:1": {}}' \
  --mount type=bind,source=~/.config/nvim,target=/nvim-config/nvim \
  --workspace-folder .

このupサブコマンドでは、ghcr.io/duduribeiro/devcontainer-features/neovim:1というNeovimのセットアップ用のfeatureを有効化し、さらにホストマシン上のNeovim設定ファイルをコンテナ内にマウントしています。これにより、普段ホストマシンで使っているNeovim環境をそのままコンテナ内でも使えるようになります。なお、devcontainer.jsonやDockerfileの内容を変更した場合など、コンテナのリビルドが必要な場合は、upコマンドに--remove-existing-containerオプションを付けてください。

次に、起動したコンテナでNeovimを実行するには以下の様にexecサブコマンドを使います。マウントした設定ファイルを使うために、XDG_CONFIG_HOME環境変数を上書きしています。(NeovimはXDG_CONFIG_HOME環境変数が設定されていればこちらの設定パスを用いて読み込み先を決定してくれます)

devcontainer exec \
  --remote-env XDG_CONFIG_HOME=/nvim-config \
  --workspace-folder .\
  nvim

このコマンドを実行することで、Neovimをホストマシンの設定ファイルを使って起動することができます。これで、開発環境に依存せず、ホストマシンで使い慣れたNeovimの設定をそのまま活用できますね。

コマンドのシェルスクリプト化

ここまでの手順を毎回手打ちするのは少し面倒です。そこで、これらの手順を簡単にするためのシェルスクリプトを作成しました。

#!/bin/bash

rebuild_flag=""
if [[ "$1" == "-r" || "$1" == "--rebuild" ]]; then
  rebuild_flag="--remove-existing-container"
fi

# Get the Neovim config path using headless nvim
config_path=$(nvim --headless -c 'lua io.stdout:write(vim.fn.stdpath("config"))' -c 'q' --clean)

# Resolve symlink for the config path
resolved_config_path=$(readlink -f "$config_path")

# Construct the command to run the devcontainer
# TODO: resolve config dir
command="devcontainer up $rebuild_flag \
    --mount type=bind,source=$resolved_config_path,target=/home/code/.config/container-nvim \
    --additional-features='{ \
        \"ghcr.io/duduribeiro/devcontainer-features/neovim:1\": { \"version\": \"stable\" }, \
        \"ghcr.io/devcontainers-community/features/deno\": {}, \
        \"ghcr.io/jungaretti/features/ripgrep:1\": {}, \
        \"ghcr.io/devcontainers/features/node:1\": {}\
    }' \
    --workspace-folder ."

eval "$command"
eval "devcontainer exec --remote-env NVIM_APPNAME=container-nvim --workspace-folder . nvim"

このスクリプトでは、以下のことを実現しています。

  • upexec を一連で実行することで、vimcontainer.shを実行するだけでコンテナの起動からNeovimの立ち上げまでを自動化している
  • ホストマシンのNeovimが実際に解決する設定ファイルのパスを取得し、更にそのパスがシンボリックリンクである場合には実際のパスを解決。それを--mountオプションに渡す様になっている
  • -r または --rebuild オプションを渡すことで、コンテナのリビルドが行われるようにしている
  • Neovimのプラグインから利用したい追加のツールなどもfeature指定でインストールする(deno, ripgrep, nodeなど)

このスクリプトを使うとお手軽にDev Container環境でNeovimを利用できますね。

類似ツールの紹介

devcontainer.vim

devcontainer.vimはGoで実装されたDev Container CLIのラッパーツールです。本記事で紹介したスクリプトと概ね似たようなことを行っています。こちらはNeovimではなくVim用のツールです。(2024/12/02 追記: Issueを立てたところ、Neovimに対応して頂けました)

ただし、devcontainer.vimは--additional-featuresではなく--override-configオプションを利用しています。devcontainer.vim.jsonという追加設定ファイルを用意し、これをdevcontainer.jsonとマージしたファイルを生成した上で--override-configに指定するという実装の様です。そのため、feature機能以外の細かな設定もカスタマイズできるものと思われます。本来--override-configを用いて独自にカスタマイズしたdevcontainer.jsonを読み込ませたい場合、本来のdevcontainer.jsonとの間で共通する設定の二重管理問題が発生してしまうのですが、こちらのツールはdevcontainer.vim.jsondevcontainer.jsonを自動で動的にマージした上で--override-configに渡してくれるためdevcontainer.vim.jsonには差分設定だけ記述すれば良く、二重管理問題を解決しています。

また、Vimのセットアップはfeature機能を介さず自前のシェルコマンド実行で行われ、またVimだけでなくクリップボード周りの小物ツールも同時にセットアップしてくれるという違いがあります。その他、便利なサブコマンド群も用意されています。

NeovimでなくVimを利用している方はこちらのツールを使うのが良いでしょう。

Remote Nvim

こちらはDev Containerに限らず、VSCodeのリモート開発機能でサポートされている接続方法全般を再現しようとしているツールです。そのためDev ContainerではなくSSH接続を用いたリモート開発も行うことができます。

また、Dev Containerの管理にDev Container CLIではなくDevPodを利用している点も興味深い点です。本筋からずれるため簡単な紹介に留めますが、DevPodはdevcontainer.jsonの仕様を開発環境セットアップのための標準DSLとして捉え、開発用コンテナ環境の立ち上げをローカル環境に留まらず任意の展開先プロバイダに対して行うことができるツールとなっています。そのため用意したdevcontainer.jsonに基づいた開発環境をAWS・GCP・任意のSSH接続先など、既存プロバイダを用いて多様な環境へとデプロイすることができます。展開先プロバイダを独自実装することも可能な様です。

DevPodからRemote Nvimの話に戻します。こちらはNeovim用のプラグインとして実装されているため、ホストマシンで立ち上げたNeovimから接続先Dev ContainerのNeovimへと接続する様な形となっており、よりVSCodeにおけるDev Container接続に近い体験が得られます。

ただ、かなり多機能のためか環境依存の不具合もまだ残っている様で、筆者の環境(Apple SiliconプロセッサのMac)では上手く動作しませんでした。Issueとして報告は行っているため、しばらく待っていれば使えるようになるかもしれませんね。

今後の課題

本記事では簡単なシェルスクリプトを紹介しましたが、今後の課題がいくつかあります。

今回のシェルスクリプトで利用しているNeovimセットアップ様のfeatureはARMプロセッサ環境が考慮されていないため、Neovimをソースコードからビルドするfeatureを自作することでARM含めた広範な環境でNeovimのセットアップが動作するようにしたい

  • NeovimだけでなくVimもオプションとして選べるようにしたい
  • Neovim/Vim内部から、「現在Dev Container環境内で実行されているかどうか」を判定できるようにしたい。
    • これはVSCodeと同じ様にステータスライン上に現在の実行環境(ローカルマシン・Dev Container・SSH、etc)を表示したいため。

このあたりの課題を解決した上であらためてツールとしてCLIツールとしてちゃんと書き上げてOSSとして公開・GitHub Releasesでビルド済みバイナリを配布したいと考えています。

Discussion