🐢

Nix × Node.js 環境構築

に公開

はじめに

最近、Zenn 記事執筆のために textlint を使った文章校正環境を Nix で整えています。

textlint は単独で使うよりも、ルールやプリセットなどの拡張を組み合わせることで真価を発揮します。
しかし、一部の日本語向け拡張は Nixpkgs に未登録でした

textlint とその拡張は Node.js パッケージです。
そこで、Node.js パッケージの依存関係を Nix に落とし込み、DevShell で利用可能にしようと考えました

実際に試してみると解決策は意外なほどシンプルで、手間のかからない方法でした。
本記事ではその手順を紹介します。

https://github.com/ryuryu333/nodejs_nix_env_template

想定読者

  • 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 だけを更新

動作検証を兼ねて作成したテンプレートはこちらで公開しています。

https://github.com/ryuryu333/nodejs_nix_env_template

フォルダ構成

package.json をプロジェクト直下に置く方法と、node-pkgs/ に分離する方法があります

Github のテンプレートでは Node.js 開発環境を想定したので、プロジェクト直下に置きました。

この記事では分離パターンで解説します
本解説では、Zenn CLItextlinttextlint 拡張を利用可能な環境を構築する事を目的とします
(npm や node を実行するのではなく、Zenn CLI 等のツールを実行できるのがゴール。)

your-repo
├─ flake.nix
└─ node-pkgs/
   ├─ package.json
   └─ package-lock.json

セットアップ手順

1. flake.nix を作成

importNpmLocklinkNodeModulesHook を使います。

flake.nix
{
  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 を入れた環境を指定して起動します。

Bash
nix develop .#node

node-pkgs フォルダに package.json を作成し、使いたいツールを devDependencies に追加します。

package.json
{
  "name": "zenn-cli-env",
  "version": "1.0.0",
  "private": "true",
  "devDependencies": {
  }
}

package.json の更新は 2 通り方法があります。

2.1 手動で編集・lock ファイル更新

package.json を編集した後、npm を利用して package-lock.json に変換します。

package.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"
  }
}
Bash
npm install --package-lock-only

2.2 npm コマンドで更新

パッケージを追加。

Bash
npm install -D zenn-cli@0.2.1 --package-lock-only
Bash
npm install -D <package-name>@<version> --package-lock-only

パッケージを除外。

Bash
npm uninstall -D <package-name> --package-lock-only

3. devShell に入る・動作確認

環境を起動します。

Bash
nix develop

Zenn CLI が確認できました。

Bash
$ zenn --version
0.2.1

Nix で Node.js を管理する他の方法

DevShell で利用したい場合

今回紹介した、importNpmLocklinkNodeModulesHook が手間が少ないのでお勧めです。

パッケージをビルド・配布したい場合

buildNpmPackage が便利そうです。

詳細はこちらをご確認ください。

https://github.com/NixOS/nixpkgs/tree/master/pkgs/build-support/node/build-npm-package

https://github.com/NixOS/nixpkgs/blob/master/doc%2Flanguages-frameworks%2Fjavascript.section.md#buildnpmpackage-javascript-buildnpmpackage

古い方法(非推奨)

node2nix は古くからあるツールです。

https://zenn.dev/trifolium/articles/32ff89d4e14815

https://github.com/svanderburg/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 も立っています。

https://github.com/NixOS/nixpkgs/issues/229475

また、2024 年に行われたコミュニティーでの議論では、node2nix の代替案として下記ツールが挙げられていました。

  • buildNpmPackage
  • linkNodeModulesHook
  • yarn2nix

https://discourse.nixos.org/t/best-practice-for-nodejs-development/55359

NixOS manual や NixOS wiki でも同様な風潮でした。

https://nixos.org/manual/nixpkgs/stable/#javascript-packages-nixpkgs

https://nixos.wiki/wiki/Node.js

Discussion