From unbuild

Migrate from unbuild to Packem

From unbuild

unbuild and Packem share a lineage: both are Rollup-based library bundlers that lean on package.json and a build.config style file, and both can infer entries automatically. Migrating is mostly a one-to-one translation of unbuild's BuildConfig into Packem's defineConfig, with Packem doing even more inference from your exports map.

Packem ships a migrate command that handles the package.json rewrites for you.

Run the automated migration

From your project root:

packem migrate

This will:

  • Replace unbuild in your dependencies, devDependencies, and peerDependencies with @visulima/packem.
  • Rewrite scripts that invoke unbuild to call packem build instead.
  • Detect a build.config.* file and warn you that it needs a manual conversion (it is not auto-converted).

Preview without writing anything:

packem migrate --dry-run

packem migrate modifies files in place and uncommitted changes can be lost. Commit first, or use --dry-run.

Option mapping

unbuild's build.config.ts exports a BuildConfig. Most fields have a direct Packem counterpart:

unbuild BuildConfigPackem equivalent
entries: ['src/index']Inferred from package.json exports/main/module/bin, or the entries option
entries with mkdist ({ builder: 'mkdist', input: 'src/' })unbundle: true (preserves the source file structure)
declaration: truedeclaration: true (also auto-detected from a types field)
declaration: 'compatible' | 'node16'declaration: 'compatible' | 'node16' (same values)
clean: trueOn by default; disable with --no-clean
sourcemap: truesourcemap: true or --sourcemap
rollup.emitCJS / cjsBridgeExpress CJS output via a require condition in exports; cjsInterop: true for interop
rollup.inlineDependencies--no-external (inline) or curate externals
externals: [...]externals option; dependencies/peerDependencies external by default
aliasalias option (also inferred from tsconfig paths)
replace--env.KEY=value / envFile for env constants
hookshooks option
failOnWarnCovered by the built-in validator (validation)
presetpreset option
outDiroutDir option (default dist)
Per-entry outDir / formatPer-entry settings via the entries array

Step-by-step

1. Translate build.config.ts

A common unbuild config:

// build.config.ts
import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
  entries: ['src/index'],
  declaration: true,
  clean: true,
  rollup: {
    emitCJS: true,
  },
})

The emitCJS flag and the explicit entry both move into package.json — a require condition produces the CJS output, and the "." export plus src/index.ts replace the entries list:

{
  "files": ["dist"],
  "main": "./dist/index.cjs",
  "module": "./dist/index.mjs",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": { "types": "./dist/index.d.mts", "default": "./dist/index.mjs" },
      "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" }
    }
  },
  "scripts": {
    "build": "packem build"
  }
}

Then packem.config.ts only needs a transformer; declaration is auto-detected from the types field, and clean is already the default:

import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
})

Unlike unbuild, Packem requires a transformer (under the default Rollup backend). esbuild is the recommended default.

2. Keep explicit entries when you need them

If you relied on unbuild's entries array for entries outside your public exports, use Packem's entries option. It accepts a string or a richer object with per-entry declaration, runtime, cjs/esm, and outDir:

import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  entries: [
    'src/index.ts',
    { input: 'src/cli.ts', executable: true },
  ],
})

3. Map the mkdist builder to unbundle

unbuild's mkdist builder transpiles a directory file-by-file without bundling. Packem's equivalent is unbundle mode, which preserves the source file structure instead of bundling into single files:

import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  unbundle: true,
})

Or per build: packem build --unbundle.

4. Carry over hooks, aliases, and externals

import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  alias: {
    '~': './src',
  },
  externals: ['some-undeclared-peer'],
  hooks: {
    // Same lifecycle-hook idea as unbuild's `hooks`
  },
})

5. Delete build.config.ts

Once packem build produces the same dist/, remove build.config.ts.

Gotchas

  • A transformer is required. unbuild picks its compiler for you; Packem makes it an explicit choice under the default Rollup backend. Add transformer to your config or the build will refuse to run.
  • emitCJS → export conditions. There is no emitCJS flag. Dual output comes from having both import and require conditions (or .mjs/.cjs extensions) in package.json.
  • mkdist becomes unbundle. If you used the mkdist builder for unbundled, directory-preserving output, switch to unbundle: true rather than expecting a bundled single file.
  • Inlining dependencies is opt-in. unbuild's inlineDependencies has no single equivalent — declared deps are external by default; use --no-external to inline everything, or curate externals.
  • Config conversion is manual. packem migrate rewrites package.json only and warns about build.config.*. Translate the config yourself using the table above.

Next steps

  • Configuration — Full option reference.
  • Unbundle — Preserve source file structure (the mkdist equivalent).
  • Entry — Configure explicit entry points.
Support

Contribute to our work and keep us going

Community is the heart of open source. The success of our packages wouldn't be possible without the incredible contributions of users, testers, and developers who collaborate with us every day.Want to get involved? Here are some tips on how you can make a meaningful impact on our open source projects.

Ready to help us out?

Be sure to check out the package's contribution guidelines first. They'll walk you through the process on how to properly submit an issue or pull request to our repositories.

Submit a pull request

Found something to improve? Fork the repo, make your changes, and open a PR. We review every contribution and provide feedback to help you get merged.

Good first issues

Simple issues suited for people new to open source development, and often a good place to start working on a package.
View good first issues