← Vim研究所

Neovim 0.12の標準プラグイン管理 vim.pack

2026-08-07 公開 ・ 更新: 2026-08-07

Neovimの設定を1から書くとき、これまで最初に書く必要があったのが「プラグインマネージャ自体をインストールするコード」でした。lazy.nvimならvim.fn.systemgit cloneしてruntimepathに足す、あの15行ほどの定型文です。プラグインを1つも入れないうちから他人のツールを取ってくる処理を書かされるのは、設定ファイルの入り口としてかなり歪んでいました。Neovim 0.12で追加されたvim.packはここを丸ごと不要にします。本体に組み込まれているので、init.luaにインストールしたいプラグインを並べるだけで済みます。

検証環境:macOS上のNeovim 0.12.4で確認しました。本文のログ・エラーメッセージ・lockfileの中身はすべて実行結果の実物です。$XDG_CONFIG_HOMEなどを一時ディレクトリに向けた隔離環境で検証しているので、手元の設定を壊さずに同じことを試せます。Windows環境では確認していません。

目次(7項目)
APIこれだけ覚える
vim.pack.add({ ... })プラグインを宣言する。未インストールなら自動で入る
vim.pack.update()更新を取得して確認画面を出す
vim.pack.del({ '名前' })ディスクから削除する
vim.pack.get()管理下のプラグイン一覧を取る

bootstrapが要らなくなった

vim.packはNeovim本体に含まれるLuaモジュールなので、インストール作業がありません。init.luaに次の3行を置いて起動すれば、それだけでプラグインが入ります。

-- ~/.config/nvim/init.lua
vim.pack.add({
  'https://github.com/nvim-lua/plenary.nvim',
})

初回起動時には確認が入ります。手元での実際の表示はこうでした。

These plugins will be installed:

plenary.nvim from https://github.com/nvim-lua/plenary.nvim

vim.pack: 100% Installing plugins (1/1) - plenary.nvim

この直後からrequire('plenary')が通ります。add()はインストールを待ってから次のコードへ進む作りになっているので、add()の下にrequireを書いても順序の問題は起きません。実際に隔離環境でpcall(require, 'plenary')を実行してtrueが返ることを確認しました。

ただし、ヘルプには位置づけがはっきり書かれています。

It is still considered experimental, yet should be stable enough for daily use.

実験的な扱いのまま日常利用に耐える程度には安定している、という表現です。APIが今後変わる可能性は頭に入れておいたほうが安全です。

どこに置かれ、何が記録されるか

インストール先はデータディレクトリ配下のsite/pack/core/opt/で固定です。runtimepathを自分で触る必要はありません。手元では次の場所に展開されました。

~/.local/share/nvim/site/pack/core/opt/plenary.nvim

startではなくoptに入るのがポイントで、vim.pack:packaddを代行する前提の配置です。ランタイムパスの考え方を知っていると、この2つのディレクトリの違いがそのまま効いてきます。

もうひとつの実体がlockfileです。$XDG_CONFIG_HOME/nvim/nvim-pack-lock.jsonに、実際に入ったコミットが記録されます。バージョン指定ありとなしで中身が変わるのが分かりやすいので、両方入れた状態の実物を貼ります。

{
  "plugins": {
    "mini.icons": {
      "rev": "397ed3807e96b59709ef3292f0a3e253d5c1dc0a",
      "src": "https://github.com/echasnovski/mini.icons",
      "version": "0.16.0 - 0.17.0"
    },
    "plenary.nvim": {
      "rev": "74b06c6c75e4eeb3108ec01852001636d85a932b",
      "src": "https://github.com/nvim-lua/plenary.nvim"
    }
  }
}

このファイルは設定ディレクトリ側に置かれるので、init.luaと一緒にGitへ入れるのが前提の設計です。lockfileがある状態で別のマシンに設定を持っていくと、versionから推測するのではなくlockfileのrevそのままでインストールされます。手で編集するファイルではありません。

バージョンを固定する

何も指定しなければ既定ブランチの先頭に追従します。固定したい場合はversionを書きます。指定できるのはブランチ名・タグ・コミットハッシュか、vim.version.range()によるバージョン範囲です。

vim.pack.add({
  {
    src = 'https://github.com/echasnovski/mini.icons',
    version = vim.version.range('0.16'),   -- 0.16.0 以上 0.17.0 未満
  },
  {
    src = 'https://github.com/user/plugin',
    version = 'main',                      -- ブランチ名でもよい
  },
})

vim.version.range('0.16')を指定して実行したところ、タグv0.16.0がチェックアウトされ、lockfileには範囲が"0.16.0 - 0.17.0"として記録されました。

ここで引っかかりやすいのが、バージョン範囲が使えるのはsemver形式のタグを打っているリポジトリだけという点です。ヘルプはv1.2.01.2.0は有効だが1.2v1は無効だと明記しています。試しにsemverタグを持たないプラグインにversion = 'v0.1'を指定したところ、次のように候補まで並べて教えてくれました。

`v0.1` is not a branch/tag/commit. Available:
Tags: compat-nvim-0.6, compat-nvim-0.5
Branches: master, ...

タグの命名がsemverでないプラグインは珍しくないので、固定したいならブランチ名かコミットハッシュを使うことになります。

