フロントエンドが苦手な人のための Nixpkgs
Nixpkgs for the Front End Impaired (fzakaria.com)
要約
この記事では、Nix を TypeScript に変換するツール「jinx」を紹介しています。これにより、23万以上の属性を持つ Nixpkgs 全体を TypeScript エコシステムで利用できるようになります。このツールは、Nix のストアモデルの再現性や堅牢性を保ちつつ、TypeScript の豊富なツールチェインを活用して、エディタでの補完、デバッグ、ブラウザでの評価などを可能にします。
全文翻訳
フロントエンドが苦手な人のための Nixpkgs
tl;dr jinx は Nix を TypeScript にコンパイルするツールです。Nixpkgs の全 231,777 属性が、Nix と 全く同じ drvPath を生成します。プレイグラウンドを開く か、Nix を一切インストールせずにブラウザタブで hello を評価することができます。😈
Nix という言語やツールに感心し、楽しんでいますが、魔法なのはそれ自体ではありません。ストアモデルこそが素晴らしいアイデアです。同じモデルに基づいた同様のツールは、再現性や堅牢性といった魔法のような特性をすべて備えるでしょう。
私は 多くの投稿 で Nix エコシステムの周辺を探求してきましたが、言語自体は小さな遅延評価関数言語で、導出(derivation)というプリミティブを持っています。週末でも別のものを設計できるでしょう。
しかし、週末では設計できないのが nixpkgs です。20年分の修正、あらゆるクロスコンパイルの回避策、すべての stdenv フェーズ、パッチが適用されたすべての configure スクリプト、「このライブラリは aarch64-darwin ではこのフラグが必要」といった、誰かがすでに解決した問題群です。2026年10月現在、ビルド可能なパッケージは14万を超えています。これが魔法のソース 🥫であり、Nix で書かれているため、それを欲するなら Nix を書く必要があります。
残念ながら、それも落とし穴です。ストアモデルの空間を異なる方向から探求したい、新しい言語、新しい評価器、新しいエディタ体験をしたいと考える人は、ゼロからパッケージを始める必要があります。これは、Nix の他の再実装やフォークが死んでいく墓場です。
しかし、もし nixpkgs のツリー全体を取得し、別のエコシステムがすでに話している言語に変換できれば、nixpkgs に費やされた20年間の作業を放棄することなく、ストアモデルを探求できるでしょう。💡
私は TypeScript を選びました。なぜかって? なぜダメなのですか!?
[画像: ジュラシック・パークのイアン・マルコム:「科学者たちは、それができるかどうかということに夢中になりすぎて、すべきかどうかを考えるのをやめてしまった」]
TypeScript は幅広いツールがあり、ほぼすべてのブラウザでサポートされています。その信じられないほど柔軟な型システムは、導出の形状と Nix の遅延評価モデル内のあらゆる奇妙さを表現することを可能にします。
私はフロントエンドが苦手な「初心者」ですが、このアイデアは探求する価値があると思いました。
jinx がその結果です。これは .nix ファイルを受け取り、同じ値、同じソーク(thunk)、同じ導出文字列、同じ drvPaths に評価される .ts ファイルを生成するトランスパイラです。
その後、nixpkgs 全体をこれでコンパイルしました。すべての属性、すべてのパッケージ、すべてのモジュール、すべての stdenv および lib 関数: 合計 231,777 属性です。
コンパイルされたツリーは fzakaria/jinx-nixpkgs にあり、nixpkgs のリビジョンごとに1つのコミットがあります。
§何をするのか
各 .nix ファイルは、ソースをミラーリングするツリー内の同じ名前の1つの .ts モジュールになります。ラムダはアロー関数、let は const のブロック、属性セットはオブジェクトリテラル、import は require です。それを読むことができます。
$ jinx compile pkgs/by-name/he/hello/package.nix
// Generated by jinx from .../pkgs/by-name/he/hello/package.nix. Do not edit.
import * as nix from "../../../../.jinx/runtime/index.ts";
const here = nix.here(import.meta.url);
const formals$1 = { args: { callPackage: false, fetchurl: false, hello: false, lib: false, nixos: false, stdenv: false, testers: false }, ellipsis: false };
export default nix.lambda((args) => {
const $ = nix.formals(args, formals$1);
const callPackage = nix.arg($, "callPackage");
const fetchurl = nix.arg($, "fetchurl");
...
return nix.app(nix.sel(stdenv, "mkDerivation"), (finalAttrs) => ({
pname: "hello",
version: "2.12.2",
src: nix.thunk(() => nix.app(fetchurl, {
url: nix.thunk(() => nix.str`mirror://gnu/hello/hello-${nix.sel(finalAttrs, "version")}.tar.gz`),
hash: "sha256-...",
})),
...
}));
}, formals$1);
Nix は遅延評価されるため、生成されるコードは Nix がソークを割り当てる場所で nix.thunk(() => ...) で式をラップします。🤮
jinx による評価は /nix/store に何も書き込みません。jinx instantiate は .drv クローザーを書き込み、nix-store --realise が導出をビルドします。
$ jinx eval hello.nix
{ drv = "/nix/store/j8q5j0x495vfrxgha5m6q40yjdi59334-hello-2.12.2.drv";
out = "/nix/store/18bbdvag5v2f3d4y37pdbkzvh7s71cw4-hello-2.12.2"; }
$ nix-instantiate --eval --strict hello.nix
{ drv = "/nix/store/j8q5j0x495vfrxgha5m6q40yjdi59334-hello-2.12.2.drv";
out = "/nix/store/18bbdvag5v2f3d4y37pdbkzvh7s71cw4-hello-2.12.2"; }
Nix が生成するであろう 全く同じ .drv と 全く同じ drvPath を生成します。これが他のエコシステムで nixpkgs をアンロックする鍵です。ストアは違いを認識しません。
§翻訳で失われるものはない
翻訳が忠実であることを証明できます。nixpkgs 自身の CI 設定ファイル ci/eval/outpaths.nix のすべての属性を、jinx と nix-eval-jobs で評価し、すべての drvPath を比較します。
nixpkgs のコミット 6510408d において、x86_64-linux、aarch64-linux、aarch64-darwin での比較結果は以下の通りです。
| 属性数 | 同じ drvPath | 両方で失敗 | 相違あり | 片方のみで失敗 |
|---|---|---|---|---|
| 231,777 | 229,063 | 2,714 | 0 | 0 |
両方で失敗する 2,714 は、サポートされていないプラットフォーム、破損、または安全でないといった理由で Nix が評価を拒否する属性です。
§真面目な話、なぜか?
TypeScript で nixpkgs を評価するのは約 3〜5 倍遅くなります。もしそれだけなら、あまり面白くありません。目的は Nix を速くすることではなく、Nixpkgs を TypeScript エコシステムにアクセス可能にすることです。
以下に、これでできる「楽しい」ことをいくつか紹介します。
評価のプロファイリング。firefox.drvPath に対して node --cpu-prof を実行し、ソースマップを通して折りたたむと、すべてのフレームが nixpkgs の1行になります。クリックすると SVG 全体が表示されます。
[画像: firefox.drvPath の評価のフレームグラフの上部、各フレームは nixpkgs の1行]
エディタでの nixpkgs の補完。nixpkgs のすべての属性に対する自動言語サーバープロトコルサポートと、元の .nix ファイルへのソースマップ。TypeScript の型システムは導出の形状をモデル化するのに十分表現力があるため、補完と型チェックが無料で得られます。
[画像: pkgs.neo が neovim、neofetch などに補完され、pkgs.rigrep がエラーとして下線表示されている]
パッケージのステップ実行。ソースマップにより、.nix の行にブレークポイントを設定できます。VS Code や Chrome DevTools でコンパイルされたツリーを開き、pkgs/by-name/he/hello/package.nix でブレークし、属性セットをビルドする stdenv.mkDerivation にステップインします。
$ node --inspect-brk --enable-source-maps src/cli/main.ts eval hello.nix
ループで nixpkgs に質問する。ツリー全体の JSON ダンプを取得して jq でパイプ処理する際の呪文を覚える必要なく、REPL やスクリプトで nixpkgs を問い合わせ、探索できます。
import { loadNixpkgs } from "jinx";
const pkgs = loadNixpkgs("/path/to/nixpkgs");
const gpl = [];
for (const name of Object.keys(pkgs.lib.licenses)) {
if (pkgs.lib.licenses[name].spdxId?.startsWith("GPL")) {
gpl.push(name);
}
}
console.log(JSON.stringify(gpl));
node:test で Nix を単体テストする。lib 関数やパッケージ定義はモジュール内の関数なので、他の言語の他の関数と同様に単体テストがあります。
test("hello builds the derivation Nix would", () => {
assert.equal(
pkgs.hello.drvPath,
"/nix/store/cnraiw0hbdl6jzdhbkn8dah2hqj7xgij-hello-2.12.3.drv",
);
});
TypeScript でパッケージを記述する。jinx からのコンパイル形式は、すべてのソークに至るまで Nix に忠実であるため、手書きするのは正確で面倒です。あるいは、より見やすい手書き形式もあります。これは nixpkgs と相互運用できるため、TypeScript のパッケージは stdenv.mkDerivation を呼び出し、pkgs の任意のパッケージに依存できます。
const hello = pkgs.stdenv.mkDerivation({
pname: "hello",
version: "2.12.2",
src: pkgs.fetchurl({
url: "mirror://gnu/hello/hello-2.12.2.tar.gz",
hash: "sha256-WpqZbcKSzCTc9BHO6H6S9qrluNE72caBm0x6nc4IGKs=",
}),
meta: { license: pkgs.lib.licenses.gpl3Plus },
});
console.log(hello.drvPath);
ブラウザで実行する。TypeScript はすべてのモダンブラウザでサポートされています。タブで nixpkgs を評価でき、必要に応じて GitHub からコンパイルされたモジュールを取得します。22コードゴルフで、Nix を一切使用せずに、取得した出力パスを trynix で起動するようなものを作るのは面白いかもしれません。
§遅いのか?
はい、遅いですが、決定的に遅すぎないわけではありません。
ウォームアップ時、モジュールがコンパイルされ、V8 のコードキャッシュが満たされた状態での、私のラップトップ上での属性ごとのパフォーマンスは以下の通りです。
| プローブ | Nix | Jinx ウォーム | ウォーム / Nix | ピーク RSS Nix | ピーク RSS Jinx |
|---|---|---|---|---|---|
lib | 0.53 秒 | 1.31 秒 | 2.5倍 | 238 MB | 501 MB |
hello | 0.37 秒 | 1.72 秒 | 4.6倍 | 88 MB | 376 MB |
firefox | 3.19 秒 | 11.44 秒 | 3.6倍 | 488 MB | 1608 MB |
nixos | 4.21 秒 | 21.35 秒 | 5.1倍 | 603 MB | 2377 MB |
すべてのモジュールをコンパイルする必要がある最初の評価は、速度で 3〜10 倍、メモリ使用量はソークがガベージコレクタにとって非常に高価であるため、約 2〜4 倍になります。3ベンチマークテーブル全体と生データは ベンチマークページ で確認できます。
20年間人々が最適化してきた C++ インタープリタと比較して、全く新しいコンパイラが JavaScript エンジン上で動作する場合、5倍遅いとしましょう。これは開始点としては悪くない場所だと思います。🤷
§試せるか?
ソースは github.com/fzakaria/jinx にあります。
依存関係、ネイティブコード、デーモンは一切ありません。Node 24 またはブラウザで実行できます。コンパイルされた nixpkgs ツリーは github.com/fzakaria/jinx-nixpkgs にあります。
コードに「AI」を使用したことを開示しなければならないことに、少しうんざりしています。人々はそれを「暴露」するのが好きで、まるで道徳的な欠陥であるかのように扱います。彼らの手間を省きましょう。AI がこのソフトウェアの多くを私と共同執筆しました。
[画像: プレミアム品質、100% AI、常に新鮮]という 100% ビーフラベルのようなスタイルの赤いゴム印]
ここにあなたのスタンプがあります。それが取引の決裂要因であれば、ここで読むのをやめても構いません。導出のハッシュはあなたのために変わりません。
Nixpkgs は今や TypeScript プログラムです。stdenv にブレークポイントを設定しましょう。