Nix × Node.js 環境構築
はじめに
最近、Zenn 記事執筆のために textlint
を使った文章校正環境を Nix で整えています。
textlint
は単独で使うよりも、ルールやプリセットなどの拡張を組み合わせることで真価を発揮します。
しかし、一部の日本語向け拡張は Nixpkgs に未登録でした。
textlint
とその拡張は Node.js パッケージです。
そこで、Node.js パッケージの依存関係を Nix に落とし込み、DevShell で利用可能にしようと考えました。
実際に試してみると解決策は意外なほどシンプルで、手間のかからない方法でした。
本記事ではその手順を紹介します。
想定読者
- Nix 経験者
- flake.nix の devShell で環境構築している方
- Node.js パッケージを Nix で管理したい方
- Nixpkgs に未登録のパッケージ含む
概要
-
Nixpkgs の
importNpmLock.buildNodeModules
を使ってnode_modules
を再現-
package-lock.json
の依存関係を Nix で再現
-
importNpmLock.hooks.linkNodeModulesHook
で devShell 起動時にnode_modules
を自動リンクnpm --package-lock-only
で lockfile だけを更新
動作検証を兼ねて作成したテンプレートはこちらで公開しています。
フォルダ構成
package.json
をプロジェクト直下に置く方法と、node-pkgs/
に分離する方法があります。
Github のテンプレートでは Node.js 開発環境を想定したので、プロジェクト直下に置きました。
この記事では分離パターンで解説します。
本解説では、Zenn CLI
、textlint
、textlint
拡張を利用可能な環境を構築する事を目的とします。
(npm や node を実行するのではなく、Zenn CLI
等のツールを実行できるのがゴール。)
your-repo
├─ flake.nix
└─ node-pkgs/
├─ package.json
└─ package-lock.json
セットアップ手順
1. flake.nix を作成
importNpmLock
と linkNodeModulesHook
を使います。
{
description = "Node pkgs environment";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { nixpkgs, flake-utils, ... }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
inherit (pkgs) importNpmLock;
# Node のバージョンはここで固定
nodejs = pkgs.nodejs_24;
# package.json / lockfile の場所
npmRoot = ./node-pkgs;
in
{
devShells.default = pkgs.mkShell {
packages = [
importNpmLock.hooks.linkNodeModulesHook
];
npmDeps = importNpmLock.buildNodeModules {
inherit npmRoot nodejs;
};
};
# パッケージ更新作業用の環境
devShells.node = pkgs.mkShell {
packages = [
nodejs
];
shellHook = ''
cd node-pkgs
echo "Node.js version: $(node -v)
add:
npm install -D <package-name>@<version> --package-lock-only
remove:
npm uninstall -D <package-name> --package-lock-only
convert package.json to package-lock.json:
npm install --package-lock-only"
'';
};
}
);
}
2. package.json を用意
Node.js を入れた環境を指定して起動します。
nix develop .#node
node-pkgs フォルダに package.json を作成し、使いたいツールを devDependencies
に追加します。
{
"name": "zenn-cli-env",
"version": "1.0.0",
"private": "true",
"devDependencies": {
}
}
package.json
の更新は 2 通り方法があります。
2.1 手動で編集・lock ファイル更新
package.json
を編集した後、npm を利用して package-lock.json
に変換します。
{
"name": "zenn-cli-env",
"version": "1.0.0",
"private": "true",
"devDependencies": {
"zenn-cli": "0.2.1",
"markdownlint-cli2": "0.18.1",
"textlint": "15.2.2",
"textlint-rule-preset-ja-spacing": "2.4.3",
"textlint-rule-preset-ja-technical-writing": "12.0.2"
}
}
npm install --package-lock-only
2.2 npm コマンドで更新
パッケージを追加。
npm install -D zenn-cli@0.2.1 --package-lock-only
npm install -D <package-name>@<version> --package-lock-only
パッケージを除外。
npm uninstall -D <package-name> --package-lock-only
3. devShell に入る・動作確認
環境を起動します。
nix develop
Zenn CLI
が確認できました。
$ zenn --version
0.2.1
Nix で Node.js を管理する他の方法
DevShell で利用したい場合
今回紹介した、importNpmLock
と linkNodeModulesHook
が手間が少ないのでお勧めです。
パッケージをビルド・配布したい場合
buildNpmPackage
が便利そうです。
詳細はこちらをご確認ください。
古い方法(非推奨)
node2nix
は古くからあるツールです。
私が体感しただけでも node2nix
には複数の問題があります。
-
lockfileVersion 3
に対応していない-
npm install --package-lock-only --lockfile-version=2
のように古いバージョンを明示する必要あり
-
-
Node.js
の既定値がpkgs.nodejs_14
と古い- 古すぎて Nix.pkgs からは除外されています
- 明示的に
Node.js
を指定しないとエラーが発生します
-
package-lock.json
から Nix ファイルへ変換が必要- 変換にかかる時間が長い
- 今回の 4 つのパッケージだけでも、数分かかりました
- 補足すると、次回からはキャッシュが効いているのか半分未満になりました
- リポジトリの最後の更新が 3 年前(2022 年)
この様な背景があってか、buildNpmPackage
に切り替えるべきとの Issue も立っています。
また、2024 年に行われたコミュニティーでの議論では、node2nix
の代替案として下記ツールが挙げられていました。
buildNpmPackage
linkNodeModulesHook
yarn2nix
NixOS manual や NixOS wiki でも同様な風潮でした。
Discussion