+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 232 of 355

🎁 Rollup Configuration: Library Bundling

Master rollup configuration: library bundling in TypeScript with practical examples, best practices, and real-world applications 🚀

💎Advanced
25 min read

Prerequisites

  • Basic understanding of JavaScript 📝
  • TypeScript installation ⚡
  • VS Code or preferred IDE 💻

What you'll learn

  • Understand rollup configuration fundamentals 🎯
  • Apply rollup in real TypeScript library projects 🏗️
  • Debug common bundling issues 🐛
  • Write optimized library bundles ✨

🎯 Introduction

Welcome to the exciting world of Rollup library bundling! 🎉 In this comprehensive guide, we’ll master the art of configuring Rollup to create beautiful, optimized TypeScript libraries that developers will love to use.

You’ll discover how Rollup can transform your TypeScript development experience, creating tree-shakeable, modern JavaScript bundles. Whether you’re building utility libraries 🔧, React components 🌐, or Node.js packages 📦, understanding Rollup is essential for creating professional-grade libraries.

By the end of this tutorial, you’ll feel confident configuring Rollup for any TypeScript library project! Let’s dive in! 🏊‍♂️

📚 Understanding Rollup for Libraries

🤔 What is Rollup?

Rollup is like a master craftsperson 🎨 who takes all your scattered TypeScript files and carefully assembles them into a beautiful, optimized package. Think of it as a specialized librarian 📚 who knows exactly how to organize and present your code for maximum efficiency.

In TypeScript terms, Rollup is a module bundler that excels at creating optimized bundles for libraries ✨. This means you can:

  • 🌳 Create tree-shakeable bundles (only import what you use)
  • 📦 Generate multiple output formats (ESM, CommonJS, UMD)
  • 🚀 Optimize bundle sizes for better performance
  • 🛡️ Maintain clean, readable output code

💡 Why Use Rollup for Libraries?

Here’s why library authors love Rollup:

  1. Tree Shaking 🌳: Removes unused code automatically
  2. Multiple Formats 📦: ESM, CommonJS, UMD in one config
  3. Smaller Bundles 🎯: Optimized output with minimal overhead
  4. Clean Output ✨: Readable, debuggable generated code

Real-world example: Imagine building a utility library 🛠️. With Rollup, users only download the functions they actually use, making their apps faster and smaller!

🔧 Basic Syntax and Usage

📝 Simple Configuration

Let’s start with a friendly example:

// 👋 rollup.config.js - Hello, Rollup!
import typescript from '@rollup/plugin-typescript';
import { defineConfig } from 'rollup';

export default defineConfig({
  // 📝 Entry point - where your library starts
  input: 'src/index.ts',
  
  // 📦 Output configurations
  output: [
    {
      file: 'dist/index.esm.js',
      format: 'esm',     // 🌟 Modern ES modules
      sourcemap: true   // 🗺️ Debug maps
    },
    {
      file: 'dist/index.cjs.js',
      format: 'cjs',     // 📦 CommonJS for Node.js
      sourcemap: true
    }
  ],
  
  // 🔌 Plugins to process your code
  plugins: [
    typescript({
      tsconfig: './tsconfig.json',
      sourceMap: true    // ✨ TypeScript magic
    })
  ]
});

💡 Explanation: This basic config creates both ESM and CommonJS versions of your library! The TypeScript plugin handles all the type compilation magic.

🎯 TypeScript Library Structure

Here’s the structure you’ll use daily:

// 📁 src/index.ts - Main entry point
export { Button } from './components/Button';
export { Modal } from './components/Modal';
export { useCounter } from './hooks/useCounter';

// 🎨 Export types too!
export type { ButtonProps } from './components/Button';
export type { ModalProps } from './components/Modal';

// 📦 Package.json configuration
{
  "name": "my-awesome-library",
  "version": "1.0.0",
  "main": "dist/index.cjs.js",        // 📦 CommonJS entry
  "module": "dist/index.esm.js",      // 🌟 ESM entry
  "types": "dist/index.d.ts",         // 🔷 TypeScript types
  "files": ["dist"]                   // 📁 What to publish
}

💡 Practical Examples

🛠️ Example 1: Utility Library

Let’s build a real utility library:

// 📁 src/math/calculator.ts
export interface CalculatorResult {
  value: number;
  operation: string;
  timestamp: Date;
}

export class Calculator {
  private history: CalculatorResult[] = [];
  
  // ➕ Add numbers with emoji power!
  add(a: number, b: number): CalculatorResult {
    const result: CalculatorResult = {
      value: a + b,
      operation: `${a}${b}`,
      timestamp: new Date()
    };
    this.history.push(result);
    return result;
  }
  
