+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 125 of 355

๐ŸŒ UMD Modules: Universal Module Definition

Master Universal Module Definition (UMD) with TypeScript for cross-platform compatibility, seamless library distribution, and multi-environment support ๐Ÿš€

๐Ÿ’ŽAdvanced
26 min read

Prerequisites

  • Understanding of CommonJS, AMD, and ES6 module systems ๐Ÿ“
  • Experience with TypeScript compilation and build tools โšก
  • Knowledge of browser and Node.js environments ๐Ÿ’ป

What you'll learn

  • Master UMD pattern for universal JavaScript module compatibility ๐ŸŽฏ
  • Build libraries that work seamlessly across all module systems ๐Ÿ—๏ธ
  • Implement advanced UMD patterns with TypeScript type safety ๐Ÿ›
  • Create production-ready universal libraries and distribution strategies โœจ

๐ŸŽฏ Introduction

Welcome to the universal world of UMD modules! ๐ŸŒ If module systems were languages, then UMD (Universal Module Definition) would be the ultimate polyglot translator - it speaks CommonJS, AMD, and global browser fluently, ensuring your TypeScript libraries work everywhere, everytime!

UMD is the Swiss Army knife of module systems. It detects the environment itโ€™s running in and adapts accordingly: CommonJS for Node.js, AMD for browsers with RequireJS, and global variables for legacy browser environments. This makes UMD perfect for creating libraries that need to work across all JavaScript environments.

By the end of this tutorial, youโ€™ll be a master of UMD patterns with TypeScript, capable of building universal libraries that seamlessly integrate into any project, regardless of the module system being used. Letโ€™s create modules that truly work everywhere! โšก

๐Ÿ“š Understanding UMD Modules

๐Ÿค” What Is UMD?

UMD (Universal Module Definition) is a pattern that combines CommonJS, AMD, and global variable patterns to create modules that work in any JavaScript environment. Itโ€™s essentially a smart wrapper that detects the available module system and uses the appropriate one.

// ๐ŸŒŸ Basic UMD pattern structure
// math-library.ts (compiled to UMD)
(function (root, factory) {
  // ๐Ÿ” Environment detection and module loading
  if (typeof exports === 'object' && typeof module !== 'undefined') {
    // ๐Ÿ“ฆ CommonJS (Node.js)
    console.log('๐ŸŸข Loading as CommonJS module');
    module.exports = factory();
  } else if (typeof define === 'function' && define.amd) {
    // ๐ŸŒ AMD (RequireJS)
    console.log('๐Ÿ”ต Loading as AMD module');
    define([], factory);
  } else {
    // ๐ŸŒ Global browser variable
    console.log('๐ŸŸก Loading as global variable');
    root.MathLibrary = factory();
  }
}(typeof self !== 'undefined' ? self : this, function () {
  'use strict';

  // ๐Ÿ“ฆ TypeScript-compiled module content
  const MathLibrary = {
    // ๐Ÿงฎ Basic arithmetic operations
    add: (a: number, b: number): number => {
      console.log(`โž• Adding ${a} + ${b}`);
      return a + b;
    },

    subtract: (a: number, b: number): number => {
      console.log(`โž– Subtracting ${a} - ${b}`);
      return a - b;
    },

    multiply: (a: number, b: number): number => {
      console.log(`โœ–๏ธ Multiplying ${a} * ${b}`);
      return a * b;
    },

    divide: (a: number, b: number): number => {
      if (b === 0) {
        throw new Error('โŒ Division by zero is not allowed');
      }
      console.log(`โž— Dividing ${a} / ${b}`);
      return a / b;
    },

    // ๐Ÿ”ข Advanced mathematical functions
    power: (base: number, exponent: number): number => {
      console.log(`๐Ÿ”ข Calculating ${base} ^ ${exponent}`);
      return Math.pow(base, exponent);
    },

    sqrt: (number: number): number => {
      if (number < 0) {
        throw new Error('โŒ Cannot calculate square root of negative number');
      }
      console.log(`โˆš Calculating square root of ${number}`);
      return Math.sqrt(number);
    },

    // ๐Ÿ“Š Statistical functions
    statistics: {
      mean: (numbers: number[]): number => {
        if (numbers.length === 0) {
          throw new Error('โŒ Cannot calculate mean of empty array');
        }
        console.log('๐Ÿ“ˆ Calculating mean...');
        const sum = numbers.reduce((acc, num) => acc + num, 0);
        return sum / numbers.length;
      },

      median: (numbers: number[]): number => {
        if (numbers.length === 0) {
          throw new Error('โŒ Cannot calculate median of empty array');
        }
        console.log('๐Ÿ“Š Calculating median...');
        const sorted = [...numbers].sort((a, b) => a - b);
        const mid = Math.floor(sorted.length / 2);
        
        if (sorted.length % 2 === 0) {
          return (sorted[mid - 1] + sorted[mid]) / 2;
        }
        return sorted[mid];
      },

      standardDeviation: (numbers: number[]): number => {
        console.log('๐Ÿ“ Calculating standard deviation...');
        const mean = MathLibrary.statistics.mean(numbers);
        const squaredDifferences = numbers.map(num => Math.pow(num - mean, 2));
        const variance = MathLibrary.statistics.mean(squaredDifferences);
        return Math.sqrt(variance);
      },
    },

    // ๐ŸŽฏ Utility functions
    utils: {
      isEven: (number: number): boolean => number % 2 === 0,
      isOdd: (number: number): boolean => number % 2 !== 0,
      isPrime: (number: number): boolean => {
        if (number < 2) return false;
        for (let i = 2; i <= Math.sqrt(number); i++) {
          if (number % i === 0) return false;
        }
        return true;
      },
      factorial: (number: number): number => {
        if (number < 0) throw new Error('โŒ Factorial of negative number is undefined');
        if (number === 0 || number === 1) return 1;
        return number * MathLibrary.utils.factorial(number - 1);
      },
    },

    // ๐Ÿ“‹ Version and metadata
    version: '1.0.0',
    author: 'TypeScript UMD Example',
    description: 'A comprehensive math library with UMD pattern',
  };

  // ๐ŸŽฏ Return the library object
  return MathLibrary;
}));

