{"article":{"slug":"nixpkgs-for-the-frontend-impaired","title":"Nixpkgs for the frontend impaired","subtitle":null,"summary":"Farid Zakaria introduces jinx, a tool that compiles Nix to TypeScript so all of nixpkgs can be evaluated in the browser with exact drvPath parity—aimed at making Nix’s package graph inspectable without installing Nix.","content_type":"blog_post","language":"en","canonical_url":"https://fzakaria.com/2026/10/12/nixpkgs-for-the-frontend-impaired","author":{"name":"Farid Zakaria","url":null,"person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"Farid Zakaria’s Blog","url":"https://fzakaria.com/","listing_slug":null,"listing":null},"topics":[{"name":"Open Source","slug":"open-source","url":"https://listedarticles.com/topics/open-source"},{"name":"Programming","slug":"programming","url":"https://listedarticles.com/topics/programming"},{"name":"Developer Tools","slug":"developer-tools","url":"https://listedarticles.com/topics/developer-tools"},{"name":"Web Development","slug":"web-development","url":"https://listedarticles.com/topics/web-development"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":1431,"reading_minutes":6,"published_at":"2026-10-12T00:00:00.000Z","added_at":"2026-10-13T20:15:37.273Z","updated_at":"2026-10-13T20:15:37.273Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/nixpkgs-for-the-frontend-impaired","markdown_url":"https://listedarticles.com/articles/nixpkgs-for-the-frontend-impaired.md","example":false,"citation":"Farid Zakaria, Farid Zakaria’s Blog. \"Nixpkgs for the frontend impaired.\" 12 Oct 2026. https://fzakaria.com/2026/10/12/nixpkgs-for-the-frontend-impaired (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://fzakaria.com/2026/10/12/nixpkgs-for-the-frontend-impaired"},"body_markdown":"**tl;dr** [jinx](https://fzakaria.github.io/jinx) is a tool that compiles Nix to TypeScript. All of nixpkgs, each 231,777 attribute, produces **the exact** drvPath Nix does. [Open the playground](https://fzakaria.github.io/jinx) or [evaluate hello in your browser tab](https://fzakaria.github.io/jinx/nixpkgs/#attr=hello) with no Nix installed anywhere. 😈\n\n\nDespite my admiration and joy for Nix, the language and tool, it is not what is magical. The store model is the great idea. Any similar tool built on the same model will have the same magical properties of reproducibility, and hermeticity.\n\nI have spent [a lot](https://fzakaria.com/2026/09/11/a-nix-store-is-three-functions) [of posts](https://fzakaria.com/2026/09/24/every-package-is-already-installed) exploring what falls out of the Nix ecosystem, but the language itself is a small lazy functional language with a derivation primitive. You could design another one in a weekend.\n\nWhat you cannot design in a weekend is [nixpkgs](https://github.com/NixOS/nixpkgs): twenty years of fixes, every cross-compilation workaround, every `stdenv` phase, every patched `configure` script, every “this library needs this flag on aarch64-darwin” that somebody already solved. As of October 2026, it is 140,000-plus packages that build. That is the magic sauce 🥫, and it is written in Nix, so if you want it you write Nix.\n\nUnfortunately it is also the trap. Anyone who wants to explore the store-model space from a different direction, a new language, a new evaluator, a new editor experience, must then start from zero packages. This is the graveyard where other reimplementations or forks of Nix go to die.\n\nIf however you could take the whole tree of nixpkgs and transform it into something another ecosystem already speaks, you could explore the store model without abandoning the twenty years of work that went into nixpkgs. 💡\n\nI chose TypeScript. Why? Why not!?\n\nTypeScript has a breadth of tooling and is supported in *nearly* every browser. It has an incredibly flexible type system that means we can express the shape of a derivation and all the oddities within Nix’s lazy evaluation model.\n\nI am a frontend impaired “noob” but I thought the idea was worth exploring.\n\n[jinx](https://fzakaria.github.io/jinx) is the result. It is a transpiler that takes a `.nix` file and produces a `.ts` file that evaluates to the same value, with the same thunks, the same derivation strings, and the same drvPaths.\n\nI then compiled all of nixpkgs with it. Every attribute, every package, every module, every `stdenv` and `lib` function: 231,777 attributes in total.\n\nThe compiled tree lives at [fzakaria/jinx-nixpkgs](https://github.com/fzakaria/jinx-nixpkgs), one commit per nixpkgs revision.\n\n## [§](https://fzakaria.com#what-it-does)What it does\n\nEach `.nix` file becomes one `.ts` module of the same name in a tree that mirrors the source. A lambda is an arrow function, a `let` is a block of consts, an attribute set is an object literal, `import` is `require`. You can *read* it.\n\n```\n$ jinx compile pkgs/by-name/he/hello/package.nix\n```\n```\n// Generated by jinx from .../pkgs/by-name/he/hello/package.nix. Do not edit.\nimport * as nix from \"../../../../.jinx/runtime/index.ts\";\nconst here = nix.here(import.meta.url);\nconst formals$1 = { args: { callPackage: false, fetchurl: false, hello: false, lib: false, nixos: false, stdenv: false, testers: false }, ellipsis: false };\nexport default nix.lambda((args) => {\n  const $ = nix.formals(args, formals$1);\n  const callPackage = nix.arg($, \"callPackage\");\n  const fetchurl = nix.arg($, \"fetchurl\");\n  ...\n  return nix.app(nix.sel(stdenv, \"mkDerivation\"), (finalAttrs) => ({\n    pname: \"hello\",\n    version: \"2.12.2\",\n    src: nix.thunk(() => nix.app(fetchurl, {\n      url: nix.thunk(() => nix.str`mirror://gnu/hello/hello-${nix.sel(finalAttrs, \"version\")}.tar.gz`),\n      hash: \"sha256-...\",\n    })),\n    ...\n  }));\n}, formals$1);\n```\nNix is lazy, so the emitted code wraps an expression in `nix.thunk(() => ...)` wherever Nix would allocate a thunk. 🤮\n\nThe `jinx` evaluation writes nothing to `/nix/store`. `jinx instantiate` writes the `.drv` closure and `nix-store --realise` builds the derivation.\n\n```\n$ jinx eval hello.nix\n{ drv = \"/nix/store/j8q5j0x495vfrxgha5m6q40yjdi59334-hello-2.12.2.drv\";\nout = \"/nix/store/18bbdvag5v2f3d4y37pdbkzvh7s71cw4-hello-2.12.2\"; }\n$ nix-instantiate --eval --strict hello.nix\n{ drv = \"/nix/store/j8q5j0x495vfrxgha5m6q40yjdi59334-hello-2.12.2.drv\";\nout = \"/nix/store/18bbdvag5v2f3d4y37pdbkzvh7s71cw4-hello-2.12.2\"; }\n```\nWe produce **the exact same** `.drv` and the exact same `drvPath` Nix would. That is the key to unlocking nixpkgs in another ecosystem: the store does not know the difference.\n\n## [§](https://fzakaria.com#nothing-lost-in-translation)Nothing lost in translation\n\nWe can prove our translation is faithful. We evaluate every attribute of nixpkgs’ own CI configuration, [ci/eval/outpaths.nix](https://github.com/NixOS/nixpkgs/blob/6510408d147d8c5e6d76043e899e9251756f6a75/ci/eval/outpaths.nix), with `jinx` and with [nix-eval-jobs](https://github.com/NixOS/nix-eval-jobs), and compare all the `drvPaths`.\n\nOn nixpkgs [6510408d](https://github.com/NixOS/nixpkgs/commit/6510408d147d8c5e6d76043e899e9251756f6a75), on x86_64-linux, aarch64-linux and aarch64-darwin:\n\n| attributes | identical drvPath | fail on both sides | differ | fail on one side only | \n|---|---|---|---|---|\n| 231,777 | 229,063 | 2,714 | 0 | 0 | \n\nZero differences or failing on one side.<sup>1</sup>1The 2,714 that fail on both sides are the attributes Nix refuses to evaluate such as unsupported platform, broken, or insecure. \n\n## [§](https://fzakaria.com#but-seriously-why)But seriously, why?\n\nEvaluating nixpkgs in TypeScript is ~3-5x slower. If that is all it did, that would not be very interesting. The point is not to make Nix faster, it is to make Nixpkgs *accessible* to the TypeScript ecosystem.\n\nHere are some “fun” things you can do with it.\n\n**Profile an evaluation.** `node --cpu-prof` on `firefox.drvPath`, folded through the source maps so every frame is a line of nixpkgs. Click it for the whole SVG.\n\n**Complete nixpkgs in your editor.** Automatic language server protocol support for every attribute in nixpkgs, with source maps to the original `.nix` file. TypeScript’s type system is expressive enough to model the shape of a derivation, so you get completion and type checking for free.\n\n**Step through a package.** Source maps put a breakpoint on a `.nix` line. Open the compiled tree in VS Code or Chrome DevTools, break in `pkgs/by-name/he/hello/package.nix`, and step into `stdenv.mkDerivation` as it builds the attribute set.\n\n```\n$ node --inspect-brk --enable-source-maps src/cli/main.ts eval hello.nix\n```\n**Ask nixpkgs a question in a loop.**  Interrogate and explore nixpkgs in a REPL or a script without having to remember the incantation to get a JSON dump of the whole tree and pipe it through `jq`.\n\n```\nimport { loadNixpkgs } from \"jinx\";\nconst pkgs = loadNixpkgs(\"/path/to/nixpkgs\");\nconst gpl = [];\nfor (const name of Object.keys(pkgs.lib.licenses)) {\n  if (pkgs.lib.licenses[name].spdxId?.startsWith(\"GPL\")) {\n    gpl.push(name);\n  }\n}\nconsole.log(JSON.stringify(gpl));\n```\n**Unit test Nix with node:test.** A `lib` function or a package definition is a function in a module, so it has a unit test like any other function in any other language.\n\n```\ntest(\"hello builds the derivation Nix would\", () => {\n  assert.equal(\n    pkgs.hello.drvPath,\n    \"/nix/store/cnraiw0hbdl6jzdhbkn8dah2hqj7xgij-hello-2.12.3.drv\",\n  );\n});\n```\n**Write a package in TypeScript.** The compiled form from `jinx` is faithful to Nix down to every thunk, which makes it exact and miserable to write by hand. Alternatively, there is a much nicer hand-written form.\nIt interoperates with nixpkgs, so a package in TypeScript can call `stdenv.mkDerivation` and depend on anything in `pkgs`.\n\n```\nconst hello = pkgs.stdenv.mkDerivation({\n  pname: \"hello\",\n  version: \"2.12.2\",\n  src: pkgs.fetchurl({\n    url: \"mirror://gnu/hello/hello-2.12.2.tar.gz\",\n    hash: \"sha256-WpqZbcKSzCTc9BHO6H6S9qrluNE72caBm0x6nc4IGKs=\",\n  }),\n  meta: { license: pkgs.lib.licenses.gpl3Plus },\n});\nconsole.log(hello.drvPath);\n```\n**It runs in a browser.** TypeScript is supported in every modern browser.\nYou can evaluate nixpkgs in your tab, and it will fetch the compiled modules from GitHub as it needs them.<sup>2</sup>2There is probably some fun *code golf* trying to create output paths that you then subsequently boot with [trynix](https://fzakaria.com/2026/09/04/any-nix-package-live-in-your-browser) without ever using Nix. \n\n## [§](https://fzakaria.com#is-it-slow)Is it slow?\n\nYep it’s slow, but crucially not *too* slow.\n\nWhen warm, with the modules compiled and V8’s code cache populated, per attribute on my laptop:\n\n| probe | nix | jinx warm | warm / nix | peak RSS nix | peak RSS jinx | \n|---|---|---|---|---|---|\n| `lib` | 0.53 s | 1.31 s | 2.5x | 238 MB | 501 MB | \n| `hello` | 0.37 s | 1.72 s | 4.6x | 88 MB | 376 MB | \n| `firefox` | 3.19 s | 11.44 s | 3.6x | 488 MB | 1608 MB | \n| `nixos` | 4.21 s | 21.35 s | 5.1x | 603 MB | 2377 MB | \n\nA first evaluation that has to compile every module it reaches is 3x to 10x in speed and memory is ~2x-4x because thunks are very expensive to the garbage-collector.<sup>3</sup>3You can find the full benchmark table and the raw data on the [benchmark page](https://fzakaria.github.io/jinx/bench/) \n\nLet’s say it is five times slower than a C++ interpreter that has had twenty years of people shaving it, from a brand new compiler that runs on a JavaScript engine. That strikes me as a fine place to start. 🤷\n\n## [§](https://fzakaria.com#can-i-try)Can I try?\n\nThe source is at [github.com/fzakaria/jinx](https://github.com/fzakaria/jinx).\n\nIt has no dependencies, no native code, and no daemon. You can run it with Node 24 or in your browser. The compiled nixpkgs tree is at [github.com/fzakaria/jinx-nixpkgs](https://github.com/fzakaria/jinx-nixpkgs).\n\nI am a little annoyed that I have to keep disclosing that I used “AI” for the code. People seem to enjoy “outing” you for you it, as it is some morale failing. Let me save them the trouble: an AI co-authored a lot of this software with me.\n\nHere is your stamp. You can stop reading here if that is a dealbreaker, the derivations will not change their hashes for you.\n\nNixpkgs is a TypeScript program now. [Go set a breakpoint in stdenv.](https://fzakaria.github.io/jinx/docs/why)\n","body_html":"<p><strong>tl;dr</strong> <a href=\"https://fzakaria.github.io/jinx\" rel=\"nofollow ugc noopener\">jinx</a> is a tool that compiles Nix to TypeScript. All of nixpkgs, each 231,777 attribute, produces <strong>the exact</strong> drvPath Nix does. <a href=\"https://fzakaria.github.io/jinx\" rel=\"nofollow ugc noopener\">Open the playground</a> or <a href=\"https://fzakaria.github.io/jinx/nixpkgs/#attr=hello\" rel=\"nofollow ugc noopener\">evaluate hello in your browser tab</a> with no Nix installed anywhere. 😈</p>\n<p>Despite my admiration and joy for Nix, the language and tool, it is not what is magical. The store model is the great idea. Any similar tool built on the same model will have the same magical properties of reproducibility, and hermeticity.</p>\n<p>I have spent <a href=\"https://fzakaria.com/2026/09/11/a-nix-store-is-three-functions\" rel=\"nofollow ugc noopener\">a lot</a> <a href=\"https://fzakaria.com/2026/09/24/every-package-is-already-installed\" rel=\"nofollow ugc noopener\">of posts</a> exploring what falls out of the Nix ecosystem, but the language itself is a small lazy functional language with a derivation primitive. You could design another one in a weekend.</p>\n<p>What you cannot design in a weekend is <a href=\"https://github.com/NixOS/nixpkgs\" rel=\"nofollow ugc noopener\">nixpkgs</a>: twenty years of fixes, every cross-compilation workaround, every <code>stdenv</code> phase, every patched <code>configure</code> script, every “this library needs this flag on aarch64-darwin” that somebody already solved. As of October 2026, it is 140,000-plus packages that build. That is the magic sauce 🥫, and it is written in Nix, so if you want it you write Nix.</p>\n<p>Unfortunately it is also the trap. Anyone who wants to explore the store-model space from a different direction, a new language, a new evaluator, a new editor experience, must then start from zero packages. This is the graveyard where other reimplementations or forks of Nix go to die.</p>\n<p>If however you could take the whole tree of nixpkgs and transform it into something another ecosystem already speaks, you could explore the store model without abandoning the twenty years of work that went into nixpkgs. 💡</p>\n<p>I chose TypeScript. Why? Why not!?</p>\n<p>TypeScript has a breadth of tooling and is supported in <em>nearly</em> every browser. It has an incredibly flexible type system that means we can express the shape of a derivation and all the oddities within Nix’s lazy evaluation model.</p>\n<p>I am a frontend impaired “noob” but I thought the idea was worth exploring.</p>\n<p><a href=\"https://fzakaria.github.io/jinx\" rel=\"nofollow ugc noopener\">jinx</a> is the result. It is a transpiler that takes a <code>.nix</code> file and produces a <code>.ts</code> file that evaluates to the same value, with the same thunks, the same derivation strings, and the same drvPaths.</p>\n<p>I then compiled all of nixpkgs with it. Every attribute, every package, every module, every <code>stdenv</code> and <code>lib</code> function: 231,777 attributes in total.</p>\n<p>The compiled tree lives at <a href=\"https://github.com/fzakaria/jinx-nixpkgs\" rel=\"nofollow ugc noopener\">fzakaria/jinx-nixpkgs</a>, one commit per nixpkgs revision.</p>\n<h2 id=\"what-it-does\"><a href=\"https://fzakaria.com#what-it-does\" rel=\"nofollow ugc noopener\">§</a>What it does</h2>\n<p>Each <code>.nix</code> file becomes one <code>.ts</code> module of the same name in a tree that mirrors the source. A lambda is an arrow function, a <code>let</code> is a block of consts, an attribute set is an object literal, <code>import</code> is <code>require</code>. You can <em>read</em> it.</p>\n<pre><code>$ jinx compile pkgs/by-name/he/hello/package.nix</code></pre>\n<pre><code>// Generated by jinx from .../pkgs/by-name/he/hello/package.nix. Do not edit.\nimport * as nix from &quot;../../../../.jinx/runtime/index.ts&quot;;\nconst here = nix.here(import.meta.url);\nconst formals$1 = { args: { callPackage: false, fetchurl: false, hello: false, lib: false, nixos: false, stdenv: false, testers: false }, ellipsis: false };\nexport default nix.lambda((args) =&gt; {\n  const $ = nix.formals(args, formals$1);\n  const callPackage = nix.arg($, &quot;callPackage&quot;);\n  const fetchurl = nix.arg($, &quot;fetchurl&quot;);\n  ...\n  return nix.app(nix.sel(stdenv, &quot;mkDerivation&quot;), (finalAttrs) =&gt; ({\n    pname: &quot;hello&quot;,\n    version: &quot;2.12.2&quot;,\n    src: nix.thunk(() =&gt; nix.app(fetchurl, {\n      url: nix.thunk(() =&gt; nix.str`mirror://gnu/hello/hello-${nix.sel(finalAttrs, &quot;version&quot;)}.tar.gz`),\n      hash: &quot;sha256-...&quot;,\n    })),\n    ...\n  }));\n}, formals$1);</code></pre>\n<p>Nix is lazy, so the emitted code wraps an expression in <code>nix.thunk(() =&gt; ...)</code> wherever Nix would allocate a thunk. 🤮</p>\n<p>The <code>jinx</code> evaluation writes nothing to <code>/nix/store</code>. <code>jinx instantiate</code> writes the <code>.drv</code> closure and <code>nix-store --realise</code> builds the derivation.</p>\n<pre><code>$ jinx eval hello.nix\n{ drv = &quot;/nix/store/j8q5j0x495vfrxgha5m6q40yjdi59334-hello-2.12.2.drv&quot;;\nout = &quot;/nix/store/18bbdvag5v2f3d4y37pdbkzvh7s71cw4-hello-2.12.2&quot;; }\n$ nix-instantiate --eval --strict hello.nix\n{ drv = &quot;/nix/store/j8q5j0x495vfrxgha5m6q40yjdi59334-hello-2.12.2.drv&quot;;\nout = &quot;/nix/store/18bbdvag5v2f3d4y37pdbkzvh7s71cw4-hello-2.12.2&quot;; }</code></pre>\n<p>We produce <strong>the exact same</strong> <code>.drv</code> and the exact same <code>drvPath</code> Nix would. That is the key to unlocking nixpkgs in another ecosystem: the store does not know the difference.</p>\n<h2 id=\"nothing-lost-in-translation\"><a href=\"https://fzakaria.com#nothing-lost-in-translation\" rel=\"nofollow ugc noopener\">§</a>Nothing lost in translation</h2>\n<p>We can prove our translation is faithful. We evaluate every attribute of nixpkgs’ own CI configuration, <a href=\"https://github.com/NixOS/nixpkgs/blob/6510408d147d8c5e6d76043e899e9251756f6a75/ci/eval/outpaths.nix\" rel=\"nofollow ugc noopener\">ci/eval/outpaths.nix</a>, with <code>jinx</code> and with <a href=\"https://github.com/NixOS/nix-eval-jobs\" rel=\"nofollow ugc noopener\">nix-eval-jobs</a>, and compare all the <code>drvPaths</code>.</p>\n<p>On nixpkgs <a href=\"https://github.com/NixOS/nixpkgs/commit/6510408d147d8c5e6d76043e899e9251756f6a75\" rel=\"nofollow ugc noopener\">6510408d</a>, on x86_64-linux, aarch64-linux and aarch64-darwin:</p>\n<div class=\"table-wrap\"><table><thead><tr><th>attributes</th><th>identical drvPath</th><th>fail on both sides</th><th>differ</th><th>fail on one side only</th></tr></thead><tbody><tr><td>231,777</td><td>229,063</td><td>2,714</td><td>0</td><td>0</td></tr></tbody></table></div>\n<p>Zero differences or failing on one side.&lt;sup&gt;1&lt;/sup&gt;1The 2,714 that fail on both sides are the attributes Nix refuses to evaluate such as unsupported platform, broken, or insecure. </p>\n<h2 id=\"but-seriously-why\"><a href=\"https://fzakaria.com#but-seriously-why\" rel=\"nofollow ugc noopener\">§</a>But seriously, why?</h2>\n<p>Evaluating nixpkgs in TypeScript is ~3-5x slower. If that is all it did, that would not be very interesting. The point is not to make Nix faster, it is to make Nixpkgs <em>accessible</em> to the TypeScript ecosystem.</p>\n<p>Here are some “fun” things you can do with it.</p>\n<p><strong>Profile an evaluation.</strong> <code>node --cpu-prof</code> on <code>firefox.drvPath</code>, folded through the source maps so every frame is a line of nixpkgs. Click it for the whole SVG.</p>\n<p><strong>Complete nixpkgs in your editor.</strong> Automatic language server protocol support for every attribute in nixpkgs, with source maps to the original <code>.nix</code> file. TypeScript’s type system is expressive enough to model the shape of a derivation, so you get completion and type checking for free.</p>\n<p><strong>Step through a package.</strong> Source maps put a breakpoint on a <code>.nix</code> line. Open the compiled tree in VS Code or Chrome DevTools, break in <code>pkgs/by-name/he/hello/package.nix</code>, and step into <code>stdenv.mkDerivation</code> as it builds the attribute set.</p>\n<pre><code>$ node --inspect-brk --enable-source-maps src/cli/main.ts eval hello.nix</code></pre>\n<p><strong>Ask nixpkgs a question in a loop.</strong>  Interrogate and explore nixpkgs in a REPL or a script without having to remember the incantation to get a JSON dump of the whole tree and pipe it through <code>jq</code>.</p>\n<pre><code>import { loadNixpkgs } from &quot;jinx&quot;;\nconst pkgs = loadNixpkgs(&quot;/path/to/nixpkgs&quot;);\nconst gpl = [];\nfor (const name of Object.keys(pkgs.lib.licenses)) {\n  if (pkgs.lib.licenses[name].spdxId?.startsWith(&quot;GPL&quot;)) {\n    gpl.push(name);\n  }\n}\nconsole.log(JSON.stringify(gpl));</code></pre>\n<p><strong>Unit test Nix with node:test.</strong> A <code>lib</code> function or a package definition is a function in a module, so it has a unit test like any other function in any other language.</p>\n<pre><code>test(&quot;hello builds the derivation Nix would&quot;, () =&gt; {\n  assert.equal(\n    pkgs.hello.drvPath,\n    &quot;/nix/store/cnraiw0hbdl6jzdhbkn8dah2hqj7xgij-hello-2.12.3.drv&quot;,\n  );\n});</code></pre>\n<p><strong>Write a package in TypeScript.</strong> The compiled form from <code>jinx</code> is faithful to Nix down to every thunk, which makes it exact and miserable to write by hand. Alternatively, there is a much nicer hand-written form.\nIt interoperates with nixpkgs, so a package in TypeScript can call <code>stdenv.mkDerivation</code> and depend on anything in <code>pkgs</code>.</p>\n<pre><code>const hello = pkgs.stdenv.mkDerivation({\n  pname: &quot;hello&quot;,\n  version: &quot;2.12.2&quot;,\n  src: pkgs.fetchurl({\n    url: &quot;mirror://gnu/hello/hello-2.12.2.tar.gz&quot;,\n    hash: &quot;sha256-WpqZbcKSzCTc9BHO6H6S9qrluNE72caBm0x6nc4IGKs=&quot;,\n  }),\n  meta: { license: pkgs.lib.licenses.gpl3Plus },\n});\nconsole.log(hello.drvPath);</code></pre>\n<p><strong>It runs in a browser.</strong> TypeScript is supported in every modern browser.\nYou can evaluate nixpkgs in your tab, and it will fetch the compiled modules from GitHub as it needs them.&lt;sup&gt;2&lt;/sup&gt;2There is probably some fun <em>code golf</em> trying to create output paths that you then subsequently boot with <a href=\"https://fzakaria.com/2026/09/04/any-nix-package-live-in-your-browser\" rel=\"nofollow ugc noopener\">trynix</a> without ever using Nix. </p>\n<h2 id=\"is-it-slow\"><a href=\"https://fzakaria.com#is-it-slow\" rel=\"nofollow ugc noopener\">§</a>Is it slow?</h2>\n<p>Yep it’s slow, but crucially not <em>too</em> slow.</p>\n<p>When warm, with the modules compiled and V8’s code cache populated, per attribute on my laptop:</p>\n<div class=\"table-wrap\"><table><thead><tr><th>probe</th><th>nix</th><th>jinx warm</th><th>warm / nix</th><th>peak RSS nix</th><th>peak RSS jinx</th></tr></thead><tbody><tr><td><code>lib</code></td><td>0.53 s</td><td>1.31 s</td><td>2.5x</td><td>238 MB</td><td>501 MB</td></tr><tr><td><code>hello</code></td><td>0.37 s</td><td>1.72 s</td><td>4.6x</td><td>88 MB</td><td>376 MB</td></tr><tr><td><code>firefox</code></td><td>3.19 s</td><td>11.44 s</td><td>3.6x</td><td>488 MB</td><td>1608 MB</td></tr><tr><td><code>nixos</code></td><td>4.21 s</td><td>21.35 s</td><td>5.1x</td><td>603 MB</td><td>2377 MB</td></tr></tbody></table></div>\n<p>A first evaluation that has to compile every module it reaches is 3x to 10x in speed and memory is ~2x-4x because thunks are very expensive to the garbage-collector.&lt;sup&gt;3&lt;/sup&gt;3You can find the full benchmark table and the raw data on the <a href=\"https://fzakaria.github.io/jinx/bench/\" rel=\"nofollow ugc noopener\">benchmark page</a> </p>\n<p>Let’s say it is five times slower than a C++ interpreter that has had twenty years of people shaving it, from a brand new compiler that runs on a JavaScript engine. That strikes me as a fine place to start. 🤷</p>\n<h2 id=\"can-i-try\"><a href=\"https://fzakaria.com#can-i-try\" rel=\"nofollow ugc noopener\">§</a>Can I try?</h2>\n<p>The source is at <a href=\"https://github.com/fzakaria/jinx\" rel=\"nofollow ugc noopener\">github.com/fzakaria/jinx</a>.</p>\n<p>It has no dependencies, no native code, and no daemon. You can run it with Node 24 or in your browser. The compiled nixpkgs tree is at <a href=\"https://github.com/fzakaria/jinx-nixpkgs\" rel=\"nofollow ugc noopener\">github.com/fzakaria/jinx-nixpkgs</a>.</p>\n<p>I am a little annoyed that I have to keep disclosing that I used “AI” for the code. People seem to enjoy “outing” you for you it, as it is some morale failing. Let me save them the trouble: an AI co-authored a lot of this software with me.</p>\n<p>Here is your stamp. You can stop reading here if that is a dealbreaker, the derivations will not change their hashes for you.</p>\n<p>Nixpkgs is a TypeScript program now. <a href=\"https://fzakaria.github.io/jinx/docs/why\" rel=\"nofollow ugc noopener\">Go set a breakpoint in stdenv.</a></p>","headings":[{"level":2,"text":"§What it does","id":"what-it-does"},{"level":2,"text":"§Nothing lost in translation","id":"nothing-lost-in-translation"},{"level":2,"text":"§But seriously, why?","id":"but-seriously-why"},{"level":2,"text":"§Is it slow?","id":"is-it-slow"},{"level":2,"text":"§Can I try?","id":"can-i-try"}]}}