  // ✖️ Multiply with style
  multiply(a: number, b: number): CalculatorResult {
    const result: CalculatorResult = {
      value: a * b,
      operation: `${a} ✖️ ${b}`,
      timestamp: new Date()
    };
    this.history.push(result);
    return result;
  }
  
  // 📊 Get calculation history
  getHistory(): CalculatorResult[] {
    return [...this.history];
  }
  
  // 🧹 Clear history
  clearHistory(): void {
    this.history = [];
    console.log('📝 History cleared! ✨');
  }
}

// 🔥 Standalone utility functions
export const formatNumber = (num: number): string => {
  return new Intl.NumberFormat('en-US').format(num);
};

export const isEven = (num: number): boolean => {
  return num % 2 === 0;
};
// 🔧 rollup.config.js for utility library
import typescript from '@rollup/plugin-typescript';
import { defineConfig } from 'rollup';
import dts from 'rollup-plugin-dts';

export default defineConfig([
  // 📦 Main bundle configuration
  {
    input: 'src/index.ts',
    output: [
      {
        file: 'dist/index.esm.js',
        format: 'esm',
        sourcemap: true
      },
      {
        file: 'dist/index.cjs.js',
        format: 'cjs',
        sourcemap: true,
        exports: 'named'  // 🎯 Important for CommonJS
      },
      {
        file: 'dist/index.umd.js',
        format: 'umd',
        name: 'MathUtils',  // 🌐 Global variable name
        sourcemap: true
      }
    ],
    plugins: [
      typescript({
        tsconfig: './tsconfig.json',
        declaration: true,
        declarationDir: 'dist/types'
      })
    ]
  },
  // 📘 Type definitions bundle
  {
    input: 'dist/types/index.d.ts',
    output: {
      file: 'dist/index.d.ts',
      format: 'esm'
    },
    plugins: [dts()]
  }
]);

🎯 Try it yourself: Add a power method and a getAverage utility function!

🎮 Example 2: React Component Library

Let’s create a component library:

// 📁 src/components/Button/Button.tsx
import React from 'react';

export interface ButtonProps {
  children: React.ReactNode;
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'small' | 'medium' | 'large';
  emoji?: string;
  onClick?: () => void;
  disabled?: boolean;
}

export const Button: React.FC<ButtonProps> = ({
  children,
  variant = 'primary',
  size = 'medium',
  emoji,
  onClick,
  disabled = false
}) => {
  // 🎨 Generate CSS classes
  const baseClass = 'btn';
  const variantClass = `btn--${variant}`;
  const sizeClass = `btn--${size}`;
  const className = [baseClass, variantClass, sizeClass].join(' ');
  
  return (
    <button 
      className={className}
      onClick={onClick}
      disabled={disabled}
      type="button"
    >
      {emoji && <span className="btn__emoji">{emoji}</span>}
      {children}
    </button>
  );
};

// 📁 src/components/Modal/Modal.tsx
import React, { useEffect } from 'react';

export interface ModalProps {
  isOpen: boolean;
  onClose: () => void;
  title?: string;
  children: React.ReactNode;
  emoji?: string;
}

export const Modal: React.FC<ModalProps> = ({
  isOpen,
  onClose,
  title,
  children,
  emoji = '💬'
}) => {
  // 🔒 Close on escape key
  useEffect(() => {
    const handleEscape = (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        onClose();
      }
    };
    
    if (isOpen) {
      document.addEventListener('keydown', handleEscape);
      return () => document.removeEventListener('keydown', handleEscape);
    }
  }, [isOpen, onClose]);
  
  if (!isOpen) return null;
  
  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        {title && (
          <div className="modal__header">
            <h3>{emoji} {title}</h3>
            <button className="modal__close" onClick={onClose}>

            </button>
          </div>
        )}
        <div className="modal__content">
          {children}
        </div>
      </div>
    </div>
  );
};
// 🔧 Advanced rollup.config.js for React library
import typescript from '@rollup/plugin-typescript';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
import { defineConfig } from 'rollup';

export default defineConfig({
  input: 'src/index.ts',
  output: [
    {
      file: 'dist/index.esm.js',
      format: 'esm',
      sourcemap: true
    },
    {
      file: 'dist/index.cjs.js',
      format: 'cjs',
      sourcemap: true,
      exports: 'named'
    }
  ],
  plugins: [
    // 🔗 Handle peer dependencies (React, etc.)
    peerDepsExternal(),
    
    // 🔍 Resolve node modules
    resolve({
      browser: true
    }),
    
    // 📦 Handle CommonJS dependencies
    commonjs(),
    
    // ⚡ TypeScript compilation
    typescript({
      tsconfig: './tsconfig.json',
      declaration: true,
      declarationDir: 'dist/types'
    })
  ],
  
  // 🚫 Don't bundle peer dependencies
  external: ['react', 'react-dom']
});

