CLI tool
unjs/unbuild avatar
unjs/unbuild

unjs/unbuild: a Rollup-based build system for JavaScript libraries

📦 A unified JavaScript build system

2,728 stars107 forksTypeScriptMIT

At a glance

What is it?
unbuild infers its build configuration from package.json and produces CommonJS, ESM and type declarations, with a bundleless mode via mkdist. It fits library authors in the unjs ecosystem; it is a poor fit for application bundling.
Who is it for?
unbuild suits library authors who publish to npm and want entries, output formats and type declarations derived from package.json rather than hand-written Rollup config. It does not suit application bundling, where a bundler with HTML and asset handling is the right tool.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 8 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What unbuild is for, and who it is not for

unbuild is a build system for JavaScript packages that are published to npm. The README describes it as "a unified JavaScript build system", and the repository description repeats that line. Its output targets are the three artifacts a published package usually needs: a CommonJS file, an ES module file, and a type declaration file. The README's usage example maps those to dist/index.cjs, dist/index.mjs and dist/index.d.ts in the exports field of package.json.

The audience is narrow on purpose. If you maintain a library, a CLI, or an internal shared package inside a monorepo, unbuild is aimed at you. If you are building a web application, it is not. There is no mention of an HTML entry, a dev server, asset pipelines, or code splitting for routes anywhere in the README. Rollup sits underneath, so in principle a plugin could be added through the rollup key in the config, but the documented surface is library output.

The project belongs to the unjs organisation, and the README points to unjs/template as a more complete project setup. That matters for evaluation: the intended workflow is not unbuild alone but unbuild plus a package.json shaped the way unjs packages are shaped.

How unbuild decides what to build

The core mechanism is inference. The README states that configuration is "automatically inferred from fields in package.json mapped to src/ directory". In practice that means the exports, main and types fields tell unbuild which output files to produce, and the source is expected under src/. The README's usage example shows exactly this shape: exports points at ./dist/index.mjs and ./dist/index.cjs, main points at ./dist/index.cjs, types points at ./dist/index.d.ts, and the files array lists dist. You write src/index.ts and run the build; you do not write a Rollup config for the simple case.

When inference is not enough, a build.config file takes over. The README lists build.config.{js,cjs,mjs,ts,mts,cts,json} as the accepted file names, and also allows an unbuild key inside package.json. Inside that config, entries is an array. A plain string entry such as "./src/index" goes through the default Rollup-based builder. An object entry with builder: "mkdist" switches to the bundleless path, where mkdist transpiles file to file and keeps the original source structure, writing into an outDir you name.

The declaration option controls type output. The README documents the values precisely: "compatible" produces dist/index.d.mts, dist/index.d.cts and dist/index.d.ts from src/index.ts; "node16" produces only the .d.mts and .d.cts files; true is equivalent to "compatible"; false disables declaration generation; and undefined auto-detects from package.json, choosing "compatible" when a types field exists and false otherwise. That auto-detection is the reason a package without a types field silently gets no declarations.

The config can also be an array, which the README shows as a way to run several builds in one pass. The example pairs a normal build into build/ with a second named "minified" build into build/min that sets rollup.esbuild.minify to true. The name field is what distinguishes the two entries in the array.

Installing unbuild and shipping a first package

The README does not give an install command. It shows the build being run with npx, which resolves the package from the registry without a prior install step. The package.json in the repository declares the binary as unbuild, pointing at ./dist/cli.mjs, so any package manager that installs the package will expose that name.

Start with a source file. The README's example is a single exported function:

js
export const log = (...args) => {
  console.log(...args);
};

Next, wire package.json so the build knows what to emit. The README's example sets the module type, the build and prepack scripts, the exports map, main, types and files:

json
{
  "type": "module",
  "scripts": {
    "build": "unbuild",
    "prepack": "unbuild"
  },
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs"
    }
  },
  "main": "./dist/index.cjs",
  "types": "./dist/index.d.ts",
  "files": ["dist"]
}

Then run the build. The README uses npx:

sh
npx unbuild

What you should see, based on the README, is output in dist containing the CJS and ESM files plus declarations, and CLI output that includes output size and exports for inspection. The README states that the CLI output "includes output size and exports for quick inspection", which is the fastest way to confirm the exports map and the emitted files agree.

If you need explicit entries or a second build, add a config file. The README's minimal version is:

js
export default {
  entries: ["./src/index"],
};

For typed configuration, import defineBuildConfig from unbuild. The README's fuller example sets outDir, declaration and a mkdist entry in one object:

js
import { defineBuildConfig } from "unbuild";

export default defineBuildConfig({
  entries: [
    "./src/index",
    {
      builder: "mkdist",
      input: "./src/package/components/",
      outDir: "./build/components",
    },
  ],
  outDir: "build",
  declaration: true,
});

Two recipes appear in the README for cases that commonly need extra configuration. Sourcemaps are enabled with a single option:

ts
import { defineBuildConfig } from "unbuild";

export default defineBuildConfig({
  sourcemap: true,
});

Decorators need esbuild's tsconfigRaw, since the README routes them through the rollup.esbuild key:

ts
import { defineBuildConfig } from "unbuild";

export default defineBuildConfig({
  rollup: {
    esbuild: {
      tsconfigRaw: {
        compilerOptions: {
          experimentalDecorators: true,
        },
      },
    },
  },
});

