--- url: /index.md --- # Introduction Bunup is the **blazing-fast build tool** for TypeScript libraries, designed for flawless developer experience and speed, **powered by Bun's native bundler**. ## Performance Instant builds by design even with type declarations. With Bun’s native speed, builds and rebuilds are extremely quick, even in monorepos. Faster feedback loops, higher productivity, calmer flow. See [benchmarks](https://gugustinette.github.io/bundler-benchmark/). ## Scaffold Spin up a modern, ready-to-publish TypeScript or React component library (or a basic starter) in ~10 seconds: ```sh bunx @bunup/cli@latest create ``` See more in [Scaffold with Bunup](./docs/scaffold-with-bunup.md). ## Quick Start Create a TypeScript file: ```ts [src/index.ts] export function greet(name: string): string { return `Hello, ${name}!`; } ``` Build it instantly: ```sh bunx bunup ``` Outputs to `dist/` with ESM and `.d.ts` types. Need CommonJS too? ```sh bunx bunup --format esm,cjs ``` Want to generate and sync package exports automatically? ```sh bunx bunup --exports ``` ### Using with package.json First, install Bunup as a dev dependency: ```sh bun add --dev bunup ``` Add a build script to your `package.json`: ```json [package.json] { "name": "my-package", "scripts": { "build": "bunup" } } ``` Then run: ```sh bun run build ``` ## Default Entry Points Bunup automatically detects common entry points. `index.ts`, `index.tsx`, `src/index.ts`, `src/index.tsx`, `cli.ts`, `src/cli.ts`, `src/cli/index.ts` This is why simply running `bunx bunup` works out of the box. For example, if your project has both `src/index.ts` and `src/cli.ts`, Bunup will build both automatically. To override the default entry points or specify exactly which files to build, list them explicitly: ```sh bunx bunup src/index.ts src/plugins.ts ``` See [Entry Points](/docs/guide/options#entry-points) for details. ## Watch Mode Bunup can watch files for changes and rebuild automatically: ```sh bunx bunup --watch ``` Or configure it in `package.json`: ```json [package.json] {5} { "name": "my-package", "scripts": { "build": "bunup", "dev": "bunup --watch" } } ``` Then run: ```sh bun run dev ``` ## Config File While most options can be set directly via the CLI, and the CLI works well on its own, in some cases you will need to use a configuration file. This is useful when you want to use plugins, leverage Bunup [workspaces](/docs/guide/workspaces), target multiple environments with different configurations, or simply centralize your build settings. See [Config File](/docs/guide/config-file) for details and [Options](/docs/guide/options) for all the available build options with side-by-side configuration and CLI examples. --- --- url: /docs/guide/config-file.md --- # Config File Centralize your build settings with a configuration file when CLI options aren't enough. ## Getting Started Create a `bunup.config.ts` file in your project root: ```ts [bunup.config.ts] import { defineConfig } from "bunup"; export default defineConfig({ // ...your configuration options go here }); ``` This is the simplest way to centralize and reuse your build configuration. See [Options](/docs/guide/options) for all the available options. ## Multiple Configurations Bunup supports exporting an **array of configurations**, useful when you want to build for multiple environments or formats in a single run. ::: info Named Configurations When using an array of configurations, the `name` property is **required** for each configuration to identify the builds in logs and reports. ::: ```ts [bunup.config.ts] export default defineConfig([ { entry: "src/index.ts", name: "node", format: "esm", target: "node", }, { entry: "src/browser.ts", name: "browser", format: ["esm", "iife"], target: "browser", outDir: "dist/browser", }, ]); ``` With this setup, Bunup will build both Node.js and browser bundles. **Another example:** if you have different entry points that need different build configurations, you can specify them separately. For instance, your main module might need both ESM and CJS formats, while a CLI entry point might only need ESM: ```ts [bunup.config.ts] export default defineConfig([ { entry: "src/index.ts", name: "main", format: ["esm", "cjs"], }, { entry: "src/cli.ts", name: "cli", format: ["esm"], }, { entry: "src/browser.ts", name: "browser", format: ["esm", "iife"], outDir: "dist/browser", }, ]); ``` ## Filtering Configurations When you have multiple configurations in an array, you can use the `--filter` option to build only specific configurations by name: ```sh [CLI] # Single bunup --filter main # Multiple bunup --filter main,browser ``` Only the configurations matching these names will be built - perfect for testing specific builds without running the entire suite. ## Custom Configuration Path If you need to use a configuration file with a non-standard name or location, you can specify its path using the `--config` CLI option: ::: code-group ```sh [CLI] bunup --config ./configs/custom.bunup.config.ts # or using alias bunup -c ./configs/custom.bunup.config.ts ``` ::: This allows you to keep your configuration files organized in custom locations or use different configuration files for different environments. ## Disabling Configuration Files To explicitly disable config file usage and rely only on CLI options: ```sh [CLI] bunup --no-config ``` --- --- url: /docs/guide/options.md --- # Options Bunup provides a rich set of options to customize your build. Use the table of contents on the right side or search to quickly navigate to the option you are looking for. ## 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: ::: code-group ```sh [CLI] bunup src/index.ts ``` ```ts [bunup.config.ts] export default defineConfig({ entry: "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: ::: code-group ```sh [CLI - method 1] bunup src/index.ts src/cli.ts ``` ```sh [CLI - using --entry flag] bunup --entry src/index.ts --entry src/cli.ts # or using alias bunup -e src/index.ts -e src/cli.ts ``` ```ts [bunup.config.ts] export default defineConfig({ entry: ["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 Glob Patterns You can use glob patterns to include multiple files that match a pattern: ::: code-group ```sh [CLI] bunup 'src/**/*.ts' '!src/**/*.test.ts' ``` ```ts [bunup.config.ts] export default defineConfig({ entry: ["src/**/*.ts", "!src/**/*.test.ts", "!src/internal/**/*.ts"], }); ``` ::: Glob pattern features: * Use patterns like `**/*.ts` to match files recursively * Prefix patterns with `!` to exclude files that match the pattern * Patterns are resolved relative to the project root ## Output Directory You can specify where Bunup should output the bundled files: ::: code-group ```sh [CLI] bunup --out-dir build # or using alias bunup -o build ``` ```ts [bunup.config.ts] export default defineConfig({ outDir: "build", }); ``` ::: The default output directory is `dist`. ## Output Formats Bunup supports three output formats: * **esm**: ECMAScript modules (default) * **cjs**: CommonJS modules * **iife**: Immediately Invoked Function Expression (for browser) You can specify one or more formats: ::: code-group ```sh [CLI] # Single format bunup --format esm # or using alias bunup -f esm # Multiple formats bunup --format esm,cjs,iife # or using alias bunup -f esm,cjs,iife ``` ```ts [bunup.config.ts] export default defineConfig({ // 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: **When package.json has `"type": "module"`:** | Format | JavaScript Extension | TypeScript Declaration Extension | | ------ | -------------------- | -------------------------------- | | esm | `.js` | `.d.ts` | | cjs | `.cjs` | `.d.cts` | | iife | `.global.js` | `.global.d.ts` | **When package.json has `"type": "commonjs"` or is unspecified:** | Format | JavaScript Extension | TypeScript Declaration Extension | | ------ | -------------------- | -------------------------------- | | esm | `.mjs` | `.d.mts` | | cjs | `.js` | `.d.ts` | | iife | `.global.js` | `.global.d.ts` | ## Managing Dependencies in Your Bundle Bunup automatically determines which packages to include in your bundle based on your `package.json` configuration. You can also customize this behavior when needed. ### Default Dependency Handling Bunup examines your `package.json` and follows these rules: | Dependency Type | Default Behavior | Result | | ------------------ | ------------------------- | ----------------------------------------- | | `dependencies` | Excluded from bundle | Installed when users install your library | | `peerDependencies` | Excluded from bundle | Users must install these separately | | `devDependencies` | Included only if imported | Bundled when your code uses them | This keeps your library lightweight and prevents version conflicts. ### Example Building a utility library with Lodash: ```json { "name": "my-utility-lib", "dependencies": { "lodash": "^4.17.21" } } ``` **Result:** * Lodash is treated as external (not bundled) * Users get Lodash automatically when they install your library * Your bundle stays small ### Configuration Options #### Bundle All Dependencies Force all dependencies into your bundle: ::: code-group ```sh [CLI] bunup --packages bundle ``` ```typescript [bunup.config.ts] export default defineConfig({ packages: "bundle", }); ``` ::: #### Externalize All Dependencies Keep all dependencies external: ::: code-group ```sh [CLI] bunup --packages external ``` ```typescript [bunup.config.ts] export default defineConfig({ packages: "external", }); ``` ::: ### Specific Package Control #### Make Packages External Exclude specific packages from your bundle: ::: code-group ```sh [CLI] # Single package bunup --external lodash # Multiple packages bunup --external lodash,react,vue ``` ```typescript [bunup.config.ts] export default defineConfig({ external: ["lodash", "react", "vue"], }); ``` ::: ##### Force Packages Into Bundle Include specific packages in your bundle: ::: code-group ```bash [CLI] # Single package bunup --no-external lodash # Multiple packages bunup --no-external lodash,react,vue ``` ```typescript [bunup.config.ts] export default defineConfig({ noExternal: ["lodash", "react", "vue"], }); ``` ::: ### Advanced Usage Both `external` and `noExternal` options support: * Exact package names: `'lodash'` * Regular expressions: `'/^@my-org\//'` The `packages` option works as a default setting. You can still override individual packages using `external` or `noExternal` options. ## Target Environments Bunup allows you to specify the target environment for your bundle: ::: code-group ```sh [CLI] bunup --target browser # or using alias bunup -t browser ``` ```ts [bunup.config.ts] export default defineConfig({ target: "browser", }); ``` ::: Available targets: * `node` (default): Optimized for Node.js * `browser`: Optimized for browsers * `bun`: For generating bundles that are intended to be run by the Bun runtime. If a file contains a Bun shebang (`#!/usr/bin/env bun`), the `bun` target will be used automatically for that file. When targeting `bun`, bundles are marked with a special `// @bun` pragma that tells the Bun runtime not to re-transpile the file before execution. While bundling isn't always necessary for server-side code, it can improve startup times and runtime performance. ## Minification Bunup provides several minification options to reduce the size of your output files. ### Basic Minification To enable all minification options: ::: code-group ```sh [CLI] bunup --minify ``` ```ts [bunup.config.ts] export default defineConfig({ minify: true, }); ``` ::: ### Granular Minification Control You can configure individual minification options: #### Using the CLI ::: code-group ```sh [CLI] # Single option - minify whitespace only bunup --minify-whitespace # Multiple options - minify whitespace and syntax, but not identifiers bunup --minify-whitespace --minify-syntax ``` ```ts [bunup.config.ts] export default defineConfig({ // 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: ::: code-group ```sh [CLI] # Linked source maps bunup --sourcemap linked # Inline source maps bunup --sourcemap ``` ```ts [bunup.config.ts] export default defineConfig({ sourcemap: "linked", // Can also use boolean // sourcemap: true // equivalent to 'inline' }); ``` ::: Available sourcemap values: * `none` * `linked` * `external` * `inline` * `true` (equivalent to 'inline') For detailed explanations of these values, see the [Bun documentation on source maps](https://bun.com/docs/bundler#sourcemap). ## Environment Variables Bunup provides flexible options for handling environment variables in your bundled code: ::: code-group ```sh [CLI] # Inline all environment variables available at build time FOO=bar API_KEY=secret bunup --env inline # Disable all environment variable inlining bunup --env disable # Only inline environment variables with a specific prefix (e.g., PUBLIC_) PUBLIC_URL=https://example.com bunup --env PUBLIC_* # Explicitly provide specific environment variables bunup --env.NODE_ENV="production" --env.API_URL="https://api.example.com" ``` ```ts [bunup.config.ts] export default defineConfig({ // Inline all available environment variables at build time env: "inline", // Or disable inlining entirely (keep process.env.FOO in the output) // env: "disable", // Or inline only variables that start with a specific prefix // env: "PUBLIC_*", // Or explicitly provide specific environment variables // These will replace both process.env.FOO and import.meta.env.FOO // env: { // API_URL: "https://api.example.com", // DEBUG: "false", // }, }); ``` ::: ### How it Works The `env` option controls how `process.env.*` and `import.meta.env.*` expressions are replaced at build time: | Value | Behavior | | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `"inline"` | Replaces all `process.env.VAR` references in your code with the actual values of those environment variables at the time of the build. | | `"disable"` | Disables environment variable replacement. Keeps `process.env.VAR` as-is in output. | | `"PREFIX_*"` | Only inlines environment variables matching the given prefix (e.g. `PUBLIC_*`). | | `{ key: value }` | Replaces both `process.env.KEY` and `import.meta.env.KEY` with the provided values, regardless of the environment. | For more information, see the [Bun documentation on environment variables](https://bun.com/docs/bundler#env). ## JSX Configure JSX transform behavior: ::: code-group ```sh [CLI] # Set JSX runtime mode bunup --jsx.runtime automatic # Configure import source bunup --jsx.import-source preact # Configure factory and fragment bunup --jsx.factory h --jsx.fragment Fragment # Configure side effects bunup --jsx.side-effects # Enable development mode bunup --jsx.development ``` ```ts [bunup.config.ts] export default defineConfig({ jsx: { runtime: "automatic", // or 'classic' importSource: "preact", factory: "h", fragment: "Fragment", sideEffects: false, development: false, }, }); ``` ::: Available JSX options: * **runtime**: JSX runtime mode (`automatic` or `classic`, default: `automatic`) * **importSource**: Import source for JSX functions (default: `react`) * **factory**: JSX factory function name (default: `React.createElement`) * **fragment**: JSX fragment function name (default: `React.Fragment`) * **sideEffects**: Whether JSX functions have side effects (default: `false`) * **development**: Use jsx-dev runtime for development (default: `false`) For more information, see the [Bun documentation on JSX](https://bun.com/docs/bundler#jsx). ## Tree Shaking Bunup tree-shakes your code by default. No configuration is needed. ## 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: ::: code-group ```sh [CLI] # Enable code splitting bunup --splitting # Disable code splitting bunup --no-splitting ``` ```ts [bunup.config.ts] export default defineConfig({ format: "esm", // Enable for all formats splitting: true, // Or disable for all formats // splitting: false, }); ``` ::: ## Custom Tsconfig Path You can specify a custom tsconfig file to use for both build path resolution and TypeScript declaration generation: ::: code-group ```sh [CLI] bunup --preferred-tsconfig ./tsconfig.build.json ``` ```ts [bunup.config.ts] export default defineConfig({ entry: "src/index.ts", preferredTsconfig: "./tsconfig.build.json", }); ``` ::: This option is useful when you want to use a different TypeScript configuration for your build than your development environment. The specified tsconfig is used for path resolution during both bundling and TypeScript declaration generation. By default, the nearest `tsconfig.json` file will be used if this option is not specified. ## Post-build Operations The `onSuccess` option runs after the build process successfully completes. It supports three different formats: ### Function Callback Execute custom JavaScript code after a successful build: ```typescript export default defineConfig({ onSuccess: (options) => { console.log("Build completed!"); const server = startDevServer(); // Optional: return a cleanup function for watch mode return () => server.close(); }, }); ``` ### Simple Command Execute a shell command as a string: ::: code-group ```sh [CLI] bunup --on-success "bun run ./scripts/server.ts" ``` ```ts [bunup.config.ts] export default defineConfig({ onSuccess: "bun run ./scripts/server.ts", }); ``` ::: ### Advanced Command Options For more control over command execution: ```typescript export default defineConfig({ onSuccess: { cmd: "bun run ./scripts/server.ts", options: { cwd: "./app", env: { ...process.env, FOO: "bar" }, timeout: 30000, // 30 seconds killSignal: "SIGKILL", }, }, }); ``` Available command options: * **cwd**: Working directory for the command * **env**: Environment variables (defaults to `process.env`) * **timeout**: Maximum execution time in milliseconds * **killSignal**: Signal used to terminate the process (defaults to `'SIGTERM'`) ::: info In watch mode, `onSuccess` runs after each successful rebuild. ::: ::: warning The function callback and advanced command options for `onSuccess` are only available in the configuration file, not via CLI flags. ::: ## Cleaning the Output Directory By default, Bunup cleans the output directory before each build. You can disable this behavior: ::: code-group ```sh [CLI] bunup --no-clean ``` ```ts [bunup.config.ts] export default defineConfig({ clean: false, }); ``` ::: ## Define Global Constants Bunup allows you to define global constants that will be replaced at build time. This is useful for feature flags, version numbers, or any other build-time constants. ::: code-group ```sh [CLI] bunup --define.PACKAGE_VERSION='"1.0.0"' --define.DEBUG='false' ``` ```typescript [bunup.config.ts] export default defineConfig({ define: { PACKAGE_VERSION: '"1.0.0"', DEBUG: "false", }, }); ``` ::: The `define` option takes an object where: * Keys are the identifiers to replace * Values are the strings to replace them with For more information on how define works, see the [Bun documentation on define](https://bun.com/docs/bundler#define). ## Banner and Footer You can add custom text to the beginning and end of your bundle files: ::: code-group ```sh [CLI] bunup --banner 'use client' --footer '// built with love in SF' ``` ```ts [bunup.config.ts] export default defineConfig({ // Add text to the beginning of bundle files banner: '"use client";', // Add text to the end of bundle files footer: "// built with love in SF", }); ``` ::: The `banner` option adds text to the beginning of the bundle, useful for directives like "use client" for React or license information. The `footer` option adds text to the end of the bundle, which can be used for license information or other closing comments. For more information, see the Bun documentation on [banner](https://bun.com/docs/bundler#banner) and [footer](https://bun.com/docs/bundler#footer). ## Drop Function Calls You can remove specific function calls from your bundle: ::: code-group ```sh [CLI] # Single function bunup --drop console # Multiple functions bunup --drop console,debugger ``` ```typescript [bunup.config.ts] export default defineConfig({ drop: ["console", "debugger", "anyIdentifier.or.propertyAccess"], }); ``` ::: The `drop` option removes function calls specified in the array. For example, `drop: ["console"]` will remove all calls to `console.log`. Arguments to calls will also be removed, regardless of if those arguments may have side effects. Dropping `debugger` will remove all `debugger` statements. For more information, see the [Bun documentation on drop](https://bun.com/docs/bundler#drop). ## Package.json Export Conditions You can specify custom package.json export conditions for import resolution: ::: code-group ```sh [CLI] # Single condition bunup --conditions development # Multiple conditions bunup --conditions development,node ``` ```typescript [bunup.config.ts] export default defineConfig({ conditions: ["development", "node"], }); ``` ::: This allows you to control which conditional exports are used when resolving imports. ## Dead Code Elimination Control how dead code elimination annotations are handled: ::: code-group ```sh [CLI] # Ignore @__PURE__ annotations and sideEffects bunup --ignore-dce-annotations # Force emit @__PURE__ annotations even with minification bunup --emit-dce-annotations ``` ```typescript [bunup.config.ts] export default defineConfig({ ignoreDCEAnnotations: true, // or emitDCEAnnotations: true, }); ``` ::: * `ignore-dce-annotations`: Ignores dead code elimination annotations like `@__PURE__` and `sideEffects` in package.json * `emit-dce-annotations`: Forces emission of `@__PURE__` annotations even when minification is enabled ## Silent Mode Disable logging during the build process: ::: code-group ```sh [CLI] bunup --silent # or using alias bunup -q ``` ```typescript [bunup.config.ts] export default defineConfig({ silent: true, }); ``` ::: This is useful when you want minimal output, such as in CI/CD environments. ## Build Report Configure the build report that shows file sizes and compression statistics: ::: code-group ```sh [CLI] # Enable brotli compression reporting (gzip is enabled by default) bunup --report.brotli # Set maximum bundle size warning threshold (in bytes) bunup --report.max-bundle-size 1048576 # Disable gzip compression reporting bunup --no-report.gzip ``` ```typescript [bunup.config.ts] export default defineConfig({ report: { gzip: true, // Enable gzip size calculation (default: true) brotli: false, // Enable brotli size calculation (default: false) maxBundleSize: 1024 * 1024, // Warn if bundle exceeds 1MB }, }); ``` ::: The `report` option controls the build output report: * **gzip**: Calculate and display gzip compressed file sizes (enabled by default) * **brotli**: Calculate and display brotli compressed file sizes (disabled by default) * **maxBundleSize**: Set a size threshold in bytes - bunup will warn if the total bundle size exceeds this limit ::: info For large output files, compression size calculation may slow down the build process. Consider disabling compression reporting if build speed is critical. ::: ## Custom Loaders You can configure how different file types are loaded: ::: code-group ```sh [CLI] bunup --loader.'.css'=text --loader.'.txt'=file ``` ```typescript [bunup.config.ts] export default defineConfig({ loader: { ".css": "text", ".txt": "file", }, }); ``` ::: The `loader` option takes a map of file extensions to built-in loader names, allowing you to customize how different file types are processed during bundling. For more information, see the [Bun documentation on loaders](https://bun.com/docs/bundler#loader). ## Public Path You can specify a prefix to be added to specific import paths in your bundled code: ::: code-group ```sh [CLI] bunup --public-path https://cdn.example.com/ ``` ```ts [bunup.config.ts] export default defineConfig({ publicPath: "https://cdn.example.com/", }); ``` ::: The `publicPath` option only affects certain types of imports in the final bundle: * Asset imports (like images or SVG files) * External modules * Chunk files when code splitting is enabled By default, these imports are relative. Setting `publicPath` will prefix these specific file paths with the specified value, which is useful for serving assets from a CDN. For example: ```js [Input] import logo from "./logo.svg"; console.log(logo); ``` ```js [Output without publicPath] var logo = "./logo-a7305bdef.svg"; console.log(logo); ``` ```js [Output with publicPath] var logo = "https://cdn.example.com/logo-a7305bdef.svg"; console.log(logo); ``` For more information, see the [Bun documentation on publicPath](https://bun.com/docs/bundler#publicpath). ## Source Base Directory You can specify the base directory for your entry points to control the output file structure: ::: code-group ```sh [CLI] bunup --source-base ./src ``` ```ts [bunup.config.ts] export default defineConfig({ sourceBase: "./src", }); ``` ::: ### What does it do? The `sourceBase` option controls how Bunup preserves your source directory structure in the output. It acts as the "root" from which all relative output paths are calculated. ### Example 1: Single Entry Point Consider this project structure: ``` my-project/ ├── src/ │ └── components/ │ └── Button/ │ └── index.ts └── package.json ``` **Without `sourceBase`:** ```ts export default defineConfig({ entry: "src/components/Button/index.ts", outDir: "dist", }); ``` Output structure: ``` my-project/ └── dist/ └── index.js // Collapsed to just the filename ``` **With `sourceBase: './src'`:** ```ts export default defineConfig({ entry: "src/components/Button/index.ts", sourceBase: "./src", outDir: "dist", }); ``` Output structure: ``` my-project/ └── dist/ └── components/ └── Button/ └── index.js // Structure preserved! ``` ### Example 2: Multiple Entry Points This is where `sourceBase` really shines. Consider bundling multiple files: ``` my-project/ ├── src/ │ ├── components/ │ │ ├── Button.ts │ │ └── Input.ts │ └── utils/ │ └── format.ts └── package.json ``` **Without `sourceBase` (auto-detected):** ```ts export default defineConfig({ entry: ["src/components/**/*.ts", "src/utils/**/*.ts"], outDir: "dist", }); ``` Bunup automatically uses `src/` as the lowest common ancestor: ``` my-project/ └── dist/ ├── components/ │ ├── Button.js │ └── Input.js └── utils/ └── format.js ``` **With explicit `sourceBase: '.'` (project root):** ```ts export default defineConfig({ entry: ["src/components/**/*.ts", "src/utils/**/*.ts"], sourceBase: ".", outDir: "dist", }); ``` Output includes the `src/` directory: ``` my-project/ └── dist/ └── src/ ├── components/ │ ├── Button.js │ └── Input.js └── utils/ └── format.js ``` ### How it works * Sets the base directory from which relative output paths are calculated * Preserves your source directory structure in the output relative to this base * If not specified, Bunup automatically uses the lowest common ancestor directory of all entry points * Useful when you want to maintain a specific folder structure in your build output This ensures your built files maintain the same organizational structure as your source code, making it easier to understand the relationship between source and output files. For more information, see the [Bun documentation on root](https://bun.com/docs/bundler#root). ## Shims Bunup can automatically provide compatibility layers for Node.js globals and ESM/CJS interoperability. When enabled, it detects usage of environment-specific features in your code and adds appropriate shims: ::: code-group ```sh [CLI] bunup --shims ``` ```ts [bunup.config.ts] export default defineConfig({ shims: true, }); ``` ::: ### How Shims Work When shims are enabled, Bunup automatically transforms environment-specific code: * **For CJS output**: `import.meta.url` references are transformed to `pathToFileURL(__filename).href` * **For ESM output**: `__dirname` and `__filename` references are transformed to use `dirname(fileURLToPath(import.meta.url))` This ensures your code works consistently across different module formats and environments without requiring manual compatibility code. --- --- url: /docs/guide/typescript-declarations.md --- # TypeScript Declarations Bunup automatically generates TypeScript declaration files (`.d.ts`, `.d.mts`, or `.d.cts`) for your library. These files tell other developers (and TypeScript) what types your library exports, enabling proper type checking and autocomplete when others use your code. ## Isolated Declarations Enable `isolatedDeclarations` in your tsconfig: ```json [tsconfig.json] {3-4} { "compilerOptions": { "declaration": true, "isolatedDeclarations": true } } ``` TypeScript 5.5's [isolated declarations](https://devblogs.microsoft.com/typescript/announcing-typescript-5-5-beta/#isolated-declarations) changes how declaration files are generated. Instead of analyzing your entire project to figure out types (slow), it processes each file independently (instant). This requires explicit return types on your **public exports only** - a good practice that makes your API clearer and more predictable. This transforms builds from seconds/minutes to milliseconds (**50-100x faster**), enabling instant builds and rebuilds, creates clearer APIs, and ensures compatibility with modern build tools. ```ts // Required: Explicit return type on public exports export function getData(): Promise { return fetchUser(); } // Internal functions don't need explicit types function fetchUser() { return api.get("/user"); } ``` Learn more about isolated declarations [here](https://arshad.fyi/writing/isolated-declarations). For new projects, we strongly recommend isolated declarations for instant builds and rebuilds and clearer APIs. Explicitly typing your public exports is considered a best practice for library development. You only need to disable isolated declarations in rare cases with complex generic types that are genuinely difficult to type explicitly (like some advanced Zod schemas). Check the [Infer Types](#infer-types) section for this alternative approach. ## Basic Bunup automatically generates TypeScript declaration files for all TypeScript entry points that require them. Files that do not contain exports, or for which declarations are unnecessary, are skipped. ## Declaration Splitting Declaration splitting prevents code duplication when multiple entry points share the same types. Instead of copying shared types into every declaration file, Bunup extracts them into separate chunk files that get imported where needed. ::: code-group ```sh [CLI] bunup --dts.splitting ``` ```typescript [bunup.config.ts] export default defineConfig({ dts: { splitting: true, }, }); ``` ::: **Without splitting:** ``` dist/ ├── index.d.ts # ~45KB (includes duplicated types) └── utils.d.ts # ~40KB (includes duplicated types) ``` **With splitting:** ``` dist/ ├── index.d.ts # ~15KB, imports shared types ├── utils.d.ts # ~10KB, imports shared types └── shared/chunk-abc123.d.ts # ~30KB, shared types extracted here ``` The result is smaller files with no duplicate type definitions. ## Minification You can minify the generated declaration files to reduce their size: ::: code-group ```sh [CLI] bunup --dts.minify ``` ```typescript [bunup.config.ts] export default defineConfig({ dts: { minify: true, }, }); ``` ::: Minification keeps your public API names unchanged but shortens internal type names and removes comments. This reduces file size significantly, useful when bundle size matters more than readable type definitions. ### Example **Original:** ```ts type DeepPartial = { [P in keyof T]?: DeepPartial }; interface Response { data: T; error?: string; meta?: Record; } declare function fetchData(url: string, options?: RequestInit): Promise>; export { fetchData, Response, DeepPartial }; ``` **Minified:** ```ts type e = { [P in keyof T]?: e }; interface t { data: T; error?: string; meta?: Record; } declare function n(url: string, options?: RequestInit): Promise>; export { n as fetchData, t as Response, e as DeepPartial }; ``` ## Infer Types By default, Bunup uses isolated declarations which require explicit type annotations. The `inferTypes` option switches back to traditional TypeScript compilation, allowing you to rely on TypeScript's automatic type inference instead of writing explicit return types. This is useful for projects with complex generic types where explicit typing is verbose or challenging. ::: code-group ```sh [CLI] bunup --dts.infer-types ``` ```typescript [bunup.config.ts] export default defineConfig({ dts: { inferTypes: true, }, }); ``` ::: ::: tip For new projects, stick with [isolated declarations](#isolated-declarations) (default behavior) for instant builds and rebuilds and clearer APIs. Only use `inferTypes` when explicit typing becomes impractical. ::: ### Tsgo When `inferTypes` is enabled, Bunup uses the regular TypeScript compiler (tsc) by default. You can switch to TypeScript's experimental native compiler ([tsgo](https://devblogs.microsoft.com/typescript/typescript-native-port/)) for ~10x faster declaration generation. First, install the required package: ```sh bun add --dev @typescript/native-preview ``` Then enable tsgo: ::: code-group ```sh [CLI] bunup --dts.infer-types --dts.tsgo ``` ```typescript [bunup.config.ts] export default defineConfig({ dts: { inferTypes: true, tsgo: true, }, }); ``` ::: ::: info `tsgo` only works with `inferTypes` enabled. It's experimental but stable enough for declaration generation. Once TypeScript officially releases it, Bunup will use tsgo by default when `inferTypes` is enabled. ::: ## Custom Entry Points By default, Bunup generates declarations for all your entry points. You can specify which files should have declarations generated: ::: code-group ```sh [CLI] # Single entry bunup src/index.ts src/utils.ts --dts.entry src/index.ts # Multiple entries bunup src/index.ts src/utils.ts src/types.ts --dts.entry src/index.ts,src/types.ts ``` ```typescript [bunup.config.ts] export default defineConfig({ entry: ["src/index.ts", "src/utils.ts"], dts: { // Only generate declarations for index.ts entry: ["src/index.ts"], }, }); ``` ::: ### Using Glob Patterns Bunup supports glob patterns to match multiple files: ::: code-group ```sh [CLI] # Single glob pattern bunup --dts.entry "src/public/**/*.ts" # Multiple patterns (including exclusions) bunup --dts.entry "src/public/**/*.ts,!src/public/dev/**/*" ``` ```typescript [bunup.config.ts] export default defineConfig({ dts: { entry: ["src/public/**/*.ts", "!src/public/dev/**/*"], }, }); ``` ::: You can use: * Simple patterns like `src/**/*.ts` to include files * Exclude patterns starting with `!` to filter out specific files * Both for main entries and declaration entries ## TypeScript Configuration You can specify a custom tsconfig file for declaration generation. This mainly affects how TypeScript resolves import paths during the declaration generation process. See [Custom Tsconfig Path](/docs/guide/options#custom-tsconfig-path) for details. By default, the nearest `tsconfig.json` file will be used. ## Resolving External Types When your code imports types from external packages, you might need to include those type definitions in your declaration files. The `resolve` option tells Bunup to look up and include external types from your dependencies. ::: code-group ```sh [CLI] # Enable resolving all external types bunup --dts.resolve ``` ```ts [bunup.config.ts] export default defineConfig({ dts: { // Enable resolving all external types resolve: true, }, }); ``` ::: You can also specify which packages to resolve types for: ::: code-group ```sh [CLI] # Single package bunup --dts.resolve react # Multiple packages bunup --dts.resolve react,lodash,@types/node ``` ```typescript [bunup.config.ts] export default defineConfig({ dts: { // Only resolve types from these specific packages resolve: ["react", "lodash", /^@types\//], }, }); ``` ::: ## Declaration Files Only If you only want to generate TypeScript declaration files without building JavaScript output, use the `dtsOnly` option: ::: code-group ```sh [CLI] bunup --dts-only ``` ```ts [bunup.config.ts] export default defineConfig({ entry: "src/index.ts", dtsOnly: true, }); ``` ::: This is useful when: * You're using a different bundler for JavaScript but want Bunup to handle TypeScript declarations * You need to generate type definitions separately from your build process * You're working on a types-only package When `dtsOnly` is enabled, Bunup skips the entire JavaScript build process and only generates `.d.ts` files. ## Disabling Declaration Generation You can completely disable automatic declaration file generation: ::: code-group ```sh [CLI] bunup --no-dts ``` ```ts [bunup.config.ts] export default defineConfig({ entry: "src/index.ts", dts: false, }); ``` ::: This is useful when you want to handle declaration generation yourself or when working on projects that don't need type definitions. --- --- url: /docs/guide/css.md --- # CSS Bunup handles CSS automatically. Just import it and it works. ## Quick Start Import CSS in your TypeScript files: ```typescript [src/index.ts] import "./styles.css"; import { Button } from "./components/button"; export { Button }; ``` ```css [src/styles.css] .button { background-color: #007bff; color: white; padding: 8px 16px; border: none; border-radius: 4px; } ``` Bunup automatically bundles your CSS into `dist/index.css` with cross-browser compatibility. Any CSS imports encountered in your files will be bundled together into `dist/index.css`. To generate separate CSS files instead of a single `index.css` output, add them as entry points rather than importing them in your files: ```typescript [bunup.config.ts] import { defineConfig } from 'bunup'; export default defineConfig({ entry: [ 'src/index.ts', 'src/components/button.css' 'src/components/alert.css' ], }); ``` This creates individual CSS files in your build output: ```plaintext dist/ ├── index.js └── components/ ├── button.css └── alert.css ``` ## CSS Modules CSS modules prevent style conflicts by automatically scoping class names. Just add `.module.css` to your filename: ::: tip New to CSS modules? Check out [this guide](https://css-tricks.com/css-modules-part-1-need/) to learn what they are and why they're useful. ::: ```css [src/components/button.module.css] .primary { background-color: #007bff; color: white; padding: 8px 16px; border: none; border-radius: 4px; } ``` ```tsx [src/components/button.tsx] import styles from "./button.module.css"; export function Button({ children }) { return ; } ``` That's it! Bunup handles the rest automatically. ### Sharing Styles Reuse styles with the `composes` property: ```css [src/components/button.module.css] {9,15} .base { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; } .primary { composes: base; background-color: #007bff; color: white; } .secondary { composes: base; background-color: transparent; color: #007bff; border: 1px solid #007bff; } ``` **Rules:** * `composes` must come first in the class * Works only with single class selectors (not `#id` or `.class1, .class2`) **From other files:** ```css [src/components/button.module.css] {2} .primary { composes: base from "../shared.module.css"; background-color: #007bff; color: white; } ``` ::: warning Avoid conflicting properties when composing from separate files. ::: ## Distributing CSS Export CSS files for package consumers: ```json [package.json] { "exports": { ".": { "import": "./dist/index.js", "types": "./dist/index.d.ts" }, "./styles.css": "./dist/index.css" // [!code ++] } } ``` Users can then import your styles: ```javascript import "your-package/styles.css"; import { Button } from "your-package"; ; } ``` ### Inject Styles Optional Want to skip the separate CSS import? Use the [inject styles](/docs/extra-options/inject-styles) option to bundle CSS directly into JavaScript: ::: code-group ```sh [CLI] bunup --css.inject ``` ```ts [bunup.config.ts] import { defineConfig } from "bunup"; export default defineConfig({ css: { inject: true, }, }); ``` ::: Or with the Tailwind CSS plugin: ```ts [bunup.config.ts] import { defineConfig } from "bunup"; import { tailwindcss } from "@bunup/plugin-tailwindcss"; export default defineConfig({ plugins: [ tailwindcss({ inject: true, }), ], }); ``` Now consumers only need to import your components: ```tsx import { Button } from "my-component-library"; function App() { return ; } ``` Styles are automatically injected at runtime. ## React Compiler Optimize your React components automatically with the React Compiler plugin. It intelligently memoizes components and hooks to minimize unnecessary re-renders without manual `useMemo`, `useCallback`, or `memo` usage. Learn more about the [React Compiler](https://react.dev/learn/react-compiler). Install the React Compiler plugin: ```bash bun add --dev @bunup/plugin-react-compiler ``` Add it to your config: ```ts [bunup.config.ts] import { defineConfig } from "bunup"; import { reactCompiler } from "@bunup/plugin-react-compiler"; export default defineConfig({ plugins: [reactCompiler()], }); ``` That's it! Your components are now automatically optimized during the build. Write React code naturally: ```tsx [src/components/counter.tsx] function ExpensiveComponent({ data, onClick }) { const processedData = expensiveProcessing(data); const handleClick = (item) => { onClick(item.id); }; return (
{processedData.map((item) => ( handleClick(item)} /> ))}
); } ``` The React Compiler plugin automatically transforms your code to be more performant without changing its behavior. ::: info Build performance Since the React Compiler uses Babel for transformations, builds will be slightly slower compared to Bunup's normally instant builds. The impact is small but may be more noticeable in larger applications. This trade-off is expected and worth it for the runtime performance improvements your components will gain. ::: ### Configuration Options Customize which files to process or pass options to the React Compiler: ```ts [bunup.config.ts] import { defineConfig } from "bunup"; import { reactCompiler } from "@bunup/plugin-react-compiler"; export default defineConfig({ plugins: [ reactCompiler({ // Only process .tsx files (default: /\.[jt]sx$/) filter: /\.tsx$/, // React Compiler configuration reactCompilerConfig: { target: "18", }, }), ], }); ``` ## Examples Check out complete examples in the [examples directory](https://github.com/bunup/bunup/tree/main/examples): * [React with Pure CSS](https://github.com/bunup/bunup/tree/main/examples/react-with-pure-css) * [React with CSS Modules](https://github.com/bunup/bunup/tree/main/examples/react-with-css-modules) * [React with Tailwind CSS](https://github.com/bunup/bunup/tree/main/examples/react-with-tailwindcss) --- --- url: /docs/extra-options/exports.md --- # Exports Bunup automatically generates and updates the `exports` field in your package.json file after each build. Bunup handles mapping all entry points to their corresponding output files, including ESM/CJS formats and type declarations. The exports field stays perfectly in sync with your build configuration always - no manual updates needed when you make any change to config. ## Usage Enable exports generation in your Bunup configuration: ::: code-group ```sh [CLI] bunup --exports ``` ```ts [bunup.config.ts] import { defineConfig } from "bunup"; export default defineConfig({ exports: true, }); ``` ::: This will automatically update your package.json with the correct exports field each time you build. For example: ```json [package.json] { "name": "my-package", "version": "1.0.0", "type": "module", "files": [ // [!code ++] "dist" // [!code ++] ], // [!code ++] "module": "./dist/index.js", // [!code ++] "main": "./dist/index.cjs", // [!code ++] "types": "./dist/index.d.ts", // [!code ++] "exports": { // [!code ++] ".": { // [!code ++] "import": { // [!code ++] "types": "./dist/index.d.ts", // [!code ++] "default": "./dist/index.js" // [!code ++] }, // [!code ++] "require": { // [!code ++] "types": "./dist/index.d.cts", // [!code ++] "default": "./dist/index.cjs" // [!code ++] } // [!code ++] } // [!code ++] } // [!code ++] } ``` ## Custom Exports The `customExports` option allows you to specify additional export fields that will be preserved alongside the automatically generated exports. This is useful when you need custom export conditions or paths that aren't automatically generated by the build process. ```ts [bunup.config.ts] import { defineConfig } from "bunup"; export default defineConfig({ exports: { customExports: (ctx) => ({ "./package.json": "./package.json", }), }, }); ``` ## Exclude The `exclude` option allows you to filter out specific export keys from the generated exports field in your package.json. This operates on the final export keys (like `"."`, `"./utils"`, `"./components"`) that appear in the exports object. You can provide an array of strings (using exact export keys, wildcards, or a mix of both), or a function that returns such an array. ::: code-group ```sh [CLI] # Single exclusion - exclude the "./internal" export key bunup --exports.exclude=./internal # Multiple exclusions bunup --exports.exclude=./utils,./internal # Using wildcards - exclude all exports under "./private" bunup --exports.exclude="./private/*" # Mix both - exact keys and wildcards bunup --exports.exclude="./internal,./private/*" ``` ```ts [bunup.config.ts] import { defineConfig } from "bunup"; export default defineConfig({ entry: ["src/index.ts", "src/utils.ts", "src/internal.ts"], exports: { // Exclude the "./internal" export key from package.json exports exclude: ["./internal"], }, }); ``` ::: This will generate exports for `"."` and `"./utils"` but exclude `"./internal"` from the final package.json: ```json [package.json] { "exports": { ".": { "import": "./dist/index.js", "types": "./dist/index.d.ts" }, "./utils": { "import": "./dist/utils.js", "types": "./dist/utils.d.ts" } // "./internal" is excluded } } ``` For more dynamic control, you can use a function: ```ts [bunup.config.ts] import { defineConfig } from "bunup"; export default defineConfig({ entry: ["src/index.ts", "src/utils.ts", "src/internal.ts"], exports: { exclude: (ctx) => { // Access build context information const { options, meta } = ctx; // Dynamically exclude export keys based on your logic return ["./internal", "./debug"]; }, }, }); ``` ## Exclude CLI By default, CLI-related entry points are automatically excluded from the package exports field. This prevents binary/command-line tools from being exposed as importable package exports, which is the correct behavior in most cases since CLI entries are typically used via the `bin` field in package.json. The plugin uses glob patterns to automatically detect and exclude common CLI entry point patterns: * Files or directories named `cli` (e.g., `cli.ts`, `cli/index.ts`) * Files or directories named `bin` (e.g., `bin.ts`, `bin/index.ts`) * CLI-related paths in any directory (e.g., `src/cli.ts`, `tools/bin/index.ts`) If you want to include CLI entries in your exports (which is rarely needed), you can disable this behavior: ::: code-group ```sh [CLI] bunup --no-exports.exclude-cli ``` ```ts [bunup.config.ts] import { defineConfig } from "bunup"; export default defineConfig({ exports: { excludeCli: false, // Include CLI entries in exports }, }); ``` ::: When disabled, CLI entries will be treated like any other entry point and included in the exports field. ## Exclude CSS When you use CSS files and import them in your JavaScript files, Bun will bundle the CSS and include it in the build output. As a result, these CSS files will be automatically added to the exports field with appropriate export keys. The `excludeCss` option allows you to prevent CSS files from being included in the exports field if you prefer to handle CSS distribution manually or don't want to expose CSS files as part of your package's public API. ::: code-group ```sh [CLI] bunup --exports.exclude-css ``` ```ts [bunup.config.ts] import { defineConfig } from "bunup"; export default defineConfig({ exports: { excludeCss: true, }, }); ``` ::: ## Include Package JSON By default, exports generation automatically adds `"./package.json": "./package.json"` to your package's exports field. This export is useful for: * **Package introspection**: Allowing consumers to access your package's metadata programmatically * **Tooling compatibility**: Many development tools and package managers expect to be able to import package.json * **Runtime information**: Enabling your package to access its own version and metadata at runtime The `includePackageJson` option allows you to control this behavior: ::: code-group ```sh [CLI] bunup --no-exports.include-package-json ``` ```ts [bunup.config.ts] import { defineConfig } from "bunup"; export default defineConfig({ exports: { includePackageJson: false, // Disable package.json export }, }); ``` ::: When enabled (default), your exports field will include: ```json [package.json] { "exports": { ".": { "import": "./dist/index.js", "types": "./dist/index.d.ts" }, "./package.json": "./package.json" // [!code ++] } } ``` ## All The `all` option controls how open your package exports are. This affects what files consumers can import from your package. When `all: true`, a wildcard subpath export is added that allows importing any file from your package: ::: code-group ```sh [CLI] bunup --exports.all ``` ```ts [bunup.config.ts] import { defineConfig } from "bunup"; export default defineConfig({ exports: { all: true, }, }); ``` ::: This generates: ```json [package.json] { "exports": { ".": { "import": "./dist/index.js", "types": "./dist/index.d.ts" }, "./*": "./*" // [!code ++] } } ``` With `all: true`, consumers can import any file that ends up in your published package. ::: warning When using `all: true`, any file that ends up in your published tarball becomes importable. Control what you publish using the `files` field in package.json or `.npmignore` to avoid exposing internal files. ::: ## Export Keys and Output Structure Bunup generates export keys based on the output file paths. In some cases, such as when using glob patterns like `src/**/*.ts`, the output structure may include the `src/` directory, which affects the generated export keys. In certain configurations, your output files may be nested under a `src/` directory: ::: code-group ```sh [CLI] bunup 'src/**/*.ts' ``` ```ts [bunup.config.ts] export default defineConfig({ entry: ["src/**/*.ts"], }); ``` ::: Output structure: ``` dist/ └── src/ ├── components/ │ └── Button.js └── utils/ └── format.js ``` When this happens, the generated export keys will include the `./src/` prefix: ```json { "exports": { "./src/components/Button": "./dist/src/components/Button.js", "./src/utils/format": "./dist/src/utils/format.js" } } ``` If you don't want `./src/` in your export keys, you can set `sourceBase` to `'./src'`. This tells Bunup to use `src/` as the base directory, which removes `src/` from the output structure: ::: code-group ```sh [CLI] bunup 'src/**/*.ts' --source-base ./src ``` ```ts [bunup.config.ts] export default defineConfig({ entry: ["src/**/*.ts"], sourceBase: "./src", }); ``` ::: Output structure: ``` dist/ ├── components/ │ └── Button.js └── utils/ └── format.js ``` As a result, the generated export keys no longer include the `./src/` prefix: ```json { "exports": { "./components/Button": "./dist/components/Button.js", "./utils/format": "./dist/utils/format.js" } } ``` ### Key Takeaway Bunup generates export keys based on the output file paths relative to your output directory. The `sourceBase` option controls the output structure, which directly affects the generated export keys. This is not specific to `./src`, you can use `sourceBase` with any directory structure to control how paths appear in your export keys. For more details on how `sourceBase` works, see the [Source Base Directory](/docs/guide/options#source-base-directory) option. --- --- url: /docs/extra-options/inject-styles.md --- # Inject Styles Inject styles automatically includes your CSS styles in your JavaScript bundle, so users don't need to manually import CSS files. Instead of creating separate `.css` files, your styles become part of your JavaScript code. ## How it works Instead of outputting CSS files in the build output, inject styles converts your CSS into JavaScript that creates `