Skip to content

ui-tui: bundle with esbuild, drop runtime node_modules#18036

Merged
ethernet8023 merged 8 commits into
mainfrom
fix/bundle-size
May 11, 2026
Merged

ui-tui: bundle with esbuild, drop runtime node_modules#18036
ethernet8023 merged 8 commits into
mainfrom
fix/bundle-size

Conversation

@ethernet8023

@ethernet8023 ethernet8023 commented Apr 30, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Replace the tsc + babel pipeline with a single esbuild invocation that produces a self-contained dist/entry.js. The nix TUI derivation no longer copies node_modules — only dist/ + package.json ship, shrinking the output from hundreds of MB to ~2.9 MB.
Also simplifies the TUI launcher: rips out the staleness-tracking maze (~100 LOC of mtime walks across source trees) in favor of three clear paths — prebuilt, dev, always-build. esbuild is fast enough (~1s) that caching the build was more bug surface than
win.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • ui-tui/scripts/build.mjs: new esbuild bundler. Aliases @hermes/ink to source (esbuild's __esm helper doesn't await nested async init, which breaks lazy-assigned exports like 'render' when re-exporting through a prebuilt submodule). Stubs
    react-devtools-core (dev-only). Injects a createRequire shim for transitive CJS deps. Strips the shebang from src/entry.tsx because Nix patchShebangs mangles '/usr/bin/env -S node --max-old-space-size=8192 --expose-gc' — it drops the 'node' token. The Python
    launcher always invokes node explicitly, so the shebang is redundant.
  • nix/tui.nix: installPhase no longer copies node_modules or the @hermes/ink packages dir.
  • nix/checks.nix: drop the 'node_modules present' assertion.
  • hermes_cli/main.py:
    • _tui_need_npm_install short-circuits when dist/entry.js exists and no package-lock.json is present. That is the prebuilt-bundle layout (nix / packaged release) and there is nothing to install. Without this, the launcher tried to npm install in a
      non-existent site-packages/ui-tui path.
    • Rewrote _make_tui_argv into three explicit paths: (1) HERMES_TUI_DIR set → run prebuilt dist/entry.js; (2) --devtsx src/entry.tsx (no build); (3) normal → always npm run build, then run.
    • Deleted dead helpers: _find_bundled_tui, _tui_build_needed, _hermes_ink_bundle_stale.
    • Added a guard so --dev + HERMES_TUI_DIR errors out instead of silently failing (prebuilt bundle has no source to hot-reload).
  • tests/hermes_cli/test_tui_npm_install.py: removed the 3 staleness-walk tests that exercised deleted helpers; kept all _tui_need_npm_install tests.

How to Test

  1. run tui under nix
  2. see it still works fine
  3. run tui not under nix
  4. see it still works fine
  5. try hermes --tui --dev with HERMES_TUI_DIR set → should error out cleanly
    :3

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform:

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

@alt-glitch alt-glitch added type/refactor Code restructuring, no behavior change P3 Low — cosmetic, nice to have comp/tui Terminal UI (ui-tui/ + tui_gateway/) area/nix Nix flake, NixOS module, container packaging labels Apr 30, 2026
@OutThisLife OutThisLife requested review from OutThisLife and Copilot and removed request for OutThisLife April 30, 2026 17:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors the Hermes TUI build/distribution pipeline to produce a single bundled dist/entry.js with esbuild, allowing Nix (and other packaged layouts) to ship the TUI without runtime node_modules.

Changes:

  • Add an esbuild-based bundler script to generate a self-contained dist/entry.js.
  • Update Nix packaging/checks to stop copying/asserting runtime node_modules.
  • Adjust the CLI’s TUI launcher logic to skip npm installs for the prebuilt-bundle layout (no package-lock.json + dist/entry.js present).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
