NixOSでフロントエンド開発するときのワークアラウンド集
これは何?
現代のフロントエンド開発ではNode.jsが必須だが、npmにはバイナリを直接ダウンロードしてくるような野蛮なパッケージがとても多い。一方、NixOSでは依存関係の問題を防ぐために独自のファイル構造を採用しており、Filesystem Hierarchy Standardに準拠していない。そのため、npmパッケージがNixを経由せずにダウンロードしてきた実行可能バイナリは共有ライブラリを見つけることができず、そのままだとNixOSでは動作しない。
このスクラップではそのような問題について、エッジケースごとに解決策を記録していく。
注意!
パッケージマネージャNixのみを使っている場合、以下に列挙した問題は発生しません。
NixOS以外のディストロやmacOSの上でNixを動かすだけなら問題は発生しない。あくまで、NixOSがFHSに準拠していないことで生じる問題であって、FHS準拠のOSの上でNix単体を動かすだけなら何も問題はない。なので、Nixを使ってみたいな〜という人は、NixOSではない普通のOSで使ってみるのがいいだろう。
それならフロントエンドでNix使う意味って何?と思う人もいるかもしれないが、aptやらhomebrewやらOSごとに異なるパッケージマネージャを使って疲弊したり、謎のNode.js用バージョン管理ツールで実行環境をバージョン管理したりするくらいなら、全部Nixでインストール・バージョン管理してしまった方が楽だ。それくらいの気楽さでよくて、Nixを使うからって別に強気になる必要はない。
結論
Nixは普通に便利、NixOSは変!
Sharp/Wrangler/その他
いきなり「その他」とかいうデカすぎる主語が出てきてしまった。というのも大体の問題はnix-ldで解決するからだ。
nix-ld
nix-ldはまさにこういった問題に対処するためのNixOS moduleで、ポン置きバイナリをNixストア内の共有ライブラリにいい感じにリンクしてくれる。
設定も超簡単で、nid-ldはNixOS本体でサポートされているので以下の設定を記述するだけでよい。
{
programs.nix-ld.enable = true;
}
ポン置きバイナリのリンク先をlddで確認すると大体not found
になっているが、nix-ld有効化後に確認するとリンク先がNixストアに向いていることが分かるだろう。
Playwright
みんな大好きPlaywrightは、ブラウザの実行可能ファイルを直接落としてきて実行するヤバいパッケージである。ブラウザは依存関係マシマシなので、上で示したように単純にnix-ldを有効化するだけでは対処できない。
解決策は3つある。
1. buildFHSEnv
Nixpkgsのコミッターnatsukiumさんに教えてもらった方法(ありがとうございます!)。
buildFHSEnvとは、FHS互換の軽量サンドボックスを構築する関数である。依存関係激ヤバパッケージのビルドに使われているのをよく見る。これをNixシェルとして使う。
以下のようにdevShellを定義する。ほとんどNixOS Discourseで見つけたコードを参考にさせていただいた。
{
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};
in
{
devShells.default =
(pkgs.buildFHSEnv {
name = "playwright";
targetPkgs =
pkgs: with pkgs; [
openssl
systemd
glibc
glibc.dev
glib
cups.lib
cups
nss
nssTools
alsa-lib
dbus
at-spi2-core
libdrm
expat
xorg.libX11
xorg.libXcomposite
xorg.libXdamage
xorg.libXext
xorg.libXfixes
xorg.libXrandr
xorg.libxcb
mesa
libxkbcommon
pango
cairo
nspr
];
profile = ''
export LD_LIBRARY_PATH=/run/opengl-driver/lib:/run/opengl-driver-32/lib:/lib
export FONTCONFIG_FILE=/etc/fonts/fonts.conf
'';
unshareUser = false;
unshareIpc = false;
unsharePid = false;
unshareNet = false;
unshareUts = false;
unshareCgroup = false;
dieWithParent = true;
}).env;
}
);
}
通常、Nixシェルを宣言的に構築するときはmkShell関数を使うが、ここではbuildFHSEnvを使う。実はmkShell関数が提供するシェル環境はstdenvそのもので、上記のコードも同様にbuildFHSEnv関数が構築したFHS互換のサンドボックスをシェル環境として利用する。
基本的にNixOSの/usr
ディレクトリには/usr/bin
ディレクトリしか存在せず、中身も/usr/bin/env
というシンボリックリンクしかないが、上記のflake.nixをnix develop
してdevShellに入った後にもう一度確認すると/usr/lib
以下にたくさんのシンボリックリンクが貼られていることがわかる。よって、Playwrightがダウンロードしてきたブラウザのバイナリは無事FHS準拠のパスから共有ライブラリをロードして実行できるようになる。
2. nix-ldを使う
nix-ldを利用することもできる。nix-ldは環境変数を経由して、nix-ldに自動解決してほしい共有ライブラリを追加できるのでそれを使う。
$NIX_LD_LIBRARY_PATH
にライブラリのパスを追加する。
{
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};
in
{
devShells.default = pkgs.mkShell {
NIX_LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath (
with pkgs;
[
openssl
systemd
glibc
glibc.dev
glib
cups.lib
cups
nss
nssTools
alsa-lib
dbus
at-spi2-core
libdrm
expat
xorg.libX11
xorg.libXcomposite
xorg.libXdamage
xorg.libXext
xorg.libXfixes
xorg.libXrandr
xorg.libxcb
mesa
libxkbcommon
pango
cairo
nspr
]
);
};
}
);
}
pkgs.lib.makeLibraryPath
関数は、与えられたパッケージのリストを見ていき、<opensslのストアパス>/lib:<systemdのストアパス>/lib
という感じに、ライブラリのパスを連結した文字列を返す。buildFHSEnvの例で使ったものと同じライブラリのパスを環境変数に突っ込んでいる。
環境変数を宣言しているだけのdevShellだが、あとはnix-ldがよしなにやってくれる。欠点を挙げるとすれば、NixOSユーザーなら誰でも使えるbuildFHSEnvと違って、nix-ldを有効化しているユーザーしかこのdevShellを使えないことだろうか。
3. launchOptions.executablePathを指定する
実はNixpkgsはPlaywrightのbrowser driverを提供しており、そのストアパスをplaywright.config.ts
のオプションで指定すれば動かせる。ただし、前述の2つの解決策と違ってコード本体にNixOSの事情が反映されてしまうので、それが許容できるかどうかで使いわけよう。
詳細なやり方は以下を参考にされたし。
上記のdevShellはchromiumにしか対応していないので注意
現在調査中
Prisma
みんな大好きPrismaは、Prisma EngineというRust実装のバイナリを使う。他と違うのは、Prisma CLIからバイナリをダウンロードしようとすると「NixOSはサポートしてないぜ!」と処理を失敗させる点だ。実は、Prismaは結構良心的なヤツで、エンジン等のバイナリのパスを環境変数から指定できるようになっており、Nixpkgsが提供しているprisma-enginesその他のパスを指定してやれば動く。
以下を参考にすればヨシ。
環境変数については以下のリファレンスを参照。