为前端“残障”人士打造的 Nixpkgs(无障碍版)

Lobsters Hottest 工具

摘要

jinx 是一个将 Nix 编译为 TypeScript 的转译器,能够在浏览器中评估完整的 nixpkgs(231,777 个属性)并生成与 Nix 完全一致的 drvPath,从而让前端开发者在不安装 Nix 的情况下探索存储模型。

<p><a href="https://lobste.rs/s/4jfegi/nixpkgs_for_frontend_impaired">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/10/13 20:01

# 面向前端残疾人的 Nixpkgs 来源:https://fzakaria.com/2026/10/12/nixpkgs-for-the-frontend-impaired > **tl;dr** jinx (https://fzakaria.github.io/jinx) 是一个将 Nix 编译为 TypeScript 的工具。整个 nixpkgs,每一个属性,共 231,777 个,都生成与 Nix **完全一致的** drvPath。打开 playground (https://fzakaria.github.io/jinx),或者直接在你的浏览器标签页中求值 hello (https://fzakaria.github.io/jinx/nixpkgs/#attr=hello),无需在任何地方安装 Nix。😈 尽管我对 Nix 这门语言和工具充满钦佩和喜悦,但真正神奇的并不是它们。Store 模型才是那个伟大的创意。任何基于同一模型构建的类似工具,都将拥有同样的可复现性和封闭性等神奇特性。 我写了很多 (https://fzakaria.com/2026/09/11/a-nix-store-is-three-functions) 文章 (https://fzakaria.com/2026/09/24/every-package-is-already-installed) 来探索 Nix 生态系统所派生出的一切,但这门语言本身只是一个带有派生(derivation)原语的小型惰性函数式语言。你可以在一个周末内设计出另一个。 但你在周末内设计不出来的是 nixpkgs (https://github.com/NixOS/nixpkgs):二十年的修复、每一个交叉编译的变通方案、每一个 `stdenv` 阶段、每一个被修补过的 `configure` 脚本、每一个"这个库在 aarch64-darwin 上需要这个标志位",这些都已经有人解决了。截至 2026 年 10 月,它包含 14 万多个可以构建的包。这就是秘方 🥫,而且它是用 Nix 写的,所以如果你想要它,你就得写 Nix。 不幸的是,这也是陷阱所在。任何想从不同方向探索 store 模型的人——无论是新语言、新求值器还是新的编辑器体验——都必须从零个包开始。这就是其他 Nix 重新实现或分叉走向消亡的坟场。 然而,如果你能把整个 nixpkgs 的树转换成另一个生态系统已经能"说"的东西,你就可以在不放弃 nixpkgs 二十年积累的前提下探索 store 模型。💡 我选择了 TypeScript。为什么?为什么不呢? 《侏罗纪公园》中的 Ian Malcolm:你的科学家们太专注于能不能做,而没有停下来想想该不该做 (https://fzakaria.com/assets/images/jurassic_park_jinx_meme.jpg) TypeScript 拥有丰富的工具生态,且被*几乎*所有浏览器支持。它有一个极其灵活的类型系统,这意味着我们可以表达派生(derivation)的形态以及 Nix 惰性求值模型中的所有奇特之处。 我是一个前端残疾的"菜鸟",但我认为这个想法值得探索。 jinx (https://fzakaria.github.io/jinx) 就是结果。它是一个转译器,接受一个 `.nix` 文件并生成一个 `.ts` 文件,该文件求值得到相同的值,拥有相同的 thunk、相同的派生字符串和相同的 drvPath。 然后我用它编译了整个 nixpkgs。每一个属性、每一个包、每一个模块、每一个 `stdenv` 和 `lib` 函数:总共 231,777 个属性。 编译后的树位于 fzakaria/jinx-nixpkgs (https://github.com/fzakaria/jinx-nixpkgs),每个 nixpkgs 修订版对应一个 commit。 ## § 它做了什么 (https://fzakaria.com/2026/10/12/nixpkgs-for-the-frontend-impaired#what-it-does) 每个 `.nix` 文件变成一个同名的 `.ts` 模块,生成在一个与源码镜像的目录树中。lambda 是箭头函数,`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 会分配一个 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` 和 `drvPath` 与 Nix 产生的**完全相同**。这就是在另一个生态系统中解锁 nixpkgs 的关键:store 无法分辨差异。 ## § 翻译无损 (https://fzakaria.com/2026/10/12/nixpkgs-for-the-frontend-impaired#nothing-lost-in-translation) 我们可以证明翻译是忠实的。我们用 `jinx` 和 nix-eval-jobs (https://github.com/NixOS/nix-eval-jobs) 分别求值 nixpkgs 自身 CI 配置中的每个属性,即 ci/eval/outpaths.nix (https://github.com/NixOS/nixpkgs/blob/6510408d147d8c5e6d76043e899e9251756f6a75/ci/eval/outpaths.nix),然后比较所有 `drvPath`。 在 nixpkgs 6510408d (https://github.com/NixOS/nixpkgs/commit/6510408d147d8c5e6d76043e899e9251756f6a75) 上,在 x86_64-linux、aarch64-linux 和 aarch64-darwin 上: 零差异或单侧失败。^1^ 在两侧都失败的 2,714 个是 Nix 拒绝求值的属性,比如不支持的平台、broken 或 insecure 的包。 ## § 但说真的,为什么?(https://fzakaria.com/2026/10/12/nixpkgs-for-the-frontend-impaired#but-seriously-why) 在 TypeScript 中求值 nixpkgs 慢了约 3-5 倍。如果它只是这样,那就没什么意思了。重点不是让 Nix 变快,而是让 nixpkgs 对 TypeScript 生态系统*可用*。 以下是一些你可以用它做的"有趣"的事。 **对求值进行性能剖析。** 对 `firefox.drvPath` 运行 `node --cpu-prof`,通过 source map 折叠后,每一帧都对应 nixpkgs 中的一行。点击查看完整 SVG。 火焰图顶部,求值 firefox.drvPath 的过程,每一帧都是 nixpkgs 中的一行 (https://fzakaria.com/assets/images/jinx_flamegraph_firefox.svg) **在编辑器中补全 nixpkgs。** 为 nixpkgs 中的每个属性提供自动的语言服务器协议(LSP)支持,并带有指向原始 `.nix` 文件的 source map。TypeScript 的类型系统表现力足够强,可以建模派生的形态,因此你可以免费获得补全和类型检查。 pkgs.neo 补全出 neovim、neofetch 及其余匹配项;pkgs.rigrep 被标记为错误 (https://fzakaria.com/assets/images/jinx_typed_nixpkgs.png) **单步调试一个包。** Source map 可以将断点放在某一行 `.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` 的编译产物忠实到每一个 thunk 都与 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 获取编译后的模块。^2^ 也许可以搞些有趣的*code golf*,生成输出路径,然后用 trynix (https://fzakaria.com/2026/09/04/any-nix-package-live-in-your-browser) 引导启动,全程不使用 Nix。 ## § 它慢吗?(https://fzakaria.com/2026/10/12/nixpkgs-for-the-frontend-impaired#is-it-slow) 是的,它很慢,但关键是没有*太*慢。 在热缓存状态下,模块已编译且 V8 代码缓存已填充,在我的笔记本上每个属性: 首次求值(需要编译它触及的每个模块)速度慢 3 到 10 倍,内存约为 2-4 倍,因为 thunk 对垃圾回收器来说非常昂贵。^3^ 完整的基准测试表和原始数据可以在 benchmark 页面 (https://fzakaria.github.io/jinx/bench/) 找到。 让我这样说:它比一个被人们优化了二十年的 C++ 解释器慢五倍,而它是一个全新的编译器,运行在 JavaScript 引擎上。在我看来,这是一个不错的起点。🤷 ## § 我可以试试吗?(https://fzakaria.com/2026/10/12/nixpkgs-for-the-frontend-impaired#can-i-try) 源码位于 github.com/fzakaria/jinx (https://github.com/fzakaria/jinx)。 它没有依赖,没有本地代码,没有守护进程。你可以在 Node 24 中运行它,或者在浏览器中运行它。编译后的 nixpkgs 树位于 github.com/fzakaria/jinx-nixpkgs (https://github.com/fzakaria/jinx-nixpkgs)。 我有点恼火的是我必须不断声明我在代码中使用了"AI"。人们似乎很享受"揭发"你这件事,好像这是一种道德缺陷。让我省去他们的麻烦:一个 AI 与我共同编写了大量软件。 一个红色橡胶印章,风格类似 100% 纯牛肉标签:优质品质,100% AI,始终新鲜 (https://fzakaria.com/assets/images/100_percent_ai_stamp.png) 这是给你的印章。如果这让你无法接受,你可以停止阅读了,派生不会为你改变它们的哈希值。 nixpkgs 现在是一个 TypeScript 程序了。去在 stdenv 中设置一个断点吧。(https://fzakaria.github.io/jinx/docs/why)

