Prerequisites
- Understanding of JavaScript modules and module systems 📝
- Basic knowledge of TypeScript compilation and configuration ⚡
- Experience with browser-based JavaScript development 💻
What you'll learn
- Master AMD module pattern for browser-based TypeScript development 🎯
- Implement efficient asynchronous module loading with RequireJS 🏗️
- Build modular browser applications with dynamic dependency management 🐛
- Create production-ready AMD module architectures and optimization strategies ✨
🎯 Introduction
Welcome to the dynamic world of AMD modules! 🌐 If ES6 modules are like modern highways with standardized on-ramps, then AMD (Asynchronous Module Definition) modules are like smart city traffic systems - they load exactly what you need, when you need it, keeping your browser performance smooth and efficient!
AMD was designed specifically for browser environments where asynchronous loading is crucial for performance. Before ES6 modules became widely supported, AMD was the go-to solution for building modular browser applications. Even today, AMD remains valuable for legacy browser support, dynamic loading scenarios, and specific optimization requirements.
By the end of this tutorial, you’ll be a master of AMD modules with TypeScript, capable of building efficient, modular browser applications that load fast and scale beautifully. Let’s dive into the asynchronous module universe! ⚡
📚 Understanding AMD Modules
🤔 What Are AMD Modules?
AMD (Asynchronous Module Definition) is a JavaScript module system designed specifically for browsers. It allows modules to be loaded asynchronously, which is crucial for browser performance where blocking the main thread can freeze the user interface.
// 🌟 Basic AMD module definition
// math-utils.ts
define(['exports'], function(exports) {
// 📦 Module implementation
const add = (a: number, b: number): number => {
console.log(`➕ Adding ${a} + ${b}`);
return a + b;
};
const multiply = (a: number, b: number): number => {
console.log(`✖️ Multiplying ${a} * ${b}`);
return a * b;
};
const 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 calculator functionality
const calculate = (
operation: 'add' | 'multiply' | 'divide' | 'power',
a: number,
b: number
): number => {
switch (operation) {
case 'add':
return add(a, b);
case 'multiply':
return multiply(a, b);
case 'divide':
return divide(a, b);
case 'power':
console.log(`🔢 Calculating ${a} ^ ${b}`);
return Math.pow(a, b);
default:
throw new Error(`❌ Unknown operation: ${operation}`);
}
};
// 📊 Statistical functions
const statistics = {
mean: (numbers: number[]): number => {
console.log('📈 Calculating mean...');
const sum = numbers.reduce((acc, num) => acc + num, 0);
return sum / numbers.length;
},
median: (numbers: number[]): number => {
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];
},
mode: (numbers: number[]): number[] => {
console.log('📋 Calculating mode...');
const frequency = new Map<number, number>();
numbers.forEach(num => {
frequency.set(num, (frequency.get(num) || 0) + 1);
});
const maxFreq = Math.max(...frequency.values());
return Array.from(frequency.entries())
.filter(([, freq]) => freq === maxFreq)
.map(([num]) => num);
},
};
// 🎯 Export functions
exports.add = add;
exports.multiply = multiply;
exports.divide = divide;
exports.calculate = calculate;
exports.statistics = statistics;
});
// 🚀 Using the AMD module
// app.ts
define(['./math-utils'], function(mathUtils) {
console.log('🎉 Math utilities loaded!');
// 🧮 Basic calculations
const sum = mathUtils.add(10, 5);
const product = mathUtils.multiply(4, 7);
const quotient = mathUtils.divide(20, 4);
console.log(`Results: ${sum}, ${product}, ${quotient}`);
// 📊 Statistical analysis
const numbers = [1, 2, 3, 4, 5, 2, 3, 2];
const mean = mathUtils.statistics.mean(numbers);
const median = mathUtils.statistics.median(numbers);
const mode = mathUtils.statistics.mode(numbers);
console.log(`📈 Statistics:`, { mean, median, mode });
});
💡 Key AMD Concepts
- 🔄 Asynchronous Loading: Modules load without blocking the browser
- 📦 Dependency Management: Explicit dependency declaration
- 🎯 On-Demand Loading: Load modules only when needed
- 🔗 Module Factory: Functions that create module instances
- 📋 Configuration: Flexible path and alias configuration
🛠️ Setting Up AMD with TypeScript
📦 TypeScript Configuration for AMD
Let’s configure TypeScript to compile to AMD modules:
// 🌟 tsconfig.json for AMD compilation
{
"compilerOptions": {
"target": "ES5", // AMD works well with ES5
"module": "AMD", // 🎯 Specify AMD module system
"lib": ["ES2015", "DOM"], // Include necessary libraries
"outDir": "./dist", // Output directory
"rootDir": "./src", // Source directory
"strict": true, // Enable strict type checking
"esModuleInterop": true, // Enable ES module interop
"skipLibCheck": true, // Skip library type checking
"forceConsistentCasingInFileNames": true, // Enforce consistent casing
"declaration": true, // Generate .d.ts files
"sourceMap": true, // Generate source maps
"removeComments": false, // Keep comments for debugging
"noImplicitAny": true, // No implicit any types
"strictNullChecks": true, // Strict null checking
"strictFunctionTypes": true, // Strict function types
"noImplicitReturns": true, // No implicit returns
"noUnusedLocals": true, // Flag unused locals
"noUnusedParameters": true, // Flag unused parameters
"exactOptionalPropertyTypes": true, // Exact optional properties
"baseUrl": "./src", // Base URL for module resolution
"paths": { // Path mapping
"@/*": ["*"],
"@components/*": ["components/*"],
"@utils/*": ["utils/*"],
"@services/*": ["services/*"],
"@types/*": ["types/*"]
}
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"dist",
"**/*.spec.ts",
"**/*.test.ts"
]
}
// 🚀 Package.json scripts for AMD development
{
"name": "typescript-amd-project",
"version": "1.0.0",
"scripts": {
"build": "tsc",
"build:watch": "tsc --watch",
"serve": "http-server dist -p 8080",
"dev": "npm run build:watch & npm run serve",
"clean": "rimraf dist",
"type-check": "tsc --noEmit"
},
"devDependencies": {
"typescript": "^5.3.0",
"http-server": "^14.1.1",
"rimraf": "^5.0.5",
"@types/requirejs": "^2.1.34"
}
}
🎯 Project Structure for AMD Modules
// 📁 Recommended project structure
src/
├── index.html // Main HTML file
├── main.ts // Application entry point
├── config/
│ └── requirejs-config.ts // RequireJS configuration
├── components/ // UI components
│ ├── button.ts
│ ├── modal.ts
│ └── form.ts
├── services/ // Business logic services
│ ├── api-service.ts
│ ├── auth-service.ts
│ └── data-service.ts
├── utils/ // Utility modules
│ ├── dom-utils.ts
│ ├── validation.ts
│ └── formatters.ts
├── types/ // Type definitions
│ ├── api-types.ts
│ ├── ui-types.ts
│ └── app-types.ts
└── assets/ // Static assets
├── styles/
└── images/
// 🎯 dist/ (generated after compilation)
dist/
├── index.html
├── main.js
├── config/
├── components/
├── services/
├── utils/
├── types/
└── assets/
🏗️ Building Advanced AMD Modules
🎯 Type-Safe AMD Module Patterns
Let’s create sophisticated AMD modules with strong TypeScript typing:
// 🌟 Advanced API service module
// src/services/api-service.ts
define(['exports'], function(exports) {
// 📦 Type definitions
interface ApiResponse<T> {
data: T;
success: boolean;
message: string;
timestamp: number;
errors?: string[];
}
interface RequestConfig {
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
headers?: Record<string, string>;
body?: any;
timeout?: number;
retries?: number;
}
interface CacheEntry<T> {
data: T;
timestamp: number;
ttl: number;
}
// 🎯 Advanced API service class
class ApiService {
private baseUrl: string;
private defaultHeaders: Record<string, string>;
private cache = new Map<string, CacheEntry<any>>();
private requestQueue = new Map<string, Promise<any>>();
constructor(baseUrl: string, defaultHeaders: Record<string, string> = {}) {
this.baseUrl = baseUrl.replace(/\/$/, ''); // Remove trailing slash
this.defaultHeaders = {
'Content-Type': 'application/json',
'Accept': 'application/json',
...defaultHeaders,
};
console.log(`🌐 API Service initialized with base URL: ${this.baseUrl}`);
}
// 🚀 Generic request method with caching and deduplication
async request<T>(
endpoint: string,
config: RequestConfig = { method: 'GET' },
useCache: boolean = false,
cacheTtl: number = 300000 // 5 minutes
): Promise<ApiResponse<T>> {
const url = `${this.baseUrl}${endpoint}`;
const cacheKey = `${config.method}:${url}:${JSON.stringify(config.body || {})}`;
// 📋 Check cache first
if (useCache && config.method === 'GET') {
const cached = this.getFromCache<T>(cacheKey);
if (cached) {
console.log(`💾 Cache hit for: ${endpoint}`);
return cached;
}
}
// 🔄 Check if request is already in progress (deduplication)
if (this.requestQueue.has(cacheKey)) {
console.log(`⏳ Deduplicating request for: ${endpoint}`);
return this.requestQueue.get(cacheKey)!;
}
// 🎯 Make the request
const requestPromise = this.executeRequest<T>(url, config, cacheTtl);
this.requestQueue.set(cacheKey, requestPromise);
try {
const result = await requestPromise;
// 💾 Cache successful GET requests
if (useCache && config.method === 'GET' && result.success) {
this.setCache(cacheKey, result, cacheTtl);
}
return result;
} finally {
this.requestQueue.delete(cacheKey);
}
}
// 🎯 Execute HTTP request with retry logic
private async executeRequest<T>(
url: string,
config: RequestConfig,
cacheTtl: number
): Promise<ApiResponse<T>> {
const maxRetries = config.retries || 3;
let attempt = 0;
while (attempt <= maxRetries) {
try {
console.log(`📡 Making ${config.method} request to: ${url} (attempt ${attempt + 1})`);
const response = await this.fetchWithTimeout(url, {
method: config.method,
headers: { ...this.defaultHeaders, ...config.headers },
body: config.body ? JSON.stringify(config.body) : undefined,
}, config.timeout || 10000);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
const apiResponse: ApiResponse<T> = {
data,
success: true,
message: 'Request successful',
timestamp: Date.now(),
};
console.log(`✅ Request successful: ${config.method} ${url}`);
return apiResponse;
} catch (error) {
attempt++;
console.error(`❌ Request failed (attempt ${attempt}):`, error);
if (attempt > maxRetries) {
return {
data: null as any,
success: false,
message: `Request failed after ${maxRetries + 1} attempts`,
timestamp: Date.now(),
errors: [error instanceof Error ? error.message : String(error)],
};
}
// 🔄 Wait before retrying with exponential backoff
const delay = Math.pow(2, attempt - 1) * 1000;
console.log(`⏳ Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw new Error('Max retries exceeded');
}
// ⏰ Fetch with timeout
private fetchWithTimeout(
url: string,
options: RequestInit,
timeout: number
): Promise<Response> {
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
reject(new Error(`Request timeout after ${timeout}ms`));
}, timeout);
fetch(url, options)
.then(resolve)
.catch(reject)
.finally(() => clearTimeout(timeoutId));
});
}
// 💾 Cache management
private getFromCache<T>(key: string): ApiResponse<T> | null {
const entry = this.cache.get(key);
if (!entry) return null;
if (Date.now() - entry.timestamp > entry.ttl) {
this.cache.delete(key);
return null;
}
return entry.data;
}
private setCache<T>(key: string, data: ApiResponse<T>, ttl: number): void {
this.cache.set(key, {
data,
timestamp: Date.now(),
ttl,
});
}
// 🧹 Cache maintenance
clearCache(): void {
console.log('🧹 Clearing API cache...');
this.cache.clear();
}
getCacheStats() {
return {
size: this.cache.size,
keys: Array.from(this.cache.keys()),
};
}
// 🎯 Convenience methods
async get<T>(endpoint: string, useCache: boolean = true): Promise<ApiResponse<T>> {
return this.request<T>(endpoint, { method: 'GET' }, useCache);
}
async post<T>(endpoint: string, data: any): Promise<ApiResponse<T>> {
return this.request<T>(endpoint, { method: 'POST', body: data });
}
async put<T>(endpoint: string, data: any): Promise<ApiResponse<T>> {
return this.request<T>(endpoint, { method: 'PUT', body: data });
}
async delete<T>(endpoint: string): Promise<ApiResponse<T>> {
return this.request<T>(endpoint, { method: 'DELETE' });
}
}
// 🎯 Export the service class and types
exports.ApiService = ApiService;
exports.createApiService = (baseUrl: string, headers?: Record<string, string>) => {
return new ApiService(baseUrl, headers);
};
});
// 🎮 User service module using the API service
// src/services/user-service.ts
define(['./api-service'], function(apiServiceModule) {
// 📦 User-related types
interface User {
id: number;
username: string;
email: string;
firstName: string;
lastName: string;
role: 'admin' | 'user' | 'moderator';
isActive: boolean;
lastLogin?: string;
preferences: UserPreferences;
}
interface UserPreferences {
theme: 'light' | 'dark';
language: string;
notifications: {
email: boolean;
push: boolean;
sms: boolean;
};
}
interface CreateUserRequest {
username: string;
email: string;
firstName: string;
lastName: string;
password: string;
role?: 'user' | 'moderator';
}
interface UpdateUserRequest {
firstName?: string;
lastName?: string;
email?: string;
preferences?: Partial<UserPreferences>;
}
// 🎯 User service implementation
class UserService {
private apiService: any;
constructor(apiService: any) {
this.apiService = apiService;
console.log('👤 User Service initialized');
}
// 👥 Get all users with pagination
async getUsers(page: number = 1, limit: number = 20) {
console.log(`📋 Fetching users (page ${page}, limit ${limit})`);
return this.apiService.get<{
users: User[];
pagination: {
page: number;
limit: number;
total: number;
pages: number;
};
}>(`/users?page=${page}&limit=${limit}`);
}
// 👤 Get user by ID
async getUserById(id: number) {
console.log(`🔍 Fetching user: ${id}`);
return this.apiService.get<User>(`/users/${id}`);
}
// ➕ Create new user
async createUser(userData: CreateUserRequest) {
console.log(`👤 Creating user: ${userData.username}`);
return this.apiService.post<User>('/users', userData);
}
// ✏️ Update user
async updateUser(id: number, userData: UpdateUserRequest) {
console.log(`📝 Updating user: ${id}`);
return this.apiService.put<User>(`/users/${id}`, userData);
}
// 🗑️ Delete user
async deleteUser(id: number) {
console.log(`🗑️ Deleting user: ${id}`);
return this.apiService.delete(`/users/${id}`);
}
// 🔐 Authentication methods
async login(username: string, password: string) {
console.log(`🔐 Logging in user: ${username}`);
return this.apiService.post<{
user: User;
token: string;
refreshToken: string;
expiresIn: number;
}>('/auth/login', { username, password });
}
async logout() {
console.log('🚪 Logging out user');
return this.apiService.post('/auth/logout', {});
}
async refreshToken(refreshToken: string) {
console.log('🔄 Refreshing authentication token');
return this.apiService.post<{
token: string;
refreshToken: string;
expiresIn: number;
}>('/auth/refresh', { refreshToken });
}
// ⚙️ Preferences management
async updatePreferences(userId: number, preferences: Partial<UserPreferences>) {
console.log(`⚙️ Updating preferences for user: ${userId}`);
return this.apiService.put<UserPreferences>(`/users/${userId}/preferences`, preferences);
}
// 🔍 Search users
async searchUsers(query: string, filters?: {
role?: string;
isActive?: boolean;
}) {
console.log(`🔍 Searching users: "${query}"`);
const params = new URLSearchParams({
q: query,
...(filters?.role && { role: filters.role }),
...(filters?.isActive !== undefined && { isActive: String(filters.isActive) }),
});
return this.apiService.get<User[]>(`/users/search?${params}`);
}
}
// 🎯 Export the user service
exports.UserService = UserService;
exports.createUserService = (apiService: any) => {
return new UserService(apiService);
};
});
🎨 UI Component AMD Modules
Let’s create reusable UI components using AMD:
// 🌟 Button component module
// src/components/button.ts
define(['exports'], function(exports) {
// 📦 Button configuration types
interface ButtonConfig {
text: string;
type?: 'primary' | 'secondary' | 'danger' | 'success';
size?: 'small' | 'medium' | 'large';
disabled?: boolean;
loading?: boolean;
icon?: string;
onClick?: (event: Event) => void;
}
interface ButtonTheme {
primary: string;
secondary: string;
danger: string;
success: string;
}
// 🎯 Button component class
class Button {
private element: HTMLButtonElement;
private config: ButtonConfig;
private theme: ButtonTheme;
constructor(config: ButtonConfig) {
this.config = config;
this.theme = {
primary: 'bg-blue-500 hover:bg-blue-600 text-white',
secondary: 'bg-gray-500 hover:bg-gray-600 text-white',
danger: 'bg-red-500 hover:bg-red-600 text-white',
success: 'bg-green-500 hover:bg-green-600 text-white',
};
this.element = this.createElement();
this.attachEventListeners();
console.log(`🔘 Button created: "${config.text}"`);
}
// 🏗️ Create button element
private createElement(): HTMLButtonElement {
const button = document.createElement('button');
// 🎨 Base styles
button.className = this.getButtonClasses();
button.textContent = this.config.text;
button.disabled = this.config.disabled || this.config.loading || false;
// 🎯 Add icon if specified
if (this.config.icon) {
const icon = document.createElement('span');
icon.className = `icon ${this.config.icon}`;
button.insertBefore(icon, button.firstChild);
}
// ⏳ Loading state
if (this.config.loading) {
this.setLoadingState(button);
}
return button;
}
// 🎨 Generate button CSS classes
private getButtonClasses(): string {
const baseClasses = 'px-4 py-2 rounded font-medium transition-colors focus:outline-none focus:ring-2';
const typeClass = this.theme[this.config.type || 'primary'];
const sizeClasses = {
small: 'text-sm px-3 py-1',
medium: 'text-base px-4 py-2',
large: 'text-lg px-6 py-3',
};
const sizeClass = sizeClasses[this.config.size || 'medium'];
const stateClasses = this.config.disabled
? 'opacity-50 cursor-not-allowed'
: 'cursor-pointer';
return `${baseClasses} ${typeClass} ${sizeClass} ${stateClasses}`;
}
// ⏳ Set loading state
private setLoadingState(button: HTMLButtonElement): void {
const spinner = document.createElement('span');
spinner.className = 'animate-spin inline-block w-4 h-4 border-2 border-white border-t-transparent rounded-full mr-2';
button.insertBefore(spinner, button.firstChild);
button.textContent = 'Loading...';
}
// 🎧 Attach event listeners
private attachEventListeners(): void {
if (this.config.onClick) {
this.element.addEventListener('click', (event) => {
if (!this.config.disabled && !this.config.loading) {
console.log(`🔘 Button clicked: "${this.config.text}"`);
this.config.onClick!(event);
}
});
}
// 🎯 Add hover effects
this.element.addEventListener('mouseenter', () => {
if (!this.config.disabled && !this.config.loading) {
console.log(`🔘 Button hovered: "${this.config.text}"`);
}
});
}
// 🔄 Update button configuration
updateConfig(newConfig: Partial<ButtonConfig>): void {
this.config = { ...this.config, ...newConfig };
// 🔄 Recreate element with new config
const parent = this.element.parentNode;
const newElement = this.createElement();
if (parent) {
parent.replaceChild(newElement, this.element);
}
this.element = newElement;
this.attachEventListeners();
console.log(`🔄 Button updated: "${this.config.text}"`);
}
// 🎯 Public methods
setText(text: string): void {
this.updateConfig({ text });
}
setDisabled(disabled: boolean): void {
this.updateConfig({ disabled });
}
setLoading(loading: boolean): void {
this.updateConfig({ loading });
}
getElement(): HTMLButtonElement {
return this.element;
}
destroy(): void {
if (this.element.parentNode) {
this.element.parentNode.removeChild(this.element);
}
console.log(`🗑️ Button destroyed: "${this.config.text}"`);
}
}
// 🎯 Button factory function
const createButton = (config: ButtonConfig): Button => {
return new Button(config);
};
// 🎮 Button group component
class ButtonGroup {
private buttons: Button[] = [];
private container: HTMLDivElement;
constructor(buttons: ButtonConfig[]) {
this.container = document.createElement('div');
this.container.className = 'flex space-x-2';
buttons.forEach(config => {
const button = new Button(config);
this.buttons.push(button);
this.container.appendChild(button.getElement());
});
console.log(`🔘 Button group created with ${buttons.length} buttons`);
}
addButton(config: ButtonConfig): void {
const button = new Button(config);
this.buttons.push(button);
this.container.appendChild(button.getElement());
}
removeButton(index: number): void {
if (index >= 0 && index < this.buttons.length) {
const button = this.buttons[index];
button.destroy();
this.buttons.splice(index, 1);
}
}
getContainer(): HTMLDivElement {
return this.container;
}
getButtons(): Button[] {
return [...this.buttons];
}
}
// 🎯 Export button components
exports.Button = Button;
exports.ButtonGroup = ButtonGroup;
exports.createButton = createButton;
exports.createButtonGroup = (configs: ButtonConfig[]) => new ButtonGroup(configs);
});
// 🎨 Modal component module
// src/components/modal.ts
define(['./button'], function(buttonModule) {
// 📦 Modal configuration types
interface ModalConfig {
title: string;
content: string | HTMLElement;
size?: 'small' | 'medium' | 'large' | 'fullscreen';
closable?: boolean;
backdrop?: boolean;
buttons?: Array<{
text: string;
type?: 'primary' | 'secondary' | 'danger';
action: () => void | Promise<void>;
}>;
onOpen?: () => void;
onClose?: () => void;
}
// 🎯 Modal component class
class Modal {
private config: ModalConfig;
private overlay: HTMLDivElement;
private modal: HTMLDivElement;
private isOpen: boolean = false;
constructor(config: ModalConfig) {
this.config = {
size: 'medium',
closable: true,
backdrop: true,
...config,
};
this.overlay = this.createOverlay();
this.modal = this.createModal();
this.attachEventListeners();
console.log(`🪟 Modal created: "${config.title}"`);
}
// 🏗️ Create modal overlay
private createOverlay(): HTMLDivElement {
const overlay = document.createElement('div');
overlay.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden';
if (this.config.backdrop) {
overlay.addEventListener('click', (e) => {
if (e.target === overlay && this.config.closable) {
this.close();
}
});
}
return overlay;
}
// 🪟 Create modal content
private createModal(): HTMLDivElement {
const modal = document.createElement('div');
modal.className = this.getModalClasses();
// 🎯 Header
const header = this.createHeader();
modal.appendChild(header);
// 📄 Content
const content = this.createContent();
modal.appendChild(content);
// 🔘 Footer with buttons
if (this.config.buttons && this.config.buttons.length > 0) {
const footer = this.createFooter();
modal.appendChild(footer);
}
this.overlay.appendChild(modal);
return modal;
}
// 🎨 Get modal CSS classes
private getModalClasses(): string {
const baseClasses = 'bg-white rounded-lg shadow-xl';
const sizeClasses = {
small: 'w-96 max-w-sm',
medium: 'w-[32rem] max-w-md',
large: 'w-[48rem] max-w-2xl',
fullscreen: 'w-full h-full max-w-none',
};
return `${baseClasses} ${sizeClasses[this.config.size!]}`;
}
// 📋 Create header
private createHeader(): HTMLElement {
const header = document.createElement('div');
header.className = 'flex items-center justify-between p-6 border-b';
const title = document.createElement('h2');
title.className = 'text-xl font-semibold text-gray-900';
title.textContent = this.config.title;
header.appendChild(title);
if (this.config.closable) {
const closeButton = buttonModule.createButton({
text: '×',
type: 'secondary',
size: 'small',
onClick: () => this.close(),
});
closeButton.getElement().className += ' ml-4 !p-1 w-8 h-8 flex items-center justify-center';
header.appendChild(closeButton.getElement());
}
return header;
}
// 📄 Create content area
private createContent(): HTMLElement {
const content = document.createElement('div');
content.className = 'p-6';
if (typeof this.config.content === 'string') {
content.innerHTML = this.config.content;
} else {
content.appendChild(this.config.content);
}
return content;
}
// 🔘 Create footer with buttons
private createFooter(): HTMLElement {
const footer = document.createElement('div');
footer.className = 'flex justify-end space-x-3 p-6 border-t';
this.config.buttons!.forEach(buttonConfig => {
const button = buttonModule.createButton({
text: buttonConfig.text,
type: buttonConfig.type || 'primary',
onClick: async () => {
try {
await buttonConfig.action();
} catch (error) {
console.error('❌ Modal button action failed:', error);
}
},
});
footer.appendChild(button.getElement());
});
return footer;
}
// 🎧 Attach event listeners
private attachEventListeners(): void {
// 🎹 ESC key to close
if (this.config.closable) {
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && this.isOpen) {
this.close();
}
});
}
}
// 🎯 Public methods
open(): void {
if (this.isOpen) return;
document.body.appendChild(this.overlay);
this.overlay.classList.remove('hidden');
this.isOpen = true;
// 🔒 Prevent body scroll
document.body.style.overflow = 'hidden';
console.log(`🪟 Modal opened: "${this.config.title}"`);
this.config.onOpen?.();
}
close(): void {
if (!this.isOpen) return;
this.overlay.classList.add('hidden');
document.body.removeChild(this.overlay);
this.isOpen = false;
// 🔓 Restore body scroll
document.body.style.overflow = '';
console.log(`🪟 Modal closed: "${this.config.title}"`);
this.config.onClose?.();
}
updateContent(content: string | HTMLElement): void {
const contentElement = this.modal.querySelector('.p-6:not(.border-t)');
if (contentElement) {
contentElement.innerHTML = '';
if (typeof content === 'string') {
contentElement.innerHTML = content;
} else {
contentElement.appendChild(content);
}
}
}
isModalOpen(): boolean {
return this.isOpen;
}
}
// 🎯 Export modal component
exports.Modal = Modal;
exports.createModal = (config: ModalConfig) => new Modal(config);
});
🔧 RequireJS Integration
📦 Setting Up RequireJS
Let’s configure RequireJS for optimal AMD module loading:
// 🌟 RequireJS configuration
// src/config/requirejs-config.ts
define([], function() {
// 🎯 RequireJS configuration
const config = {
// 📍 Base URL for module loading
baseUrl: '/dist',
// 🗺️ Path mappings for cleaner imports
paths: {
// 📦 External libraries
'jquery': 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min',
'lodash': 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min',
'moment': 'https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min',
// 🎯 Internal modules
'components': 'components',
'services': 'services',
'utils': 'utils',
'types': 'types',
// 🔧 Specific component aliases
'button': 'components/button',
'modal': 'components/modal',
'form': 'components/form',
'api-service': 'services/api-service',
'user-service': 'services/user-service',
'auth-service': 'services/auth-service',
},
// 🔧 Shim configuration for non-AMD libraries
shim: {
'jquery': {
exports: '$'
},
'lodash': {
exports: '_'
},
'moment': {
exports: 'moment'
}
},
// ⏰ Timeout for module loading (in seconds)
waitSeconds: 30,
// 🔍 Enable detailed error reporting
enforceDefine: true,
// 📊 Bundle configuration for optimization
bundles: {
'components-bundle': ['components/button', 'components/modal', 'components/form'],
'services-bundle': ['services/api-service', 'services/user-service', 'services/auth-service'],
'utils-bundle': ['utils/dom-utils', 'utils/validation', 'utils/formatters']
},
// 🎯 Map specific module versions
map: {
'*': {
'old-jquery': 'jquery'
}
},
// 📦 Package configuration
packages: [
{
name: 'components',
location: 'components',
main: 'index'
},
{
name: 'services',
location: 'services',
main: 'index'
}
],
// 🔧 URL arguments for cache busting
urlArgs: `bust=${Date.now()}`,
// 📡 Configure for cross-domain loading
xhtml: true
};
return config;
});
// 🚀 Initialize RequireJS with configuration
// src/main.ts
require(['config/requirejs-config'], function(config) {
// 🔧 Apply RequireJS configuration
require.config(config);
console.log('🚀 RequireJS configured successfully!');
// 🎯 Load main application modules
require([
'services/api-service',
'services/user-service',
'components/button',
'components/modal',
'utils/dom-utils'
], function(
apiServiceModule,
userServiceModule,
buttonModule,
modalModule,
domUtils
) {
console.log('📦 All core modules loaded!');
// 🏗️ Initialize the application
const app = new Application(
apiServiceModule,
userServiceModule,
buttonModule,
modalModule,
domUtils
);
app.initialize();
});
// 🎯 Application class
class Application {
private apiService: any;
private userService: any;
private components: any;
constructor(
apiServiceModule: any,
userServiceModule: any,
buttonModule: any,
modalModule: any,
domUtils: any
) {
// 🔧 Initialize services
this.apiService = apiServiceModule.createApiService('https://api.example.com');
this.userService = userServiceModule.createUserService(this.apiService);
// 📦 Store component modules
this.components = {
button: buttonModule,
modal: modalModule,
};
console.log('🎉 Application services initialized!');
}
// 🚀 Initialize the application
async initialize(): Promise<void> {
console.log('🚀 Initializing application...');
try {
// 🎨 Setup UI
this.setupUI();
// 🔐 Check authentication
await this.checkAuthentication();
// 📊 Load initial data
await this.loadInitialData();
console.log('✅ Application initialized successfully!');
} catch (error) {
console.error('❌ Application initialization failed:', error);
this.showErrorModal('Failed to initialize application');
}
}
// 🎨 Setup user interface
private setupUI(): void {
console.log('🎨 Setting up UI components...');
// 🔘 Create login button
const loginButton = this.components.button.createButton({
text: 'Login',
type: 'primary',
onClick: () => this.showLoginModal(),
});
// 🔘 Create user dashboard button
const dashboardButton = this.components.button.createButton({
text: 'Dashboard',
type: 'secondary',
onClick: () => this.showDashboard(),
});
// 📦 Add buttons to page
const buttonContainer = document.getElementById('button-container');
if (buttonContainer) {
buttonContainer.appendChild(loginButton.getElement());
buttonContainer.appendChild(dashboardButton.getElement());
}
}
// 🔐 Check user authentication
private async checkAuthentication(): Promise<void> {
console.log('🔐 Checking authentication status...');
const token = localStorage.getItem('authToken');
if (token) {
// Validate token with server
try {
const response = await this.apiService.get('/auth/validate');
if (response.success) {
console.log('✅ User is authenticated');
return;
}
} catch (error) {
console.log('❌ Token validation failed');
}
}
console.log('🔓 User not authenticated');
localStorage.removeItem('authToken');
}
// 📊 Load initial application data
private async loadInitialData(): Promise<void> {
console.log('📊 Loading initial data...');
try {
const usersResponse = await this.userService.getUsers(1, 10);
console.log('👥 Users loaded:', usersResponse.data);
} catch (error) {
console.error('❌ Failed to load initial data:', error);
}
}
// 🔐 Show login modal
private showLoginModal(): void {
const loginForm = this.createLoginForm();
const modal = this.components.modal.createModal({
title: 'User Login',
content: loginForm,
size: 'medium',
buttons: [
{
text: 'Cancel',
type: 'secondary',
action: () => modal.close(),
},
{
text: 'Login',
type: 'primary',
action: () => this.handleLogin(modal, loginForm),
},
],
});
modal.open();
}
// 📝 Create login form
private createLoginForm(): HTMLElement {
const form = document.createElement('form');
form.className = 'space-y-4';
// 👤 Username field
const usernameGroup = document.createElement('div');
const usernameLabel = document.createElement('label');
usernameLabel.textContent = 'Username:';
usernameLabel.className = 'block text-sm font-medium text-gray-700';
const usernameInput = document.createElement('input');
usernameInput.type = 'text';
usernameInput.name = 'username';
usernameInput.className = 'mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500';
usernameGroup.appendChild(usernameLabel);
usernameGroup.appendChild(usernameInput);
// 🔒 Password field
const passwordGroup = document.createElement('div');
const passwordLabel = document.createElement('label');
passwordLabel.textContent = 'Password:';
passwordLabel.className = 'block text-sm font-medium text-gray-700';
const passwordInput = document.createElement('input');
passwordInput.type = 'password';
passwordInput.name = 'password';
passwordInput.className = 'mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500';
passwordGroup.appendChild(passwordLabel);
passwordGroup.appendChild(passwordInput);
form.appendChild(usernameGroup);
form.appendChild(passwordGroup);
return form;
}
// 🔐 Handle login submission
private async handleLogin(modal: any, form: HTMLElement): Promise<void> {
const formData = new FormData(form as HTMLFormElement);
const username = formData.get('username') as string;
const password = formData.get('password') as string;
if (!username || !password) {
alert('Please enter both username and password');
return;
}
try {
console.log('🔐 Attempting login...');
const response = await this.userService.login(username, password);
if (response.success) {
localStorage.setItem('authToken', response.data.token);
console.log('✅ Login successful!');
modal.close();
this.showSuccessMessage('Login successful!');
} else {
this.showErrorMessage('Login failed: ' + response.message);
}
} catch (error) {
console.error('❌ Login error:', error);
this.showErrorMessage('Login failed. Please try again.');
}
}
// 📊 Show user dashboard
private showDashboard(): void {
console.log('📊 Showing dashboard...');
// Implementation would load dashboard components
}
// 🎯 Utility methods
private showErrorModal(message: string): void {
const modal = this.components.modal.createModal({
title: 'Error',
content: `<p class="text-red-600">${message}</p>`,
size: 'small',
buttons: [
{
text: 'OK',
type: 'primary',
action: () => modal.close(),
},
],
});
modal.open();
}
private showSuccessMessage(message: string): void {
console.log(`✅ ${message}`);
// Could show toast notification
}
private showErrorMessage(message: string): void {
console.error(`❌ ${message}`);
// Could show toast notification
}
}
});
🎉 Conclusion
Congratulations! You’ve mastered the art of AMD modules with TypeScript! 🌐
🎯 What You’ve Learned
- 📦 AMD Fundamentals: Understanding asynchronous module definition patterns
- 🔧 TypeScript Configuration: Setting up TypeScript for AMD compilation
- 🏗️ Advanced Module Patterns: Building sophisticated, reusable AMD modules
- 🎨 Component Architecture: Creating modular UI components with AMD
- 🔧 RequireJS Integration: Optimizing module loading with RequireJS
- 🚀 Production Deployment: Building scalable AMD-based applications
🚀 Key Benefits
- ⚡ Asynchronous Loading: Non-blocking module loading for better performance
- 🎯 Dependency Management: Explicit dependency declaration and resolution
- 📦 Modular Architecture: Clean separation of concerns and reusability
- 🔧 Legacy Support: Works in older browsers without ES6 module support
- 🛠️ Flexible Configuration: Powerful configuration options with RequireJS
🔥 Best Practices Recap
- 📋 Clear Dependencies: Always declare module dependencies explicitly
- 🎯 Type Safety: Use TypeScript interfaces for all module exports
- 🔧 Configuration: Leverage RequireJS configuration for optimization
- 📦 Bundle Strategy: Group related modules for efficient loading
- 🎨 Component Design: Create reusable, self-contained components
You’re now equipped to build efficient, modular browser applications using AMD modules with TypeScript! 🌟
Happy coding, and may your modules load asynchronously and smoothly! ⚡✨