// ๐ŸŽฎ Usage examples in different environments:

// ๐Ÿ“ฆ CommonJS (Node.js)
// const MathLibrary = require('./math-library');
// console.log(MathLibrary.add(5, 3));

// ๐ŸŒ AMD (RequireJS)
// define(['./math-library'], function(MathLibrary) {
//   console.log(MathLibrary.multiply(4, 7));
// });

// ๐ŸŒ Global (Browser)
// <script src="math-library.js"></script>
// <script>
//   console.log(MathLibrary.divide(20, 4));
// </script>

๐Ÿ’ก UMD Pattern Benefits

  • ๐ŸŒ Universal Compatibility: Works in Node.js, browsers, and AMD loaders
  • ๐Ÿ“ฆ Single Build: One file that works everywhere
  • ๐Ÿ”„ Automatic Detection: No manual configuration needed
  • ๐Ÿ“ˆ Easy Distribution: Perfect for libraries and packages
  • ๐Ÿ”ง Fallback Support: Graceful degradation to global variables

๐Ÿ› ๏ธ TypeScript Configuration for UMD

๐Ÿ“ฆ Setting Up TypeScript for UMD Compilation

Letโ€™s configure TypeScript to generate UMD modules:

// ๐ŸŒŸ tsconfig.json for UMD compilation
{
  "compilerOptions": {
    "target": "ES5",                          // Compatible with older browsers
    "module": "UMD",                          // ๐ŸŽฏ Generate UMD modules
    "lib": ["ES2015", "DOM"],                // Include necessary libraries
    "outDir": "./dist",                      // Output directory
    "rootDir": "./src",                      // Source directory
    "declaration": true,                     // Generate .d.ts files
    "declarationDir": "./dist/types",       // Type declarations directory
    "sourceMap": true,                       // Generate source maps
    "strict": true,                          // Enable strict type checking
    "esModuleInterop": true,                 // Enable ES module interop
    "allowSyntheticDefaultImports": true,    // Allow synthetic imports
    "skipLibCheck": true,                    // Skip library type checking
    "forceConsistentCasingInFileNames": true, // Enforce consistent casing
    "moduleResolution": "node",              // Node-style module resolution
    "resolveJsonModule": true,               // Enable JSON imports
    "isolatedModules": true,                 // Ensure isolated modules
    "noEmitOnError": true,                   // Don't emit on errors
    "removeComments": false,                 // Keep comments for debugging
    "experimentalDecorators": true,          // Enable decorators
    "emitDecoratorMetadata": true,          // Emit decorator metadata
    "strictNullChecks": true,               // Strict null checking
    "noImplicitAny": true,                  // No implicit any types
    "noImplicitReturns": true,              // No implicit returns
    "noUnusedLocals": true,                 // Flag unused locals
    "noUnusedParameters": true,             // Flag unused parameters
    "exactOptionalPropertyTypes": true,     // Exact optional properties
    
    // ๐ŸŽฏ UMD-specific options
    "outFile": "./dist/bundle.js",          // Single output file for UMD
    "globalThis": "globalThis"              // Global object reference
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "dist",
    "**/*.spec.ts",
    "**/*.test.ts"
  ]
}

// ๐Ÿš€ Enhanced package.json for UMD library development
{
  "name": "typescript-umd-library",
  "version": "1.0.0",
  "description": "A TypeScript library using UMD pattern",
  "main": "dist/index.js",                  // CommonJS entry point
  "module": "dist/index.esm.js",           // ES module entry point
  "browser": "dist/index.umd.js",          // Browser UMD bundle
  "types": "dist/types/index.d.ts",        // TypeScript declarations
  "files": [
    "dist/"
  ],
  "scripts": {
    "build": "npm run clean && npm run build:umd && npm run build:esm && npm run build:types",
    "build:umd": "tsc --module umd --outFile dist/index.umd.js",
    "build:esm": "tsc --module es2015 --outDir dist/esm",
    "build:cjs": "tsc --module commonjs --outDir dist/cjs",
    "build:types": "tsc --declaration --emitDeclarationOnly --outDir dist/types",
    "build:watch": "tsc --watch",
    "clean": "rimraf dist",
    "test": "jest",
    "test:watch": "jest --watch",
    "lint": "eslint src/**/*.ts",
    "lint:fix": "eslint src/**/*.ts --fix",
    "docs": "typedoc src/index.ts",
    "bundle": "webpack --mode production",
    "dev": "webpack serve --mode development",
    "prepublishOnly": "npm run build && npm test"
  },
  "keywords": [
    "typescript",
    "umd",
    "library",
    "universal",
    "modules"
  ],
  "author": "Your Name",
  "license": "MIT",
  "devDependencies": {
    "typescript": "^5.3.0",
    "webpack": "^5.89.0",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^4.15.1",
    "ts-loader": "^9.5.1",
    "rimraf": "^5.0.5",
    "jest": "^29.7.0",
    "@types/jest": "^29.5.8",
    "ts-jest": "^29.1.1",
    "eslint": "^8.54.0",
    "@typescript-eslint/eslint-plugin": "^6.12.0",
    "@typescript-eslint/parser": "^6.12.0",
    "typedoc": "^0.25.4"
  }
}

