跳到正文
Hacker News (official RSS)(fzakaria.com)·· 3 天前精選AI 評分77

Jinx:將 Nixpkgs 編譯為 TypeScript,讓前端可在瀏覽器中使用

Nixpkgs for the Front End Impaired

AI 導讀

Jinx 是一款將 Nixpkgs 轉譯成 TypeScript 的工具,允許在瀏覽器或 Node 環境中直接評估 Nix 表示式,無需安裝 Nix。

它將整個 nixpkgs(231,777 個屬性)編譯為一棵與原始目錄結構相同的 TypeScript 模組樹,並保留所有 drvPath,確保與原始 Nix 的輸出完全一致。

推薦理由

此工具讓前端開發者可直接在瀏覽器中使用 Nixpkgs,打破語言隔閡,提升跨語言開發效率,並提供完整型別檢查與即時除錯體驗。

正文 · AI 翻譯

tl;dr jinx 是一個將 Nix 編譯成 TypeScript 的工具。所有 nixpkgs 的每一個 231,777 個屬性都產生與 Nix 完全相同的 drvPath。開啟 playground 或 在瀏覽器分頁中評估 hello,且不需要在任何地方安裝 Nix。 😈

儘管我對 Nix、這門語言和工具充滿敬佩與喜悅,但並非它本身是神奇的。商店模型才是偉大的理念。任何在同一模型上構建的類似工具,都將擁有可重現性與密閉性的相同神奇特性。

我已經花了 大量 文章 探討 Nix 生態系統帶來的內容,但語言本身是一個小型的延遲函式式語言,帶有衍生原語。你可以在一個週末設計另一個。

一個週末無法設計的,是 nixpkgs:二十年的修正、所有交叉編譯的變通方法、每個 stdenv 階段、每個修補的 configure 指令碼、以及「這個函式庫需要在 aarch64-darwin 上使用此旗標」等已被解決的問題。到 2026 年 10 月,已有超過 140,000 個可構建的套件。這就是魔法醬料 🥫,而它是用 Nix 寫成的,所以如果你想要它,就寫 Nix。

不幸的是,這也是陷阱。任何想從不同方向探索商店模型空間、使用新語言、新評估器、新編輯器體驗的人,都必須從零套件開始。這就是其他 Nix 的重新實作或分支走向終結的墓場。

然而,如果你能將整棵 nixpkgs 樹轉換成另一個生態系統已經使用的語言,你就能在不放棄投入 nixpkgs 的二十年工作的前提下探索商店模型。💡

我選擇了 TypeScript。為什麼?為什麼不呢!?

Ian Malcolm from Jurassic Park: your scientists were so preoccupied with whether or not they could, they didn't stop to think if they should

TypeScript 擁有廣泛的工具鏈,並且在 幾乎 所有瀏覽器中受支援。它擁有極其靈活的型別系統,讓我們能夠表達衍生的結構以及 Nix 延遲評估模型中的所有怪異之處。

我是一個前端入門者,但我認為這個想法值得探索。

jinx 是結果。它是一個轉譯器,將 .nix 檔案轉成 .ts 檔案,評估結果相同,使用相同的 thunk、相同的衍生字串,以及相同的 drvPath。

我隨後用它編譯了整個 nixpkgs。每一個屬性、每一個套件、每一個模組、每一個 stdenv 和 lib 函式:總計 231,777 個屬性。

編譯好的樹存放在 fzakaria/jinx-nixpkgs,每一次 nixpkgs 修訂都有一個提交。

§它的功能

每個 .nix 檔案都會變成一個同名的 .ts 模組,位於映象原始碼的樹中。lambda 是箭頭函式,let 是 const 區塊,attribute set 是物件文字,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 會分配 thunk 的地方,用 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"; }

我們產生 完全相同 的 .drv 以及 Nix 會產生的完全相同的 drvPath。這是解鎖另一個生態系統中 nixpkgs 的關鍵:商店不會分辨差異。

§翻譯無遺失

