Umi Uyuraのブログ

プログラミング関連の作業ログ

WindowsのNode.jsバージョン管理をNodistからfnmに変えてみる

これまでWindowsでNode.jsのバージョン管理はNodistを使っていましたが、改めて環境構築を構築をしていくにあたってMicrosoftのドキュメントを眺めていたところ、Nodist以外にもそういったツールがあることを知りました。

WSL2(Linux)向けの方で掲載されているパッケージ管理ツールのほうが多いですが、その中のいくつかはマルチプラットフォームで、ネイティブWindowsでも使えそうなものもあります。

2020 年ではもう使えない Nodist はアンインストールする (Windows)

この記事に触発されたというわけでもないのですが、最近WSLを触り始めたこともあり、Windows/Mac/Linux(WSL)で同じように使えるツールにしておけると迷うことも少ないかなと思い、Nodistから乗り換えてみることにしました。

マルチプラットフォームなNode.jsバージョン管理ツール

Windowsをサポートしているマルチプラットフォームなものをピックアップしてみました。

それぞれのリポジトリや公式サイトを眺めてみての個人的に気になった点を挙げてみました。

名前 対応プラットフォーム ポイント
nvm-windows Windows nvmWindows版、なのかと思いきやが互換性があるわけではないみたい
・Go製
・自動バージョン切り替えはない
nvs Windows/Mac/Linux VSCode連携あり
コマンドプロンプトのサポートが弱い(PowerShellだと自動バージョン切り替えができる)
・wingetでインストールできる
Volta Windows/Mac/Linux ・Rust製、速度重視
・他のツールと若干使い方が違う印象
・package.jsonに使うNodeのバージョンなどを記録する
・wingetでインストールできる
fnm Windows/Mac/Linux ・Rust製、こちらも速度重視
・自動バージョン切り替えに対応
(番外)asdf-nodejs Mac/Linux asdf公式
・自動バージョン切り替えに対応

Nodistも備えていましたが、プロジェクトディレクトリにある .node-version.nvmrc などを参照して使うNodeのバージョンを切り替えてくれる機能は欲しいところ。

Linux(WSL)とMacだけで考えれば asdf公式のNode.jsプラグイン が自動切り替えにも対応していて良さそうではありますが、Windowsでも使えるものからとなると、nvs、Volta、fnmあたりが選択肢になりそうです。

(nvm-windowsは紛らわしいですが、本家nvmのクローンということではないらしい。コマンドも微妙に使い方が違っていたりと今回の目的に合わないため選外)

その中でnvsとVoltaについては以下のような点が自分的に気になったので、今回はfnmを使ってみることにしました。

  • nvsコマンドプロンプト主体で考えると切り替え機能がサポートされない
  • Voltaは他のツールと違ってpackage.jsonにバージョンを記録する(業務利用を考えたときに、自分の会社ではそのあたりのツールが統一されているわけではないため、自分のためだけに編集はしにくい)

Nodistをアンインストールする

まずは既存環境のNodistを削除する手順で、この点については先のNodistアンインストールの記事がとても参考になりました。

2020 年ではもう使えない Nodist はアンインストールする (Windows)

以下、その記事の手順をなぞったときのメモ。

  • Nodistのアンインストール
    • 記事ではコントロールパネルから実施していましたが、私はChocolateyでインストールしていたので、 choco uninstall nodist で削除しました
  • Nodistインストール先フォルダの削除
    • 確かに残っていて、npmの各バージョンが残っていたと1GB近くありました
  • npm-cacheフォルダの削除
    • これも残っていて、3.5GBもありました…
  • .npmrcの削除
    • これについては、私の環境ではNodistに関するもの以外の設定もあったので、該当行のみ削除してファイル自体は残しました

fnm (Fast Node Manager)をインストールする

インストールは簡単で、 Releases · Schniz/fnm から利用しているプラットフォーム向けのバイナリをダウンロードして、PATHが通っているところに置くだけでも使えます。

またWindows向けにはChocolateyからもインストールすることもできるので、私はこちらの方法を使いました。

# 要管理者権限
> choco install fnm -y

or

# Use gsudo
> gsudo choco install fnm -y

gerardog/gsudo を使うと、わざわざ管理者権限でターミナルを開き直さなくても良いので便利です。

コマンドプロンプト向けシェルセットアップ

さてインストールが完了したら、使っているシェルに合わせてfnmを使うための初期化処理を設定する必要があります。

Bash向けなどは .bashrceval "$(fnm env)" を追記すれば良いという感じで非常に簡単なのですが、Windows特にコマンドプロンプト向けのセットアップは一筋縄ではいきませんでした。

Windows Command Prompt aka Batch aka WinCMD

コマンドプロンプトを起動したときに何か処理をおこないたい場合、レジストリHKEY_CURRENT_USER\SOFTWARE\Microsoft\Command ProcessorAutoRun キーに設定することで実行させることができます。