// ๐Ÿ”ง Webpack configuration for UMD bundling
// webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.ts',
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'index.umd.js',
    library: {
      name: 'TypeScriptUMDLibrary',
      type: 'umd',
    },
    globalObject: 'this',
    clean: true,
  },
  devtool: 'source-map',
  optimization: {
    minimize: true,
  },
};

๐Ÿ—๏ธ Building Advanced UMD Libraries

๐ŸŽฏ Complete UMD Library Implementation

Letโ€™s create a comprehensive UMD library with advanced TypeScript features:

// ๐ŸŒŸ Advanced UMD library with comprehensive features
// src/data-structures/collection.ts
export interface CollectionItem {
  id: string | number;
  [key: string]: any;
}

export interface CollectionOptions {
  idField?: string;
  compareFn?: (a: any, b: any) => number;
  allowDuplicates?: boolean;
  maxSize?: number;
}

export interface FilterFunction<T> {
  (item: T, index: number, collection: T[]): boolean;
}

export interface MapFunction<T, U> {
  (item: T, index: number, collection: T[]): U;
}

export interface ReduceFunction<T, U> {
  (accumulator: U, item: T, index: number, collection: T[]): U;
}

// ๐ŸŽฏ Advanced Collection class with UMD export
export class Collection<T extends CollectionItem> {
  private items: T[] = [];
  private options: Required<CollectionOptions>;
  private indexes: Map<string, Map<any, T[]>> = new Map();

  constructor(items: T[] = [], options: CollectionOptions = {}) {
    this.options = {
      idField: options.idField || 'id',
      compareFn: options.compareFn || this.defaultCompareFn,
      allowDuplicates: options.allowDuplicates || false,
      maxSize: options.maxSize || Infinity,
    };

    console.log(`๐Ÿ“ฆ Collection initialized with ${items.length} items`);
    this.addMany(items);
  }

  // ๐Ÿ”ง Default comparison function
  private defaultCompareFn(a: any, b: any): number {
    if (a < b) return -1;
    if (a > b) return 1;
    return 0;
  }

  // โž• Add single item
  add(item: T): boolean {
    if (this.items.length >= this.options.maxSize) {
      console.warn('โš ๏ธ Collection has reached maximum size');
      return false;
    }

    if (!this.options.allowDuplicates && this.has(item[this.options.idField])) {
      console.warn(`โš ๏ธ Item with ID ${item[this.options.idField]} already exists`);
      return false;
    }

    this.items.push(item);
    this.updateIndexes(item);
    console.log(`โž• Added item: ${item[this.options.idField]}`);
    return true;
  }

  // โž• Add multiple items
  addMany(items: T[]): number {
    let addedCount = 0;
    for (const item of items) {
      if (this.add(item)) {
        addedCount++;
      }
    }
    console.log(`๐Ÿ“ฆ Added ${addedCount} out of ${items.length} items`);
    return addedCount;
  }

  // ๐Ÿ—‘๏ธ Remove item by ID
  remove(id: string | number): T | null {
    const index = this.items.findIndex(item => item[this.options.idField] === id);
    if (index === -1) {
      console.warn(`โš ๏ธ Item with ID ${id} not found`);
      return null;
    }

    const removedItem = this.items.splice(index, 1)[0];
    this.removeFromIndexes(removedItem);
    console.log(`๐Ÿ—‘๏ธ Removed item: ${id}`);
    return removedItem;
  }

  // ๐Ÿ” Find item by ID
  get(id: string | number): T | null {
    const item = this.items.find(item => item[this.options.idField] === id);
    if (item) {
      console.log(`๐Ÿ” Found item: ${id}`);
    } else {
      console.log(`โŒ Item not found: ${id}`);
    }
    return item || null;
  }

  // ๐Ÿ” Check if item exists
  has(id: string | number): boolean {
    return this.items.some(item => item[this.options.idField] === id);
  }

  // ๐Ÿ” Filter items
  filter(predicate: FilterFunction<T>): Collection<T> {
    console.log('๐Ÿ” Filtering collection...');
    const filteredItems = this.items.filter(predicate);
    return new Collection(filteredItems, this.options);
  }

  // ๐Ÿ—‚๏ธ Map items to new collection
  map<U extends CollectionItem>(mapFn: MapFunction<T, U>): Collection<U> {
    console.log('๐Ÿ—‚๏ธ Mapping collection...');
    const mappedItems = this.items.map(mapFn);
    return new Collection(mappedItems);
  }

  // ๐Ÿ“Š Reduce collection to single value
  reduce<U>(reduceFn: ReduceFunction<T, U>, initialValue: U): U {
    console.log('๐Ÿ“Š Reducing collection...');
    return this.items.reduce(reduceFn, initialValue);
  }

  // ๐Ÿ”€ Sort collection
  sort(compareFn?: (a: T, b: T) => number): Collection<T> {
    console.log('๐Ÿ”€ Sorting collection...');
    const sortedItems = [...this.items].sort(compareFn || this.options.compareFn);
    return new Collection(sortedItems, this.options);
  }