🚀 Advanced Concepts

🧙‍♂️ Advanced Plugin Configuration

When you’re ready to level up, try these advanced patterns:

// 🎯 Production-ready rollup.config.js
import typescript from '@rollup/plugin-typescript';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import terser from '@rollup/plugin-terser';
import { defineConfig } from 'rollup';

const isProduction = process.env.NODE_ENV === 'production';

export default defineConfig({
  input: 'src/index.ts',
  output: [
    {
      file: 'dist/index.esm.js',
      format: 'esm',
      sourcemap: !isProduction,
      plugins: isProduction ? [terser()] : []  // 🗜️ Minify in production
    },
    {
      file: 'dist/index.cjs.js',
      format: 'cjs',
      sourcemap: !isProduction,
      exports: 'named'
    }
  ],
  plugins: [
    resolve({ 
      preferBuiltins: true,
      browser: false  // 🏗️ For Node.js libraries
    }),
    commonjs(),
    typescript({
      tsconfig: './tsconfig.json',
      sourceMap: !isProduction,
      inlineSources: !isProduction
    })
  ],
  
  // 📊 Bundle analysis
  onwarn(warning, warn) {
    // 🚨 Custom warning handling
    if (warning.code === 'CIRCULAR_DEPENDENCY') {
      console.log('⚠️ Circular dependency detected:', warning.message);
    }
    warn(warning);
  }
});

🏗️ Multi-Entry Configuration

For complex libraries with multiple entry points:

// 🚀 Multi-entry rollup configuration
import typescript from '@rollup/plugin-typescript';
import { defineConfig } from 'rollup';

export default defineConfig([
  // 📦 Main library bundle
  {
    input: 'src/index.ts',
    output: {
      file: 'dist/index.esm.js',
      format: 'esm'
    },
    plugins: [typescript()]
  },
  
  // 🛠️ Utilities-only bundle
  {
    input: 'src/utils/index.ts',
    output: {
      file: 'dist/utils.esm.js',
      format: 'esm'
    },
    plugins: [typescript()]
  },
  
  // 🎨 Components-only bundle
  {
    input: 'src/components/index.ts',
    output: {
      file: 'dist/components.esm.js',
      format: 'esm'
    },
    plugins: [typescript()]
  }
]);

⚠️ Common Pitfalls and Solutions

😱 Pitfall 1: External Dependencies Bundled

// ❌ Wrong way - bundling React in your library!
export default defineConfig({
  input: 'src/index.ts',
  output: { file: 'dist/index.js', format: 'esm' },
  // 💥 Missing external config!
});

// ✅ Correct way - keep externals external!
export default defineConfig({
  input: 'src/index.ts',
  output: { file: 'dist/index.js', format: 'esm' },
  external: ['react', 'react-dom', 'lodash'],  // 🛡️ Don't bundle these!
  plugins: [peerDepsExternal()]  // ✨ Auto-detect peer deps
});

🤯 Pitfall 2: Missing Type Declarations

// ❌ Dangerous - no type declarations!
export default defineConfig({
  input: 'src/index.ts',
  plugins: [
    typescript()  // 💥 Missing declaration config!
  ]
});

// ✅ Safe - proper type generation!
export default defineConfig({
  input: 'src/index.ts',
  plugins: [
    typescript({
      declaration: true,        // ✅ Generate .d.ts files
      declarationDir: 'dist/types',  // 📁 Where to put them
      rootDir: 'src'           // 🎯 Source root
    })
  ]
});

🛠️ Best Practices

  1. 🎯 Use External Dependencies: Don’t bundle libraries your users already have
  2. 📝 Generate Type Declarations: Always include TypeScript definitions
  3. 🌳 Enable Tree Shaking: Use ESM format for optimal bundling
  4. 🗜️ Minify for Production: Use terser for smaller bundles
  5. ✨ Multiple Formats: Support both ESM and CommonJS
// 🏆 Perfect library configuration
export default defineConfig({
  input: 'src/index.ts',
  output: [
    { file: 'dist/index.esm.js', format: 'esm' },      // 🌟 Modern
    { file: 'dist/index.cjs.js', format: 'cjs' },      // 📦 Compatible
    { file: 'dist/index.umd.js', format: 'umd', name: 'MyLib' }  // 🌐 Universal
  ],
  external: id => !id.startsWith('.') && !id.startsWith('/'),  // 🎯 Smart externals
  plugins: [/* your plugins */]
});

