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
- ๐ Environment Detection: Properly detect and handle all module environments
- ๐ฏ Type Safety: Maintain TypeScript types in all compilation targets
- ๐งช Universal Testing: Test your modules in all target environments
- ๐ฆ Build Pipeline: Set up comprehensive build processes for multiple outputs
- ๐ 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! ๐โจ