  // ๐Ÿ“„ Paginate collection
  paginate(page: number, pageSize: number): {
    items: T[];
    pagination: {
      page: number;
      pageSize: number;
      total: number;
      pages: number;
      hasNext: boolean;
      hasPrev: boolean;
    };
  } {
    console.log(`๐Ÿ“„ Paginating collection (page ${page}, size ${pageSize})`);
    
    const startIndex = (page - 1) * pageSize;
    const endIndex = startIndex + pageSize;
    const items = this.items.slice(startIndex, endIndex);
    const total = this.items.length;
    const pages = Math.ceil(total / pageSize);

    return {
      items,
      pagination: {
        page,
        pageSize,
        total,
        pages,
        hasNext: page < pages,
        hasPrev: page > 1,
      },
    };
  }

  // ๐Ÿท๏ธ Create index for fast lookups
  createIndex(field: string): void {
    console.log(`๐Ÿท๏ธ Creating index for field: ${field}`);
    
    const index = new Map<any, T[]>();
    
    for (const item of this.items) {
      const value = item[field];
      if (!index.has(value)) {
        index.set(value, []);
      }
      index.get(value)!.push(item);
    }
    
    this.indexes.set(field, index);
  }

  // ๐Ÿ” Find by indexed field
  findByIndex(field: string, value: any): T[] {
    const index = this.indexes.get(field);
    if (!index) {
      console.warn(`โš ๏ธ Index for field ${field} does not exist`);
      return [];
    }
    
    console.log(`๐Ÿ” Finding by index ${field}: ${value}`);
    return index.get(value) || [];
  }

  // ๐Ÿ”ง Update indexes
  private updateIndexes(item: T): void {
    for (const [field, index] of this.indexes) {
      const value = item[field];
      if (!index.has(value)) {
        index.set(value, []);
      }
      index.get(value)!.push(item);
    }
  }

  // ๐Ÿ—‘๏ธ Remove from indexes
  private removeFromIndexes(item: T): void {
    for (const [field, index] of this.indexes) {
      const value = item[field];
      const items = index.get(value);
      if (items) {
        const itemIndex = items.indexOf(item);
        if (itemIndex > -1) {
          items.splice(itemIndex, 1);
          if (items.length === 0) {
            index.delete(value);
          }
        }
      }
    }
  }

  // ๐Ÿ“Š Collection statistics
  getStats(): {
    size: number;
    indexes: string[];
    memory: {
      items: number;
      indexes: number;
    };
  } {
    return {
      size: this.items.length,
      indexes: Array.from(this.indexes.keys()),
      memory: {
        items: this.items.length,
        indexes: Array.from(this.indexes.values()).reduce(
          (total, index) => total + index.size,
          0
        ),
      },
    };
  }

  // ๐Ÿ”„ Clone collection
  clone(): Collection<T> {
    console.log('๐Ÿ”„ Cloning collection...');
    const cloned = new Collection([...this.items], this.options);
    
    // Recreate indexes
    for (const field of this.indexes.keys()) {
      cloned.createIndex(field);
    }
    
    return cloned;
  }

  // ๐Ÿงน Clear collection
  clear(): void {
    console.log('๐Ÿงน Clearing collection...');
    this.items = [];
    this.indexes.clear();
  }

  // ๐Ÿ“‹ Convert to array
  toArray(): T[] {
    return [...this.items];
  }

  // ๐Ÿ“„ Convert to JSON
  toJSON(): T[] {
    return this.toArray();
  }

  // ๐Ÿ”„ Iterator support
  *[Symbol.iterator](): Iterator<T> {
    for (const item of this.items) {
      yield item;
    }
  }

  // ๐Ÿ“Š Getters
  get size(): number {
    return this.items.length;
  }

  get isEmpty(): boolean {
    return this.items.length === 0;
  }

  get first(): T | null {
    return this.items[0] || null;
  }

  get last(): T | null {
    return this.items[this.items.length - 1] || null;
  }
}

// ๐ŸŽฏ Export utilities and factory functions
export const createCollection = <T extends CollectionItem>(
  items: T[] = [],
  options: CollectionOptions = {}
): Collection<T> => {
  return new Collection(items, options);
};

export const fromArray = <T extends CollectionItem>(array: T[]): Collection<T> => {
  return new Collection(array);
};

export const fromJSON = <T extends CollectionItem>(json: string): Collection<T> => {
  try {
    const data = JSON.parse(json);
    return new Collection(Array.isArray(data) ? data : []);
  } catch (error) {
    console.error('โŒ Failed to parse JSON:', error);
    return new Collection([]);
  }
};

// ๐ŸŒŸ Event system for collections
export interface CollectionEventMap<T> {
  'item:added': { item: T; collection: Collection<T> };
  'item:removed': { item: T; collection: Collection<T> };
  'item:updated': { item: T; collection: Collection<T> };
  'collection:cleared': { collection: Collection<T> };
  'collection:sorted': { collection: Collection<T> };
}

export type CollectionEventHandler<T, K extends keyof CollectionEventMap<T>> = (
  data: CollectionEventMap<T>[K]
) => void;

// ๐ŸŽง Event emitter for collections
export class EventEmitter<T extends CollectionItem> {
  private listeners: Map<string, Set<Function>> = new Map();