🧪 Hands-On Exercise

🎯 Challenge: Build a Data Validation Library

Create a type-safe validation library with Rollup:

📋 Requirements:

  • ✅ Email, phone, and password validators
  • 🏷️ Custom validation rules
  • 👤 Type-safe error messages
  • 📅 Date range validation
  • 🎨 Each validator needs an emoji!

🚀 Bonus Points:

  • Multiple output formats (ESM, CJS, UMD)
  • Minified production build
  • Complete TypeScript declarations
  • Tree-shakeable exports

💡 Solution

🔍 Click to see solution
// 📁 src/validators/email.ts
export interface ValidationResult {
  isValid: boolean;
  message: string;
  emoji: string;
}

export const validateEmail = (email: string): ValidationResult => {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  const isValid = emailRegex.test(email);
  
  return {
    isValid,
    message: isValid ? 'Valid email! 📧' : 'Invalid email format 📧❌',
    emoji: isValid ? '✅' : '❌'
  };
};

// 📁 src/validators/password.ts
export interface PasswordRules {
  minLength?: number;
  requireNumbers?: boolean;
  requireSpecialChars?: boolean;
  requireUppercase?: boolean;
}

export const validatePassword = (
  password: string, 
  rules: PasswordRules = {}
): ValidationResult => {
  const {
    minLength = 8,
    requireNumbers = true,
    requireSpecialChars = true,
    requireUppercase = true
  } = rules;
  
  const checks = [
    { test: password.length >= minLength, msg: `At least ${minLength} characters 🔤` },
    { test: !requireNumbers || /\d/.test(password), msg: 'Contains numbers 🔢' },
    { test: !requireSpecialChars || /[!@#$%^&*]/.test(password), msg: 'Special characters ✨' },
    { test: !requireUppercase || /[A-Z]/.test(password), msg: 'Uppercase letters 🔠' }
  ];
  
  const failed = checks.filter(check => !check.test);
  const isValid = failed.length === 0;
  
  return {
    isValid,
    message: isValid ? 'Strong password! 🔒✅' : `Missing: ${failed.map(f => f.msg).join(', ')}`,
    emoji: isValid ? '🔒' : '🔓'
  };
};

// 📁 src/index.ts
export { validateEmail } from './validators/email';
export { validatePassword } from './validators/password';
export type { ValidationResult, PasswordRules } from './validators/password';

// 📁 rollup.config.js
import typescript from '@rollup/plugin-typescript';
import terser from '@rollup/plugin-terser';
import { defineConfig } from 'rollup';

const isProduction = process.env.NODE_ENV === 'production';

export default defineConfig({
  input: 'src/index.ts',
  output: [
    {
      file: 'dist/index.esm.js',
      format: 'esm',
      sourcemap: !isProduction
    },
    {
      file: 'dist/index.cjs.js',
      format: 'cjs',
      exports: 'named',
      sourcemap: !isProduction
    },
    {
      file: 'dist/index.umd.js',
      format: 'umd',
      name: 'Validators',
      sourcemap: !isProduction
    }
  ],
  plugins: [
    typescript({
      tsconfig: './tsconfig.json',
      declaration: true,
      declarationDir: 'dist/types'
    }),
    ...(isProduction ? [terser()] : [])
  ]
});

🎓 Key Takeaways

You’ve learned so much! Here’s what you can now do:

  • Configure Rollup for TypeScript libraries with confidence 💪
  • Generate multiple formats (ESM, CommonJS, UMD) like a pro 🛡️
  • Handle external dependencies correctly 🎯
  • Create type declarations for perfect TypeScript support 🐛
  • Build production-ready libraries that developers love! 🚀

Remember: Rollup is your library’s best friend! It helps create clean, optimized bundles that make your users happy. 🤝

🤝 Next Steps

Congratulations! 🎉 You’ve mastered Rollup configuration for TypeScript libraries!

Here’s what to do next:

  1. 💻 Practice with the validation library exercise above
  2. 🏗️ Build and publish your own TypeScript library
  3. 📚 Explore advanced plugins like rollup-plugin-visualizer
  4. 🌟 Share your amazing libraries with the community!

Remember: Every successful library started with a great build configuration. Keep coding, keep bundling, and most importantly, have fun creating awesome tools for other developers! 🚀


Happy bundling! 🎉🚀✨