Windows + NeovimでMarp CLIのwatchが途中から効かなくなる問題を解決した
現象について
Windows 11 上で Marp CLI の watch モード(-w)を使い、Neovim で保存するたびに HTML を自動生成しようとしたところ、最初の1回は変換されるものの、その後は保存しても HTML が更新されなくなってしまいました。その解消法を記録しておきます。なお、環境は以下の通りです。
- Windows 11 Pro 64bit
- WezTerm
- Nushell 0.116.0
- Neovim
- Node.js
- Marp CLI 4.5.1(
@marp-team/marp-core4.4.0)
Marp CLI は npm のグローバル環境にインストールしています。
marp --version
@marp-team/marp-cli v4.5.1 (w/ @marp-team/marp-core v4.4.0)
調査の経緯を書いているので、解決方法だけ知りたい方はこちらまで飛んでください。
本来の動作
次のように watch モードを起動します。
marp -w test.md
起動時に変換が行われ、
[ INFO ] Converting 1 markdown...
[ INFO ] test.md => test.html
[ INFO ] [Watch mode] Start watching...
となります。あとは Neovim で :w するたびに
[ INFO ] test.md => test.html
と表示されて test.html が更新されるはずです。
ところが、私の環境では1回目の保存以降、test.html が更新されなくなりました。
原因の切り分け
PowerShell では動く
調査中、PowerShell から同じコマンドを実行すると、保存のたびに正常に更新されることが分かりました。WezTerm 上でも、
- WezTerm + PowerShell → 正常
- WezTerm + Nushell → 問題発生
という結果です。「Nushell が原因か?」と疑いました。
marp.cmd を疑う
marp の解決先が Nushell と PowerShell で違っていました。
Nushell:
which marp
C:\Users\ユーザー名\AppData\Roaming\npm\marp.cmd
PowerShell:
Get-Command marp | Format-List Source,Path,CommandType
こちらでは npm が生成した marp.ps1 が使われていました。そこで marp.cmd を経由しないよう、Node.js から直接起動してみました。
node C:\Users\ユーザー名\AppData\Roaming\npm\node_modules\@marp-team\marp-cli\marp-cli.js -w test.md
しかし、これでも症状は変わらず、.cmd だけが原因とは言えないことが分かりました。
Chokidar 単体で確認する
Marp CLI はファイル監視に Chokidar を使っています。Nushell 上で Node.js のファイル監視自体が動いていないのでは、と考え、Chokidar だけでテストしました。
npm install chokidar
watch-test.mjs
import chokidar from "chokidar";
console.log("watch start");
chokidar.watch("test.md").on("all", (event, path) => {
console.log(new Date().toISOString(), event, path);
});
node watch-test.mjs
Neovim で test.md を保存すると、Nushell でも PowerShell でも change が検出されました。これで「Nushell 上で Node.js のファイル監視が動かない」という仮説は否定されました。
Marp CLI のデバッグログを見る
Marp CLI が実際にどんなイベントを受け取っているのかを確認するため、デバッグログを出します。Nushell では次のように環境変数を設定します。
$env.DEBUG = "marp-cli:*"
marp -w test.md
すると、Chokidar で監視を始めていることが確認できます。
marp-cli:watcher Start watching with chokidar:
[
[
'test.md',
'...themes\\tech-network.css'
],
{ ignoreInitial: true }
]
1回目の保存は正常でした。
marp-cli:watcher Chokidar event: [change] test.md
[ INFO ] test.md => test.html
ところが、2回目の保存では、
marp-cli:watcher Chokidar event: [unlink] test.md
となり、その後は何度保存してもイベント自体が発生しなくなりました。「change ではなく unlink が来ている」。これが大きな手掛かりでした。
原因
問題が起きていたときの Neovim のオプションを確認すると、次の状態でした。
nobackup
writebackup
backupcopy=auto
nobackup なのでバックアップは無効だと思い込んでいたのですが、backup と writebackup は別のオプションです。writebackup が有効だと、保存時にバックアップを伴う書き込み処理が行われます。
今回の環境では、この保存処理によって、Chokidar から test.md が change ではなく unlink として観測されることがあり、その後は変更を検出しなくなっていました。流れとしては次のとおりです。
Neovim :w(1回目)
↓
Chokidar: change test.md
↓
Marp が HTML を再生成
↓
Neovim :w(2回目)
↓
Chokidar: unlink test.md
↓
以降、test.md の変更を検出しない
解決方法
Neovim の設定を次のように変更します。
init.lua
vim.opt.backup = false
vim.opt.writebackup = false
vim.opt.backupcopy = "yes"
Vimscript の場合はこちらです。
set nobackup
set nowritebackup
set backupcopy=yes
Marp CLI を再起動して、何度か :w してみます。
marp -w test.md
[ INFO ] [Watch mode] Start watching...
marp-cli:watcher Chokidar event: [change] test.md
[ INFO ] test.md => test.html
marp-cli:watcher Chokidar event: [change] test.md
[ INFO ] test.md => test.html
(以下、5回連続で change として検出)
5回連続ですべて change として検出され、HTML も毎回更新されました。Marp CLI 側に wrapper などを用意する必要はなく、marp -w test.md のままで動作しています。
なお、私の環境では backupcopy=yes だけでは直らず、nowritebackup まで設定して初めて安定しました。
補足
設定の確認方法
Neovim 側の現在値は次のコマンドで確認できます。
:set backup?
:set writebackup?
:set backupcopy?
同じ症状が出たときの調査方法
Marp CLI の watch が途中で止まるときは、デバッグログを見ると原因を絞り込みやすいです。
Nushell:
$env.DEBUG = "marp-cli:*"
marp -w test.md
PowerShell:
$env:DEBUG = "marp-cli:*"
marp -w test.md
正常なら保存のたびに [change] test.md が出ます。[unlink] test.md が出たあとに反応しなくなる場合は、エディタの保存方式を疑ってみてください。
調査が終わったら、Nushell の DEBUG は次のコマンドで解除できます。
hide-env DEBUG
今回の教訓
見た目は「Nushell だと Marp CLI の watch が動かない」という症状でしたが、実際はシェルでも監視ツールでもなく、エディタが保存時にファイルをどう書き換えるかが原因でした。ファイル監視系のツールが保存後に反応しなくなったら、エディタ側の保存方式も確認してみてください。