  on<K extends keyof CollectionEventMap<T>>(
    event: K,
    handler: CollectionEventHandler<T, K>
  ): void {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, new Set());
    }
    this.listeners.get(event)!.add(handler);
    console.log(`๐ŸŽง Event listener added for: ${event}`);
  }

  off<K extends keyof CollectionEventMap<T>>(
    event: K,
    handler: CollectionEventHandler<T, K>
  ): void {
    const listeners = this.listeners.get(event);
    if (listeners) {
      listeners.delete(handler);
      console.log(`๐Ÿ”‡ Event listener removed for: ${event}`);
    }
  }

  emit<K extends keyof CollectionEventMap<T>>(
    event: K,
    data: CollectionEventMap<T>[K]
  ): void {
    const listeners = this.listeners.get(event);
    if (listeners) {
      console.log(`๐Ÿ“ข Emitting event: ${event}`);
      listeners.forEach(handler => {
        try {
          handler(data);
        } catch (error) {
          console.error(`โŒ Error in event handler for ${event}:`, error);
        }
      });
    }
  }

  removeAllListeners(event?: string): void {
    if (event) {
      this.listeners.delete(event);
      console.log(`๐Ÿ”‡ All listeners removed for: ${event}`);
    } else {
      this.listeners.clear();
      console.log('๐Ÿ”‡ All event listeners removed');
    }
  }
}

// ๐Ÿš€ Enhanced collection with events
export class ObservableCollection<T extends CollectionItem> extends Collection<T> {
  private eventEmitter = new EventEmitter<T>();

  on<K extends keyof CollectionEventMap<T>>(
    event: K,
    handler: CollectionEventHandler<T, K>
  ): void {
    this.eventEmitter.on(event, handler);
  }

  off<K extends keyof CollectionEventMap<T>>(
    event: K,
    handler: CollectionEventHandler<T, K>
  ): void {
    this.eventEmitter.off(event, handler);
  }

  add(item: T): boolean {
    const result = super.add(item);
    if (result) {
      this.eventEmitter.emit('item:added', { item, collection: this });
    }
    return result;
  }

  remove(id: string | number): T | null {
    const item = super.remove(id);
    if (item) {
      this.eventEmitter.emit('item:removed', { item, collection: this });
    }
    return item;
  }

  clear(): void {
    super.clear();
    this.eventEmitter.emit('collection:cleared', { collection: this });
  }
}

๐Ÿ”ง UMD Wrapper and Export Strategy

Now letโ€™s create the UMD wrapper for our library:

// ๐ŸŒŸ UMD wrapper for the collection library
// dist/collection-library.umd.js (generated)
(function (root, factory) {
  // ๐Ÿ” Environment detection
  if (typeof exports === 'object' && typeof module !== 'undefined') {
    // ๐Ÿ“ฆ CommonJS environment (Node.js)
    console.log('๐ŸŸข Loading Collection Library as CommonJS module');
    module.exports = factory();
  } else if (typeof define === 'function' && define.amd) {
    // ๐ŸŒ AMD environment (RequireJS)
    console.log('๐Ÿ”ต Loading Collection Library as AMD module');
    define([], factory);
  } else {
    // ๐ŸŒ Browser global environment
    console.log('๐ŸŸก Loading Collection Library as global variable');
    root.CollectionLibrary = factory();
  }
}(typeof self !== 'undefined' ? self : this, function () {
  'use strict';

  // ๐ŸŽฏ Include all our compiled TypeScript code here
  // (This would be the actual compiled output from TypeScript)
  
  // ๐Ÿ“ฆ Library namespace
  const CollectionLibrary = {
    // ๐Ÿ—๏ธ Main classes
    Collection: Collection,
    ObservableCollection: ObservableCollection,
    EventEmitter: EventEmitter,

    // ๐ŸŽฏ Factory functions
    createCollection: createCollection,
    fromArray: fromArray,
    fromJSON: fromJSON,

    // ๐Ÿ“‹ Utilities
    utils: {
      // ๐Ÿ” Query builder for complex filtering
      createQuery: function<T extends CollectionItem>() {
        return new QueryBuilder<T>();
      },

      // ๐Ÿ“Š Data transformation utilities
      transforms: {
        groupBy: function<T extends CollectionItem>(
          items: T[],
          keyFn: (item: T) => string | number
        ): Record<string | number, T[]> {
          console.log('๐Ÿ“Š Grouping items...');
          const groups: Record<string | number, T[]> = {};
          
          for (const item of items) {
            const key = keyFn(item);
            if (!groups[key]) {
              groups[key] = [];
            }
            groups[key].push(item);
          }
          
          return groups;
        },

        pivot: function<T extends CollectionItem>(
          items: T[],
          rowKey: string,
          colKey: string,
          valueKey: string
        ): Record<string, Record<string, any>> {
          console.log('๐Ÿ”„ Pivoting data...');
          const result: Record<string, Record<string, any>> = {};
          
          for (const item of items) {
            const row = item[rowKey];
            const col = item[colKey];
            const value = item[valueKey];
            
            if (!result[row]) {
              result[row] = {};
            }
            result[row][col] = value;
          }
          
          return result;
        },

        aggregate: function<T extends CollectionItem>(
          items: T[],
          operations: Record<string, {
            field: string;
            operation: 'sum' | 'avg' | 'count' | 'min' | 'max';
          }>
        ): Record<string, number> {
          console.log('๐Ÿ“ˆ Aggregating data...');
          const result: Record<string, number> = {};
          
          for (const [key, { field, operation }] of Object.entries(operations)) {
            const values = items.map(item => Number(item[field])).filter(v => !isNaN(v));
            
            switch (operation) {
              case 'sum':
                result[key] = values.reduce((sum, val) => sum + val, 0);
                break;
              case 'avg':
                result[key] = values.length > 0 ? values.reduce((sum, val) => sum + val, 0) / values.length : 0;
                break;
              case 'count':
                result[key] = values.length;
                break;
              case 'min':
                result[key] = values.length > 0 ? Math.min(...values) : 0;
                break;
              case 'max':
                result[key] = values.length > 0 ? Math.max(...values) : 0;
                break;
            }
          }
          
          return result;
        },
      },

      // ๐Ÿ” Advanced query builder
      QueryBuilder: function<T extends CollectionItem>() {
        const conditions: Array<(item: T) => boolean> = [];
        
        return {
          where: function(field: string, operator: string, value: any) {
            conditions.push((item: T) => {
              const itemValue = item[field];
              switch (operator) {
                case '=':
                case '==':
                  return itemValue === value;
                case '!=':
                  return itemValue !== value;
                case '>':
                  return itemValue > value;
                case '>=':
                  return itemValue >= value;
                case '<':
                  return itemValue < value;
                case '<=':
                  return itemValue <= value;
                case 'like':
                  return String(itemValue).toLowerCase().includes(String(value).toLowerCase());
                case 'in':
                  return Array.isArray(value) && value.includes(itemValue);
                case 'between':
                  return Array.isArray(value) && value.length === 2 && 
                         itemValue >= value[0] && itemValue <= value[1];
                default:
                  return false;
              }
            });
            return this;
          },

          and: function(conditionFn: (item: T) => boolean) {
            conditions.push(conditionFn);
            return this;
          },

          execute: function(collection: Collection<T>): T[] {
            console.log(`๐Ÿ” Executing query with ${conditions.length} conditions`);
            return collection.toArray().filter(item => 
              conditions.every(condition => condition(item))
            );
          }
        };
      },
    },

    // ๐Ÿ“‹ Constants and metadata
    version: '1.0.0',
    author: 'TypeScript UMD Library',
    description: 'Advanced collection library with UMD pattern',
    
    // ๐ŸŽฏ Environment detection utilities
    environment: {
      isNode: typeof process !== 'undefined' && process.versions && process.versions.node,
      isBrowser: typeof window !== 'undefined',
      isAMD: typeof define === 'function' && define.amd,
      isCommonJS: typeof exports === 'object' && typeof module !== 'undefined',
    },

    // ๐Ÿ”ง Configuration and initialization
    config: {
      defaultOptions: {
        idField: 'id',
        allowDuplicates: false,
        maxSize: Infinity,
      },
      
      setDefaults: function(options: Partial<CollectionOptions>) {
        Object.assign(this.defaultOptions, options);
        console.log('โš™๏ธ Default options updated:', this.defaultOptions);
      },
      
      getDefaults: function() {
        return { ...this.defaultOptions };
      }
    },

    // ๐Ÿงช Debug and development utilities
    debug: {
      enableLogging: true,
      
      log: function(message: string, ...args: any[]) {
        if (this.enableLogging) {
          console.log(`[CollectionLibrary] ${message}`, ...args);
        }
      },
      
      benchmark: function<T>(fn: () => T, name: string = 'operation'): T {
        const start = performance.now();
        const result = fn();
        const end = performance.now();
        console.log(`โฑ๏ธ ${name} took ${(end - start).toFixed(2)}ms`);
        return result;
      }
    }
  };

  // ๐ŸŽฏ Return the complete library
  return CollectionLibrary;
}));

