From skills
Use when bundling JS/TS directly with esbuild, Rollup, or tsup (outside a framework's Vite setup) — choosing the tool, output formats (ESM/CJS/IIFE), tree-shaking and `sideEffects`, `external`/peer deps, code splitting, minification, source maps, `.d.ts` emission, and the `package.json` `exports`/`main`/`module`/`types` map for publishing a library. Covers why a bundler differs from `tsc`.
How this skill is triggered — by the user, by Claude, or both
Slash command
/skills:bundler-expertiseThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
A standalone **hub** for driving JS/TS bundlers directly. For app builds inside Vite (which _is_ Rollup + esbuild preconfigured) use [[vite-expertise]]; for `bun build` and Bun as the runtime see [[bun-expertise]].
A standalone hub for driving JS/TS bundlers directly. For app builds inside Vite (which is Rollup + esbuild preconfigured) use [[vite-expertise]]; for bun build and Bun as the runtime see [[bun-expertise]].
Tool versions move fast; verify flags against the live docs before relying on them.
rollup-plugin-dts wrapper)package.json exports: https://nodejs.org/api/packages.html#exportsexports/types mapWebFetch the relevant tool's options page for exact flag names — esbuild and tsup rename options between minors.
The concepts that decide how to bundle and ship:
.d.ts from type analysis. tsc is the checker and the declaration emitter. Run both: tsc --noEmit for checking, the bundler (or tsc --emitDeclarationOnly) for output. See [[typescript-expertise]].node_modules into one artifact. A library marks runtime/peer deps external so the consumer's copy is used and deduped — bundling React into a library ships two Reacts and breaks hooks.import), CJS (require), IIFE/UMD (<script> global). A library usually ships ESM+CJS so both consumer styles resolve; the package.json exports map routes each."sideEffects": false (or a file list) is the promise that lets dead code drop.exports is authoritative. In modern Node/bundlers the exports map overrides main/module/types. Most "no IntelliSense" and "wrong entry" bugs are a malformed exports map.Pick the tool:
| Tool | Strength | Reach for it when |
|---|---|---|
| esbuild | extremely fast; bundles + transpiles TS/JSX | apps, CLIs, functions, dev speed; you don't need .d.ts |
| Rollup | best tree-shaking, plugin ecosystem, clean library output | publishing libraries; fine-grained output control |
| tsup | thin wrapper over esbuild + .d.ts via rollup-plugin-dts | the common case: a TS library, both formats, types, one config |
| Rolldown | Rust Rollup-compatible bundler (powers newer Vite) | Rollup semantics at esbuild-ish speed |
Default for a TS library: tsup. It gives ESM+CJS, minification, source maps, and .d.ts from one small config. Drop to raw Rollup only when you need plugin control tsup doesn't expose.
// tsup.config.ts
import { defineConfig } from "tsup";
export default defineConfig({
entry: ["src/index.ts"],
format: ["esm", "cjs"], // ship both for broad consumer compat
dts: true, // emit index.d.ts
sourcemap: true,
clean: true,
treeshake: true,
external: ["react"], // never bundle peers (see below)
});
<script> tag / global usage. Most libraries don't need it.Dual ESM+CJS is the safe library default. Going ESM-only is increasingly viable but excludes require() consumers — decide deliberately.
sideEffects// package.json — your library promises no import-time side effects
{ "sideEffects": false }
// or list the files that DO have them (CSS, polyfills):
{ "sideEffects": ["*.css", "./src/polyfill.ts"] }
"sideEffects": false, bundlers conservatively keep modules whose exports look unused — the #1 reason dead code survives.import "./thing") rare and declared.external — never bundle peersexternal: ["react", "react-dom", /^node:/],
external so the consumer's copy is used and deduped.node:fs, etc.) for Node-targeted builds.external is mainly a library concern.package.json map — how consumers resolve your code{
"type": "module",
"main": "./dist/index.cjs", // legacy CJS entry
"module": "./dist/index.js", // legacy ESM hint (bundlers)
"types": "./dist/index.d.ts", // legacy types entry
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js", // ESM consumers
"require": "./dist/index.cjs", // CJS consumers
},
"./package.json": "./package.json",
},
"files": ["dist"],
"sideEffects": false,
}
exports is authoritative and overrides main/module. Keep the legacy fields for old tooling, but get exports right first.types must come first in each exports condition, ideally per format. A mismatched types path is the usual cause of "library works but has no IntelliSense."import/require split ESM vs CJS. Add "default" last as a catch-all.dist in files so you publish the build, not the source. Verify with npm pack --dry-run.import { build } from "esbuild";
await build({
entryPoints: ["src/cli.ts"],
bundle: true,
platform: "node", // or "browser"
target: "node20",
format: "esm",
outfile: "dist/cli.js",
sourcemap: true,
minify: true,
packages: "external", // don't bundle node_modules deps (for Node apps)
});
.d.ts. Run tsc --noEmit for checking and tsc --emitDeclarationOnly (or tsup) for types. See [[typescript-expertise]].packages: "external" keeps node_modules out of a Node bundle; for a single-file deploy artifact, omit it to inline everything.external; bundled React/Vue breaks consumers and bloats size."sideEffects" and wondering why tree-shaking keeps dead code. Declare it.tsc --noEmit separately. See [[typescript-expertise]].types condition in exports. Consumers lose IntelliSense even though the code runs. Put types first.main/module contradict exports. exports wins in modern resolvers — fix it first.require() consumers. Fine if intentional, surprising if not.dist (or vice versa). Set files and verify with npm pack --dry-run.bun build as another bundler/target, and Bun as the runtime.tsc for type-check and .d.ts; isolatedModules for single-file transpilers; module resolution.npx claudepluginhub thomasfosterau/skills --plugin skillsGuides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.