Bunup
An extremely fast, zero-config bundler for TypeScript & JavaScript, powered by Bun and oxc.
Benchmarks
Bunup outperforms other popular bundlers by a significant margin:
Bundler | Format | Build Time | Build Time (with dts) |
---|---|---|---|
bunup | esm, cjs | 0.86ms ⚡️ | 7.54ms ⚡️ |
tsdown | esm, cjs | 3.57ms | 15.73ms |
unbuild | esm, cjs | 12.79ms | 252.01ms |
tsup | esm, cjs | 10.53ms | 665.55ms |
Lower build time is better. Benchmark run on the same code with identical output formats.
To run the benchmarks yourself, clone this repo and run pnpm benchmark
and check results.md
in the benchmarks folder.
What Can It Bundle?
Bunup handles various file types:
- JavaScript/TypeScript (
.js
,.jsx
,.ts
,.tsx
,.mjs
,.cjs
,.mts
,.cts
) - Data files (
.json
,.toml
,.txt
) - Parsed and inlined automatically - Assets (images, fonts, etc.) - Handled as external files
Prerequisites
Bunup requires Bun to be installed on your system. Bun is a fast all-in-one JavaScript runtime that powers Bunup's exceptional performance.
To install Bun, please visit the official Bun installation page.
Quick Start
Get started with Bunup in seconds - install, configure, and build your TypeScript/JavaScript projects with minimal setup.
Installation
bun add --dev bunup
pnpm add --save-dev bunup
npm install --save-dev bunup
yarn add --dev bunup
Basic Usage
Create a simple TypeScript file:
// src/index.ts
export function greet(name: string): string {
return `Hello, ${name}!`;
}
Bundle it with bunup:
bunup src/index.ts
This will create a bundled output in the dist
directory with CommonJS format (the default).
Using with package.json
Add a build script to your package.json
:
{
"name": "my-package",
"scripts": {
"build": "bunup src/index.ts --format esm,cjs --dts"
}
}
Then run:
npm run build
Configuration File
Create a bunup.config.ts
file for more control:
import {defineConfig} from 'bunup';
export default defineConfig({
entry: ['src/index.ts'],
outDir: 'dist',
format: ['esm', 'cjs'],
dts: true,
minify: true,
});
Configuration
Bunup offers flexible configuration options to customize your build process. You can configure Bunup through a configuration file or command-line arguments.
Configuration File
Bunup supports configuration files in multiple formats:
bunup.config.ts
bunup.config.js
bunup.config.mjs
bunup.config.cjs
bunup.config.json
bunup.config.jsonc
Example configuration:
import {defineConfig} from 'bunup';
export default defineConfig({
// Name for this build configuration (used in logs)
name: 'my-library',
// Entry points (can be an array or object)
entry: ['src/index.ts', 'src/cli.ts'],
// Output directory
outDir: 'dist',
// Output formats
format: ['esm', 'cjs'],
// TypeScript declaration generation
dts: true,
// Target environment
target: 'node',
// Minification options
minify: true,
// External dependencies
external: ['react', 'react-dom'],
// Clean output directory before build
clean: true,
});
You can also export an array of configurations:
export default defineConfig([
{
name: 'node',
entry: ['src/index.ts'],
format: ['cjs'],
target: 'node',
},
{
name: 'browser',
entry: ['src/index.ts'],
format: ['esm', 'iife'],
target: 'browser',
},
]);
JSON Configuration
If you prefer using a JSON configuration file (bunup.config.json
or bunup.config.jsonc
), the structure is similar but with a few differences:
{
"name": "my-library",
"entry": ["src/index.ts", "src/cli.ts"],
"outDir": "dist",
"format": ["esm", "cjs"],
"dts": true,
"target": "node",
"minify": true,
"external": ["react", "react-dom"],
"clean": true
}
To define multiple configurations in JSON, use the bunup
property with an array:
{
"bunup": [
{
"name": "node",
"entry": ["src/index.ts"],
"format": ["cjs"],
"target": "node"
},
{
"name": "browser",
"entry": ["src/index.ts"],
"format": ["esm", "iife"],
"target": "browser"
}
]
}
For autocomplete and validation in your JSON configuration files, you can reference the Bunup JSON schema:
{
"$schema": "https://bunup.arshadyaseen.com/schema.json",
"name": "my-library",
"entry": ["src/index.ts"]
}
CLI Options
Bunup supports various command-line options:
Option | Alias | Description | Default |
---|---|---|---|
--entry <path> | Entry file path | [] | |
--entry.<name> <path> | Named entry file path | - | |
--out-dir <dir> | -o | Output directory | dist |
--format <formats> | -f | Output formats (comma-separated: esm,cjs,iife) | cjs |
--minify | -m | Enable all minification options | false |
--minify-whitespace | -mw | Minify whitespace | false |
--minify-identifiers | -mi | Minify identifiers | false |
--minify-syntax | -ms | Minify syntax | false |
--watch | -w | Watch mode | false |
--dts | -d | Generate TypeScript declarations | false |
--external <deps> | -e | External dependencies (comma-separated) | [] |
--no-external <deps> | -ne | Force include dependencies (comma-separated) | - |
--target <target> | -t | Target environment (node, browser, bun) | node |
--clean | -c | Clean output directory before build | true |
--splitting | -s | Enable code splitting | Format dependent |
--sourcemap <type> | -sm | Sourcemap generation (none,linked,external,inline) | none |
--name <name> | -n | Name for this build configuration | - |
--help | -h | Display help information | - |
Entry Points
Bunup supports multiple ways to define entry points. Entry points are the source files that Bunup will use as starting points for bundling.
Single Entry Point
The simplest way to define an entry point is to provide a single file path:
bunup src/index.ts
This will generate an output file named after the input file (e.g., dist/index.js
).
Multiple Entry Points
You can specify multiple entry points in several ways:
Using the CLI with Multiple Positional Arguments
bunup src/index.ts src/cli.ts
This will generate output files named after each input file (e.g., dist/index.js
and dist/cli.js
).
Using the CLI with --entry Flag Multiple Times
bunup --entry src/index.ts --entry src/cli.ts
This achieves the same result as using positional arguments.
Using Named Entries in the CLI
Named entries allow you to specify custom output filenames:
bunup --entry.main src/index.ts --entry.cli src/cli.ts
This will generate output files with the specified names (e.g., dist/main.js
and dist/cli.js
).
Using a Configuration File with an Array
export default defineConfig({
entry: ['src/index.ts', 'src/cli.ts'],
});
This will generate output files named after each input file.
Using a Configuration File with Named Entries
export default defineConfig({
entry: {
main: 'src/index.ts',
cli: 'src/cli.ts',
utils: 'src/utils/index.ts',
},
});
This will generate output files with the specified names (e.g., dist/main.js
, dist/cli.js
, and dist/utils.js
).
Why Use Named Entries?
Named entries are useful when:
- You want to customize the output filenames
- Your input filenames don't match your desired output names
- You have multiple files with the same basename in different directories
- You want to create a specific public API structure
For example, if you have src/utils/index.ts
and src/components/index.ts
, using named entries prevents naming conflicts in the output.
Output Formats
Bunup supports three output formats:
- esm: ECMAScript modules (
.mjs
extension) - cjs: CommonJS modules (
.js
or.cjs
extension) - iife: Immediately Invoked Function Expression (
.global.js
extension)
You can specify one or more formats:
In the CLI
# Single format
bunup src/index.ts --format esm
# Multiple formats (comma-separated, no spaces)
bunup src/index.ts --format esm,cjs,iife
In a Configuration File
export default defineConfig({
entry: ['src/index.ts'],
// Single format
format: ['esm'],
// Or multiple formats
// format: ['esm', 'cjs', 'iife'],
});
Output File Extensions
The file extensions are determined automatically based on the format and your package.json type
field:
Format | package.json type: "module" | package.json type: "commonjs" or unspecified |
---|---|---|
esm | .mjs | .mjs |
cjs | .cjs | .js |
iife | .global.js | .global.js |
TypeScript Declarations
Bunup can generate TypeScript declaration files (.d.ts
) for your code:
Basic Declaration Generation
To generate declarations for all entry points:
# CLI
bunup src/index.ts --dts
# Configuration file
export default defineConfig({
entry: ['src/index.ts'],
dts: true,
});
Custom Declaration Entry Points
For more control, you can specify custom entry points for declarations:
export default defineConfig({
entry: ['src/index.ts', 'src/cli.ts'],
dts: {
// Only generate declarations for index.ts
entry: ['src/index.ts'],
},
});
Named Declaration Entries
You can use named entries for declarations:
export default defineConfig({
entry: {
main: 'src/index.ts',
cli: 'src/cli.ts',
},
dts: {
entry: {
// Generate types.d.ts from index.ts
types: 'src/index.ts',
},
},
});
Custom TypeScript Configuration
You can specify a custom tsconfig file for declaration generation:
export default defineConfig({
entry: ['src/index.ts'],
dts: true,
preferredTsconfigPath: './tsconfig.build.json',
});
Declaration File Extensions
Declaration file extensions follow the same pattern as JavaScript files:
Format | package.json type: "module" | package.json type: "commonjs" or unspecified |
---|---|---|
esm | .d.mts | .d.mts |
cjs | .d.cts | .d.ts |
iife | .d.ts | .d.ts |
External Dependencies
By default, Bunup treats all dependencies from your package.json
(dependencies
and peerDependencies
) as external. This means they won't be included in your bundle.
Specifying External Dependencies
You can explicitly mark additional packages as external:
In the CLI
# Single external dependency
bunup src/index.ts --external lodash
# Multiple external dependencies (comma-separated, no spaces)
bunup src/index.ts --external lodash,react,react-dom
In a Configuration File
export default defineConfig({
entry: ['src/index.ts'],
external: ['lodash', 'react', '@some/package'],
});
Including Specific External Dependencies
You can force include specific dependencies that would otherwise be external:
In the CLI
bunup src/index.ts --external lodash --no-external lodash/merge
In a Configuration File
export default defineConfig({
entry: ['src/index.ts'],
external: ['lodash'],
noExternal: ['lodash/merge'], // Include lodash/merge even though lodash is external
});
Both external
and noExternal
support string patterns and regular expressions.
Code Splitting
Code splitting allows Bunup to split your code into multiple chunks for better performance and caching.
Default Behavior
- Code splitting is enabled by default for ESM format
- Code splitting is disabled by default for CJS and IIFE formats
Configuring Code Splitting
You can explicitly enable or disable code splitting:
In the CLI
# Enable code splitting
bunup src/index.ts --splitting
# Disable code splitting
bunup src/index.ts --splitting=false
In a Configuration File
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
// Enable for all formats
splitting: true,
// Or disable for all formats
// splitting: false,
});
Minification
Bunup provides several minification options to reduce the size of your output files.
Basic Minification
To enable all minification options:
# CLI
bunup src/index.ts --minify
# Configuration file
export default defineConfig({
entry: ['src/index.ts'],
minify: true,
});
Granular Minification Control
You can configure individual minification options:
In the CLI
# Minify whitespace only
bunup src/index.ts --minify-whitespace
# Minify whitespace and syntax, but not identifiers
bunup src/index.ts --minify-whitespace --minify-syntax
In a Configuration File
export default defineConfig({
entry: ['src/index.ts'],
// Configure individual options
minifyWhitespace: true,
minifyIdentifiers: false,
minifySyntax: true,
});
The minify
option is a shorthand that enables all three specific options. If you set individual options, they take precedence over the minify
setting.
Source Maps
Bunup can generate source maps for your bundled code:
# CLI
bunup src/index.ts --sourcemap linked
# Configuration file
export default defineConfig({
entry: ['src/index.ts'],
sourcemap: 'linked',
});
Available sourcemap values:
none
linked
external
inline
For detailed explanations of these values, see the Bun documentation on source maps.
Watch Mode
Bunup can watch your files for changes and rebuild automatically:
# CLI
bunup src/index.ts --watch
# Configuration file
export default defineConfig({
entry: ['src/index.ts'],
watch: true,
});
In watch mode, Bunup will monitor your source files and their dependencies, rebuilding only what's necessary when files change.
Target Environments
Bunup allows you to specify the target environment for your bundle:
# CLI
bunup src/index.ts --target browser
# Configuration file
export default defineConfig({
entry: ['src/index.ts'],
target: 'browser',
});
Available targets:
node
(default): Optimized for Node.jsbrowser
: Optimized for browsersbun
: Optimized for the Bun runtime
Output Directory
You can specify where Bunup should output the bundled files:
# CLI
bunup src/index.ts --out-dir build
# Configuration file
export default defineConfig({
entry: ['src/index.ts'],
outDir: 'build',
});
The default output directory is dist
.
Cleaning the Output Directory
By default, Bunup cleans the output directory before each build. You can disable this behavior:
# CLI
bunup src/index.ts --clean=false
# Configuration file
export default defineConfig({
entry: ['src/index.ts'],
clean: false,
});
Build Callbacks
Bunup provides callback functions that allow you to execute custom logic during the build process.
onBuildEnd
The onBuildEnd
callback runs after the build process completes. This is useful for performing custom post-build operations:
export default defineConfig({
entry: ['src/index.ts'],
onBuildEnd: () => {
console.log('Build completed successfully!');
// Perform post-build operations here
// e.g., copying files, running additional tools, etc.
},
});
In watch mode, the onBuildEnd
callback is executed after each successful rebuild.
Named Configurations
You can give your build configurations names for better logging:
# CLI
bunup src/index.ts --name my-library
# Configuration file
export default defineConfig({
name: 'my-library',
entry: ['src/index.ts'],
});
This is especially useful when you have multiple configurations:
export default defineConfig([
{
name: 'node-build',
entry: ['src/index.ts'],
format: ['cjs'],
target: 'node',
},
{
name: 'browser-build',
entry: ['src/index.ts'],
format: ['esm', 'iife'],
target: 'browser',
},
]);
Workspaces
Bunup provides robust support for monorepo workspaces, allowing you to define build configurations for multiple packages in a single configuration file.
Basic Workspace Configuration
To define a workspace configuration, use the defineWorkspace
function:
import {defineWorkspace} from 'bunup';
export default defineWorkspace([
{
name: 'core-package',
root: 'packages/core',
config: {
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
},
},
{
name: 'utils-package',
root: 'packages/utils',
config: {
entry: ['src/index.ts'],
format: ['esm'],
dts: true,
},
},
]);
Workspace Structure
Each workspace entry requires three properties:
- name: A unique identifier for the workspace (used in logs)
- root: The relative path to the workspace root directory
- config: The build configuration for the workspace (same options as
defineConfig
)
Multiple Configurations per Workspace
You can define multiple build configurations for a single workspace by using an array for the config
property:
export default defineWorkspace([
{
name: 'web-package',
root: 'packages/web',
config: [
{
name: 'esm-build',
entry: ['src/index.ts'],
format: ['esm'],
target: 'browser',
},
{
name: 'cjs-build',
entry: ['src/index.ts'],
format: ['cjs'],
target: 'node',
},
],
},
]);
Working with Workspace Paths
All paths in workspace configurations are resolved relative to each workspace's root directory. This means:
- Entry points are resolved from the workspace root
- Output is generated relative to the workspace root
- TypeScript configurations are loaded from the workspace root
For example, with the following configuration:
{
name: 'my-package',
root: 'packages/my-package',
config: {
entry: ['src/index.ts'],
outDir: 'dist',
},
}
The entry point will be resolved as packages/my-package/src/index.ts
and the output will be written to packages/my-package/dist/
.
Running Workspace Builds
To build all workspaces, simply run the bunup
command with no additional arguments:
bunup
Bunup will automatically detect and build all workspaces defined in your configuration file.
To watch the packages in workspaces and automatically rebuild on file changes, run:
bunup --watch
This single command enables continuous monitoring and rebuilding of all packages in your workspaces.