// ๐ŸŽฎ Usage examples in different environments:

// ๐Ÿ“ฆ Node.js (CommonJS)
/*
const CollectionLibrary = require('./collection-library.umd.js');

const collection = CollectionLibrary.createCollection([
  { id: 1, name: 'John', age: 30 },
  { id: 2, name: 'Jane', age: 25 },
  { id: 3, name: 'Bob', age: 35 }
]);

const adults = collection.filter(item => item.age >= 30);
console.log('Adults:', adults.toArray());
*/

// ๐ŸŒ AMD (RequireJS)
/*
define(['./collection-library.umd.js'], function(CollectionLibrary) {
  const collection = CollectionLibrary.createCollection();
  collection.add({ id: 1, name: 'AMD User', score: 100 });
  console.log('Collection size:', collection.size);
});
*/

// ๐ŸŒ Browser Global
/*
<script src="collection-library.umd.js"></script>
<script>
  const collection = CollectionLibrary.createCollection();
  collection.add({ id: 1, name: 'Browser User', active: true });
  
  const stats = collection.getStats();
  console.log('Collection stats:', stats);
</script>
*/

// ๐Ÿ”ง Environment-specific initialization
if (typeof CollectionLibrary !== 'undefined') {
  // ๐ŸŒ Browser environment setup
  if (CollectionLibrary.environment.isBrowser) {
    console.log('๐ŸŒ Collection Library loaded in browser environment');
    
    // Add browser-specific utilities
    CollectionLibrary.browser = {
      // ๐Ÿ’พ Local storage integration
      saveToLocalStorage: function(key: string, collection: any) {
        try {
          localStorage.setItem(key, JSON.stringify(collection.toArray()));
          console.log(`๐Ÿ’พ Collection saved to localStorage: ${key}`);
        } catch (error) {
          console.error('โŒ Failed to save to localStorage:', error);
        }
      },
      
      loadFromLocalStorage: function(key: string) {
        try {
          const data = localStorage.getItem(key);
          if (data) {
            const items = JSON.parse(data);
            console.log(`๐Ÿ“‚ Collection loaded from localStorage: ${key}`);
            return CollectionLibrary.fromArray(items);
          }
        } catch (error) {
          console.error('โŒ Failed to load from localStorage:', error);
        }
        return CollectionLibrary.createCollection();
      },
      
      // ๐Ÿ“ก Fetch integration
      fetchCollection: async function(url: string) {
        try {
          console.log(`๐Ÿ“ก Fetching collection from: ${url}`);
          const response = await fetch(url);
          const data = await response.json();
          return CollectionLibrary.fromArray(Array.isArray(data) ? data : [data]);
        } catch (error) {
          console.error('โŒ Failed to fetch collection:', error);
          return CollectionLibrary.createCollection();
        }
      }
    };
  }
  
  // ๐Ÿ“ฆ Node.js environment setup
  if (CollectionLibrary.environment.isNode) {
    console.log('๐Ÿ“ฆ Collection Library loaded in Node.js environment');
    
    // Add Node.js-specific utilities
    CollectionLibrary.node = {
      // ๐Ÿ“„ File system integration
      saveToFile: function(filename: string, collection: any) {
        try {
          const fs = require('fs');
          const data = JSON.stringify(collection.toArray(), null, 2);
          fs.writeFileSync(filename, data);
          console.log(`๐Ÿ“„ Collection saved to file: ${filename}`);
        } catch (error) {
          console.error('โŒ Failed to save to file:', error);
        }
      },
      
      loadFromFile: function(filename: string) {
        try {
          const fs = require('fs');
          const data = fs.readFileSync(filename, 'utf8');
          const items = JSON.parse(data);
          console.log(`๐Ÿ“‚ Collection loaded from file: ${filename}`);
          return CollectionLibrary.fromArray(items);
        } catch (error) {
          console.error('โŒ Failed to load from file:', error);
          return CollectionLibrary.createCollection();
        }
      }
    };
  }
}