我們可以證明翻譯是忠實的。我們用 jinx 和 nix-eval-jobs 評估 nixpkgs 自己的 CI 配置中的每一個屬性 ci/eval/outpaths.nix,並比較所有 drvPaths。

在 nixpkgs 6510408d、x86_64-linux、aarch64-linux 與 aarch64-darwin 上:

屬性 相同 drvPath 雙方皆失敗 不同 僅一側失敗
231,777 229,063 2,714 0 0

零差異或僅一側失敗。11兩千七百十四個同時失敗的屬性是 Nix 拒絕評估的,例如不支援的平臺、損壞或不安全的專案。 

§但說真的,為什麼?

在 TypeScript 中評估 nixpkgs 約慢 3-5 倍。如果這就是它所做的,將不太有趣。重點不是讓 Nix 更快,而是讓 Nixpkgs 可存取 到 TypeScript 生態系。

以下是你可以用它做的一些「趣味」操作。

分析一次評估。 node --cpu-prof 在 firefox.drvPath,透過來源對映折疊,使每一幀都是 nixpkgs 的一行。點選可檢視整個 SVG。

The top of a flame graph of evaluating firefox.drvPath, every frame a nixpkgs line

在編輯器中完整載入 nixpkgs。 為 nixpkgs 的每個屬性自動支援語言伺服器協定,並提供指向原始 .nix 檔案的來源對映。TypeScript 的型別系統足以描述衍生物的結構,因此你可以免費獲得補全與型別檢查。

pkgs.neo completing to neovim, neofetch and the rest; pkgs.rigrep underlined as an error

逐步執行一個套件。 來源對映在 .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 提問。 在 REPL 或指令碼中詢問並探索 nixpkgs,而不必記住獲取整棵樹 JSON 輸出的咒語,並將其傳遞給 jq。

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 完全忠實,甚至每個 thunk 都保留,這使得手動編寫既精確又麻煩。另一種方式是使用更好看的手寫形式。 它可與 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可能還有趣味的 code golf,嘗試建立輸出路徑,然後使用 trynix 啟動,而不使用 Nix。 

§它慢嗎?

是的,它很慢,但關鍵是並非太慢。

當熱起來,模組已編譯且 V8 的程式碼快取已填滿時,在我的筆電上每個屬性的耗時:

探測 nix jinx warm warm / nix peak RSS nix peak RSS jinx
lib 0.53 s 1.31 s 2.5x 238 MB 501 MB
hello 0.37 s 1.72 s 4.6x 88 MB 376 MB
firefox 3.19 s 11.44 s 3.6x 488 MB 1608 MB
nixos 4.21 s 21.35 s 5.1x 603 MB 2377 MB

首次評估需要編譯它所觸及的每個模組,速度慢 3~10 倍,記憶體則約 2~4 倍,因為 thunk 對垃圾回收器非常昂貴。33完整的基準表和原始資料可在 benchmark page 找到。 

假設它比經過二十年最佳化的 C++ 直譯器慢五倍,且這個直譯器是執行於 JavaScript 引擎的新編譯器,這對我來說是一個不錯的起點。🤷

§我可以試試嗎?

程式碼位於 github.com/fzakaria/jinx。

它沒有任何相依性、原生程式碼或守護程式。你可以在 Node 24 或瀏覽器中執行它。編譯後的 nixpkgs 樹位於 github.com/fzakaria/jinx-nixpkgs。

我有點不高興,我必須不斷透露我使用了“AI”來編寫程式碼。人們似乎喜歡把你曝露出來,因為那是某種道德失敗。讓我省下他們的麻煩:一個 AI 與我共同撰寫了這段軟體的相當一部份。

A red rubber stamp in the style of a 100% beef label: premium quality, 100% AI, always fresh

這裡是你的印章。若這是決定性因素,你可以在此停止閱讀;推導結果對你來說雜湊值不會改變。

Nixpkgs 現在是一個 TypeScript 程式。在 stdenv 設定斷點。