Switch package manager from npm to bun (experiment + benchmark) - #97204
Draft
roryabraham wants to merge 3 commits into
Draft
Switch package manager from npm to bun (experiment + benchmark)#97204roryabraham wants to merge 3 commits into
roryabraham wants to merge 3 commits into
Conversation
Migrate the root Expensify/App project from npm to bun as the package manager. Mobile-Expensify (a separate-repo submodule) stays on npm until a companion PR migrates it. Key changes: - Generate bun.lock via `bun pm migrate` (preserves npm's exact locked versions so all patch-package patches still apply); remove package-lock.json. - bunfig.toml: linker = "hoisted" so patch-package finds packages at the top level of node_modules (Bun's default isolated linker does not). - package.json: packageManager "bun@1.3.14"; trustedDependencies allowlist for build deps (Bun blocks dependency lifecycle scripts by default); remove 9 nested overrides Bun does not support (redundant with direct deps) and add a flat typescript-eslint override for clean-resolve stability. - postInstall.sh: prune Bun's dangling workspace symlinks so the victory-chart-renderer workspace resolves deps from the hoisted root; keep the Mobile-Expensify submodule install on npm. - Convert scripts and CI to bun/bunx (setupNode installs bun and runs `bun install --frozen-lockfile`, cache keys rekeyed on bun.lock); version tooling uses `bun pm version`; remove the now-unnecessary lockfile version normalization. Regenerate the ncc-compiled GitHub Action bundles under bun. - Remove .npmrc (engine-strict); pin the toolchain via .bun-version. - Update contributor setup docs to bun. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up from peer review of the npm→bun migration. Convert developer- and agent-facing references that the first pass missed: - CLAUDE.md: Command Reference, Post-Edit Checklist, and Dev Server commands npm→bun (leaving the third-party `npm install -g agent-device`). - lefthook.yml: pre-commit oxfmt hook `npx`→`bunx`. - src/CONST/index.ts: stale comment referenced the removed package-lock.json; point it at bun.lock. - .oxfmtrc.json: drop dead package-lock.json ignore entry. - Docs/config consistency: HYBRID_APP.md, SETUP_IOS/ANDROID (`npx rock`→`bunx rock`), victory-chart-renderer README, docs/README, check-compiler.sh, ExpensifyNitroUtils scripts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Final doc-consistency pass from peer review. Convert the contributor guides and Claude skill docs that still referenced npm to bun (npm run -> bun run, npx -> bunx): LINTING, REACT_COMPILER, STYLE, STORYBOOK, DEPLOYING, REASSURE_PERFORMANCE_TEST, PATCHES, PERFORMANCE, and the agent-device / playwright-app-testing / measure-telemetry-span skills. Third-party global installs (npm install -g agent-device, npm root -g) are left as-is. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
Draft / experiment. This migrates the package manager from npm to bun and benchmarks install performance. Opened as a draft because the CI-side changes cannot be validated locally (they only run on GitHub Actions) and a few items need a human decision before merge (see Open items below). Not tied to a GitHub issue.
Explanation of Change
Switches the root
Expensify/Appproject from npm to bun as the package manager.bunwas already present as a runtime (test runner, several scripts,.bun-version,engines.bun); this makes it the install/lockfile toolchain too. The Mobile-Expensify submodule (a separate repo) intentionally stays on npm until a companion PR migrates it.Install benchmark (npm vs bun), median wall-clock on this branch (macOS/APFS/arm64):
--ignore-scripts(pure resolve+link)--ignore-scripts(pure resolve+link)Cold = empty package-manager cache; warm = primed cache (both with
node_modulesremoved). Caches were isolated to temp dirs so global caches were untouched. The warm case (the common one in dev and CI-with-cache) is dramatically faster.What the migration changes (and the bun-specific gotchas it resolves)
bun.lockviabun pm migrate(preserves npm's exact locked versions so all 148 patch-package patches still apply unchanged); removepackage-lock.json. A plainbun installthat auto-migrates the npm lockfile 404s on annpm:alias (strip-ansi-cjs);bun pm migrateavoids that.bunfig.toml:linker = "hoisted"— patch-package walks a flatnode_modules/<pkg>tree, which bun's default isolated linker does not produce.package.json: addpackageManager: "bun@1.3.14"; add atrustedDependenciesallowlist (bun blocks dependency lifecycle scripts by default — this re-enables the ones that download/build binaries:@sentry/cli,esbuild,sharp,protobufjs,bun,lefthook,shellcheck,@expensify/nitro-utils). Remove 9 nestedoverridesbun doesn't support (all redundant with direct deps — verified singleexpo@56.0.8copy, etc.) and add a flattypescript-eslintoverride so the pin is clean-resolve-stable.postInstall.sh: prune bun's dangling workspace symlinks so thevictory-chart-rendererworkspace resolves its deps from the hoisted root; keep the Mobile-Expensify submodule install onnpm i.setupNodecomposite action installs bun (oven-sh/setup-bun) and runsbun install --frozen-lockfile; cache keys rekeyed onbun.lockand~/.bun/install/cache; thejqlockfile-version-normalization is removed (bun.lock does not embed the root version). All workflow/scriptnpm run→bun run,npx→bunx. Version tooling usesbun pm version. The ncc-compiled GitHub Action bundles were regenerated under bun (deterministic;validateGithubActionsreproduces them).CLAUDE.md, and skills updated to bun..npmrc(engine-strict) removed; toolchain pinned via.bun-version+packageManager+engines.Peer review (3 rounds, converged)
Independent subagent reviewers audited the diff across install/lockfile, CI, scripts/version-tooling, benchmark methodology, submodule/docs, and the regenerated action bundles.
npm test/npx) — all fixed.supports-colorviadebug's unpinned optional peer — not an unbuilt-dep bug). New items were doc-consistency + aCLAUDE.mdfootgun → fixed.Verified locally (green):
bun installend-to-end (2358 pkgs), 148 patches apply,bun install --frozen-lockfilestable,bun run typecheck-tsgo,bun run test:bun(32/32).Warning
Open items before merge — (1) pin
oven-sh/setup-bunto a full commit SHA (currently@v2with a TODO, matching the repo's SHA-pinning convention); (2) the CI changes can only be validated by running the workflows; (3) a companion Mobile-Expensify PR must migrate that submodule to bun before its CIMobile-Expensify/bun.lockcache path and a bun-based old-dot install become correct; (4)engine-strictinstall-time enforcement is lost (bun ignores it) — CI still pins node via.nvmrc.Benchmark caveats: numbers are macOS/APFS/arm64 local, not Linux CI — the warm speedup leans partly on APFS copy-on-write and won't transfer 1:1. npm was measured with
package-lock.jsonpresent (pre-removal) againstmain'spackage.json, i.e. the same version setbun.lockwas migrated from. The--ignore-scriptsrows are the clean pure-PM comparison; thefullrows also reflect bun running fewer dependency lifecycle scripts.Fixed Issues
$ N/A — exploratory package-manager migration, not tied to a GitHub issue.
PROPOSAL: N/A
Tests
bun installfrom the repo root; verify it completes and printspatch-package succeeded without errors or warnings.bun install --frozen-lockfile; verify it succeeds with no lockfile-drift error.bun run typecheck-tsgo; verify it exits 0.bun run test:bun; verify all tests pass (32/32).bun run weband verify the dev server boots athttps://dev.new.expensify.com:8082/.Offline tests
N/A — this is a build-tooling/package-manager change with no runtime or network behavior in the app.
QA Steps
[No QA] — package-manager/tooling change with no user-facing behavior. Validation is via CI (install, lint, typecheck, test, build workflows) and the local Tests above.
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectionAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))bun run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
N/A — package-manager/tooling change with no UI impact.
Android: Native
N/A
Android: mWeb Chrome
N/A
iOS: Native
N/A
iOS: mWeb Safari
N/A
MacOS: Chrome / Safari
N/A
🤖 Generated with Claude Code