๐Ÿš€ Testing UMD Modules

๐Ÿงช Comprehensive Testing Strategy

Letโ€™s create tests that work across all module environments:

// ๐Ÿงช Universal test suite for UMD modules
// tests/collection-library.test.ts
// This test file works in both Node.js and browser environments

// ๐Ÿ”ง Dynamic import handling for different environments
declare let require: any;
declare let define: any;
declare let CollectionLibrary: any;

// ๐ŸŽฏ Get the library based on environment
function getLibrary(): Promise<any> {
  return new Promise((resolve, reject) => {
    // ๐Ÿ“ฆ Node.js/CommonJS environment
    if (typeof require !== 'undefined' && typeof module !== 'undefined') {
      try {
        const lib = require('../dist/collection-library.umd.js');
        resolve(lib);
      } catch (error) {
        reject(error);
      }
    }
    // ๐ŸŒ AMD environment
    else if (typeof define === 'function' && define.amd) {
      define(['../dist/collection-library.umd.js'], function(lib: any) {
        resolve(lib);
      });
    }
    // ๐ŸŒ Browser global environment
    else if (typeof CollectionLibrary !== 'undefined') {
      resolve(CollectionLibrary);
    }
    // โŒ Unknown environment
    else {
      reject(new Error('Unknown module environment'));
    }
  });
}

// ๐ŸŽฏ Test suite that works universally
async function runTests() {
  console.log('๐Ÿงช Starting UMD module tests...');
  
  try {
    const CollectionLib = await getLibrary();
    console.log('โœ… Library loaded successfully');
    
    // ๐Ÿงช Test 1: Basic collection operations
    await testBasicOperations(CollectionLib);
    
    // ๐Ÿงช Test 2: Advanced collection features
    await testAdvancedFeatures(CollectionLib);
    
    // ๐Ÿงช Test 3: Environment-specific features
    await testEnvironmentFeatures(CollectionLib);
    
    // ๐Ÿงช Test 4: Error handling
    await testErrorHandling(CollectionLib);
    
    console.log('๐ŸŽ‰ All tests passed!');
    
  } catch (error) {
    console.error('โŒ Test failed:', error);
  }
}

// ๐Ÿ”ง Test basic collection operations
async function testBasicOperations(CollectionLib: any) {
  console.log('๐Ÿงช Testing basic operations...');
  
  // Create collection
  const collection = CollectionLib.createCollection();
  assert(collection.size === 0, 'Empty collection should have size 0');
  
  // Add items
  const item1 = { id: 1, name: 'Test Item 1', value: 100 };
  const item2 = { id: 2, name: 'Test Item 2', value: 200 };
  
  assert(collection.add(item1), 'Should add item successfully');
  assert(collection.add(item2), 'Should add second item successfully');
  assert(collection.size === 2, 'Collection should have 2 items');
  
  // Get items
  const retrieved = collection.get(1);
  assert(retrieved && retrieved.name === 'Test Item 1', 'Should retrieve item by ID');
  
  // Remove items
  const removed = collection.remove(1);
  assert(removed && removed.id === 1, 'Should remove and return item');
  assert(collection.size === 1, 'Collection should have 1 item after removal');
  
  console.log('โœ… Basic operations test passed');
}

// ๐Ÿ”ง Test advanced collection features
async function testAdvancedFeatures(CollectionLib: any) {
  console.log('๐Ÿงช Testing advanced features...');
  
  const items = [
    { id: 1, name: 'Alice', age: 30, department: 'Engineering' },
    { id: 2, name: 'Bob', age: 25, department: 'Design' },
    { id: 3, name: 'Charlie', age: 35, department: 'Engineering' },
    { id: 4, name: 'Diana', age: 28, department: 'Marketing' }
  ];
  
  const collection = CollectionLib.fromArray(items);
  
  // Test filtering
  const engineers = collection.filter((item: any) => item.department === 'Engineering');
  assert(engineers.size === 2, 'Should filter engineers correctly');
  
  // Test mapping
  const names = collection.map((item: any) => ({ id: item.id, name: item.name }));
  assert(names.size === 4, 'Should map to names correctly');
  
  // Test sorting
  const sortedByAge = collection.sort((a: any, b: any) => a.age - b.age);
  const first = sortedByAge.first;
  assert(first && first.age === 25, 'Should sort by age correctly');
  
  // Test pagination
  const page1 = collection.paginate(1, 2);
  assert(page1.items.length === 2, 'Should paginate correctly');
  assert(page1.pagination.pages === 2, 'Should calculate pages correctly');
  
  // Test indexing
  collection.createIndex('department');
  const engineersIndexed = collection.findByIndex('department', 'Engineering');
  assert(engineersIndexed.length === 2, 'Should find by index correctly');
  
  console.log('โœ… Advanced features test passed');
}