The stub mode and why it changes local development

The README describes a passive watcher: run unbuild --stub once and the dist directory is stubbed, powered by jiti, so you can link the project without watching and rebuilding during development. That is a different model from a conventional watch task. Instead of a process that stays resident and recompiles on every save, the stub is written once and the linked consumer resolves through jiti at runtime.

The trade-off is that a stub is not a real build. It exists so that another project can import your package during development without a rebuild loop. Anything that inspects the emitted files, such as checking bundle size, verifying that declarations resolve, or running a packaging step, needs a real build. The README also lists stub as a script in the repository's own package.json, which is how the maintainers build their own source for local use.

This is the feature that most distinguishes unbuild from a plain Rollup configuration. A hand-written Rollup config gives you watch mode; unbuild gives you a one-shot stub and expects your consuming project to handle the resolution.

Dependency checks as a build failure

The README lists a feature it calls secure builds: unbuild automatically checks for potential missing and unused dependencies and fails CI. The README links to the npm documentation for the dependencies field, which is the field being compared against. So the check is about what your source imports versus what your package.json declares.

This is a genuinely useful default for published libraries, because a missing dependency is the kind of bug that only appears for consumers after publish, and an unused dependency is dead weight in every install. Making it fail CI rather than warn is the stronger choice, and it is also the one most likely to annoy you the first time it fires on a dependency you thought was implicit.

There is no documentation in the README of how to suppress or configure this check. If it produces a false positive in your project, the README does not say what to do about it, and the option reference is pointed at src/types.ts rather than spelled out on the page. That is a real gap: a check that fails CI needs a documented escape hatch.

Where unbuild is the wrong tool

The clearest boundary is that unbuild is not an application bundler. Nothing in the README covers HTML entries, dev servers, hot reload for an app, or asset handling. Reaching for unbuild to build a front-end application means working against the documented surface and configuring Rollup manually through the rollup key, at which point you have a Rollup setup with extra inference on top.

The second boundary is the inference itself. Automatic config is convenient exactly as long as your package.json matches the expected shape. The README's own declaration rules show how much rides on that: if your package.json has no types field and you leave declaration undefined, no declarations are generated, and the failure is silent rather than an error. A package that ships without types is a real regression for TypeScript consumers, and it can happen through omission rather than a mistake.

The README also carries a note that the maintainers are experimenting with obuild as a next-generation successor based on rolldown, and it says to try obuild if you mainly need faster build speeds and do not mind beta software. That is the maintainers' own framing, and it is a signal about where future effort may go. It is not a deprecation notice: unbuild is not archived, and the last push to the repository was on 2026-09-23.

How unbuild compares with a hand-written Rollup config

The honest alternative is Rollup configured by hand. unbuild depends on rollup directly, along with @rollup/plugin-alias, @rollup/plugin-commonjs, @rollup/plugin-json, @rollup/plugin-node-resolve and @rollup/plugin-replace, plus rollup-plugin-dts for declarations and esbuild for transpilation. A manual setup would assemble the same pieces.

The difference is where the decisions live. With Rollup alone, you write the input, the output array with both formats, the plugin list, and the declaration step, and you keep them in sync with package.json yourself. With unbuild, package.json is the source of truth for entries and output paths, and the plugin stack is chosen for you. The cost is that the plugin stack is not yours to rearrange except through the rollup key, and the inference rules are documented only in part on the README page.

For a package with unusual output requirements, such as several formats beyond CJS and ESM, or a custom plugin ordering, hand-written Rollup remains the more direct route. For a standard library package in the unjs style, unbuild removes a config file that would otherwise be mostly boilerplate.

Editorial conclusion

unbuild suits library authors who publish to npm and want entries, output formats and type declarations derived from package.json rather than hand-written Rollup config. It does not suit application bundling, where a bundler with HTML and asset handling is the right tool. Before adopting, check whether your package.json exports map already matches the dist paths unbuild expects, and read src/types.ts, which the README points to as the option reference, since the README itself only documents a subset.

Frequently asked questions

How do I install unbuild and run a first build?

The README runs the build with npx unbuild rather than showing a separate install step, and the repository's package.json declares the unbuild binary. After that, the README's example package.json expects dist/index.mjs, dist/index.cjs and dist/index.d.ts to be produced from src/index.ts.

Does unbuild generate type declarations automatically?

Only when the declaration option resolves to a truthy value. The README states that undefined auto-detects from package.json and becomes "compatible" when a types field exists, otherwise false, so a package without a types field gets no declarations unless you set the option explicitly.

What does the unbuild --stub flag do?

The README describes it as a passive watcher: it stubs dist once, powered by jiti, so you can link your project without watching and rebuilding during development. It is not a full build, so anything that inspects emitted files needs a real run.

Which config file names does unbuild accept?

The README lists build.config.{js,cjs,mjs,ts,mts,cts,json}, and also allows an unbuild key inside package.json. The config may export a single object or an array of builds.

What does unbuild mean as a package name on npm?

It is the package published under the name unbuild by the unjs organisation, described in its README as a unified JavaScript build system built on Rollup. The repository's package.json lists version 3.6.1 and an MIT licence.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. unjs/unbuild on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/unjs-unbuild.svg)](https://hysenlabs.com/projects/unjs-unbuild)