From 0792ff4dc022a6143865412ede6977751c344d25 Mon Sep 17 00:00:00 2001 From: asepharyana Date: Mon, 10 Aug 2026 22:17:38 +0700 Subject: [PATCH] perf(nix): prune devDependencies from shipped node_modules gateway output 1.4G -> 424M (-70%), backend 198M -> 60M (-70%). - pruneProd: delete every .pnpm dir not in 'pnpm list --prod' graph (biome/typescript/esbuild/drizzle-kit/vitest/tsx ~150MB+) then drop dangling symlinks (top-level, scoped dirs, hoist, .bin) so stdenv noBrokenSymlinks fixup passes. - NOT using 'pnpm install --prod': it collapses the public-hoist dir (.pnpm/node_modules) that peer resolution relies on for @lng2004/node-datachannel + @seydx/node-av-linux-x64 (voice breaks). - node-datachannel: strip build/_deps (cmake FetchContent ~380MB) + nested node_modules (nw-gyp/typescript/puppeteer ~380MB) after compile; runtime needs only build/Release/node_datachannel.node. - verified: native binaries (datachannel/opus/zeromq) intact, all 27 runtime modules resolve, dev tools 0. --- flake.nix | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/flake.nix b/flake.nix index 23609bb..bbf071a 100644 --- a/flake.nix +++ b/flake.nix @@ -59,6 +59,36 @@ pnpm rebuild 2>&1 || true ''; + # Shrink the shipped node_modules to production deps only. The full + # install's .pnpm virtual store carries dev-only packages (biome, + # typescript, esbuild, drizzle-kit, vitest, ... ~150MB+) that are never + # needed at runtime, so we delete every .pnpm dir that is not part of + # the resolved production graph (`pnpm list --prod`). + # + # NOTE: do NOT use `pnpm install --prod` here — it collapses the + # public-hoist dir (.pnpm/node_modules) that runtime peer resolution + # relies on (e.g. @lng2004/node-datachannel and @seydx/node-av-linux-x64 + # are only reachable through it), silently breaking voice/screenshare. + # Instead we keep the full install's symlink layout and only prune + # orphaned package dirs + broken symlinks. + # Must run AFTER tsc (typescript is a devDep) and after native builds. + pruneProd = '' + echo "=== Pruning devDependencies (production-only node_modules) ===" + pnpm list --prod --depth 999 --parseable 2>/dev/null \ + | grep -o '\.pnpm/[^/]*' | sort -u > $TMPDIR/prod-pnms.txt + ( cd node_modules/.pnpm \ + && for d in */; do \ + d="''${d%/}"; \ + [ "$d" = "node_modules" ] && continue; \ + grep -qF ".pnpm/$d" $TMPDIR/prod-pnms.txt || rm -rf "$d"; \ + done ) || true + # Drop symlinks whose .pnpm target was pruned (top-level, scoped dirs, + # hoist, .bin — any depth). Mirrors stdenv's noBrokenSymlinks check, + # which would otherwise fail the fixupPhase. + find node_modules -type l ! -exec test -e {} \; -delete 2>/dev/null || true + du -sh node_modules + ''; + # ---- Backend ---- backend = pkgs.stdenv.mkDerivation { pname = "gmw-backend"; @@ -97,7 +127,7 @@ console.log('Fixed ' + count + ' files'); " echo "=== Build complete ===" - ''; + '' + pruneProd; installPhase = '' mkdir -p $out/lib/gmw-backend @@ -170,6 +200,20 @@ WRAPPER fi fi done + echo "=== Cleaning node-datachannel build tree ===" + # Runtime only needs build/Release/node_datachannel.node + dist/ — + # the cmake FetchContent sources (build/_deps, ~380MB), intermediate + # cmake files, and the nested node_modules of build tooling (nw-gyp, + # typescript, puppeteer, eslint, ... ~380MB) are build-time only. + for pkg in node_modules/.pnpm/@lng2004+node-datachannel@*/node_modules/@lng2004/node-datachannel + do + if [ -d "$pkg" ]; then + ( cd "$pkg/build" \ + && find . -mindepth 1 -maxdepth 1 ! -name 'Release' -exec rm -rf {} + ) 2>/dev/null || true + rm -rf "$pkg/node_modules" 2>/dev/null || true + echo "node-datachannel cleaned: $(du -sh "$pkg" | cut -f1)" + fi + done echo "=== Compiling TypeScript ===" npx tsc 2>&1 echo "=== Fixing @/ path aliases to relative paths ===" @@ -198,7 +242,7 @@ WRAPPER console.log('Fixed ' + count + ' files'); " echo "=== Build complete ===" - ''; + '' + pruneProd; installPhase = '' mkdir -p $out/lib/gmw-discord-gateway