// ๐Ÿ”ง Test environment-specific features
async function testEnvironmentFeatures(CollectionLib: any) {
  console.log('๐Ÿงช Testing environment-specific features...');
  
  // Test environment detection
  const env = CollectionLib.environment;
  assert(typeof env.isNode === 'boolean', 'Should detect Node.js environment');
  assert(typeof env.isBrowser === 'boolean', 'Should detect browser environment');
  
  // Test configuration
  const originalDefaults = CollectionLib.config.getDefaults();
  CollectionLib.config.setDefaults({ maxSize: 100 });
  const newDefaults = CollectionLib.config.getDefaults();
  assert(newDefaults.maxSize === 100, 'Should update default configuration');
  
  // Restore original defaults
  CollectionLib.config.setDefaults(originalDefaults);
  
  // Test utilities
  const items = [
    { id: 1, category: 'A', value: 10 },
    { id: 2, category: 'B', value: 20 },
    { id: 3, category: 'A', value: 30 }
  ];
  
  const grouped = CollectionLib.utils.transforms.groupBy(items, (item: any) => item.category);
  assert(grouped.A.length === 2, 'Should group by category correctly');
  assert(grouped.B.length === 1, 'Should group by category correctly');
  
  console.log('โœ… Environment-specific features test passed');
}

// ๐Ÿ”ง Test error handling
async function testErrorHandling(CollectionLib: any) {
  console.log('๐Ÿงช Testing error handling...');
  
  const collection = CollectionLib.createCollection([], { allowDuplicates: false });
  
  // Test duplicate prevention
  const item = { id: 1, name: 'Test' };
  assert(collection.add(item), 'Should add item first time');
  assert(!collection.add(item), 'Should not add duplicate item');
  
  // Test invalid operations
  const removed = collection.remove(999);
  assert(removed === null, 'Should return null for non-existent item');
  
  const retrieved = collection.get(999);
  assert(retrieved === null, 'Should return null for non-existent item');
  
  // Test JSON parsing
  const invalidJson = CollectionLib.fromJSON('invalid json');
  assert(invalidJson.size === 0, 'Should handle invalid JSON gracefully');
  
  console.log('โœ… Error handling test passed');
}

// ๐Ÿ”ง Simple assertion function
function assert(condition: boolean, message: string) {
  if (!condition) {
    throw new Error(`Assertion failed: ${message}`);
  }
}

// ๐Ÿš€ Run tests based on environment
if (typeof window !== 'undefined') {
  // ๐ŸŒ Browser environment
  document.addEventListener('DOMContentLoaded', () => {
    runTests().catch(console.error);
  });
} else if (typeof module !== 'undefined') {
  // ๐Ÿ“ฆ Node.js environment
  runTests().catch(console.error);
} else {
  // ๐ŸŒ AMD environment
  define([], () => {
    runTests().catch(console.error);
  });
}

// ๐ŸŽฏ Export for other test frameworks
if (typeof module !== 'undefined' && module.exports) {
  module.exports = { runTests, testBasicOperations, testAdvancedFeatures };
}

๐ŸŽ‰ Conclusion

Congratulations! Youโ€™ve mastered the universal art of UMD modules with TypeScript! ๐ŸŒ

๐ŸŽฏ What Youโ€™ve Learned

  • ๐ŸŒ Universal Compatibility: Creating modules that work everywhere
  • ๐Ÿ”ง Environment Detection: Smart module loading based on runtime environment
  • ๐Ÿ“ฆ Advanced UMD Patterns: Building sophisticated libraries with TypeScript
  • ๐Ÿ› ๏ธ Build Configuration: Setting up TypeScript and Webpack for UMD output
  • ๐Ÿงช Universal Testing: Testing strategies that work across all environments
  • ๐Ÿ“š Library Distribution: Publishing and distributing UMD libraries

๐Ÿš€ Key Benefits

  • ๐ŸŒ Write Once, Run Everywhere: Single codebase for all environments
  • ๐Ÿ“ฆ Zero Configuration: Automatic environment detection and adaptation
  • ๐Ÿ”„ Backward Compatibility: Works with legacy systems and modern applications
  • ๐ŸŽฏ Type Safety: Full TypeScript support across all module systems
  • ๐Ÿ“ˆ Easy Distribution: Perfect for npm packages and CDN distribution

๐Ÿ”ฅ Best Practices Recap

  1. ๐Ÿ” Environment Detection: Properly detect and handle all module environments
  2. ๐ŸŽฏ Type Safety: Maintain TypeScript types in all compilation targets
  3. ๐Ÿงช Universal Testing: Test your modules in all target environments
  4. ๐Ÿ“ฆ Build Pipeline: Set up comprehensive build processes for multiple outputs
  5. ๐Ÿ“š Documentation: Provide usage examples for all supported environments

Youโ€™re now equipped to build universal TypeScript libraries that seamlessly work in Node.js, browsers, and AMD environments! ๐ŸŒŸ

Happy coding, and may your modules be universally compatible! ๐ŸŒโœจ