Bunup Workspaces ​
Effortlessly manage multiple packages in a monorepo with Bunup’s built-in workspace support.
This eliminates the need for separate build configurations and multiple commands for each package. With a single configuration file and a single command, you can build all your packages at once.
Creating a Workspace Configuration ​
Define your workspace using the defineWorkspace
function:
import { defineWorkspace } from "bunup";
export default defineWorkspace([
// Package configurations go here
]);
Package Configuration ​
Each package requires three properties:
Property | Type | Description |
---|---|---|
name | string | Unique identifier for the package |
root | string | Path to the package directory, relative to the monorepo root |
config | BunupConfig | BunupConfig[] | Optional build configuration(s) for this package |
👉 If you omit config
, Bunup will use defaults:
- ESM-only build
- One of the default entry points (e.g.
src/index.ts
)
Basic Usage ​
A minimal workspace with two packages:
import { defineWorkspace } from "bunup";
export default defineWorkspace([
{
name: "core",
root: "packages/core",
config: {
// Bunup finds 'src/index.ts' by default
// Or specify exactly which files to build
// entry: ["src/index.ts", "src/plugins.ts"],
format: ["esm", "cjs"],
},
},
{
name: "utils",
root: "packages/utils",
// Uses default entry: src/index.ts
// Uses default format: esm
},
]);
Here, core
has custom formats, while utils
works out of the box with defaults.
Shared Options ​
You can define shared options for all packages, reducing repetition:
export default defineWorkspace(
[
{
name: "core",
root: "packages/core",
config: {
format: ["esm"], // overrides shared format
},
},
{
name: "utils",
root: "packages/utils",
// config is optional, shared options apply
},
],
{
// Shared options
format: ["esm", "cjs"],
exports: true,
unused: true,
}
);
Multiple Build Configurations ​
Each package can have multiple builds by passing an array:
export default defineWorkspace([
{
name: "web",
root: "packages/web",
config: [
{
entry: "src/index.ts",
name: "node",
format: "esm",
target: "node",
},
{
entry: "src/browser.ts",
name: "browser",
format: ["esm", "iife"],
target: "browser",
outDir: "dist/browser",
},
],
},
]);
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:
export default defineWorkspace([
{
name: "main",
root: "packages/main",
config: [
{
entry: "src/index.ts",
format: ["esm", "cjs"],
},
{
entry: "src/cli.ts",
format: ["esm"],
},
],
},
]);
TIP
Use the name
property inside each config to easily distinguish builds in logs.
Path Resolution ​
All paths in package configs are relative to the package root:
myproject/
├── packages/
│ ├── core/ <- package root
│ │ ├── src/ <- entries resolved here
│ │ └── dist/ <- outputs here
│ └── utils/
├── bunup.config.ts
└── package.json
Example:
{
name: "core",
root: "packages/core",
config: {
entry: "src/index.ts", // resolves to packages/core/src/index.ts
outDir: "dist", // outputs to packages/core/dist/
},
}
Plugin Paths
When using plugins (like copy
), paths are also resolved relative to the package root. For example, copy("assets/**/*.svg")
in the core
package will copy from packages/core/assets
.
Building Packages ​
Build all packages ​
bunx bunup
Watch mode ​
bunx bunup --watch
Bunup will watch all packages and rebuild only those that change.
Build specific packages ​
Use the --filter
option with package names:
bunx bunup --filter core,utils
# or in watch mode
bunx bunup --filter core,utils --watch
Incremental Builds
Workspaces are incremental: only changed packages are rebuilt.