相似文章

Nixmac

Product Hunt

Nixmac 是一个为 Nix-darwin 提供简洁英文界面的工具,简化了 macOS 上的 Nix 配置。

Any Nix package, live in your browser

Lobsters Hottest

trynix.dev is a browser-based runner that boots a Linux VM in WebAssembly to let users execute any historical Nix package instantly without local installation. It can pull binaries from the public Nix cache, Cachix, or any CORS-enabled store, enabling over 310,000 nixpkgs versions to run directly in a browser tab.

nixpkgs 状况不佳

Lobsters Hottest

一篇博文讨论了最近 Nixpkgs 核心团队的解散,强调了 Nix/NixOS/Nixpkgs 项目中持续存在的治理困境、成员流失和志愿者倦怠问题。

Nix Flakes 及其在 Guix 中的对应物

Lobsters Hottest

详细比较 Nix Flakes 与 Guix 包管理系统中的对应物,涵盖依赖声明、锁定、纯净性、输出、开发环境和系统配置。

任意 Nix 包,浏览器中实时运行

Simon Willison's Blog

文章介绍了 trynix,一个基于 WebAssembly 的工具,可在浏览器中运行过去13年内的任何 Nix 包,并重点介绍了一个 GitHub Action,用于交互式地启动拉取请求。