✅ Zero-config ESM/TS package builder powered by rolldown.
# bundle npx obuild ./src/index.ts # transform npx obuild ./src/runtime/:./dist/runtime
You can use --dir to set the working directory.
If paths end with /, obuild uses transpile mode using oxc-transform instead of bundle mode with rolldown.
import { build } from "obuild"; await build({ cwd: ".", entries: ["./src/index.ts"], });
You can use build.config.mjs (or .ts) or pass config to build() function.
import { defineBuildConfig } from "obuild/config"; export default defineBuildConfig({ entries: [ { type: "bundle", input: ["./src/index.ts", "./src/cli.ts"], // outDir: "./dist", // minify: false, // minifyLibs: true, // minify only bundled deps in `_chunks/libs/` (or a list of package names) // compressLibs: true, // compress bundled deps in `_chunks/libs/` into self-extracting ES modules (or a list of package names) // compressLibs: { algorithm: "deflate" }, // browser compatible (default "brotli" is ~20% smaller, Node.js only) // stub: false, // rolldown: {}, // https://rolldown.rs/reference/config-options // dts: {}, // https://github.com/sxzz/rolldown-plugin-dts#options // license: { gzip: false }, // emit plain `THIRD-PARTY-LICENSES.md` instead of `.md.gz` (set `false` to disable) // trace: ["some-dep"], // trace listed deps with nf3 instead of bundling (see below) }, { type: "transform", input: "./src/runtime", outDir: "./dist/runtime", // minify: false, // stub: false, // oxc: {}, // resolve: {} }, ], hooks: { // start: (ctx) => {}, // end: (ctx) => {}, // entries: (entries, ctx) => {}, // rolldownConfig: (config, ctx) => {}, // rolldownOutput: (output, res, ctx) => {}, }, });
Some node_modules dependencies cannot be bundled reliably (native bindings, relative file access, dynamic requires, ...).
Set trace on a bundle entry to a list of package names to opt them out of bundling via nf3: listed packages (including subpath imports) are kept as external imports, and only the files actually required at runtime are copied into <outDir>/node_modules (tree-shaken and deduplicated). All other node_modules imports are bundled as usual.
export default defineBuildConfig({ entries: [ { type: "bundle", input: ["./src/index.ts"], trace: ["youch", "cookie-es"], }, ], });
Pass an object to also customize nf3 trace options:
trace: { include: ["youch", "cookie-es"], // traceInclude: ["some-native-dep"], // force trace even if not statically imported // fullTraceInclude: ["pkg-with-assets"], // copy all files of a package // transform: [{ filter: (id) => /\.m?js$/.test(id), handler: (code, id) => minify(id, code).code }], // hooks: { tracedPackages: (pkgs) => {} }, }
Bundle entries support the import bytes and import text proposals. Files imported with a bytes or text type attribute are inlined into the bundle as a Uint8Array or a string, regardless of their extension:
import wasm from "./lib.wasm" with { type: "bytes" }; // Uint8Array import readme from "../README.md" with { type: "text" }; // string const { default: template } = await import("./template.html", { with: { type: "text" } });
Generated .d.mts files type these as Uint8Array and string. TypeScript itself does not implement the proposals yet, so the importing source needs a // @ts-expect-error comment (or a module declaration) until it does.
When working on a package locally, it can be tedious to rebuild or run the watch command every time.
You can use stub: true (per entry config) or the --stub CLI flag. In this mode, obuild skips the actual build and instead links the expected dist paths to the source files.
- For bundle entries,
.mjsand.d.mtsfiles re-export the source file. - For transpile entries, src dir is symlinked to dist.
Caveats:
- You need a runtime that natively supports TypeScript. Deno, Bun, Vite, and Node.js (1)
- For transpile mode, you need to configure your bundler to resolve either
.tsor.mjsextensions. - For bundle mode, if you add a new entry or add/remove a
defaultexport, you need to run the stub build again.
(1) For Node.js, you have several options:
- Using
node --experimental-strip-types(Available in 22.6) - Using jiti (
node --import jiti/register) - Using oxc-node (
node --import @oxc-node/core/register) - Using unloader (
node --import unloader/register)
💛 Released under the MIT license.