更新と削除

更新はvim.pack.update()です。引数なしで全部、名前を渡せばそのプラグインだけを対象にできます。実行すると差分の確認用バッファが別タブページで開くので、内容を見てから:writeで確定、:quitで破棄という流れになります。更新したコードを使い始めるには:restartで入れ直します。

:lua vim.pack.update()
:lua vim.pack.update({ 'plenary.nvim' })
:lua vim.pack.update(nil, { offline = true })

3つ目のoffline = trueは通信せずに、いま入っているプラグインの一覧を同じ確認画面で眺めるための書き方です。何を入れていたか忘れたときに便利で、[[]]でプラグイン間を移動できます。

削除はvim.pack.del()に名前を渡します。init.luaからadd()の行を消しただけではディスクに残り続けるので、この一手が必要です。

:lua vim.pack.del({ 'vim-surround' })

手元で実行するとvim.pack: Removed plugin 'vim-surround'と表示され、pack/core/opt/のディレクトリとlockfileの該当エントリが両方消えていることを確認しました。なお、現在のセッションで読み込み済みのプラグインを消そうとすると既定では拒否されます。消したいときは{ force = true }を付けるか、init.luaから外して再起動してから実行します。

短い書き方にはGit側の設定が要る

URLをフルで書くのが冗長に感じたとき、gh:user/pluginのような短縮形を紹介している記事があります。これはvim.packの機能ではなく、GitのinsteadOf設定に依存した書き方です。設定なしで試すと、ghをSSHホスト名だと解釈されて失敗しました。

ssh: Could not resolve hostname gh: nodename nor servname provided, or not known
fatal: Could not read from remote repository.

先にGit側へ置き換えルールを登録しておけば通ります。

git config --global url."https://github.com/".insteadOf "gh:"

この設定を入れてからvim.pack.add({ 'gh:tpope/vim-surround' })を実行すると、問題なくインストールできました。ただしlockfileには"src": "gh:tpope/vim-surround"と短縮形のまま記録されます。設定ファイルを別のマシンへ持っていくときは、そのマシンにも同じGit設定が必要です。Neovimの設定だけ同期していると動かないので、素直にフルURLで書くか、Lua側で組み立てる関数を用意するほうが移植性は高くなります。

local gh = function(x) return 'https://github.com/' .. x end
vim.pack.add({ gh('nvim-lua/plenary.nvim') })

lazy.nvimから移るときに変わること

vim.packはインストールと更新に責任を持つだけの小さな道具で、lazy.nvimが引き受けていた仕事のうち何割かは自分で書く側に戻ってきます。kickstart.nvimやLazyVimのようなテンプレートから入った人ほど差が大きく出ます。

やりたいことlazy.nvimvim.pack
マネージャの導入bootstrapコードが必要不要(本体組み込み)
setup()の呼び出しoptsを書けば自動自分でrequire().setup()を書く
遅延読み込みevent/ft/keysで宣言loadオプションで自分で組む
依存関係の解決dependenciesで宣言並べる順序で表現する
lockfilelazy-lock.jsonnvim-pack-lock.json

遅延読み込みが完全に無いわけではありません。add()loadオプションにfalseを渡すと:packadd!相当になり、plugin/以下を読み込まずにパスだけ通せます。関数を渡せば読み込みのタイミングも自分で決められます。細かい話として、loadの既定値はinit.luaを読んでいる最中はfalse、それ以降はtrueです。つまり通常の設定ファイルに書いたadd()plugin/の読み込みをNeovim本来の起動処理に任せる形になり、起動中に無理やり前倒しされることはありません。

逆に言えば、event = 'VeryLazy'のような宣言的な指定を並べるだけで済んでいた構成をそのまま移植することはできません。プラグインを30個も40個も抱えて起動時間を詰めている人はlazy.nvimに残る判断のほうが妥当ですし、10個前後で「入れたものが素直に読み込まれればいい」という使い方ならvim.packで十分足ります。

使ってみて

検証用のNeovim設定をゼロから作るとき、これまでは他のdotfilesからbootstrapの15行をコピーしてくるところから始めていました。それがvim.pack.addの3行に置き換わったのは想像以上に気分が違います。一方で最初にやらかしたのが、init.luaからadd()の行を消せばプラグインも消えると思い込んでいたことでした。ディスクには残り続けるのでvim.pack.del()が要ります。あとgh:の短縮形は、他の人の設定を真似して書いてしばらく原因が分からず、エラーにssh:と出ているのを見てようやくGit設定の話だと気づきました。

まとめ

vim.packはNeovim 0.12から本体に入ったプラグインマネージャで、init.luavim.pack.add()を書くだけでインストールまで済みます。プラグインはsite/pack/core/opt/に置かれ、実際に入ったコミットは設定ディレクトリのnvim-pack-lock.jsonに記録されるので、これをGitに入れておけば別のマシンでも同じ状態を再現できます。バージョン範囲はsemverタグを打っているリポジトリでしか使えない点と、init.luaから消してもディスクには残る点だけ押さえておけば、日常の追加・更新・削除はaddupdatedelの3つで回ります。Neovimそのものの位置づけから確認したい場合はそちらもどうぞ。

← Vim研究所 トップへ戻る