mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
feat(bundler): add statically-analyzable dead-code elimination via feature flags (#25462)
## Summary
- Adds `import { feature } from "bun:bundle"` for compile-time feature
flag checking
- `feature("FLAG_NAME")` calls are replaced with `true`/`false` at
bundle time
- Enables dead-code elimination through `--feature=FLAG_NAME` CLI
argument
- Works in `bun build`, `bun run`, and `bun test`
- Available in both CLI and `Bun.build()` JavaScript API
## Usage
```ts
import { feature } from "bun:bundle";
if (feature("SUPER_SECRET")) {
console.log("Secret feature enabled!");
} else {
console.log("Normal mode");
}
```
### CLI
```bash
# Enable feature during build
bun build --feature=SUPER_SECRET index.ts
# Enable at runtime
bun run --feature=SUPER_SECRET index.ts
# Enable in tests
bun test --feature=SUPER_SECRET
```
### JavaScript API
```ts
await Bun.build({
entrypoints: ['./index.ts'],
outdir: './out',
features: ['SUPER_SECRET', 'ANOTHER_FLAG'],
});
```
## Implementation
- Added `bundler_feature_flags` (as `*const bun.StringSet`) to
`RuntimeFeatures` and `BundleOptions`
- Added `bundler_feature_flag_ref` to Parser struct to track the
`feature` import
- Handle `bun:bundle` import at parse time (similar to macros) - capture
ref, return empty statement
- Handle `feature()` calls in `e_call` visitor - replace with boolean
based on flags
- Wire feature flags through CLI arguments and `Bun.build()` API to
bundler options
- Added `features` option to `JSBundler.zig` for JavaScript API support
- Added TypeScript types in `bun.d.ts`
- Added documentation to `docs/bundler/index.mdx`
## Test plan
- [x] Basic feature flag enabled/disabled tests (both CLI and API
backends)
- [x] Multiple feature flags test
- [x] Dead code elimination verification tests
- [x] Error handling for invalid arguments
- [x] Runtime tests with `bun run --feature=FLAG`
- [x] Test runner tests with `bun test --feature=FLAG`
- [x] Aliased import tests (`import { feature as checkFeature }`)
- [x] Ternary operator DCE tests
- [x] Tests use `itBundled` with both `backend: "cli"` and `backend:
"api"`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Alistair Smith <hi@alistair.sh>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
This commit is contained in:
@@ -65,6 +65,7 @@ In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Ot
|
||||
| `--chunk-names` | `--chunk-naming` | Renamed for consistency with naming in JS API |
|
||||
| `--color` | n/a | Always enabled |
|
||||
| `--drop` | `--drop` | |
|
||||
| n/a | `--feature` | Bun-specific. Enable feature flags for compile-time dead-code elimination via `import { feature } from "bun:bundle"` |
|
||||
| `--entry-names` | `--entry-naming` | Renamed for consistency with naming in JS API |
|
||||
| `--global-name` | n/a | Not applicable, Bun does not support `iife` output at this time |
|
||||
| `--ignore-annotations` | `--ignore-dce-annotations` | |
|
||||
|
||||
@@ -1141,6 +1141,84 @@ Remove function calls from a bundle. For example, `--drop=console` will remove a
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### features
|
||||
|
||||
Enable compile-time feature flags for dead-code elimination. This provides a way to conditionally include or exclude code paths at bundle time using `import { feature } from "bun:bundle"`.
|
||||
|
||||
```ts title="app.ts" icon="/icons/typescript.svg"
|
||||
import { feature } from "bun:bundle";
|
||||
|
||||
if (feature("PREMIUM")) {
|
||||
// Only included when PREMIUM flag is enabled
|
||||
initPremiumFeatures();
|
||||
}
|
||||
|
||||
if (feature("DEBUG")) {
|
||||
// Only included when DEBUG flag is enabled
|
||||
console.log("Debug mode");
|
||||
}
|
||||
```
|
||||
|
||||
<Tabs>
|
||||
<Tab title="JavaScript">
|
||||
```ts title="build.ts" icon="/icons/typescript.svg"
|
||||
await Bun.build({
|
||||
entrypoints: ['./app.ts'],
|
||||
outdir: './out',
|
||||
features: ["PREMIUM"], // PREMIUM=true, DEBUG=false
|
||||
})
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="CLI">
|
||||
```bash terminal icon="terminal"
|
||||
bun build ./app.ts --outdir ./out --feature PREMIUM
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
The `feature()` function is replaced with `true` or `false` at bundle time. Combined with minification, unreachable code is eliminated:
|
||||
|
||||
```ts title="Input" icon="/icons/typescript.svg"
|
||||
import { feature } from "bun:bundle";
|
||||
const mode = feature("PREMIUM") ? "premium" : "free";
|
||||
```
|
||||
|
||||
```js title="Output (with --feature PREMIUM --minify)" icon="/icons/javascript.svg"
|
||||
var mode = "premium";
|
||||
```
|
||||
|
||||
```js title="Output (without --feature PREMIUM, with --minify)" icon="/icons/javascript.svg"
|
||||
var mode = "free";
|
||||
```
|
||||
|
||||
**Key behaviors:**
|
||||
|
||||
- `feature()` requires a string literal argument — dynamic values are not supported
|
||||
- The `bun:bundle` import is completely removed from the output
|
||||
- Works with `bun build`, `bun run`, and `bun test`
|
||||
- Multiple flags can be enabled: `--feature FLAG_A --feature FLAG_B`
|
||||
- For type safety, augment the `Registry` interface to restrict `feature()` to known flags (see below)
|
||||
|
||||
**Use cases:**
|
||||
|
||||
- Platform-specific code (`feature("SERVER")` vs `feature("CLIENT")`)
|
||||
- Environment-based features (`feature("DEVELOPMENT")`)
|
||||
- Gradual feature rollouts
|
||||
- A/B testing variants
|
||||
- Paid tier features
|
||||
|
||||
**Type safety:** By default, `feature()` accepts any string. To get autocomplete and catch typos at compile time, create an `env.d.ts` file (or add to an existing `.d.ts`) and augment the `Registry` interface:
|
||||
|
||||
```ts title="env.d.ts" icon="/icons/typescript.svg"
|
||||
declare module "bun:bundle" {
|
||||
interface Registry {
|
||||
features: "DEBUG" | "PREMIUM" | "BETA_FEATURES";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Ensure the file is included in your `tsconfig.json` (e.g., `"include": ["src", "env.d.ts"]`). Now `feature()` only accepts those flags, and invalid strings like `feature("TYPO")` become type errors.
|
||||
|
||||
## Outputs
|
||||
|
||||
The `Bun.build` function returns a `Promise<BuildOutput>`, defined as:
|
||||
|
||||
Reference in New Issue
Block a user