fnmのREADMEによれば、そこに以下のコマンドを登録することでセットアップがおこなわれるようです。

FOR /f "tokens=*" %i IN ('fnm env --use-on-cd') DO CALL %i

ただ自分の場合は、他にも初期化処理でやりたいことがあったので、バッチファイルを登録して、その中で上記の処理を呼び出すことにしました。

[HKEY_CURRENT_USER\SOFTWARE\Microsoft\Command Processor]
"AutoRun"="C:\Users\<user>\mystartup.cmd"

ところがこれがうまくいかず、コマンドプロンプトを起動すると i was unexpected at this time. というメッセージが表示されました。

これはfnmのREADMEにも対処法が書かれていますが、原因としてはバッチファイルの中で FOR を使う場合の変数の書き方を %i ではなく %%i としなければいけないからでした。

for | Microsoft Docs

よって以下のように書き換えます。

FOR /f "tokens=*" %%i IN ('fnm env --use-on-cd') DO CALL %%i

これでうまくいくかと思いきや、確かに先のメッセージは出なくなったものの、今度は処理が戻ってこない状態になりました。

いろいろ調べた結果、次のことがわかりました。

  • for /f はサブプロセス実行時に cmd.exe を起動するため、そこでまたAutoRunが走ってしまうことで無限ループに陥る可能性がある
  • cmd /d を指定することでAutoRunを無効にできるが、 for の中では効かないっぽい

参考:

fnmのREADMEにはCmderというターミナルアプリの起動スクリプト機能を使うことで回避する方法が載っていますが、これだとWindows Terminalが使えないので個人的にはイマイチ。

Usage with Cmder

そこで起動バッチのなかで多重実行を防ぐような仕組みを入れたところ、無限ループにならずに初期化処理が実行できるようになりました。

具体的には初回実行時にある環境変数に値を入れるようにして、実行時にその環境変数に値がある場合はそこで処理を終了するというもので、以下のような実装です。

mystartup.cmd

@ECHO OFF

IF "%MYSTARTUP_INIT%"=="OK" (
    EXIT /b
)

ECHO ... Running startup script
SET MYSTARTUP_INIT=OK

REM Setup fnm
FOR /f "tokens=*" %%z IN ('fnm env --use-on-cd') DO CALL %%z

ECHO ... Complete startup script

これでfnmを使うことができるようになりました。

基本的な使い方

# インストール可能なバージョンの一覧
> fnm ls-remote
v0.1.14
v0.1.15
v0.1.16
... # 省略
v16.8.0
v16.9.0
v16.9.1
v16.10.0

# 指定したバージョンをインストール
> fnm install v16.10.0
Installing Node v16.10.0 (x64)

# インストール済のバージョン一覧
> fnm ls
* v14.17.6
* v16.10.0 default
* system

# 現在のデフォルトで使われるバージョン
> fnm current
v16.10.0

# デフォルトバージョンの設定
> fnm default v14.17.6
> node -v
v14.17.6

自動バージョン切り替えを試してみる

あらかじめ、16系と14系をインストールしておきます。現在のデフォルトは16。

> fnm install 14
Installing Node v14.17.6 (x64)

> fnm list
* v14.17.6
* v16.10.0 default
* system

わかりやすく node14node16 フォルダを作り、それぞれに対応するバージョンを書いた .node-version ファイルを配置します。

> type node14\.node-version
v14
> type node16\.node-version
v16.10.0

それぞれのフォルダに移動してみます。

> cd node14
Using Node v14.17.6

> fnm current
v14.17.6

> node -v
v14.17.6

> cd ..\node16
Using Node v16.10.0

> fnm current
v16.10.0

> node -v
v16.10.0

node14 フォルダに入るとカレントバージョンが14に、 node16 フォルダに移動するとカレントバージョンは16になりました。

.node-version ファイルの内容に合わせて使うNodeバージョンが切り替わってくれることが確認できました。

※2021/10/06追記

プロジェクトのバージョン指定に使うファイルは .node-version だけでなく、nvm由来の .nvmrc を使うツールもあります。

fnmは .nvmrc にも対応しているようだったので試してみたところ、同じようにバージョンが切り替わってくれました。

> type node14\.nvmrc
14

> type node16\.nvmrc
v16.10.0

> node -v
v14.17.6

> cd node16
Using Node v16.10.0

node16> fnm current
v16.10.0

node16> node -v
v16.10.0

node16> cd ..\node14
Using Node v14.17.6

node14> fnm current
v14.17.6

node14> node -v
v14.17.6

おわり

Rust製で売りの一つにスピードとあるだけに、たしかに速い気がします。

Linux(WSL)やMacにもHomebrewで入れられるので、そちらにもセットアップしていこうと思います。