Web TypeScript Web端功能示例
Web TypeScript Web端功能示例,包括路由处理、中间件和静态文件服务
💻 路由处理 typescript
🟡 intermediate
⭐⭐⭐
解析URL路由、处理哈希路由和管理浏览器历史
⏱️ 25 min
🏷️ typescript, web, web features
Prerequisites:
Intermediate TypeScript, History API, URL API
// Web TypeScript Routing Examples
// Client-side routing with URL parsing, hash routing, and history management
// 1. Route Interface
interface Route {
path: string;
handler: (params: Record<string, string>, query: Record<string, string>) => void;
}
// 2. Router
class Router {
private routes: Route[] = [];
private notFoundHandler?: () => void;
private currentParams: Record<string, string> = {};
private currentQuery: Record<string, string> = {};
// Add route
addRoute(path: string, handler: Route['handler']): void {
this.routes.push({ path, handler });
}
// Set 404 handler
setNotFound(handler: () => void): void {
this.notFoundHandler = handler;
}
// Navigate to path
navigate(path: string, params: Record<string, string> = {}): void {
// Build URL with params
let url = path;
if (Object.keys(params).length > 0) {
url = this.replaceParams(path, params);
}
// Update browser URL
window.history.pushState({}, '', url);
// Handle route
this.handleRoute(url);
}
// Replace current route
replace(path: string, params: Record<string, string> = {}): void {
let url = path;
if (Object.keys(params).length > 0) {
url = this.replaceParams(path, params);
}
window.history.replaceState({}, '', url);
this.handleRoute(url);
}
// Go back
back(): void {
window.history.back();
}
// Go forward
forward(): void {
window.history.forward();
}
// Go to specific history entry
go(delta: number): void {
window.history.go(delta);
}
// Handle route change
private handleRoute(url: string): void {
const { path, query } = this.parseURL(url);
// Find matching route
const route = this.findRoute(path);
if (route) {
const params = this.extractParams(path, route.path);
this.currentParams = params;
this.currentQuery = query;
route.handler(params, query);
} else if (this.notFoundHandler) {
this.notFoundHandler();
}
}
// Find matching route
private findRoute(path: string): Route | null {
for (const route of this.routes) {
if (this.matchRoute(path, route.path)) {
return route;
}
}
return null;
}
// Check if path matches route pattern
private matchRoute(path: string, pattern: string): boolean {
// Convert pattern to regex
const regexPattern = pattern
.replace(/:\w+/g, '([^/]+)')
.replace(/\*/g, '.*');
const regex = new RegExp(`^${regexPattern}$`);
return regex.test(path);
}
// Extract params from path
private extractParams(path: string, pattern: string): Record<string, string> {
const params: Record<string, string> = {};
const patternParts = pattern.split('/');
const pathParts = path.split('/');
for (let i = 0; i < patternParts.length; i++) {
const part = patternParts[i];
if (part.startsWith(':')) {
const paramName = part.substring(1);
params[paramName] = pathParts[i] || '';
}
}
return params;
}
// Replace params in path
private replaceParams(path: string, params: Record<string, string>): string {
let result = path;
for (const [key, value] of Object.entries(params)) {
result = result.replace(`:${key}`, value);
}
return result;
}
// Parse URL into path and query
private parseURL(url: string): {
path: string;
query: Record<string, string>;
} {
const [path, queryString] = url.split('?');
const query: Record<string, string> = {};
if (queryString) {
const params = new URLSearchParams(queryString);
params.forEach((value, key) => {
query[key] = value;
});
}
return { path, query };
}
// Initialize router
initialize(): void {
// Handle popstate event (back/forward buttons)
window.addEventListener('popstate', () => {
this.handleRoute(window.location.pathname);
});
// Handle initial route
this.handleRoute(window.location.pathname);
}
}
// 3. Hash Router
class HashRouter {
private routes: Route[] = [];
private notFoundHandler?: () => void;
// Add route
addRoute(path: string, handler: Route['handler']): void {
// Remove leading slash for hash matching
const hashPath = path.startsWith('/') ? path.substring(1) : path;
this.routes.push({ path: hashPath, handler });
}
// Set 404 handler
setNotFound(handler: () => void): void {
this.notFoundHandler = handler;
}
// Navigate to hash
navigate(hash: string, params: Record<string, string> = {}): void {
let url = hash;
if (Object.keys(params).length > 0) {
url = this.replaceParams(hash, params);
}
window.location.hash = url;
}
// Get current hash
getHash(): string {
return window.location.hash.substring(1) || '/';
}
// Handle hash change
private handleHashChange(): void {
const hash = this.getHash();
const [path, queryString] = hash.split('?');
const query: Record<string, string> = {};
if (queryString) {
const params = new URLSearchParams(queryString);
params.forEach((value, key) => {
query[key] = value;
});
}
// Find matching route
const route = this.findRoute(path);
if (route) {
const params = this.extractParams(path, route.path);
route.handler(params, query);
} else if (this.notFoundHandler) {
this.notFoundHandler();
}
}
// Find matching route
private findRoute(path: string): Route | null {
for (const route of this.routes) {
if (this.matchRoute(path, route.path)) {
return route;
}
}
return null;
}
// Check if path matches route pattern
private matchRoute(path: string, pattern: string): boolean {
const regexPattern = pattern
.replace(/:\w+/g, '([^/]+)')
.replace(/\*/g, '.*');
const regex = new RegExp(`^${regexPattern}$`);
return regex.test(path);
}
// Extract params from path
private extractParams(path: string, pattern: string): Record<string, string> {
const params: Record<string, string> = {};
const patternParts = pattern.split('/');
const pathParts = path.split('/');
for (let i = 0; i < patternParts.length; i++) {
const part = patternParts[i];
if (part.startsWith(':')) {
const paramName = part.substring(1);
params[paramName] = pathParts[i] || '';
}
}
return params;
}
// Replace params in path
private replaceParams(path: string, params: Record<string, string>): string {
let result = path;
for (const [key, value] of Object.entries(params)) {
result = result.replace(`:${key}`, value);
}
return result;
}
// Initialize hash router
initialize(): void {
window.addEventListener('hashchange', () => {
this.handleHashChange();
});
// Handle initial hash
this.handleHashChange();
}
}
// 4. Query Params Manager
class QueryParamsManager {
// Get query param
get(param: string): string | null {
const params = new URLSearchParams(window.location.search);
return params.get(param);
}
// Get all params
getAll(): Record<string, string> {
const params = new URLSearchParams(window.location.search);
const result: Record<string, string> = {};
params.forEach((value, key) => {
result[key] = value;
});
return result;
}
// Set query param
set(param: string, value: string): void {
const params = new URLSearchParams(window.location.search);
params.set(param, value);
this.update(params.toString());
}
// Delete query param
delete(param: string): void {
const params = new URLSearchParams(window.location.search);
params.delete(param);
this.update(params.toString());
}
// Clear all params
clear(): void {
this.update('');
}
// Update URL
private update(queryString: string): void {
const url = new URL(window.location.href);
url.search = queryString;
window.history.replaceState({}, '', url.toString());
}
// Watch for changes
watch(callback: (params: Record<string, string>) => void): () => void {
const handler = () => {
callback(this.getAll());
};
window.addEventListener('popstate', handler);
// Return cleanup function
return () => {
window.removeEventListener('popstate', handler);
};
}
}
// 5. Route Guard
class RouteGuard {
private guards: Map<string, () => boolean | Promise<boolean>> = new Map();
// Add guard for route
addGuard(route: string, guard: () => boolean | Promise<boolean>): void {
this.guards.set(route, guard);
}
// Check if route is allowed
async canNavigate(route: string): Promise<boolean> {
const guard = this.guards.get(route);
if (guard) {
return await guard();
}
return true;
}
// Add authentication guard
addAuthGuard(route: string, isAuthenticated: () => boolean): void {
this.addGuard(route, isAuthenticated);
}
// Add permission guard
addPermissionGuard(route: string, hasPermission: () => boolean | Promise<boolean>): void {
this.addGuard(route, hasPermission);
}
}
// 6. Navigation Manager
class NavigationManager {
private router: Router;
private history: string[] = [];
private currentIndex: number = -1;
constructor(router: Router) {
this.router = router;
}
// Navigate with history tracking
navigate(path: string, params: Record<string, string> = {}): void {
// Remove forward history if we're not at the end
if (this.currentIndex < this.history.length - 1) {
this.history = this.history.slice(0, this.currentIndex + 1);
}
// Add to history
this.history.push(path);
this.currentIndex++;
// Navigate
this.router.navigate(path, params);
}
// Go back in custom history
back(): void {
if (this.currentIndex > 0) {
this.currentIndex--;
const path = this.history[this.currentIndex];
window.history.back();
}
}
// Go forward in custom history
forward(): void {
if (this.currentIndex < this.history.length - 1) {
this.currentIndex++;
const path = this.history[this.currentIndex];
window.history.forward();
}
}
// Can go back
canGoBack(): boolean {
return this.currentIndex > 0;
}
// Can go forward
canGoForward(): boolean {
return this.currentIndex < this.history.length - 1;
}
// Get current position
getCurrentPosition(): number {
return this.currentIndex;
}
// Get history length
getHistoryLength(): number {
return this.history.length;
}
}
// 7. Route Transition Manager
class RouteTransitionManager {
private transitions: Map<string, (from: string, to: string) => void> = new Map();
// Add transition
addTransition(from: string, to: string, handler: () => void): void {
const key = this.getKey(from, to);
this.transitions.set(key, handler);
}
// Execute transition
execute(from: string, to: string): void {
const key = this.getKey(from, to);
const handler = this.transitions.get(key);
if (handler) {
handler(from, to);
}
}
// Add global transition
addGlobalTransition(handler: (from: string, to: string) => void): void {
this.transitions.set('*', handler);
}
// Generate transition key
private getKey(from: string, to: string): string {
return `${from}>${to}`;
}
}
// Usage Examples
async function demonstrateRouting() {
console.log('=== Web TypeScript Routing Examples ===\n');
// 1. Basic router
console.log('--- 1. Basic Router ---');
const router = new Router();
router.addRoute('/', (params, query) => {
console.log('Home page');
});
router.addRoute('/about', (params, query) => {
console.log('About page');
});
router.addRoute('/users/:id', (params, query) => {
console.log(`User page: ${params.id}`);
});
router.addRoute('/posts/:postId/comments/:commentId', (params, query) => {
console.log(`Comment: ${params.commentId} on post ${params.postId}`);
});
// 2. Navigation
console.log('\n--- 2. Navigation ---');
router.navigate('/');
await new Promise(resolve => setTimeout(resolve, 100));
router.navigate('/about');
await new Promise(resolve => setTimeout(resolve, 100));
router.navigate('/users/123');
await new Promise(resolve => setTimeout(resolve, 100));
// 3. Query params
console.log('\n--- 3. Query Params ---');
router.navigate('/search?query=typescript&page=1');
await new Promise(resolve => setTimeout(resolve, 100));
// 4. Hash router
console.log('\n--- 4. Hash Router ---');
const hashRouter = new HashRouter();
hashRouter.addRoute('/', (params, query) => {
console.log('Hash home');
});
hashRouter.addRoute('profile/:userId', (params, query) => {
console.log(`Hash profile: ${params.userId}`);
});
hashRouter.navigate('profile/456');
await new Promise(resolve => setTimeout(resolve, 100));
// 5. Query params manager
console.log('\n--- 5. Query Params Manager ---');
const queryManager = new QueryParamsManager();
queryManager.set('tab', 'profile');
queryManager.set('section', 'details');
const allParams = queryManager.getAll();
console.log('All params:', allParams);
// 6. Route guard
console.log('\n--- 6. Route Guard ---');
const guard = new RouteGuard();
guard.addAuthGuard('/admin', () => {
const isAuthenticated = false; // Simulate
console.log(`Auth check: ${isAuthenticated}`);
return isAuthenticated;
});
const canAccess = await guard.canNavigate('/admin');
console.log(`Can access /admin: ${canAccess}`);
// 7. Navigation manager
console.log('\n--- 7. Navigation Manager ---');
const navManager = new NavigationManager(router);
navManager.navigate('/page1');
await new Promise(resolve => setTimeout(resolve, 100));
navManager.navigate('/page2');
await new Promise(resolve => setTimeout(resolve, 100));
console.log(`Can go back: ${navManager.canGoBack()}`);
console.log(`History length: ${navManager.getHistoryLength()}`);
console.log('\n=== All Routing Examples Completed ===');
}
// Export functions
export { Router, HashRouter, QueryParamsManager, RouteGuard, NavigationManager, RouteTransitionManager };
export { demonstrateRouting };
export type { Route };
💻 中间件 typescript
🟡 intermediate
⭐⭐⭐⭐
实现请求/响应中间件链以处理和修改数据
⏱️ 30 min
🏷️ typescript, web, web features
Prerequisites:
Intermediate TypeScript, Promise chains, Async/await
// Web TypeScript Middleware Examples
// Request/response processing middleware chain
// 1. Context Interface
interface Context {
request: any;
response: any;
state: Map<string, any>;
headers: Map<string, string>;
metadata: Record<string, any>;
}
// 2. Middleware Function Type
type Middleware = (context: Context, next: () => Promise<void>) => Promise<void>;
// 3. Middleware Pipeline
class MiddlewarePipeline {
private middlewares: Middleware[] = [];
// Add middleware
use(middleware: Middleware): this {
this.middlewares.push(middleware);
return this;
}
// Execute pipeline
async execute(context: Context): Promise<void> {
let index = 0;
const next = async (): Promise<void> => {
if (index < this.middlewares.length) {
const middleware = this.middlewares[index++];
await middleware(context, next);
}
};
await next();
}
// Clear all middlewares
clear(): void {
this.middlewares = [];
}
// Get middleware count
count(): number {
return this.middlewares.length;
}
}
// 4. Common Middlewares
class CommonMiddlewares {
// Logging middleware
static logging(): Middleware {
return async (context: Context, next: () => Promise<void>) => {
const start = performance.now();
console.log('[Request]', {
url: context.request?.url || 'N/A',
method: context.request?.method || 'N/A',
headers: Object.fromEntries(context.headers)
});
await next();
const duration = performance.now() - start;
console.log('[Response]', {
status: context.response?.status || 'N/A',
duration: `${duration.toFixed(2)}ms`
});
};
}
// Timing middleware
static timing(): Middleware {
return async (context: Context, next: () => Promise<void>) => {
const start = performance.now();
await next();
const duration = performance.now() - start;
context.metadata.duration = duration;
};
}
// Error handling middleware
static errorHandler(errorHandler: (error: Error) => void): Middleware {
return async (context: Context, next: () => Promise<void>) => {
try {
await next();
} catch (error) {
errorHandler(error as Error);
throw error;
}
};
}
// Header manipulation middleware
static headers(addHeaders: Record<string, string>): Middleware {
return async (context: Context, next: () => Promise<void>) => {
for (const [key, value] of Object.entries(addHeaders)) {
context.headers.set(key, value);
}
await next();
};
}
// Authentication middleware
static authentication(authCheck: (context: Context) => boolean | Promise<boolean>): Middleware {
return async (context: Context, next: () => Promise<void>) => {
const isAuthenticated = await authCheck(context);
if (!isAuthenticated) {
throw new Error('Unauthorized');
}
context.metadata.authenticated = true;
await next();
};
}
// Rate limiting middleware
static rateLimit(options: {
maxRequests: number;
windowMs: number;
}): Middleware {
const requests = new Map<string, number[]>();
return async (context: Context, next: () => Promise<void>) => {
const key = context.request?.ip || 'unknown';
const now = Date.now();
if (!requests.has(key)) {
requests.set(key, []);
}
const userRequests = requests.get(key)!;
// Remove old requests outside the window
const windowStart = now - options.windowMs;
const validRequests = userRequests.filter(time => time > windowStart);
// Check if limit exceeded
if (validRequests.length >= options.maxRequests) {
throw new Error('Rate limit exceeded');
}
// Add current request
validRequests.push(now);
requests.set(key, validRequests);
context.metadata.rateLimit = {
remaining: options.maxRequests - validRequests.length,
reset: now + options.windowMs
};
await next();
};
}
// CORS middleware
static cors(options: {
origin?: string | string[];
methods?: string[];
headers?: string[];
credentials?: boolean;
} = {}): Middleware {
return async (context: Context, next: () => Promise<void>) => {
const origin = context.request?.headers?.get('Origin') || '*';
// Set CORS headers
if (options.origin === '*' || !options.origin) {
context.headers.set('Access-Control-Allow-Origin', '*');
} else {
const allowedOrigins = Array.isArray(options.origin) ? options.origin : [options.origin];
if (allowedOrigins.includes(origin)) {
context.headers.set('Access-Control-Allow-Origin', origin);
}
}
if (options.methods) {
context.headers.set('Access-Control-Allow-Methods', options.methods.join(', '));
}
if (options.headers) {
context.headers.set('Access-Control-Allow-Headers', options.headers.join(', '));
}
if (options.credentials) {
context.headers.set('Access-Control-Allow-Credentials', 'true');
}
await next();
};
}
// Compression middleware (simulated)
static compression(): Middleware {
return async (context: Context, next: () => Promise<void>) => {
await next();
// Check if response should be compressed
const acceptEncoding = context.request?.headers?.get('Accept-Encoding') || '';
if (acceptEncoding.includes('gzip') || acceptEncoding.includes('br')) {
context.headers.set('Content-Encoding', 'gzip');
context.metadata.compressed = true;
}
};
}
// Body parser middleware
static bodyParser(): Middleware {
return async (context: Context, next: () => Promise<void>) => {
const contentType = context.request?.headers?.get('Content-Type') || '';
if (contentType.includes('application/json')) {
try {
const body = context.request?.body;
if (typeof body === 'string') {
context.request.parsedBody = JSON.parse(body);
}
} catch (error) {
throw new Error('Invalid JSON body');
}
}
await next();
};
}
// Validation middleware
static validation(validator: (context: Context) => boolean | Promise<boolean>): Middleware {
return async (context: Context, next: () => Promise<void>) => {
const isValid = await validator(context);
if (!isValid) {
throw new Error('Validation failed');
}
await next();
};
}
// Cache middleware
static cache(cacheKey: (context: Context) => string, ttl: number = 60000): Middleware {
const cache = new Map<string, { data: any; expiry: number }>();
return async (context: Context, next: () => Promise<void>) => {
const key = cacheKey(context);
const now = Date.now();
// Check cache
const cached = cache.get(key);
if (cached && cached.expiry > now) {
context.response = cached.data;
context.metadata.cached = true;
return;
}
await next();
// Store in cache
if (context.response) {
cache.set(key, {
data: context.response,
expiry: now + ttl
});
}
};
}
}
// 5. Middleware Composer
class MiddlewareComposer {
private pipeline: MiddlewarePipeline;
constructor() {
this.pipeline = new MiddlewarePipeline();
}
// Add middleware
use(middleware: Middleware): this {
this.pipeline.use(middleware);
return this;
}
// Add multiple middlewares
useAll(middlewares: Middleware[]): this {
middlewares.forEach(mw => this.pipeline.use(mw));
return this;
}
// Execute with context
async execute(context: Context): Promise<void> {
return this.pipeline.execute(context);
}
// Create from array
static from(middlewares: Middleware[]): MiddlewareComposer {
const composer = new MiddlewareComposer();
return composer.useAll(middlewares);
}
}
// 6. Request/Response Mock
class MockRequest {
constructor(
public url: string,
public method: string = 'GET',
public headers: Map<string, string> = new Map(),
public body?: any,
public ip: string = '127.0.0.1'
) {}
}
class MockResponse {
public status: number = 200;
public headers: Map<string, string> = new Map();
public body?: any;
setStatus(code: number): this {
this.status = code;
return this;
}
setHeader(key: string, value: string): this {
this.headers.set(key, value);
return this;
}
send(data: any): this {
this.body = data;
return this;
}
}
// 7. Application Builder
class ApplicationBuilder {
private composer: MiddlewareComposer;
private contextFactory: () => Context;
constructor() {
this.composer = new MiddlewareComposer();
this.contextFactory = () => ({
request: null,
response: null,
state: new Map(),
headers: new Map(),
metadata: {}
});
}
// Set context factory
setContextFactory(factory: () => Context): this {
this.contextFactory = factory;
return this;
}
// Add middleware
use(middleware: Middleware): this {
this.composer.use(middleware);
return this;
}
// Handle request
async handle(request: MockRequest): Promise<MockResponse> {
const context = this.contextFactory();
context.request = request;
context.response = new MockResponse();
await this.composer.execute(context);
return context.response as MockResponse;
}
}
// Usage Examples
async function demonstrateMiddleware() {
console.log('=== Web TypeScript Middleware Examples ===\n');
// 1. Basic middleware
console.log('--- 1. Basic Middleware ---');
const pipeline = new MiddlewarePipeline();
pipeline.use(async (context, next) => {
console.log('Middleware 1: Before');
await next();
console.log('Middleware 1: After');
});
pipeline.use(async (context, next) => {
console.log('Middleware 2: Before');
await next();
console.log('Middleware 2: After');
});
const context1: Context = {
request: { url: '/test' },
response: null,
state: new Map(),
headers: new Map(),
metadata: {}
};
await pipeline.execute(context1);
// 2. Common middlewares
console.log('\n--- 2. Common Middlewares ---');
const app = new ApplicationBuilder();
app.use(CommonMiddlewares.logging());
app.use(CommonMiddlewares.timing());
app.use(CommonMiddlewares.headers({
'X-Powered-By': 'TypeScript-Middleware',
'X-Response-Time': '0ms'
}));
const request = new MockRequest('/api/users', 'GET');
request.headers.set('Accept', 'application/json');
const response = await app.handle(request);
console.log('Response status:', response.status);
// 3. Error handling
console.log('\n--- 3. Error Handling ---');
const errorApp = new ApplicationBuilder();
errorApp.use(CommonMiddlewares.errorHandler((error) => {
console.error('Error caught:', error.message);
}));
errorApp.use(async (context, next) => {
if (context.request?.url === '/error') {
throw new Error('Intentional error');
}
await next();
});
await errorApp.handle(new MockRequest('/error'));
// 4. Authentication
console.log('\n--- 4. Authentication ---');
const authApp = new ApplicationBuilder();
authApp.use(CommonMiddlewares.authentication(async (context) => {
const token = context.request?.headers?.get('Authorization');
return token === 'Bearer valid-token';
}));
authApp.use(async (context, next) => {
console.log('Authenticated successfully');
context.response = new MockResponse().setStatus(200).send({ message: 'Success' });
await next();
});
const authRequest = new MockRequest('/protected', 'GET');
authRequest.headers.set('Authorization', 'Bearer valid-token');
const authResponse = await authApp.handle(authRequest);
console.log('Auth response status:', authResponse.status);
// 5. Rate limiting
console.log('\n--- 5. Rate Limiting ---');
const rateLimitApp = new ApplicationBuilder();
rateLimitApp.use(CommonMiddlewares.rateLimit({
maxRequests: 3,
windowMs: 10000
}));
rateLimitApp.use(async (context, next) => {
context.response = new MockResponse().setStatus(200).send({ message: 'OK' });
await next();
});
// Send multiple requests
const rlRequest = new MockRequest('/api/test');
for (let i = 0; i < 4; i++) {
try {
const rlResponse = await rateLimitApp.handle(rlRequest);
console.log(`Request ${i + 1}: ${rlResponse.status}`);
} catch (error) {
console.log(`Request ${i + 1}: Rate limited`);
}
}
// 6. Compose
console.log('\n--- 6. Middleware Composer ---');
const composed = MiddlewareComposer.from([
CommonMiddlewares.logging(),
CommonMiddlewares.timing(),
async (context, next) => {
console.log('Final handler');
await next();
}
]);
const context2: Context = {
request: { url: '/composed' },
response: null,
state: new Map(),
headers: new Map(),
metadata: {}
};
await composed.execute(context2);
console.log('\n=== All Middleware Examples Completed ===');
}
// Export functions
export { MiddlewarePipeline, CommonMiddlewares, MiddlewareComposer, ApplicationBuilder, MockRequest, MockResponse };
export { demonstrateMiddleware };
export type { Context, Middleware };
💻 静态文件 typescript
🟡 intermediate
⭐⭐⭐
提供静态文件服务,包括缓存、压缩和MIME类型检测
⏱️ 25 min
🏷️ typescript, web, web features
Prerequisites:
Intermediate TypeScript, Fetch API, Blob
// Web TypeScript Static File Serving Examples
// Serving static files with various strategies
// 1. MIME Type Detector
class MIMETypeDetector {
private static mimeTypes: Record<string, string> = {
'.html': 'text/html',
'.htm': 'text/html',
'.css': 'text/css',
'.js': 'text/javascript',
'.json': 'application/json',
'.xml': 'application/xml',
'.txt': 'text/plain',
'.pdf': 'application/pdf',
'.zip': 'application/zip',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
'.ttf': 'font/ttf',
'.eot': 'application/vnd.ms-fontobject',
'.mp3': 'audio/mpeg',
'.mp4': 'video/mp4',
'.webm': 'video/webm',
'.ogg': 'video/ogg'
};
// Get MIME type by extension
static getByExtension(filename: string): string {
const ext = filename.substring(filename.lastIndexOf('.')).toLowerCase();
if (ext && this.mimeTypes[ext]) {
return this.mimeTypes[ext];
}
return 'application/octet-stream';
}
// Get MIME type by file signature (magic bytes)
static async getBySignature(file: Blob): Promise<string> {
const header = await this.readFileHeader(file);
// Check common signatures
if (this.startsWith(header, [0x89, 0x50, 0x4E, 0x47])) return 'image/png';
if (this.startsWith(header, [0xFF, 0xD8, 0xFF])) return 'image/jpeg';
if (this.startsWith(header, [0x47, 0x49, 0x46, 0x38])) return 'image/gif';
if (this.startsWith(header, [0x50, 0x4B, 0x03, 0x04])) return 'application/zip';
if (this.startsWith(header, [0x25, 0x50, 0x44, 0x46])) return 'application/pdf';
if (this.startsWith(header, [0x47, 0x49, 0x46, 0x38, 0x39, 0x61])) return 'image/gif';
return 'application/octet-stream';
}
// Read file header
private static async readFileHeader(file: Blob, bytes: number = 8): Promise<Uint8Array> {
const slice = file.slice(0, bytes);
const arrayBuffer = await slice.arrayBuffer();
return new Uint8Array(arrayBuffer);
}
// Check if header starts with bytes
private static startsWith(header: Uint8Array, bytes: number[]): boolean {
if (header.length < bytes.length) return false;
for (let i = 0; i < bytes.length; i++) {
if (header[i] !== bytes[i]) return false;
}
return true;
}
}
// 2. Static File Server
class StaticFileServer {
private baseUrl: string;
private cache: Map<string, { data: any; mime: string; timestamp: number }> = new Map();
private cacheTimeout: number = 60000; // 1 minute
constructor(baseUrl: string = '/static') {
this.baseUrl = baseUrl;
}
// Serve file
async serve(filePath: string): Promise<{
data: any;
mime: string;
status: number;
}> {
// Check cache
const cached = this.cache.get(filePath);
if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
return {
data: cached.data,
mime: cached.mime,
status: 200
};
}
// Fetch file
const url = `${this.baseUrl}${filePath}`;
try {
const response = await fetch(url);
if (!response.ok) {
return {
data: null,
mime: 'text/plain',
status: response.status
};
}
const mime = response.headers.get('Content-Type') ||
MIMETypeDetector.getByExtension(filePath);
let data: any;
const contentType = mime.split(';')[0];
if (contentType.includes('application/json')) {
data = await response.json();
} else if (contentType.includes('text/')) {
data = await response.text();
} else {
data = await response.blob();
}
// Cache response
this.cache.set(filePath, {
data,
mime,
timestamp: Date.now()
});
return { data, mime, status: 200 };
} catch (error) {
return {
data: null,
mime: 'text/plain',
status: 500
};
}
}
// Serve with fallback
async serveWithFallback(filePath: string, fallbackPath: string): Promise<{
data: any;
mime: string;
status: number;
}> {
const result = await this.serve(filePath);
if (result.status === 404) {
return this.serve(fallbackPath);
}
return result;
}
// Clear cache
clearCache(): void {
this.cache.clear();
}
// Clear specific cache entry
clearCacheEntry(filePath: string): void {
this.cache.delete(filePath);
}
// Get cache size
getCacheSize(): number {
return this.cache.size;
}
}
// 3. File Cache Manager
class FileCacheManager {
private cache: Map<string, {
content: any;
mimeType: string;
etag: string;
lastModified: string;
}> = new Map();
// Add to cache
set(filePath: string, content: any, mimeType: string, etag?: string, lastModified?: string): void {
this.cache.set(filePath, {
content,
mimeType,
etag: etag || this.generateETag(content),
lastModified: lastModified || new Date().toUTCString()
});
}
// Get from cache
get(filePath: string): {
content: any;
mimeType: string;
etag: string;
lastModified: string;
} | null {
return this.cache.get(filePath) || null;
}
// Check if file is cached
has(filePath: string): boolean {
return this.cache.has(filePath);
}
// Remove from cache
remove(filePath: string): void {
this.cache.delete(filePath);
}
// Clear all cache
clear(): void {
this.cache.clear();
}
// Get cache stats
getStats(): {
size: number;
entries: number;
keys: string[];
} {
return {
size: this.cache.size,
entries: this.cache.size,
keys: Array.from(this.cache.keys())
};
}
// Generate ETag
private generateETag(content: any): string {
const str = typeof content === 'string' ? content : JSON.stringify(content);
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return `"${hash.toString(16)}"`;
}
}
// 4. Asset Preloader
class AssetPreloader {
private loadedAssets: Set<string> = new Set();
private failedAssets: Set<string> = new Set();
// Preload single asset
async preload(url: string): Promise<{
success: boolean;
error?: Error;
}> {
if (this.loadedAssets.has(url)) {
return { success: true };
}
if (this.failedAssets.has(url)) {
return { success: true, error: new Error('Previously failed to load') };
}
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
this.loadedAssets.add(url);
return { success: true };
} catch (error) {
this.failedAssets.add(url);
return { success: false, error: error as Error };
}
}
// Preload multiple assets
async preloadMultiple(urls: string[]): Promise<{
successful: string[];
failed: Array<{ url: string; error: Error }>;
}> {
const results = await Promise.allSettled(
urls.map(url => this.preload(url))
);
const successful: string[] = [];
const failed: Array<{ url: string; error: Error }> = [];
results.forEach((result, index) => {
if (result.status === 'fulfilled' && result.value.success) {
successful.push(urls[index]);
} else {
const error = result.status === 'rejected'
? result.reason
: (result.value as any).error;
failed.push({ url: urls[index], error });
}
});
return { successful, failed };
}
// Preload image
preloadImage(url: string): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
this.loadedAssets.add(url);
resolve(img);
};
img.onerror = () => {
this.failedAssets.add(url);
reject(new Error('Failed to load image'));
};
img.src = url;
});
}
// Preload images
async preloadImages(urls: string[]): Promise<HTMLImageElement[]> {
const results = await Promise.allSettled(
urls.map(url => this.preloadImage(url))
);
return results
.filter((result): result is PromiseFulfilledResult<HTMLImageElement> =>
result.status === 'fulfilled'
)
.map(result => result.value);
}
// Get loaded assets
getLoadedAssets(): string[] {
return Array.from(this.loadedAssets);
}
// Get failed assets
getFailedAssets(): string[] {
return Array.from(this.failedAssets);
}
// Clear tracking
clear(): void {
this.loadedAssets.clear();
this.failedAssets.clear();
}
}
// 5. Compression Handler
class CompressionHandler {
// Check if compression is supported
static isSupported(): boolean {
return 'CompressionStream' in window;
}
// Compress data (if supported)
static async compress(data: string, format: 'gzip' | 'deflate' = 'gzip'): Promise<Uint8Array> {
if (!this.isSupported()) {
throw new Error('Compression not supported');
}
const stream = new CompressionStream(format);
const writer = stream.writable.getWriter();
const encoder = new TextEncoder();
await writer.write(encoder.encode(data));
await writer.close();
const compressed = new ReadableStream({
start(controller) {
// Would need to implement proper stream reading
controller.close();
}
});
// This is a simplified version
return encoder.encode(data);
}
// Get best compression format
static getBestCompression(acceptEncoding: string): 'gzip' | 'deflate' | 'none' {
if (acceptEncoding.includes('br')) return 'gzip';
if (acceptEncoding.includes('gzip')) return 'gzip';
if (acceptEncoding.includes('deflate')) return 'deflate';
return 'none';
}
}
// 6. CDN Manager
class CDNManager {
private cdnUrls: string[] = [];
private currentCdnIndex: number = 0;
constructor(cdnUrls: string[]) {
this.cdnUrls = cdnUrls;
}
// Get CDN URL for asset
getCdnUrl(assetPath: string): string {
const cdnUrl = this.cdnUrls[this.currentCdnIndex];
return `${cdnUrl}${assetPath}`;
}
// Rotate CDN
rotateCdn(): void {
this.currentCdnIndex = (this.currentCdnIndex + 1) % this.cdnUrls.length;
}
// Get all CDN URLs for asset
getAllCdnUrls(assetPath: string): string[] {
return this.cdnUrls.map(cdn => `${cdn}${assetPath}`);
}
// Try each CDN until one succeeds
async tryAllCdns(assetPath: string): Promise<{
success: boolean;
cdnUrl?: string;
data?: any;
}> {
const urls = this.getAllCdnUrls(assetPath);
for (const url of urls) {
try {
const response = await fetch(url);
if (response.ok) {
const data = await response.blob();
return { success: true, cdnUrl: url, data };
}
} catch (error) {
console.warn(`CDN failed: ${url}`, error);
}
}
return { success: false };
}
}
// 7. Asset Bundle Manager
class AssetBundleManager {
private bundles: Map<string, string[]> = new Map();
// Register bundle
registerBundle(name: string, assets: string[]): void {
this.bundles.set(name, assets);
}
// Get bundle assets
getBundle(name: string): string[] | null {
return this.bundles.get(name) || null;
}
// Preload bundle
async preloadBundle(name: string, preloader: AssetPreloader): Promise<{
successful: string[];
failed: Array<{ url: string; error: Error }>;
}> {
const assets = this.getBundle(name);
if (!assets) {
return { successful: [], failed: [] };
}
return preloader.preloadMultiple(assets);
}
// Get bundle URL (for script/link tags)
getBundleHtml(name: string, type: 'script' | 'style'): string {
const assets = this.getBundle(name);
if (!assets) {
return '';
}
return assets.map(url => {
if (type === 'script') {
return `<script src="${url}"></script>`;
} else {
return `<link rel="stylesheet" href="${url}">`;
}
}).join('\n');
}
}
// Usage Examples
async function demonstrateStaticFiles() {
console.log('=== Web TypeScript Static File Serving Examples ===\n');
// 1. MIME type detection
console.log('--- 1. MIME Type Detection ---');
console.log('HTML:', MIMETypeDetector.getByExtension('index.html'));
console.log('JavaScript:', MIMETypeDetector.getByExtension('app.js'));
console.log('PNG:', MIMETypeDetector.getByExtension('image.png'));
// 2. Static file server
console.log('\n--- 2. Static File Server ---');
const server = new StaticFileServer('/assets');
// Note: These would make actual HTTP requests in a real environment
console.log('Would serve:', server.baseUrl + '/styles/main.css');
console.log('Would serve:', server.baseUrl + '/js/app.js');
// 3. File cache manager
console.log('\n--- 3. File Cache Manager ---');
const cacheManager = new FileCacheManager();
cacheManager.set('/index.html', '<html>...</html>', 'text/html', '"abc123"');
cacheManager.set('/app.js', 'console.log("Hello");', 'text/javascript');
console.log('Cache stats:', cacheManager.getStats());
const cached = cacheManager.get('/index.html');
console.log('Cached file:', cached?.mimeType);
// 4. Asset preloader
console.log('\n--- 4. Asset Preloader ---');
const preloader = new AssetPreloader();
// Note: These would be real URLs in production
const preloadResults = await preloader.preloadMultiple([
'/assets/image1.png',
'/assets/image2.png',
'/assets/image3.png'
]);
console.log('Preloaded:', preloadResults.successful);
console.log('Failed:', preloadResults.failed);
// 5. CDN manager
console.log('\n--- 5. CDN Manager ---');
const cdnManager = new CDNManager([
'https://cdn1.example.com',
'https://cdn2.example.com',
'https://cdn3.example.com'
]);
console.log('CDN URL:', cdnManager.getCdnUrl('/assets/app.js'));
cdnManager.rotateCdn();
console.log('Rotated CDN URL:', cdnManager.getCdnUrl('/assets/app.js'));
// 6. Asset bundle manager
console.log('\n--- 6. Asset Bundle Manager ---');
const bundleManager = new AssetBundleManager();
bundleManager.registerBundle('vendor', [
'/assets/js/vendor/react.js',
'/assets/js/vendor/lodash.js'
]);
bundleManager.registerBundle('app', [
'/assets/js/app.js',
'/assets/js/utils.js'
]);
const bundle = bundleManager.getBundle('vendor');
console.log('Vendor bundle:', bundle);
console.log('\n=== All Static File Serving Examples Completed ===');
}
// Export functions
export { MIMETypeDetector, StaticFileServer, FileCacheManager, AssetPreloader, CompressionHandler, CDNManager, AssetBundleManager };
export { demonstrateStaticFiles };