library-bundler
Configure build systems, optimize bundle size, manage exports for ESM/CJS/UMD, and publish packages to NPM with proper versioning
When & Why to Use This Skill
The Library Bundler skill is an expert-level utility designed to automate the configuration, optimization, and publishing of JavaScript and TypeScript libraries. It simplifies complex build workflows using modern tools like tsup, Vite, and Rollup, ensuring packages are optimized for performance through tree-shaking and minification. By managing multi-format exports (ESM, CJS, UMD) and the full NPM publishing lifecycle, it helps developers maintain high-quality, compatible, and lightweight packages while adhering to modern web standards.
Use Cases
- Case 1: Bootstrapping a new TypeScript library with multi-format support (ESM and CJS) to ensure compatibility across modern browsers and Node.js.
- Case 2: Optimizing an existing package's bundle size using advanced tree-shaking, code splitting, and dependency externalization techniques.
- Case 3: Configuring complex 'exports' maps in package.json to solve module resolution issues and the 'Dual Package Hazard'.
- Case 4: Automating the NPM release workflow, including semantic versioning, changelog generation, and publishing with provenance for improved security.
- Case 5: Migrating legacy build systems (like Webpack or manual scripts) to modern, high-performance bundlers like tsup or esbuild for faster iteration.
| name | library-bundler |
|---|---|
| description | Configure build systems, optimize bundle size, manage exports for ESM/CJS/UMD, and publish packages to NPM with proper versioning |
Library Bundler
Expert skill for building, bundling, and publishing component libraries to NPM. Specializes in modern build tools (tsup, Vite, Rollup), bundle optimization, multi-format exports, and package publishing workflows.
Core Capabilities
1. Build System Configuration
- tsup: Fast TypeScript bundler with zero config
- Vite: Modern build tool with HMR and optimization
- Rollup: Powerful module bundler for libraries
- esbuild: Extremely fast JavaScript bundler
- Webpack: Full-featured bundler (when needed)
2. Bundle Optimization
- Tree-shaking for dead code elimination
- Code splitting for optimal loading
- Minification (Terser, esbuild)
- Compression (gzip, brotli)
- Bundle size analysis
- Dependency externalization
- Chunk optimization
3. Multi-Format Support
- ESM (ES Modules): Modern standard
- CJS (CommonJS): Node.js compatibility
- UMD (Universal Module Definition): Browser globals
- IIFE: Self-executing browser bundles
- Dual package support (ESM + CJS)
4. TypeScript Integration
- Type declaration generation (.d.ts)
- Declaration maps for IDE navigation
- Type checking during build
- Multiple tsconfig.json support
- Path aliasing resolution
5. Package Publishing
- NPM registry publishing
- Semantic versioning (semver)
- Changelog generation
- Git tagging
- Pre-publish validation
- Scoped packages (@org/package)
- Package provenance
6. Development Workflow
- Watch mode for rapid iteration
- Source maps for debugging
- Hot Module Replacement (HMR)
- Build caching
- Parallel builds
- Incremental compilation
Workflow
Phase 1: Build Configuration Setup
Analyze Project Structure
- Entry points (index.ts, components/)
- Output formats needed (ESM, CJS, UMD)
- External dependencies
- Target environments (browsers, Node.js)
Choose Build Tool
- tsup: Best for simple TS libraries
- Vite: Best for modern ESM-first libraries
- Rollup: Best for maximum control
- esbuild: Best for raw speed
Configure package.json
- Entry points (main, module, types, exports)
- Build scripts
- Files to include
- Peer dependencies
Phase 2: Optimization
Tree-Shaking Setup
- Mark side effects in package.json
- Use ESM imports/exports
- Avoid namespace imports
- Test tree-shaking effectiveness
Bundle Size Analysis
- Use bundle analyzer tools
- Identify large dependencies
- Consider alternatives
- Externalize heavy deps
Code Splitting
- Split by route/feature
- Shared chunk optimization
- Dynamic imports where beneficial
Phase 3: Publication Preparation
Version Management
- Follow semantic versioning
- Update package.json version
- Create git tag
- Generate changelog
Pre-publish Checks
- Run tests
- Build all formats
- Verify type declarations
- Check bundle sizes
- Test in consuming projects
Publish to NPM
- npm login
- npm publish (or publish:dry-run)
- Verify on npmjs.com
- Test installation
Build Tool Configurations
tsup Configuration
// tsup.config.ts
import { defineConfig } from 'tsup'
export default defineConfig({
entry: ['src/index.ts'],
format: ['cjs', 'esm'],
dts: true,
splitting: false,
sourcemap: true,
clean: true,
treeshake: true,
minify: true,
external: ['react', 'react-dom'],
esbuildOptions(options) {
options.banner = {
js: '"use client"', // For React Server Components
}
},
})
Vite Library Mode
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { resolve } from 'path'
import dts from 'vite-plugin-dts'
export default defineConfig({
plugins: [
react(),
dts({
insertTypesEntry: true,
}),
],
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'MyLibrary',
formats: ['es', 'cjs', 'umd'],
fileName: (format) => `my-library.${format}.js`,
},
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM',
},
},
},
sourcemap: true,
minify: 'esbuild',
},
})
Rollup Configuration
// rollup.config.mjs
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import typescript from '@rollup/plugin-typescript'
import { terser } from 'rollup-plugin-terser'
import peerDepsExternal from 'rollup-plugin-peer-deps-external'
import postcss from 'rollup-plugin-postcss'
export default {
input: 'src/index.ts',
output: [
{
file: 'dist/index.cjs.js',
format: 'cjs',
sourcemap: true,
},
{
file: 'dist/index.esm.js',
format: 'esm',
sourcemap: true,
},
{
file: 'dist/index.umd.js',
format: 'umd',
name: 'MyLibrary',
sourcemap: true,
globals: {
react: 'React',
'react-dom': 'ReactDOM',
},
},
],
plugins: [
peerDepsExternal(),
resolve(),
commonjs(),
typescript({
tsconfig: './tsconfig.json',
declaration: true,
declarationDir: 'dist',
}),
postcss({
extensions: ['.css'],
minimize: true,
inject: false,
extract: 'styles.css',
}),
terser(),
],
external: ['react', 'react-dom'],
}
Package.json Configuration
Modern Package.json (ESM-First)
{
"name": "@myorg/ui-library",
"version": "1.0.0",
"description": "Modern React component library",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./button": {
"types": "./dist/button.d.ts",
"import": "./dist/button.js",
"require": "./dist/button.cjs"
},
"./package.json": "./package.json"
},
"files": [
"dist",
"README.md",
"LICENSE"
],
"sideEffects": false,
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"prepublishOnly": "npm run build && npm test",
"publish:dry": "npm publish --dry-run"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"@types/react": "^18.2.0",
"react": "^18.2.0",
"tsup": "^8.0.0",
"typescript": "^5.3.0"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/myorg/ui-library"
},
"keywords": [
"react",
"components",
"ui",
"library"
],
"author": "Your Name",
"license": "MIT"
}
Dual Package (ESM + CJS)
{
"name": "my-library",
"version": "1.0.0",
"type": "module",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
}
},
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts"
}
Monorepo Package (Turborepo/Nx)
{
"name": "@myorg/ui-components",
"private": false,
"version": "1.0.0",
"exports": {
"./button": {
"types": "./dist/button.d.ts",
"import": "./dist/button.js"
},
"./input": {
"types": "./dist/input.d.ts",
"import": "./dist/input.js"
}
},
"typesVersions": {
"*": {
"button": ["./dist/button.d.ts"],
"input": ["./dist/input.d.ts"]
}
}
}
TypeScript Configuration
Library tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"removeComments": true,
"strict": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": false,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.test.tsx"]
}
Optimization Strategies
1. Tree-Shaking Optimization
// package.json
{
"sideEffects": false
}
// Or specify files with side effects
{
"sideEffects": ["*.css", "*.scss"]
}
// Use named exports (tree-shakeable)
export { Button } from './Button'
export { Input } from './Input'
// Avoid default exports for libraries
// ❌ export default { Button, Input }
2. Code Splitting Strategy
// tsup.config.ts
export default defineConfig({
entry: {
index: 'src/index.ts',
button: 'src/components/Button/index.ts',
input: 'src/components/Input/index.ts',
},
format: ['esm', 'cjs'],
splitting: true, // Enable code splitting
dts: true,
})
3. Bundle Size Analysis
# Install bundle analyzer
npm install -D rollup-plugin-visualizer
# Add to rollup config
import { visualizer } from 'rollup-plugin-visualizer'
plugins: [
visualizer({
filename: './dist/stats.html',
open: true,
}),
]
4. External Dependencies
// Don't bundle these (keep as peerDependencies)
external: [
'react',
'react-dom',
'framer-motion',
'@radix-ui/react-*',
]
// Regex pattern for dynamic externals
external: (id) => {
return /^react/.test(id) || /^@radix-ui/.test(id)
}
5. Minification Options
// esbuild (fastest)
minify: true
// Terser (best compression)
import { terser } from 'rollup-plugin-terser'
plugins: [
terser({
compress: {
drop_console: true,
drop_debugger: true,
},
format: {
comments: false,
},
}),
]
Publishing Workflow
1. Semantic Versioning
# Patch: Bug fixes (1.0.0 → 1.0.1)
npm version patch
# Minor: New features, backwards compatible (1.0.0 → 1.1.0)
npm version minor
# Major: Breaking changes (1.0.0 → 2.0.0)
npm version major
# Pre-release
npm version prerelease --preid=beta
# 1.0.0 → 1.0.1-beta.0
2. Changelog Generation
# Install conventional-changelog
npm install -D conventional-changelog-cli
# Generate changelog
npx conventional-changelog -p angular -i CHANGELOG.md -s
3. Pre-publish Script
{
"scripts": {
"prepublishOnly": "npm run lint && npm test && npm run build",
"prepack": "cp README.md LICENSE dist/"
}
}
4. Publishing Commands
# Dry run (test without publishing)
npm publish --dry-run
# Publish to npm
npm publish
# Publish scoped package
npm publish --access public
# Publish with tag
npm publish --tag beta
# Publish specific folder
cd dist && npm publish
5. NPM Provenance (Recommended)
# Publish with provenance (requires GitHub Actions)
npm publish --provenance
6. GitHub Actions for Publishing
# .github/workflows/publish.yml
name: Publish Package
on:
push:
tags:
- 'v*'
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm test
- run: npm run build
- run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Build Scripts
Development Scripts
{
"scripts": {
"dev": "tsup --watch",
"build": "tsup",
"build:analyze": "tsup && rollup-plugin-visualizer",
"type-check": "tsc --noEmit",
"clean": "rm -rf dist"
}
}
CI/CD Scripts
{
"scripts": {
"ci:build": "npm run clean && npm run build",
"ci:test": "npm run type-check && npm test",
"ci:publish": "npm run ci:test && npm run ci:build && npm publish"
}
}
Testing Build Output
1. Local Testing
# Create tarball
npm pack
# Install in test project
cd ../test-project
npm install ../my-library/my-library-1.0.0.tgz
2. Link for Development
# In library
npm link
# In consuming project
npm link my-library
3. Verify Exports
// test.mjs
import * as lib from 'my-library'
console.log(Object.keys(lib))
// test.cjs
const lib = require('my-library')
console.log(Object.keys(lib))
Monorepo Considerations
Turborepo Configuration
// turbo.json
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}
Workspace Package
// packages/ui/package.json
{
"name": "@myorg/ui",
"version": "1.0.0",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
}
}
}
// apps/docs/package.json
{
"dependencies": {
"@myorg/ui": "workspace:*"
}
}
Best Practices
Package Design
- ESM-First: Modern standard, better tree-shaking
- Dual Package: Provide both ESM and CJS for compatibility
- Explicit Exports: Use exports field for better control
- Tree-Shakeable: Mark side effects, use named exports
- Small Bundles: Externalize dependencies, optimize code
Build Configuration
- Source Maps: Always generate for debugging
- Type Declarations: Essential for TypeScript users
- Declaration Maps: Enable IDE navigation
- Minification: For production builds
- Clean Output: Clear dist/ before building
Publishing
- Semantic Versioning: Follow strictly
- Changelog: Document all changes
- Git Tags: Tag releases
- Pre-publish Tests: Run comprehensive checks
- Provenance: Use for supply chain security
Performance
- Bundle Size: Keep minimal
- Code Splitting: Split by feature
- Tree-Shaking: Maximize dead code elimination
- Compression: Use gzip/brotli
- Lazy Loading: Dynamic imports where beneficial
Troubleshooting
Common Issues
Dual Package Hazard
// Ensure consistent resolution
// Use exports field properly
{
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
}
}
Missing Type Declarations
// Check tsconfig.json
{
"compilerOptions": {
"declaration": true,
"declarationMap": true
}
}
// Check package.json
{
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts"
}
}
}
Large Bundle Size
# Analyze bundle
npm run build:analyze
# Check for duplicate dependencies
npm dedupe
# Externalize peer dependencies
external: ['react', 'react-dom']
CJS/ESM Compatibility
// Use conditional exports
{
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
}
}
// Add "type": "module" to package.json
// Name CJS files with .cjs extension
When to Use This Skill
Activate this skill when you need to:
- Set up build configuration for a library
- Optimize bundle size
- Configure multi-format exports (ESM/CJS/UMD)
- Prepare package for NPM publishing
- Set up TypeScript declaration generation
- Configure tree-shaking
- Implement code splitting
- Create build scripts
- Set up CI/CD for publishing
- Troubleshoot build issues
- Migrate build tools
- Configure monorepo builds
Output Format
When configuring builds, provide:
- Complete Configuration: Build tool config files
- package.json Setup: Proper entry points and scripts
- Build Instructions: How to build and test
- Publishing Guide: Step-by-step publishing process
- Optimization Report: Bundle sizes and improvements
- Verification Steps: How to verify build output
Always optimize for modern standards (ESM-first) while maintaining backwards compatibility where needed.