Developing an npm package or design system? Incorrect bundler configuration leads to bloated bundles and tree-shaking issues. Our team, with 5 years of experience and 50+ Rollup projects, sets up optimal configs. According to our data, properly configured Rollup reduces bundle size by 30–40% on average. In this article, we'll walk through real cases: preparing a library for ESM/CJS/UMD, excluding unnecessary dependencies, and automating CI builds. As noted in Rollup Documentation, tree-shaking relies on static import analysis — enabling removal of unused code with symbol-level precision.
When to Choose Rollup Over Vite or Webpack?
Rollup is not a universal tool. For SPAs with hot reload and a dev server, Vite (which uses Rollup internally for production builds) is more convenient. Rollup is chosen when you need to:
- bundle a library in ESM + CJS + UMD formats simultaneously
- get the cleanest output without unnecessary wrappers
- control which dependencies are included in the bundle and which remain external
- generate TypeScript declarations alongside built files
We’ve migrated projects where replacing Webpack with Rollup reduced the final file size from 200 KB to 120 KB and cut build time by 40%.
Installation and Basic Config
npm install --save-dev rollup @rollup/plugin-node-resolve @rollup/plugin-commonjs @rollup/plugin-typescript rollup-plugin-dts rollup-plugin-postcss @rollup/plugin-url rollup-plugin-visualizer glob rollup.config.ts for a typical TypeScript library with ESM/CJS support and declarations:
import resolve from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; import typescript from '@rollup/plugin-typescript'; import dts from 'rollup-plugin-dts'; import { defineConfig } from 'rollup'; import pkg from './package.json' assert { type: 'json' }; export default defineConfig([ { input: 'src/index.ts', external: Object.keys(pkg.peerDependencies ?? {}), plugins: [ resolve({ extensions: ['.ts', '.tsx'] }), commonjs(), typescript({ tsconfig: './tsconfig.build.json' }), ], output: [ { file: pkg.module, format: 'esm', sourcemap: true }, { file: pkg.main, format: 'cjs', sourcemap: true, exports: 'named' }, ], }, { input: 'dist/types/index.d.ts', output: { file: 'dist/index.d.ts', format: 'esm' }, plugins: [dts()], }, ]); How to Properly Declare Exports in package.json?
A modern package.json for a library should include an exports field for clear module resolution. Example correct mapping:
| Field | Value |
|---|---|
| main | dist/index.cjs.js |
| module | dist/index.esm.js |
| types | dist/index.d.ts |
| exports."." | { import: "./dist/index.esm.js", require: "./dist/index.cjs.js", types: "./dist/index.d.ts" } |
| files | ["dist"] |
How to Exclude Peer Dependencies from the Bundle?
A common mistake is including React or lodash in a library's bundle. Use external to list all peer dependencies and dependencies that should not appear in the final file:
const external = [ ...Object.keys(pkg.peerDependencies ?? {}), ...Object.keys(pkg.dependencies ?? {}), ]; // Partial externalization — exclude only part of a package // external: (id) => id.startsWith('react') || /^lodash/.test(id), How to Add CSS and Assets?
For CSS modules, use rollup-plugin-postcss. For images and SVG, use @rollup/plugin-url. Configuration:
- postcss with
modules: trueandextract: 'dist/styles.css' - url with
limit: 8192(inline up to 8KB) anddestDir: 'dist/assets'
Multi-Entry Build for Components
If your library allows importing each component separately (e.g., import Button from 'ui/Button'), use multiple entries with preserveModules:
import { glob } from 'glob'; const entries = Object.fromEntries( (await glob('src/components/**/*.tsx')).map((file) => [ file.replace('src/', '').replace(/\.tsx$/, ''), file, ]) ); export default defineConfig({ input: entries, output: { dir: 'dist', format: 'esm', preserveModules: true, preserveModulesRoot: 'src', }, }); preserveModules maintains the directory structure, enabling file-level tree-shaking.
Bundle Size Analysis
import { visualizer } from 'rollup-plugin-visualizer'; plugins: [ visualizer({ filename: 'dist/stats.html', gzipSize: true, brotliSize: true, }), ] After the build, open dist/stats.html — an interactive dependency tree with real sizes. The concept of tree-shaking — removing unused code at build time — is visualized here.
Watch Mode and Development
For library development in parallel with an application, use rollup -c --watch or set watch: { include: 'src/**', exclude: 'node_modules/**' }. In monorepos, use workspace links.
Turnkey Setup Steps
- Audit existing build (if any) — evaluate config and dependencies.
- Design exports map — determine formats and paths.
- Configure TypeScript and declarations — tsconfig, plugins.
- Integrate CSS and assets — postcss, url.
- Optimize external dependencies — exclude unnecessary packages.
- Add visualizer and analyzer — generate stats.
- Test in CI/CD — ensure stability.
Each step includes quality checks: after the audit we provide a report with recommendations; after configuration we run a test build and verify it in CI. One month of free support after implementation.
What's Included in the Result?
Upon completion, you receive:
- A working Rollup config with plugins tailored to your stack
- An exports map in package.json for all formats
- TypeScript declarations (d.ts) next to built files
- CSS and asset integration, if needed
- CI/CD configuration (GitHub Actions or GitLab CI)
- Bundle size analysis report (plato, stats.html)
- Consultation on using the finished build
Why Are External Dependencies So Important?
Without proper external configuration, you risk bundling entire frameworks like React, inflating your library from 10 KB to 500+ KB. Users of your library already have these dependencies in their project — duplication leads to errors and bundle bloat. Setting external with peerDependencies and dependencies is mandatory for any library.
Timelines
| Setup Type | Estimated Time |
|---|---|
| Basic (single entry, ESM+CJS, declarations) | 2–4 hours |
| Complex (CSS, assets, multiple entries, CI) | 1–2 days |
Guarantee of functionality in CI/CD and post-setup support. Order an audit of your current manual build — we'll identify bottlenecks and suggest optimization. Get a consultation from an engineer who has configured Rollup for dozens of libraries.







