Skip to content

nix-typescript SKILL.md: enrich with Bun-flavored patterns #34

@srid

Description

@srid

Suggestion to enrich skills/nix-typescript/SKILL.md with conventions that have crystallized around recent Bun-based projects (srid/anywhen, srid/drishti). The current skill is implicitly Node/tsx-shaped; the Bun path is now consistent enough across two real apps that the patterns belong as first-class guidance.

Conventions worth lifting

Flake + npins layout

  • One flake input only: juspay/bun2nix#rawflake. nixpkgs and any other Nix-store inputs (e.g. @kolu/surface source) are pinned via npinsnpins/sources.json. Cold nix develop eval stays sub-second.
  • lib.mkBun2nix { pkgs } from the rawflake branch lets you feed your own npins-pinned pkgs, so bun2nix's transitive nixpkgs never enters your eval graph.
  • flake.lock and npins/sources.json end up as two independent pin surfaces (bun2nix's CLI build vs. the app's build). That partition is the property worth naming explicitly.

Workspace + hoisted linker

  • bunfig.toml: [install] linker = "hoisted" is load-bearing whenever you hydrate any package source into node_modules/ from outside Bun (the workspace pattern, the Nix-store-symlink pattern). Isolated linker leaves each workspace package's deps in its own node_modules, breaking transitive resolution from the hydrated source.

Build derivation

  • bun2nix.hook as nativeBuildInputs; bunDeps = bun2nix.fetchBunDeps { bunNix = ./bun.nix; }.
  • dontUseBunBuild = true to skip the hook's default bun build --compile (single-binary mode is wrong for server-plus-static-bundle apps).
  • Custom buildPhase that invokes a Bun script (e.g. bun src/server/build.ts dist) — same code path the dev server uses at startup, so dev and Nix share one bundler implementation.
  • dontFixup = true and dontPatchShebangs = true — Bun apps don't have shebangs we care about, and the fixupPhase walking node_modules is pure overhead.

Client bundler (when SolidJS / JSX is involved)

  • Bun's default JSX transform emits React.createElement, which breaks SolidJS. A ~20-line solidJsxPlugin driving babel-preset-solid + babel-preset-typescript via Bun.build's plugin array is the working pattern.
  • Bun.serve's HTML-import bundler does not honor bunfig preload plugins — so Bun.serve+HMR is unavailable for SolidJS today. Worth flagging.
  • For Tailwind v4: invoke @tailwindcss/cli via createRequire(import.meta.url).resolve("@tailwindcss/cli/package.json") (NOT bunx, which falls back to a network fetch and fails in the Nix sandbox). The transitive @parcel/watcher dlopen of libstdc++ requires LD_LIBRARY_PATH = ${stdenv.cc.cc.lib}/lib:$LD_LIBRARY_PATH in the buildPhase.

Vendoring Nix-store source into node_modules

  • For workspace-style packages that aren't on npm (e.g. @kolu/surface), expose them through a Nix overlay (one per leaf, or via a mkPackage factory if N > 1), set *_KOLU_* env vars in nix/env.nix, and hydrate via a shell script: cp -rL "$src" node_modules/<dest>; chmod -R u+w …. Three callers per project (shellHook, justfile install, postBunNodeModulesInstallPhase).

Common pitfalls worth a heading

  • bun.lock + bun.nix both committed; CI gates on freshness.
  • bun2nix builds via juspay/bun2nix Cargo crates — current bun2nix flake pins old nixpkgs that fails to download crates from crates.io (User-Agent now mandatory, fetchurl path stuck with SSL_CERT_FILE=/no-cert-file.crt). Workaround: pre-download with proper UA and nix store add-file until the upstream pin bumps.
  • Multi-system flake outputs via eachSystem over [x86_64-linux, aarch64-linux, aarch64-darwin] so nix run github:… works without --system flag.

Example projects

  • srid/anywhen — single-package app, no workspace; canonical anywhen-pattern reference.
  • srid/drishti — workspace, two @kolu/* packages hydrated, SolidJS + Tailwind v4 client; mostly extends the anywhen pattern.

Happy to draft an actual PR against the skill if the structure above looks right.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions