| name | bun-package-manager |
| description | Bun package management, lockfile policy, workspaces, CI integration, and Bun-native tooling |
Bun Package Manager
Overview
Bun best practices for frontend projects:
- Bun commands (add, remove, update, run, x)
- Lockfile management (
bun.lock text vs bun.lockb binary)
bunfig.toml configuration
- Workspace support (monorepos)
- CI/CD integration
- Troubleshooting common issues
- Bun-native tooling primer (
bun test, bun build, Bun runtime)
Hard Rules
These rules are NON-NEGOTIABLE. Violating any of them is a bug.
- ALWAYS use
bun for all package operations — NEVER npm, yarn, or pnpm in Bun projects
- ALWAYS use
bun run <script> (or implicit bun <script>) — NEVER npm run/yarn/pnpm run
- ALWAYS commit exactly one Bun lockfile (
bun.lock preferred, bun.lockb for legacy projects) — the lockfile MUST be in version control and the two formats MUST NOT coexist
- PREFER
bun.lock (text) over bun.lockb (binary) — Bun 1.2+ default, diff-friendly in PRs; migrate via bun install --save-text-lockfile when feasible
- ALWAYS use
bunx instead of npx for one-off package execution
- ALWAYS use
--frozen-lockfile in CI — NEVER allow lockfile modifications in CI
- NEVER delete the lockfile to "fix" issues — resolve the underlying problem
- NEVER mix package managers — if
pnpm-lock.yaml or package-lock.json exists, use that manager
- ALWAYS check for existing lockfile before running
bun install in a new project
Detecting Bun Projects
Before using Bun commands, verify the project uses Bun:
ls bun.lock bun.lockb 2>/dev/null
ls bunfig.toml 2>/dev/null
bun --version
Lockfile detection priority:
| Found | Manager |
|---|
bun.lock or bun.lockb | Bun |
pnpm-lock.yaml | pnpm |
package-lock.json | npm |
yarn.lock | yarn |
If two or more lockfiles are present, flag this as an anti-pattern and ask the user which manager to keep. Never mix package managers.
Essential Commands
Installing Dependencies
bun install
bun install --frozen-lockfile
bun install --production
bun install --save-text-lockfile
Adding Dependencies
bun add react
bun add -d vitest @testing-library/react
bun add react@18.3.1
cd packages/utils && bun add lodash
Removing Dependencies
bun remove lodash
cd packages/utils && bun remove lodash
Updating Dependencies
bun update
bun update react
bun update react --latest
bun update --dry-run
bun outdated
Running Scripts
bun run dev
bun run build
bun run test
bun run lint
bun dev
bun start
Note: bun test runs Bun's built-in test runner (NOT the test script in package.json). To run the test script use bun run test. See the "Bun-native Tooling" section below.
One-Off Execution (bunx)
bunx create-vite my-app --template react-ts
bunx shadcn@latest add button
bunx tsc --noEmit
npx create-vite my-app
Package.json Scripts Template
Standard Frontend Scripts
{
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview",
"test": "vitest run --coverage",
"test:watch": "vitest",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"typecheck": "tsc --noEmit",
"lint": "eslint . --fix",
"lint:check": "eslint .",
"format": "prettier --write .",
"format:check": "prettier --check ."
}
}
Alternative with Biome
{
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"test": "vitest run --coverage",
"test:watch": "vitest",
"typecheck": "tsc --noEmit",
"lint": "biome check --fix .",
"lint:check": "biome check .",
"format": "biome format --write .",
"format:check": "biome format ."
}
}
Invoke with bun run <script> (or the implicit bun <script> shortcut for unambiguous names).
Lockfile Management
Bun supports two lockfile formats:
| Format | File | Status | Use case |
|---|
| Text | bun.lock | Preferred (Bun 1.2+ default) | New projects, all PR-reviewable codebases |
| Binary | bun.lockb | Legacy (Bun 1.0–1.1 default) | Existing projects pre-migration |
Note on defaults: Bun 1.2+ writes text-form bun.lock by default at the CLI level, but the bunfig.toml flag [install].saveTextLockfile defaults to false. New projects created with bun init on Bun 1.2+ get bun.lock; if you don't see it, run bun install --save-text-lockfile once or set saveTextLockfile = true in bunfig.toml to make the behaviour explicit.
Why prefer bun.lock (text)?
- Reviewable in PRs — diffs are human-readable
- Conflict-resolvable — standard text merge tools work
- Tooling-friendly — scrapers, linters, and dependency auditors can read it
Migrating from bun.lockb → bun.lock
bun install --save-text-lockfile
git add bun.lock
git rm bun.lockb
git commit -m "chore: migrate bun lockfile to text form"
After migration, all collaborators and CI must use Bun 1.2 or later (which recognises bun.lock).
Coexistence rules
- Never have both
bun.lock and bun.lockb committed simultaneously. Pick one (prefer text) and delete the other.
- Never have any Bun lockfile and
pnpm-lock.yaml / package-lock.json / yarn.lock committed simultaneously.
bunfig.toml Configuration
bunfig.toml is Bun's TOML-format configuration file (analog of .npmrc).
Recommended settings for frontend projects
[install]
saveTextLockfile = true
exact = false
[install.cache]
dir = "~/.bun/install/cache"
Private registries / scoped tokens (analog of .npmrc auth)
[install.scopes]
"@my-private-scope" = { token = "$BUN_AUTH_TOKEN", url = "https://npm.my-company.com" }
What each setting does
| Setting | Value | Purpose |
|---|
install.saveTextLockfile | true | Use text-form bun.lock for PR-friendly diffs |
install.exact | false | Allow semver range updates (^x.y.z) |
Workspace Support (Monorepos)
Workspace Configuration
Bun uses the standard workspaces field in the root package.json (npm-style; no separate workspace file like pnpm-workspace.yaml).
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"apps/*",
"packages/*"
]
}
Directory Structure
my-monorepo/
package.json # Root — workspaces + shared dev deps
bun.lock
apps/
web/ # @myapp/web
package.json
admin/ # @myapp/admin
package.json
packages/
ui/ # @myapp/ui
package.json
utils/ # @myapp/utils
package.json
Workspace Commands
bun run --filter @myapp/web build
bun run --filter '*' build
cd packages/utils && bun add lodash
bun add -d typescript
Cross-Package Dependencies
{
"dependencies": {
"@myapp/ui": "workspace:*",
"@myapp/utils": "workspace:*"
}
}
workspace:* — Always resolves to the local workspace version. When publishing, replace with the actual version.
CI/CD Integration
GitHub Actions with Bun
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.0
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Type check
run: bun run typecheck
- name: Lint
run: bun run lint:check
- name: Test
run: bun run test
- name: Build
run: bun run build
Note: bun-version: latest exists but is discouraged — it produces non-reproducible CI runs because an unannounced Bun release can be picked up silently, and --frozen-lockfile gives false reproducibility confidence when the runtime itself is unpinned. Always pin (see Key CI Principle #2 below).
Key CI Principles
--frozen-lockfile — Lockfile must not change in CI. If it would, the build fails.
- Pin Bun version — Either via
bun-version: input or a .bun-version file at repo root.
- Pipeline order: typecheck → lint → test → build (fail fast on cheapest checks first).
Troubleshooting
Lockfile drift between bun.lock and bun.lockb
Problem: Project has both files committed (mixed state during migration).
Fix: Pick one (prefer bun.lock), delete the other.
bun install --save-text-lockfile
git add bun.lock
git rm bun.lockb
Peer Dependency Conflicts
Problem: bun install warns about peer dependency conflicts.
bun install 2>&1 | grep -i "peer"
bun update conflicting-package
For Bun, dependency overrides are configured via the top-level overrides field in package.json (npm-style). Bun also recognises the Yarn-style top-level resolutions field as a fallback for projects migrating from Yarn:
{
"overrides": {
"react": "^18.3.0"
}
}
Stale Lock File
bun install
git diff bun.lock
git add bun.lock
Never delete the lockfile to "fix" issues — it contains resolved versions for reproducible builds.
Cache Issues
bun pm cache rm
rm -rf node_modules
bun install
Module Resolution Issues
bun pm ls
bun pm ls --all
bun pm why react
Bun-native Tooling (Informational)
Bun ships with a built-in test runner, bundler, and JavaScript runtime. These are separate from the package-manager concerns above.
Hard rule: Do NOT migrate existing projects from Vitest/Vite to Bun-native tooling as part of unrelated work. Only adopt Bun-native tools if the project already uses them (detected via bunfig.toml [test] section, bun test in package.json scripts, bun build in package.json scripts, or explicit user request).
bun test
Detect: the project uses bun test if either of these is true:
bunfig.toml has a [test] section, OR
package.json scripts.test runs bun test (not vitest)
When the project uses bun test:
bun test
bun test --watch
bun test --coverage
bun test path/to/file.test.ts
Test files: *.test.ts, *.test.tsx, *.spec.ts, *.spec.tsx (configurable in bunfig.toml).
When the project uses Vitest, do NOT switch to bun test. Use bun run test (which executes the Vitest script).
bun build
Detect: the project uses bun build if package.json scripts.build runs bun build (not vite build).
When the project uses bun build:
bun build ./src/index.ts --outdir ./dist
bun build ./src/index.ts --outdir ./dist --minify
bun build ./src/index.ts --outdir ./dist --target browser
When the project uses Vite/Webpack/Rollup, do NOT switch to bun build. Use bun run build.
Bun as runtime
Bun can run TypeScript and JSX natively without Node.js. Adopt only if:
package.json scripts already use bun ./script.ts directly, OR
- The user explicitly asks for runtime adoption.
Common runtime flags:
bun --hot ./server.ts
bun --bun next dev
bun --bun vite
Adopting Bun runtime affects dependency compatibility — some packages assume Node APIs not present in Bun. Verify by running the dev server before committing the switch.
Common Mistakes
❌ Using npm/yarn/pnpm in a Bun Project
npm install lodash
yarn add lodash
pnpm add lodash
bun add lodash
❌ Running bun test when the project uses Vitest
bun test
bun run test
❌ Deleting Lock File
rm bun.lock
bun install
bun install
bun update affected-package
❌ Missing --frozen-lockfile in CI
- run: bun install
- run: bun install --frozen-lockfile
❌ Using npx Instead of bunx
npx create-vite my-app
bunx create-vite my-app
Summary
- ✅
bun for all package operations — never npm/yarn/pnpm in Bun projects
- ✅
bun run for scripts, bunx instead of npx
- ✅ Prefer
bun.lock (text) over bun.lockb (binary); always commit lockfile
- ✅
--frozen-lockfile in CI
- ✅
bunfig.toml with saveTextLockfile = true
- ✅
oven-sh/setup-bun@v2 for GitHub Actions
- ✅
workspace:* for monorepo cross-dependencies (no separate workspace file needed)
- ✅ Do NOT migrate from Vitest/Vite to
bun test/bun build as unrelated work