ui-tui/scripts/build.mjs New esbuild bundling script producing a single dist/entry.js and stripping the shebang.
ui-tui/package.json Replace build pipeline with esbuild script; simplify dev script.
nix/tui.nix Stop installing/copying runtime node_modules; ship only dist/ + package.json.
nix/checks.nix Remove assertion that node_modules exists in the packaged TUI.
hermes_cli/main.py Skip npm install when running from a prebuilt, self-contained TUI bundle layout.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ui-tui/package.json Outdated
Comment thread hermes_cli/main.py
Comment thread ui-tui/scripts/build.mjs Outdated
Replace the tsc + babel pipeline with a single esbuild invocation that
produces a self-contained dist/entry.js. The nix TUI derivation no
longer copies node_modules — only dist/ + package.json ship, shrinking
the output from hundreds of MB to ~2.9 MB.

- ui-tui/scripts/build.mjs: new esbuild bundler. Aliases @hermes/ink
  to source (esbuild's __esm helper doesn't await nested async init,
  which breaks lazy-assigned exports like 'render' when re-exporting
  through a prebuilt submodule). Stubs react-devtools-core (dev-only).
  Injects a createRequire shim for transitive CJS deps. Strips the
  shebang from src/entry.tsx because Nix patchShebangs mangles
  '/usr/bin/env -S node --max-old-space-size=8192 --expose-gc' — it
  drops the 'node' token. The Python launcher always invokes node
  explicitly, so the shebang is redundant.
- nix/tui.nix: installPhase no longer copies node_modules or the
  @hermes/ink packages dir.
- nix/checks.nix: drop the 'node_modules present' assertion.
- hermes_cli/main.py: _tui_need_npm_install short-circuits when
  dist/entry.js exists and no package-lock.json is present. That is
  the prebuilt-bundle layout (nix / packaged release) and there is
  nothing to install. Without this, the launcher tried to npm install
  in a non-existent site-packages/ui-tui path.
github-actions Bot and others added 6 commits April 30, 2026 19:49
the esbuild pipeline (scripts/build.mjs) already bundles ink into a
single self-contained dist/entry.js.

remove the Dockerfile steps that manually copied packages/hermes-ink
into node_modules/@hermes/ink and ran a nested
npm install there.

- Dockerfile: simplify TUI build step to just 'npm run build'
- hermes_cli/main.py: _tui_build_needed now checks dist/entry.js
staleness against source files before falling back to the old
ink-bundle.js logic
- tests: update TUI npm install tests and drop the Dockerfile contract
test for the removed ink materialization step
@github-actions

github-actions Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor

🔎 Lint report: fix/bundle-size vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 8168 on HEAD, 8168 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 4303 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

The old mtime-tracking staleness machinery (_tui_build_needed,
_hermes_ink_bundle_stale, _find_bundled_tui) tried to avoid rebuilding
by comparing source timestamps to dist/entry.js. This was fragile and
added ~100 lines of code. Replace with three clear paths:

1. HERMES_TUI_DIR set (prebuilt/nix): just node dist/entry.js, no build
2. --dev mode: tsx src/entry.tsx, no build, hot reload
3. Normal: always npm run build (esbuild is ~1s, correctness > caching)

Also error when HERMES_TUI_DIR is set with --dev (footgun: prebuilt
bundle has no source code to hot-reload).
@ethernet8023 ethernet8023 merged commit 825bd50 into main May 11, 2026
18 of 21 checks passed
@ethernet8023 ethernet8023 deleted the fix/bundle-size branch May 11, 2026 21:46
02356abc pushed a commit to 02356abc/hermes-agent that referenced this pull request May 14, 2026
ui-tui: bundle with esbuild, drop runtime node_modules
jsboige pushed a commit to jsboige/hermes-agent that referenced this pull request May 14, 2026
ui-tui: bundle with esbuild, drop runtime node_modules
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
ui-tui: bundle with esbuild, drop runtime node_modules
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/nix Nix flake, NixOS module, container packaging comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have type/refactor Code restructuring, no behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants