A source map generator is a critical tool for modern web development that enables debugging of minified or transpiled code by mapping compiled output back to original source files, requiring careful handling of line/column mappings and file path resolution.
1. Problem Statement & Requirements
Clarifying questions
- What is the target audience for this source map generator? Is it meant to be a standalone tool, an internal service, or part of a larger build system?
- Should this support multiple programming languages (JavaScript, TypeScript, CoffeeScript, etc.) or focus on a specific language ecosystem?
- Are there specific performance requirements for processing large codebases or real-time generation?
- Will this need to support incremental builds and cache invalidation for continuous integration environments?
- Should the source map generator integrate with existing build tools like Webpack, Rollup, or Vite?
- Do we need to support source map validation and debugging capabilities in the UI?
- What level of accessibility is required for any UI components that may be built?
Functional requirements (user-facing)
- Generate source maps from compiled code (e.g., JavaScript from TypeScript)
- Support multiple input formats (ES6, CommonJS, etc.)
- Provide visual debugging capabilities for source map visualization
- Allow users to upload compiled code and corresponding source files
- Display line-by-line mapping between compiled and original code
- Support for inline source maps and external files
- Enable users to navigate from compiled code back to original source locations
- Provide export functionality for generated source maps
Non-functional requirements
- Performance: Source map generation should complete within 500ms for medium-sized codebases (100-500KB)
- Scalability: Support for files up to 10MB in size without memory issues
- Accessibility: WCAG 2.1 AA compliance for any UI components
- Reliability: 99.9% uptime for the service layer (if applicable)
- Security: Input sanitization to prevent code injection attacks
- Maintainability: Modular architecture supporting future language additions
Constraints and assumptions
- Target browsers: Chrome 90+, Firefox 85+, Safari 14+, Edge 90+
- No external dependencies beyond core build tools (Webpack, Rollup)
- Source maps will be generated in-memory and optionally persisted to disk
- Assumption that input files are well-formed and valid code
- Support for Node.js 16+ runtime environment
- No client-side rendering of source maps (focus on generation and validation)
Out of scope
- Full IDE integration or plugin development for editors like VSCode
- Source map compression algorithms (e.g., base64 encoding optimizations)
- Real-time collaborative editing of source maps
- Support for minified code with complex obfuscation patterns
- Integration with cloud-based build systems (e.g., GitHub Actions, CircleCI)
- Advanced debugging features beyond basic line mapping
- Source map validation against official specification (RFC 6919)
2. Component Architecture
graph TD
A[SourceMapGenerator] --> B[FileUpload]
A --> C[EditorPanel]
A --> D[SourceMapPreview]
A --> E[OutputControls]
B --> F[FileList]
B --> G[FileUploader]
C --> H[CodeEditor]
C --> I[LineMapping]
D --> J[SourceMapViewer]
D --> K[SourceMapDiff]
E --> L[DownloadButton]
E --> M[CopyToClipboard]
Component Tree Breakdown
Smart Components (Container/Stateful):
SourceMapGenerator- Main container managing overall state and orchestrating child componentsFileUpload- Handles file selection, validation, and upload flowEditorPanel- Manages code editing and line mapping stateSourceMapPreview- Renders source map visualization and diff views
Dumb Components (Presentational/Stateless):
FileList- Displays list of uploaded filesFileUploader- Handles file upload UI and progress trackingCodeEditor- Code editing interface (wrapper around Monaco or similar)LineMapping- Line-by-line mapping UISourceMapViewer- Visualization of source map dataSourceMapDiff- Diff view between original and generated mapsDownloadButton- Download functionality UICopyToClipboard- Copy to clipboard button
TypeScript Props Contracts
// SourceMapGenerator.tsx
interface SourceMapGeneratorProps {
initialFiles?: File[];
onSourceMapGenerated: (sourceMap: SourceMap) => void;
onError: (error: Error) => void;
}
// FileUpload.tsx
interface FileUploadProps {
onFilesSelected: (files: File[]) => void;
onFileUploaded: (file: UploadedFile) => void;
onUploadError: (error: Error) => void;
maxFileSize?: number;
acceptedTypes?: string[];
}
// EditorPanel.tsx
interface EditorPanelProps {
files: UploadedFile[];
onCodeChange: (fileId: string, code: string) => void;
onLineMappingChange: (fileId: string, mapping: LineMapping[]) => void;
selectedFileId?: string;
onFileSelect: (fileId: string) => void;
}
// SourceMapPreview.tsx
interface SourceMapPreviewProps {
sourceMap: SourceMap;
originalFiles: UploadedFile[];
generatedFiles: UploadedFile[];
viewMode: 'viewer' | 'diff';
onDiffChange: (diff: SourceMapDiff) => void;
}
// CodeEditor.tsx
interface CodeEditorProps {
value: string;
onChange: (value: string) => void;
language?: 'javascript' | 'typescript' | 'css' | 'html';
readOnly?: boolean;
height?: string;
}
UI State Machine
stateDiagram-v2
[*] --> Idle
Idle --> Uploading : upload_files
Uploading --> Processing : files_uploaded
Processing --> Editing : processing_complete
Editing --> Generating : generate_map
Generating --> Generated : map_generated
Generated --> Downloading : download_map
Downloading --> Idle : download_complete
state Uploading {
[*] --> Waiting
Waiting --> Validating : files_selected
Validating --> UploadInProgress : validation_passed
UploadInProgress --> Uploaded : upload_complete
Uploaded --> [*]
}
state Processing {
[*] --> Parsing
Parsing --> Validating : files_parsed
Validating --> [*]
}
state Editing {
[*] --> Ready
Ready --> [*]
}
state Generating {
[*] --> Creating
Creating --> [*]
}
state Generated {
[*] --> Ready
Ready --> [*]
}
state Downloading {
[*] --> Preparing
Preparing --> [*]
}
Component Responsibilities
SourceMapGenerator:
- Manages global state (files, source map, current view)
- Coordinates between file upload, editor, and preview components
- Handles the complete workflow lifecycle
FileUpload:
- Accepts file uploads and validates file types
- Manages file list state and upload progress
- Communicates uploaded files to parent component
EditorPanel:
- Displays code editors for each file
- Manages line mapping between original and generated files
- Handles file selection and code editing
SourceMapPreview:
- Renders source map visualization
- Displays diff between original and generated maps
- Handles view mode switching
OutputControls:
- Provides download and copy functionality
- Manages output format selection (JSON, etc.)
Reusability Decisions
- FileUploader - Highly reusable across file upload flows
- CodeEditor - Reusable with language parameter for various code types
- DownloadButton - Reusable generic download component
- FileList - Reusable file list with different selection modes
Component Lifecycle Considerations
- Mounting: Initialize file upload handlers, set up event listeners
- Updating: Handle file state changes, update editor content, refresh preview
- Unmounting: Clean up event listeners, cancel ongoing uploads, clear temporary state
- Error Handling: Graceful degradation when file upload fails or parsing errors occur
The component architecture follows a clear separation of concerns where:
- State management is centralized in the top-level
SourceMapGenerator - UI state transitions are well-defined through the state machine
- Components communicate via well-typed props and callbacks
- Reusable components reduce code duplication while maintaining flexibility
3. State Management Design
Local state vs. global state decisions
For a source map generator, we need to distinguish between:
- Local state: Component-specific UI state like active tab, file selection, editor content
- Global state: Cross-component state like the current source map configuration, generated maps, and user preferences
The core source map generation process is stateful and involves multiple steps that need to be coordinated across components.
State management approach
Zustand is selected as the state management solution because:
- Simplicity: Minimal boilerplate for complex state flows
- TypeScript support: Excellent type inference and safety
- No provider wrapping: Clean component architecture without unnecessary context providers
- Middleware support: Easy to add logging, persistence, or devtools integration
- Small bundle size: ~1KB gzipped
State shape and normalization strategy
interface SourceMapState {
// Core source map configuration
config: {
sourceRoot: string;
includeSources: boolean;
names: boolean;
sourcesContent: boolean;
};
// Source files and their content
files: {
[key: string]: {
name: string;
content: string;
language: 'javascript' | 'typescript' | 'css' | 'html';
sourceMap?: SourceMap;
};
};
// Generated source maps
generatedMaps: {
[key: string]: SourceMap;
};
// UI state
ui: {
activeTab: 'editor' | 'preview' | 'config';
isGenerating: boolean;
error: string | null;
progress: number;
};
// User preferences
preferences: {
autoGenerate: boolean;
theme: 'light' | 'dark';
fontSize: number;
};
}
Server state vs. client state separation
- Client state: Editor content, UI state, user preferences, local file cache
- Server state: Generated source maps (when persisted), configuration templates, analytics data
Cache invalidation strategy
- File change: Invalidate all source maps that depend on changed files
- Config change: Invalidate all generated maps when configuration changes
- Manual invalidation: Clear cache button in UI
- Time-based: Auto-invalidate after 30 minutes of inactivity
Optimistic updates design
When generating source maps:
- Show "generating..." state immediately
- Display optimistic preview with current file content
- Update with actual generated map when complete
- Handle errors gracefully with rollback to previous state
Critical-path hook implementation
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
interface SourceMapState {
files: Record<string, { name: string; content: string; language: string }>;
config: {
sourceRoot: string;
includeSources: boolean;
names: boolean;
sourcesContent: boolean;
};
generatedMaps: Record<string, any>;
ui: {
activeTab: 'editor' | 'preview' | 'config';
isGenerating: boolean;
error: string | null;
progress: number;
};
preferences: {
autoGenerate: boolean;
theme: 'light' | 'dark';
fontSize: number;
};
}
interface SourceMapActions {
addFile: (name: string, content: string, language: string) => void;
updateFileContent: (name: string, content: string) => void;
removeFile: (name: string) => void;
updateConfig: (config: Partial<SourceMapState['config']>) => void;
generateSourceMaps: () => Promise<void>;
setActiveTab: (tab: SourceMapState['ui']['activeTab']) => void;
setPreferences: (prefs: Partial<SourceMapState['preferences']>) => void;
setError: (error: string | null) => void;
}
export const useSourceMapStore = create<SourceMapState & SourceMapActions>(
immer((set, get) => ({
files: {},
config: {
sourceRoot: '',
includeSources: true,
names: true,
sourcesContent: true,
},
generatedMaps: {},
ui: {
activeTab: 'editor',
isGenerating: false,
error: null,
progress: 0,
},
preferences: {
autoGenerate: true,
theme: 'light',
fontSize: 14,
},
addFile: (name, content, language) => {
set((state) => {
state.files[name] = { name, content, language };
});
},
updateFileContent: (name, content) => {
set((state) => {
if (state.files[name]) {
state.files[name].content = content;
}
});
},
removeFile: (name) => {
set((state) => {
delete state.files[name];
delete state.generatedMaps[name];
});
},
updateConfig: (config) => {
set((state) => {
Object.assign(state.config, config);
});
},
generateSourceMaps: async () => {
const state = get();
set((state) => {
state.ui.isGenerating = true;
state.ui.error = null;
});
try {
// Simulate async source map generation
const files = Object.values(state.files);
const generatedMaps = await Promise.all(
files.map(async (file) => {
// In real implementation, this would call a source map generation service
await new Promise(resolve => setTimeout(resolve, 500));
return {
[file.name]: {
version: 3,
sources: [file.name],
names: [],
mappings: '',
}
};
})
);
set((state) => {
state.generatedMaps = Object.assign(
{},
...generatedMaps.map(map => map)
);
state.ui.isGenerating = false;
});
} catch (error) {
set((state) => {
state.ui.isGenerating = false;
state.ui.error = error instanceof Error ? error.message : 'Failed to generate source maps';
});
}
},
setActiveTab: (tab) => {
set((state) => {
state.ui.activeTab = tab;
});
},
setPreferences: (prefs) => {
set((state) => {
Object.assign(state.preferences, prefs);
});
},
setError: (error) => {
set((state) => {
state.ui.error = error;
});
},
}))
);
// Custom hook for source map operations
export const useSourceMapOperations = () => {
const { files, config, generatedMaps, ui } = useSourceMapStore();
const hasFiles = Object.keys(files).length > 0;
const isGenerating = ui.isGenerating;
const error = ui.error;
return {
files,
config,
generatedMaps,
hasFiles,
isGenerating,
error,
generateSourceMaps: useSourceMapStore.getState().generateSourceMaps,
addFile: useSourceMapStore.getState().addFile,
updateFileContent: useSourceMapStore.getState().updateFileContent,
removeFile: useSourceMapStore.getState().removeFile,
};
};
The critical-path hook returns:
files: Current source files with contentconfig: Source map generation configurationgeneratedMaps: Generated source maps by file namehasFiles: Boolean indicating if any files existisGenerating: Loading state for generation processerror: Error message if generation failed- Action methods:
generateSourceMaps,addFile,updateFileContent,removeFile
Key state transitions:
- File operations: Add/remove files → Update file list and generated maps
- Config changes: Update configuration → Invalidate dependent source maps
- Generation flow: Start generation → Show loading state → Complete with results or error
Side-effect handler:
generateSourceMapsmethod includes optimistic UI updates and proper error handling- Caches generated maps by file name for quick retrieval
- Maintains clean separation between file content and generated source map data
4. Routing & Navigation
Route Structure and Hierarchy
The source map generator will have a clean, intuitive route structure that supports both public and protected access patterns:
graph TD
A[Root] --> B[/]
A --> C[/editor]
A --> D[/history]
A --> E[/settings]
C --> F[/editor/:id]
C --> G[/editor/new]
/- Public landing page with feature overview and demo/editor- Main editor interface (protected)/editor/new- New source map creation flow/editor/:id- Edit existing source map by ID/history- Source map history and version control (protected)/settings- User preferences and configuration (protected)
Code Splitting Strategy
Route-based code splitting with dynamic imports for optimal bundle sizes:
// routes.ts
const routes = [
{
path: '/',
component: lazy(() => import('pages/LandingPage')),
exact: true
},
{
path: '/editor',
component: lazy(() => import('pages/EditorPage')),
exact: false,
protected: true
},
{
path: '/editor/new',
component: lazy(() => import('pages/EditorNewPage')),
exact: true,
protected: true
},
{
path: '/editor/:id',
component: lazy(() => import('pages/EditorEditPage')),
exact: true,
protected: true
},
{
path: '/history',
component: lazy(() => import('pages/HistoryPage')),
exact: true,
protected: true
},
{
path: '/settings',
component: lazy(() => import('pages/SettingsPage')),
exact: true,
protected: true
}
];
Justification: Route-based splitting allows us to load only the necessary code for each view, reducing initial bundle size. The editor pages contain heavy dependencies (source map parsing libraries) that shouldn't be loaded on the landing page.
Navigation Guards and Authentication Flows
Authentication guard implementation:
// guards/AuthGuard.tsx
const AuthGuard: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const { isAuthenticated, loading } = useAuth();
if (loading) return <LoadingSpinner />;
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
return children;
};
// ProtectedRoute.tsx
const ProtectedRoute: React.FC<{
component: React.ComponentType;
path: string
}> = ({ component: Component, path }) => (
<Route
path={path}
element={
<AuthGuard>
<Component />
</AuthGuard>
}
/>
);
Deep Linking Support and URL Parameter Design
URL parameters follow a consistent pattern:
id- Source map identifier (UUID format)mode- Editor mode (visual,code,debug)theme- UI theme (light,dark)view- View state (editor,preview,history)
// URL structure examples:
// /editor/new?theme=dark&mode=visual
// /editor/12345678-1234-1234-1234-123456789012?view=preview&mode=code
Browser History Management and Scroll Restoration
// App.tsx
const App = () => {
const location = useLocation();
useEffect(() => {
// Custom scroll restoration logic
if (location.state?.scrollToTop) {
window.scrollTo(0, 0);
}
}, [location]);
return (
<BrowserRouter>
<ScrollRestoration />
<Routes>
{/* route definitions */}
</Routes>
</BrowserRouter>
);
};
// Custom scroll restoration component
const ScrollRestoration = () => {
const location = useLocation();
useEffect(() => {
// Restore scroll position for same route but different params
const savedScrollPosition = sessionStorage.getItem(`scroll-position-${location.pathname}`);
if (savedScrollPosition) {
window.scrollTo(0, parseInt(savedScrollPosition));
}
}, [location.pathname]);
useEffect(() => {
const handleScroll = () => {
sessionStorage.setItem(`scroll-position-${location.pathname}`, window.scrollY.toString());
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [location.pathname]);
return null;
};
The scroll restoration strategy uses sessionStorage to persist scroll positions across route changes, ensuring a seamless user experience when navigating between different source map views.
5. API Integration Layer
API client design pattern
We'll implement a centralized API client using axios with request/response interceptors to handle authentication, error handling, and response normalization. The client will be built as a singleton with proper configuration for retries, timeouts, and base URL management.
// api/client.ts
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3000/api';
const createApiClient = (): AxiosInstance => {
const client = axios.create({
baseURL: API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
// Request interceptor for adding auth token
client.interceptors.request.use(
(config) => {
const token = localStorage.getItem('authToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// Response interceptor for handling errors
client.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
// Handle auth errors
localStorage.removeItem('authToken');
window.location.href = '/login';
}
return Promise.reject(error);
}
);
return client;
};
export const apiClient = createApiClient();
Request/response interceptors
Authentication token injection is handled in the request interceptor, while response normalization and error transformation occur in the response interceptor. The client handles automatic retry logic for transient network errors.
// api/interceptors.ts
import { AxiosRequestConfig, AxiosResponse } from 'axios';
export const withRetry = (config: AxiosRequestConfig, maxRetries = 3): AxiosRequestConfig => {
return {
...config,
retries: maxRetries,
};
};
export const normalizeResponse = (response: AxiosResponse): any => {
// Normalize response structure
return {
data: response.data,
status: response.status,
headers: response.headers,
};
};
Error handling and retry strategy
The API client implements a retry mechanism for network errors (5xx status codes) with exponential backoff. For client errors (4xx), retries are not attempted as they typically indicate invalid requests that won't succeed on retry.
// api/errorHandling.ts
import { AxiosError, AxiosRequestConfig } from 'axios';
export class ApiError extends Error {
constructor(
public status: number,
public message: string,
public originalError?: AxiosError
) {
super(message);
this.name = 'ApiError';
}
}
export const handleNetworkError = async (
error: AxiosError,
config: AxiosRequestConfig
): Promise<void> => {
if (error.response?.status >= 500 && config.retries && config.retries > 0) {
const delay = Math.pow(2, config.retries) * 1000; // Exponential backoff
await new Promise(resolve => setTimeout(resolve, delay));
config.retries -= 1;
throw new Error(`Retrying after ${delay}ms`);
}
throw new ApiError(
error.response?.status || 500,
error.message,
error
);
};
Loading, error, and empty states design
The API integration layer provides consistent state management for loading, error, and success conditions. Components will use a standardized approach to handle these states:
// api/types.ts
export interface ApiResponse<T> {
data: T | null;
loading: boolean;
error: ApiError | null;
refetch: () => void;
}
export interface SourceMapRequest {
sourceCode: string;
sourceMapUrl?: string;
options?: {
includeSourceContent?: boolean;
excludeSourcesContent?: boolean;
};
}
export interface SourceMapResponse {
id: string;
url: string;
generatedCodeUrl: string;
sourceMap: any;
createdAt: Date;
}
Data fetching strategy
For a source map generator, we'll use client-side rendering (CSR) since the feature is interactive and requires real-time processing. The main route will be /source-map-generator with a loading state during processing.
// pages/source-map-generator.tsx
import { useEffect, useState } from 'react';
import { useSourceMapGenerator } from '@/hooks/useSourceMapGenerator';
const SourceMapGeneratorPage = () => {
const { data, loading, error, generateSourceMap } = useSourceMapGenerator();
return (
<div>
{loading && <div>Generating source map...</div>}
{error && <div>Error: {error.message}</div>}
{data && (
<div>
<h2>Generated Source Map</h2>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)}
</div>
);
};
API response transformation layer
The API response is transformed from raw JSON to domain models that are more suitable for frontend consumption:
// api/transformers.ts
import { SourceMapResponse, SourceMapRequest } from './types';
export const transformSourceMapResponse = (rawResponse: any): SourceMapResponse => {
return {
id: rawResponse.id,
url: rawResponse.url,
generatedCodeUrl: rawResponse.generatedCodeUrl,
sourceMap: rawResponse.sourceMap,
createdAt: new Date(rawResponse.createdAt),
};
};
export const transformSourceMapRequest = (request: SourceMapRequest): any => {
return {
sourceCode: request.sourceCode,
sourceMapUrl: request.sourceMapUrl,
options: {
includeSourceContent: request.options?.includeSourceContent,
excludeSourcesContent: request.options?.excludeSourcesContent,
},
};
};
Sequence diagram for primary data flows
sequenceDiagram
participant U as User
participant C as Client (React)
participant A as API Server
participant S as Source Map Service
U->>C: Submit source code
C->>A: POST /api/source-maps
A->>S: Process source code (generate source map)
S-->>A: Return generated source map
A-->>C: Return response with source map data
C->>U: Display generated source map
6. Performance Design
Core Web Vitals Targets
- Largest Contentful Paint (LCP): <2.5s for 95% of users
- Interaction to Next Paint (INP): <200ms for 90% of interactions
- Cumulative Layout Shift (CLS): <0.1 for 95% of users
Bundle Size Budget per Route
- Source Map Generator Page: <300KB (including all dependencies and polyfills)
- Editor Component: <150KB (editor + syntax highlighting)
- Preview Panel: <100KB (visualization libraries)
Critical Rendering Path Optimization
- Critical CSS: Inline essential styles for editor and preview panels
- Lazy Loading: Non-critical components (settings, help panel) loaded on demand
- Resource Hints: Preload essential fonts and critical assets
Image Optimization Strategy
- Format Selection: WebP for modern browsers, JPEG fallback for older
- Lazy Loading: Images in preview only loaded when visible in viewport
- Responsive Images: Multiple sizes based on device pixel ratio
Font Loading Strategy
- Font Display:
swapfor all custom fonts to prevent FOIT - Preload Critical Fonts: Critical font subsets loaded via
<link rel="preload"> - Font Optimization: Subsetting for minimal bundle size
Memoization Strategy
// Memoize expensive operations in editor component
const memoizedParseSourceMap = useMemo(() => {
return parseSourceMap(sourceCode, sourceMap);
}, [sourceCode, sourceMap]);
const memoizedTransformedLines = useMemo(() => {
return transformLines(sourceCode, parsedMap);
}, [sourceCode, parsedMap]);
const memoizedHighlightedCode = useMemo(() => {
return highlightSyntax(sourceCode, language);
}, [sourceCode, language]);
Virtualization for Large Lists
For large source maps with thousands of lines:
// Using react-window for efficient list rendering
const VirtualizedLineList = ({ lines, onLineClick }: VirtualizedLineListProps) => {
const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => (
<div style={style} className="line-item">
{lines[index].content}
</div>
);
return (
<List
height={500}
itemCount={lines.length}
itemSize={24}
width="100%"
>
{Row}
</List>
);
};
Prefetching and Preloading Strategy
- Source Map Files: Preload when user navigates to editor view
- Syntax Highlighting Themes: Load theme files on demand with prefetch
- Preview Dependencies: Preload visualization libraries when editor is active
- API Responses: Cache recent API responses for faster re-renders
// Prefetching strategy in route loader
const prefetchSourceMapData = async (sourceMapUrl: string) => {
const response = await fetch(sourceMapUrl, {
method: 'GET',
headers: { 'Cache-Control': 'max-age=300' }
});
const data = await response.json();
// Store in local cache for immediate use
localStorage.setItem('prefetched-source-map', JSON.stringify(data));
return data;
};
Memory Management
- Source Map Cleanup: Clear parsed source map objects when component unmounts
- Event Listener Cleanup: Remove all event listeners to prevent memory leaks
- Worker Pooling: Reuse Web Workers for parsing operations instead of creating new ones
Rendering Optimization
- Debounced Updates: Delay UI updates during rapid source code changes
- Throttled Scroll: Limit scroll event handlers in preview panel
- Conditional Rendering: Only render components when they're visible in viewport
Performance Monitoring
// Performance tracking hook for key operations
const usePerformanceTracker = (operationName: string) => {
const [metrics, setMetrics] = useState<PerformanceMetrics>({});
useEffect(() => {
const start = performance.now();
return () => {
const end = performance.now();
const duration = end - start;
setMetrics(prev => ({
...prev,
[operationName]: {
duration,
timestamp: Date.now()
}
}));
};
}, [operationName]);
return metrics;
};
Bundle Analysis Targets
- Tree-shaking: Ensure unused code is removed from bundles
- Code Splitting: Route-based splitting for editor and preview components
- Minification: All production builds must be minified with dead code elimination
- Compression: Gzip or Brotli compression enabled for all assets
Client-Side Caching Strategy
- Local Storage: Cache parsed source maps for quick re-rendering
- Session Storage: Store recent editor configurations and settings
- Service Worker: Cache API responses for offline capability where possible
Responsive Performance Targets
- Mobile Devices: <500ms for source map parsing on devices with <2GB RAM
- Desktop Devices: <100ms for parsing and rendering operations
- High-Density Displays: Optimized rendering for 4K displays with proper scaling
7. Error Handling & Resilience
Error Boundary Placement Strategy
Error boundaries should be strategically placed to prevent cascading failures while providing meaningful feedback to users. The placement hierarchy follows:
- Application-level error boundary - Catches unhandled errors in the entire app
- Feature-specific boundaries - Wrap major feature components (e.g., editor, preview)
- Component-level boundaries - Handle specific UI component failures (e.g., chart rendering, file parsing)
graph TD
A[App Root] --> B[ErrorBoundary]
B --> C[SourceMapGenerator]
C --> D[EditorPanel]
C --> E[PreviewPanel]
D --> F[FileParser]
D --> G[CodeEditor]
E --> H[SourceMapRenderer]
Global Error Handler Design
A centralized error handler will capture and process all unhandled errors with proper logging and user feedback:
// src/core/errorHandler.ts
import { ErrorInfo } from 'react';
interface ErrorContext {
componentStack?: string;
timestamp: number;
userAgent: string;
url: string;
}
class GlobalErrorHandler {
private errorCount = 0;
private maxErrorsPerSession = 10;
handleError(error: Error, errorInfo: ErrorInfo): void {
if (this.errorCount >= this.maxErrorsPerSession) {
console.warn('Max error limit reached, skipping error reporting');
return;
}
this.errorCount++;
const context: ErrorContext = {
componentStack: errorInfo.componentStack,
timestamp: Date.now(),
userAgent: navigator.userAgent,
url: window.location.href
};
// Report to monitoring service (Sentry, etc.)
this.reportToMonitoring(error, context);
// Log to console for debugging
console.error('Unhandled error:', error, context);
}
private reportToMonitoring(error: Error, context: ErrorContext): void {
// Implementation would integrate with Sentry or similar service
// Example:
// Sentry.captureException(error, {
// contexts: { errorContext: context }
// });
}
resetErrorCount(): void {
this.errorCount = 0;
}
}
export const globalErrorHandler = new GlobalErrorHandler();
Offline / Network Failure Handling
The source map generator must gracefully handle offline scenarios and network interruptions:
// src/core/networkManager.ts
class NetworkManager {
private isOnline = navigator.onLine;
constructor() {
window.addEventListener('online', () => this.handleOnlineStatus(true));
window.addEventListener('offline', () => this.handleOnlineStatus(false));
}
handleOnlineStatus(isNowOnline: boolean): void {
this.isOnline = isNowOnline;
if (!isNowOnline) {
// Show offline indicator in UI
// Pause any pending network operations
// Suggest user to reconnect or work offline
}
}
async fetchWithRetry<T>(
url: string,
options: RequestInit = {},
retries = 3
): Promise<T> {
let lastError: Error;
for (let attempt = 0; attempt <= retries; attempt++) {
try {
if (!this.isOnline) {
throw new Error('Network is offline');
}
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
lastError = error as Error;
if (attempt < retries && this.isOnline) {
// Exponential backoff
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 1000)
);
continue;
}
throw lastError;
}
}
throw lastError;
}
}
export const networkManager = new NetworkManager();
Graceful Degradation Patterns
When features are unavailable, provide fallback experiences:
// src/components/SourceMapGenerator.tsx
interface SourceMapGeneratorProps {
sourceFiles: SourceFile[];
onSourceMapGenerated: (sourceMap: SourceMap) => void;
}
const SourceMapGenerator: React.FC<SourceMapGeneratorProps> = ({
sourceFiles,
onSourceMapGenerated
}) => {
const [isOnline, setIsOnline] = useState(navigator.onLine);
const [error, setError] = useState<Error | null>(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
const generateSourceMap = async () => {
if (!isOnline) {
setError(new Error('Cannot generate source map: offline'));
return;
}
setIsLoading(true);
setError(null);
try {
const sourceMap = await generateSourceMapService(sourceFiles);
onSourceMapGenerated(sourceMap);
} catch (err) {
setError(err as Error);
// Fallback to basic source map generation if advanced features fail
if (err instanceof FeatureNotAvailableError) {
// Fallback logic here
}
} finally {
setIsLoading(false);
}
};
if (!isOnline) {
return (
<div className="offline-warning">
<p>Offline mode detected. Some features may be limited.</p>
<button onClick={() => window.location.reload()}>
Retry Connection
</button>
</div>
);
}
if (error) {
return (
<div className="error-container">
<h3>Generation Failed</h3>
<p>{error.message}</p>
<button onClick={generateSourceMap}>Retry</button>
</div>
);
}
return (
<div className="source-map-generator">
{isLoading && <Spinner />}
<EditorPanel />
<PreviewPanel />
</div>
);
};
User Feedback on Errors
Implement comprehensive error feedback mechanisms:
// src/components/ToastManager.tsx
interface Toast {
id: string;
message: string;
type: 'error' | 'warning' | 'success';
duration?: number;
}
class ToastManager {
private toasts: Toast[] = [];
private toastIdCounter = 0;
addToast(message: string, type: 'error' | 'warning' | 'success', duration = 5000): void {
const toast: Toast = {
id: `toast-${++this.toastIdCounter}`,
message,
type,
duration
};
this.toasts.push(toast);
// Auto-remove after duration
if (duration > 0) {
setTimeout(() => this.removeToast(toast.id), duration);
}
}
removeToast(id: string): void {
this.toasts = this.toasts.filter(toast => toast.id !== id);
}
getToasts(): Toast[] {
return this.toasts;
}
}
export const toastManager = new ToastManager();
// src/components/Toast.tsx
interface ToastProps {
toast: Toast;
onRemove: (id: string) => void;
}
const Toast: React.FC<ToastProps> = ({ toast, onRemove }) => {
const [isVisible, setIsVisible] = useState(true);
useEffect(() => {
if (toast.duration && toast.duration > 0) {
const timer = setTimeout(() => {
setIsVisible(false);
setTimeout(() => onRemove(toast.id), 300); // Allow animation time
}, toast.duration);
return () => clearTimeout(timer);
}
}, [toast.duration, toast.id, onRemove]);
return (
<div
className={`toast toast--${toast.type} ${isVisible ? 'toast--visible' : 'toast--hidden'}`}
role="alert"
>
<span className="toast__message">{toast.message}</span>
<button
className="toast__close"
onClick={() => {
setIsVisible(false);
setTimeout(() => onRemove(toast.id), 300);
}}
aria-label="Close toast"
>
×
</button>
</div>
);
};
Error Boundary Implementation
// src/components/ErrorBoundary.tsx
interface ErrorBoundaryState {
hasError: boolean;
error?: Error;
errorInfo?: ErrorInfo;
}
class ErrorBoundary extends React.Component<
{ children: React.ReactNode },
ErrorBoundaryState
> {
constructor(props: { children: React.ReactNode }) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
console.error('Error caught by boundary:', error, errorInfo);
// Report to monitoring service
globalErrorHandler.handleError(error, errorInfo);
}
render(): React.ReactNode {
if (this.state.hasError) {
return (
<div className="error-boundary">
<h2>Something went wrong.</h2>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error?.message}
<br />
{this.state.errorInfo?.componentStack}
</details>
</div>
);
}
return this.props.children;
}
}
Error Types and Handling Strategy
// src/core/errors.ts
export class FeatureNotAvailableError extends Error {
constructor(message: string = 'Feature not available in current environment') {
super(message);
this.name = 'FeatureNotAvailableError';
}
}
export class InvalidSourceFileError extends Error {
constructor(message: string = 'Invalid source file format') {
super(message);
this.name = 'InvalidSourceFileError';
}
}
export class SourceMapGenerationError extends Error {
constructor(message: string = 'Failed to generate source map') {
super(message);
this.name = 'SourceMapGenerationError';
}
}
export class NetworkError extends Error {
constructor(message: string = 'Network request failed') {
super(message);
this.name = 'NetworkError';
}
}
Retry Strategy for Critical Operations
// src/core/retryStrategy.ts
type RetryableOperation<T> = () => Promise<T>;
interface RetryOptions {
maxRetries?: number;
delay?: number;
backoffMultiplier?: number;
shouldRetry?: (error: Error) => boolean;
}
class RetryStrategy {
static async executeWithRetry<T>(
operation: RetryableOperation<T>,
options: RetryOptions = {}
): Promise<T> {
const {
maxRetries = 3,
delay = 1000,
backoffMultiplier = 2,
shouldRetry = () => true
} = options;
let lastError: Error;
let currentDelay = delay;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
if (
attempt < maxRetries &&
shouldRetry(lastError) &&
(error instanceof NetworkError || error instanceof SourceMapGenerationError)
) {
console.warn(`Retry attempt ${attempt + 1} after ${currentDelay}ms`, error);
await new Promise(resolve => setTimeout(resolve, currentDelay));
currentDelay *= backoffMultiplier;
continue;
}
throw lastError;
}
}
throw lastError;
}
}
export const retryStrategy = new RetryStrategy();
8. Testing Strategy
Unit Testing Approach
Component Tests (70% coverage target):
- Test all interactive components with various prop combinations
- Verify UI state transitions and error handling paths
- Test edge cases like empty states, loading states, and invalid inputs
- Use React Testing Library with Jest for DOM-focused testing
Utility Tests (90% coverage target):
- Test core source map generation algorithms with mock ASTs
- Validate transformation logic between different formats (JS → Source Map)
- Test error boundary conditions and malformed input handling
- Include edge case testing for large files and complex mappings
Integration Testing Strategy
API Integration Tests (50% coverage):
- Test complete data flow from file upload → processing → source map generation
- Validate server-side transformation pipeline with real HTTP responses
- Test error propagation through the entire integration chain
State Management Integration (80% coverage):
- Verify Redux store updates with realistic action sequences
- Test optimistic update behavior and cache invalidation logic
- Validate persistence of user selections across component re-renders
E2E Testing Scope
Cypress Test Coverage (100% of critical user flows):
- Full end-to-end source map generation workflow from file upload to download
- Multi-file upload and batch processing scenarios
- Error state recovery (e.g., invalid file types, network failures)
- Cross-browser compatibility testing for key browsers (Chrome, Firefox, Safari)
Key E2E Flows:
- File upload → processing → download flow
- Error recovery from malformed input files
- Concurrent file processing and state synchronization
- User session persistence across page refreshes
Visual Regression Testing
Tool: Chromatic with Storybook integration Scope: All interactive UI components and state variations:
- Loading states with skeleton screens
- Error states with appropriate messaging
- Success states with downloadable artifacts
- Responsive layouts across breakpoints (mobile, tablet, desktop)
Accessibility Testing Automation
Tool: axe-core with Cypress integration Coverage:
- WCAG 2.1 AA compliance for all interactive elements
- Keyboard navigation testing across all form controls
- Screen reader compatibility verification for assistive technologies
- Focus order validation in multi-step workflows
Test Data Strategy
Test Fixtures:
- Mock JavaScript files with various complexity levels (simple, medium, complex)
- Source map files in multiple formats (v3, v4) for validation
- Edge case files: empty files, very large files, malformed syntax
- International character sets and special symbols for encoding tests
Data Generation:
- Auto-generate test files with controlled complexity levels
- Use fixtures for consistent reproduction of edge cases
- Implement parameterized tests for different file types and configurations
Test Isolation:
- Each test runs in isolated environment with clean state
- Mock external dependencies (API calls, file system access)
- Use deterministic randomization for any non-deterministic test data
9. Accessibility (A11y)
WCAG 2.1 AA compliance checklist for this feature
The source map generator must meet WCAG 2.1 AA standards:
- Text Contrast: All text must have a minimum 4.5:1 contrast ratio against its background (including source code snippets, file names, and UI labels)
- Keyboard Navigation: All interactive elements must be fully operable via keyboard (Tab/Shift+Tab navigation, Enter/Space activation)
- Focus Management: Clear visual focus indicators for all interactive elements (buttons, input fields, code editors)
- Screen Reader Support: Complete semantic structure for screen reader users (landmark regions, proper heading hierarchy)
- Error Handling: Clear error messages with sufficient context for screen reader users
- Alternative Text: Descriptive labels for all visual elements (file icons, status indicators)
- Skip Links: Skip to main content functionality for keyboard users
- Dynamic Content Updates: Proper announcements when source map content updates dynamically
Keyboard navigation support
All interactive elements must be fully keyboard accessible:
- Tab Order: Logical tab order that follows visual reading flow (file tree → editor → controls)
- Focus Traps: Modal dialogs and file selection screens properly trap focus
- Activation Keys: All buttons and controls activated via Enter or Space key
- Escape Handling: Escape key closes modals and cancels ongoing operations
- Shortcuts: Keyboard shortcuts for common actions (Ctrl+S to save, Ctrl+O to open)
Focus management strategy
- Focus Trapping: When modals open, focus is trapped within modal boundaries
- Focus Restoration: Focus returns to previous location after modals close
- Visual Indicators: Clear, high-contrast focus rings for all interactive elements
- Programmatic Focus: Auto-focus on first interactive element when views change
- Focus Management in Editors: Proper focus handling within code editors (monaco/ace)
ARIA roles and attributes plan
// Example ARIA implementation for key components
interface SourceMapGeneratorProps {
// ... other props
/** ARIA label for the main editor container */
ariaLabel?: string;
/** ARIA role for file tree navigation */
ariaRole?: 'tree' | 'navigation';
/** ARIA-expanded state for collapsible sections */
ariaExpanded?: boolean;
/** ARIA-describedby for error messages */
ariaDescribedBy?: string;
}
Screen reader compatibility
- Semantic HTML: Use proper heading levels (h1-h6) for content hierarchy
- Landmark Regions: Main, navigation, and complementary regions properly marked
- Live Regions: Dynamic content updates announced via
aria-live="polite" - Role Descriptions: Custom roles and states clearly defined for screen reader users
- Alternative Content: Text alternatives for all visual-only content (file icons, status indicators)
- Dynamic Updates: Proper announcements when source maps are generated or errors occur
Keyboard navigation flow
- File Tree Navigation: Tab to file tree, use arrow keys to navigate files
- Editor Interaction: Tab to editor area, use Ctrl+Shift+Up/Down for line navigation
- Control Panel: Tab to action buttons (Generate, Save, Export)
- Modal Dialogs: Use Tab to navigate modal elements, Escape to close
- Error Messages: Focus moves to error messages when they appear
Focus trapping implementation
// Example focus trap utility for modals
const useFocusTrap = (modalRef: React.RefObject<HTMLElement>) => {
useEffect(() => {
const handleFocusTrap = (e: KeyboardEvent) => {
if (e.key === 'Tab') {
const focusableElements = modalRef.current?.querySelectorAll(
'button, input, select, textarea, [tabindex]:not([tabindex="-1"])'
) || [];
const firstElement = focusableElements[0] as HTMLElement;
const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement;
if (e.shiftKey && document.activeElement === firstElement) {
lastElement.focus();
e.preventDefault();
} else if (!e.shiftKey && document.activeElement === lastElement) {
firstElement.focus();
e.preventDefault();
}
}
};
document.addEventListener('keydown', handleFocusTrap);
return () => document.removeEventListener('keydown', handleFocusTrap);
}, [modalRef]);
};
Error state accessibility
- Error Announcements:
aria-live="assertive"for critical errors - Descriptive Messages: Error messages include context and suggested actions
- Visual + Auditory Feedback: Both visual indicators and screen reader announcements for errors
- Focus Management on Errors: Automatic focus to error messages when they appear
- Error Summary: Screen reader accessible summary of all current errors
Dynamic content announcements
// Example live region for status updates
const StatusAnnouncer = ({ message, type }: { message: string; type: 'success' | 'error' | 'info' }) => {
return (
<div
aria-live="polite"
aria-atomic="true"
className={`sr-only ${type === 'error' ? 'text-red-500' : ''}`}
>
{message}
</div>
);
};
Color contrast considerations
- Code Editor: Ensure proper contrast between syntax highlighting and background
- UI Elements: All buttons, inputs, and controls maintain 4.5:1 contrast ratio
- Status Indicators: Color-coded status indicators must be distinguishable by color and text
- Error States: Error messages have sufficient contrast against background
- Hover/Focus States: Hover and focus states maintain accessibility standards
Alternative text implementation
// Example file icon with alternative text
const FileIcon = ({ fileName, isFolder }: { fileName: string; isFolder: boolean }) => {
return (
<span
role="img"
aria-label={isFolder ? `${fileName} folder` : `${fileName} file`}
className="file-icon"
>
{isFolder ? '📁' : '📄'}
</span>
);
};
Skip links implementation
// Example skip to content link
const SkipToContent = () => {
return (
<a
href="#main-content"
className="sr-only sr-only-focusable"
aria-label="Skip to main content"
>
Skip to main content
</a>
);
};
Keyboard-accessible file tree
// Example accessible file tree component
const AccessibleFileTree = ({ files }: { files: FileNode[] }) => {
return (
<nav
role="navigation"
aria-label="Source files navigation"
className="file-tree"
>
{files.map((file) => (
<div
key={file.id}
role="treeitem"
aria-expanded={file.isFolder ? file.expanded : undefined}
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
// Handle selection
} else if (e.key === 'ArrowRight' && file.isFolder) {
// Expand folder
} else if (e.key === 'ArrowLeft' && file.isFolder) {
// Collapse folder
}
}}
>
{file.name}
</div>
))}
</nav>
);
};
Screen reader-friendly status updates
// Example status update component with screen reader support
const SourceMapStatus = ({
status,
progress,
message
}: {
status: 'idle' | 'generating' | 'success' | 'error';
progress?: number;
message?: string;
}) => {
const statusText = status === 'generating' ? `Generating source map: ${progress}%` :
status === 'success' ? 'Source map generated successfully' :
status === 'error' ? 'Error generating source map' :
'Ready to generate source map';
return (
<div
role="status"
aria-live="polite"
className={`status-indicator ${status}`}
>
<span className="sr-only">{statusText}</span>
<span aria-hidden="true">
{message || statusText}
</span>
</div>
);
};
Focus management for code editors
// Example editor focus handling
const CodeEditor = ({
value,
onChange,
onEditorFocus,
onEditorBlur
}: {
value: string;
onChange: (value: string) => void;
onEditorFocus?: () => void;
onEditorBlur?: () => void;
}) => {
const editorRef = useRef<HTMLDivElement>(null);
useEffect(() => {
// Ensure editor gets proper focus management
const handleFocus = () => {
onEditorFocus?.();
// Add visual focus indicator
};
const handleBlur = () => {
onEditorBlur?.();
// Remove visual focus indicator
};
const editor = editorRef.current;
if (editor) {
editor.addEventListener('focus', handleFocus);
editor.addEventListener('blur', handleBlur);
}
return () => {
if (editor) {
editor.removeEventListener('focus', handleFocus);
editor.removeEventListener('blur', handleBlur);
}
};
}, [onEditorFocus, onEditorBlur]);
return (
<div
ref={editorRef}
tabIndex={0}
className="code-editor"
role="textbox"
aria-label="Source map content editor"
>
{value}
</div>
);
};
10. Security Considerations
XSS Prevention Strategy
The source map generator must prevent cross-site scripting vulnerabilities through comprehensive input sanitization and output encoding:
// Input validation for source file content
const sanitizeSourceContent = (content: string): string => {
// Remove dangerous patterns that could lead to XSS
return content
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
.replace(/on\w+\s*=\s*["'][^"']*["']/gi, '')
.replace(/javascript:/gi, '');
};
// Output encoding for source map data
const encodeSourceMapData = (data: any): string => {
return JSON.stringify(data)
.replace(/</g, '\\u003c')
.replace(/>/g, '\\u003e')
.replace(/&/g, '\\u0026');
};
CSRF Protection
Implement anti-CSRF tokens for protected API endpoints to prevent cross-site request forgery:
// CSRF token management in React context
interface CsrfContextType {
csrfToken: string | null;
refreshCsrfToken: () => Promise<void>;
}
const CsrfContext = createContext<CsrfContextType>({
csrfToken: null,
refreshCsrfToken: async () => {},
});
// Token inclusion in API requests
const apiRequestWithCsrf = async (url: string, options: RequestInit) => {
const csrfToken = getCsrfToken();
return fetch(url, {
...options,
headers: {
...options.headers,
'X-CSRF-Token': csrfToken,
},
});
};
Content Security Policy (CSP) Headers
Configure strict CSP headers to prevent unauthorized code execution and data injection:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob:;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
Sensitive Data Handling in Frontend State
Protect sensitive source code content from being stored in frontend state or logs:
// Secure state management for sensitive content
interface SourceMapState {
// Sensitive source content is never stored in component state
// Only metadata and processed map data are retained
sourceFiles: SourceFileMetadata[];
generatedMap: SourceMapData | null;
processingStatus: 'idle' | 'processing' | 'completed' | 'error';
}
// Secure file upload handling
const handleFileUpload = async (file: File): Promise<SourceFileMetadata> => {
// Validate file type and size
if (!file.name.endsWith('.js') && !file.name.endsWith('.ts')) {
throw new Error('Invalid file type');
}
// Process file content without storing raw source
const content = await file.text();
const sanitizedContent = sanitizeSourceContent(content);
return {
name: file.name,
size: file.size,
lastModified: file.lastModified,
// Never store raw content in frontend state
};
};
Input Sanitization Approach
Implement comprehensive input validation to prevent injection attacks:
// Comprehensive source map input sanitization
class SourceMapSanitizer {
static sanitizeSourcePath(path: string): string {
// Remove path traversal attempts
if (path.includes('..') || path.includes('\0')) {
throw new Error('Invalid source path');
}
// Normalize path separators
return path.replace(/\\/g, '/');
}
static sanitizeSourceContent(content: string): string {
// Remove potentially dangerous content
const dangerousPatterns = [
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
/javascript:/gi,
/on\w+\s*=\s*["'][^"']*["']/gi,
/eval\s*\(/g,
/setTimeout\s*\(/g,
/setInterval\s*\(/g,
];
let sanitized = content;
dangerousPatterns.forEach(pattern => {
sanitized = sanitized.replace(pattern, '');
});
return sanitized;
}
static validateSourceMapObject(map: any): boolean {
// Ensure source map structure is valid and doesn't contain malicious keys
const allowedKeys = ['version', 'sources', 'names', 'mappings', 'file', 'sourceRoot'];
const mapKeys = Object.keys(map);
return mapKeys.every(key => allowedKeys.includes(key));
}
}
Security Headers Implementation
Implement security headers through middleware or server configuration:
// Security headers configuration
const securityHeaders = {
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
'Content-Security-Policy': "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'",
};
// Middleware for applying security headers
const applySecurityHeaders = (response: Response): Response => {
Object.entries(securityHeaders).forEach(([key, value]) => {
response.headers.set(key, value);
});
return response;
};
Data Validation and Sanitization
Implement robust validation for all user-provided data in the source map generation pipeline:
// Source map validation and sanitization pipeline
const validateAndSanitizeSourceMap = (rawMap: any): SourceMapData => {
// Validate structure
if (!rawMap || typeof rawMap !== 'object') {
throw new Error('Invalid source map structure');
}
// Validate required fields
if (!rawMap.version || rawMap.version !== 3) {
throw new Error('Unsupported source map version');
}
// Sanitize sources array
const sanitizedSources = Array.isArray(rawMap.sources)
? rawMap.sources.map((source: string) =>
SourceMapSanitizer.sanitizeSourcePath(source)
)
: [];
// Sanitize mappings
const sanitizedMappings = typeof rawMap.mappings === 'string'
? rawMap.mappings.replace(/[^\w\+\=\/\:,;]/g, '')
: '';
return {
version: rawMap.version,
sources: sanitizedSources,
names: Array.isArray(rawMap.names) ? rawMap.names : [],
mappings: sanitizedMappings,
file: rawMap.file || '',
sourceRoot: rawMap.sourceRoot || '',
};
};
Authentication and Authorization
Implement proper authentication and authorization for source map generation endpoints:
// Authentication guard for source map operations
const requireAuth = async (req: Request): Promise<boolean> => {
const token = req.headers.get('Authorization')?.split(' ')[1];
if (!token) {
throw new Error('Authentication required');
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
return !!decoded;
} catch (error) {
throw new Error('Invalid authentication token');
}
};
// Authorization check for source map generation
const checkSourceMapPermissions = async (userId: string, project: string): Promise<boolean> => {
// Validate user has access to the project
const userProjectAccess = await getUserProjectAccess(userId, project);
return userProjectAccess?.canGenerateSourceMaps === true;
};
Secure File Handling
Implement secure file handling to prevent directory traversal and malicious file uploads:
// Secure file handling with validation
class SecureSourceFileHandler {
static async processUploadedFile(file: File): Promise<ProcessedSourceFile> {
// Validate file size (max 10MB)
if (file.size > 10 * 1024 * 1024) {
throw new Error('File too large');
}
// Validate file extension
const allowedExtensions = ['.js', '.ts', '.jsx', '.tsx'];
const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase();
if (!allowedExtensions.includes(fileExtension)) {
throw new Error('Unsupported file type');
}
// Read file content safely
const content = await file.text();
// Sanitize content to prevent XSS
const sanitizedContent = SourceMapSanitizer.sanitizeSourceContent(content);
return {
name: file.name,
content: sanitizedContent,
size: file.size,
lastModified: file.lastModified,
};
}
static generateSafeSourceMap(
sourceFiles: ProcessedSourceFile[],
options: SourceMapOptions
): SourceMapData {
// Validate all inputs before processing
sourceFiles.forEach(file => {
if (!file.name || !file.content) {
throw new Error('Invalid source file');
}
});
// Generate source map with sanitized data
return this.buildSourceMap(sourceFiles, options);
}
}
Secure Communication
Ensure all communication with backend services is encrypted and authenticated:
// Secure API client configuration
const secureApiClient = axios.create({
baseURL: process.env.API_BASE_URL,
timeout: 30000,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
withCredentials: true,
});
// Request interceptor for authentication
secureApiClient.interceptors.request.use(
(config) => {
const token = localStorage.getItem('authToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// Response interceptor for error handling
secureApiClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
// Handle authentication errors
localStorage.removeItem('authToken');
window.location.href = '/login';
}
return Promise.reject(error);
}
);
Security Testing Considerations
Implement security testing as part of the CI/CD pipeline:
# Security scanning in CI pipeline
npm run security-scan
# OWASP ZAP automated scanning
docker run -t owasp/zap2docker-stable zap.sh -cmd -url "https://app.example.com" -alertOnAll
# Dependency vulnerability scanning
npm audit --audit-level high
Security Monitoring and Alerting
Implement security monitoring for suspicious activities:
// Security event logging
const logSecurityEvent = (event: SecurityEvent) => {
// Log to secure audit trail
console.log(`[SECURITY] ${event.type}: ${event.description}`);
// Send to security monitoring system
if (process.env.SECURITY_MONITORING_ENABLED) {
sendToSecurityMonitoring(event);
}
};
// Security event types
type SecurityEvent = {
type: 'source_map_generation' | 'file_upload' | 'authentication_failure';
userId?: string;
description: string;
timestamp: Date;
ipAddress?: string;
};
The security design ensures that source map generation is performed in a secure environment, protecting against common web vulnerabilities while maintaining the core functionality of source map creation and validation.
11. Internationalization (i18n)
Translation Key Structure and Naming Convention
Following BEM-style naming with feature-scoped keys to prevent collisions:
source-map-generator:
title: "Source Map Generator"
description: "Generate source maps from compiled code"
upload:
button: "Upload Source Files"
placeholder: "Drag and drop files here or click to browse"
supported: "Supported formats: .js, .ts, .jsx, .tsx"
generate:
button: "Generate Map"
status:
idle: "Ready to generate"
processing: "Processing files..."
success: "Map generated successfully"
error: "Generation failed"
file:
name: "File Name"
size: "Size"
type: "Type"
error:
upload: "Failed to upload file"
generate: "Failed to generate source map"
RTL Support Considerations
RTL layout support will be implemented through CSS classes and direction properties:
- Use
dir="rtl"attribute on root element for RTL languages - Apply CSS
direction: rtlto containers that need right-to-left layout - Implement bidirectional text handling for file names and paths
- Ensure proper alignment of UI elements (buttons, form fields) in RTL mode
Date, Number, Currency Formatting Strategy
For displaying file sizes and timestamps:
- Use
Intl.NumberFormatfor file size formatting (KB, MB, GB) - Use
Intl.DateTimeFormatfor timestamp display with locale-aware formatting - Support multiple number systems (Arabic, Han, etc.) through
Intloptions
Locale Detection and Switching Strategy
Automatic locale detection using browser's navigator.language:
// i18n/utils.ts
export const detectLocale = (): string => {
const browserLang = navigator.language || 'en';
const supportedLocales = ['en', 'es', 'fr', 'de', 'ja'];
// Match exact locale or fallback to language code
const matched = supportedLocales.find(locale =>
locale === browserLang || locale === browserLang.split('-')[0]
);
return matched || 'en';
};
// i18n/context.ts
export const useLocale = () => {
const [locale, setLocale] = useState<string>(detectLocale());
const switchLocale = (newLocale: string) => {
setLocale(newLocale);
localStorage.setItem('preferred-locale', newLocale);
};
return { locale, switchLocale };
};
Translation Key Management
- Keys will be organized in a hierarchical structure under
source-map-generatornamespace - Translation files will follow the pattern:
locales/{locale}/source-map-generator.json - Keys will be validated at build time using a custom script that ensures all keys are present in all language files
- Missing translation keys will be flagged during development and build process
i18n Implementation Approach
The application will use react-i18next with:
- Language detection based on browser settings
- Manual language switching capability
- Fallback to English for missing translations
- Dynamic import of translation files to support code splitting
Translation File Format
{
"source-map-generator": {
"title": "Source Map Generator",
"description": "Generate source maps from compiled code",
"upload": {
"button": "Upload Source Files",
"placeholder": "Drag and drop files here or click to browse",
"supported": "Supported formats: .js, .ts, .jsx, .tsx"
},
"generate": {
"button": "Generate Map",
"status": {
"idle": "Ready to generate",
"processing": "Processing files...",
"success": "Map generated successfully",
"error": "Generation failed"
}
},
"file": {
"name": "File Name",
"size": "Size",
"type": "Type"
},
"error": {
"upload": "Failed to upload file",
"generate": "Failed to generate source map"
}
}
}
12. Monitoring & Observability
Real User Monitoring (RUM) Metrics to Track
The source map generator will implement comprehensive RUM tracking to monitor user experience and performance:
- First Input Delay (FID): Track user interaction responsiveness for source map generation actions
- Largest Contentful Paint (LCP): Monitor time to render the main source map visualization
- Cumulative Layout Shift (CLS): Track layout stability during dynamic source map updates
- Navigation Time: Measure time from user request to complete source map rendering
- Generation Success Rate: Track percentage of successful source map creation vs. failures
- Memory Usage: Monitor peak memory consumption during large file processing
- API Response Times: Track backend service latency for source map generation requests
Error Tracking Integration
Integration with error monitoring platforms (Sentry/Datadog) will capture:
- Source Map Generation Failures: Compilation errors, syntax issues in input files
- Network Errors: API connectivity issues during source map upload/download
- Memory Exhaustion: Out-of-memory errors when processing large files
- Invalid Input Errors: Malformed source files or unsupported formats
- User Action Failures: Failed user interactions (e.g., failed file uploads)
Custom Events and User Journey Tracking
Key user journey events to track:
// Event tracking interface for source map generator
interface SourceMapEvent {
event: 'source_map_generated' | 'file_uploaded' | 'error_occurred' | 'download_started';
timestamp: number;
userId?: string;
metadata?: {
fileSize?: number;
fileCount?: number;
processingTime?: number;
errorType?: string;
success?: boolean;
};
}
- File Upload Events: Track file selection, validation, and upload completion
- Generation Events: Start/end of source map creation process with timing data
- Download Events: Source map download initiation and completion
- Error Events: Detailed error context with stack traces and user actions
Performance Monitoring and Regression Detection
Performance monitoring will include:
- Baseline Performance Tracking: Store performance baselines for different file sizes and types
- Regression Detection: Alert when generation times exceed 20% of baseline performance
- Threshold Alerts: Notify on memory usage exceeding 80% of available heap
- User Impact Metrics: Track performance impact on user experience (e.g., >2s delays)
// Performance monitoring configuration
interface PerformanceConfig {
baselineGenerationTime: Record<string, number>; // file type -> ms
memoryThreshold: number; // MB
alertThresholds: {
generationTimeIncrease: number; // percentage
memoryUsage: number; // percentage of max
};
}
Integration Points
- Sentry SDK: For error capture and performance monitoring
- DataDog RUM: For real user experience tracking
- Custom Metrics Service: For internal performance dashboards and alerting
- Application Insights: For end-to-end user journey tracking
Alerting Strategy
Critical alerts will be configured for:
- Generation failure rate >5% over 5-minute window
- Memory usage consistently above 80% for 10+ minutes
- Average generation time >30s for 5+ consecutive requests
- User session abandonment rate >15% during generation process
Data Privacy Considerations
All monitoring data will:
- Exclude sensitive source code content
- Anonymize user identifiers where possible
- Comply with data retention policies (30-day default)
- Support user opt-out mechanisms for analytics
Dashboard Requirements
Dashboard metrics will include:
- Real-time generation success/failure rates
- Performance trends over time
- User session breakdown by file size/type
- Error distribution by type and frequency
- Memory usage patterns and peak consumption
Integration with CI/CD Pipeline
Performance benchmarks will be integrated into:
- Pre-commit hooks for performance regression checks
- CI pipeline performance validation steps
- Deployment validation to ensure no performance degradation
13. File & Folder Structure
Proposed Directory Structure
/src
/features
/source-map-generator
/components
SourceMapGenerator.tsx
CodeEditor.tsx
MapPreview.tsx
FileTree.tsx
/hooks
useSourceMapGenerator.ts
useCodeEditor.ts
/services
sourceMapService.ts
fileSystemService.ts
/types
index.ts
/utils
codeParser.ts
mapGenerator.ts
/constants
index.ts
/shared
/components
Button.tsx
Modal.tsx
Spinner.tsx
/hooks
useDebounce.ts
useLocalStorage.ts
/utils
logger.ts
validation.ts
/types
index.ts
/app
/routes
SourceMapGeneratorRoute.tsx
/layout
MainLayout.tsx
/types
global.d.ts
Module Boundaries and Import Rules
- Feature modules (
/features/source-map-generator) are self-contained with no cross-feature imports - Shared components and utilities can be imported by any feature module
- All feature modules export a barrel file (
index.ts) for clean imports - No circular dependencies between feature modules
Shared vs Feature-Specific Code Separation
Shared code:
- Reusable UI components (Button, Modal)
- Generic hooks (useDebounce, useLocalStorage)
- Utility functions with broad applicability (logger, validation)
- Type definitions that span multiple features
Feature-specific code:
- Source map generation logic and UI components
- Route-specific layouts and navigation
- Feature-level services and data processing utilities
This structure follows a feature-based organization that scales well for large teams while maintaining clear module boundaries and reducing coupling between features.
14. Dependencies & Third-Party Libraries
Key Libraries Selected with Justification
1. @babel/core & @babel/parser
- Justification: Essential for parsing JavaScript/TypeScript source code into ASTs. Babel's parser provides robust, standardized parsing that handles modern JavaScript syntax and TypeScript extensions.
- Bundle size impact: ~200KB (minified) - acceptable for build-time tooling
- Alternatives considered: Acorn (smaller but less comprehensive), but Babel's ecosystem integration makes it the better choice
2. @babel/traverse
- Justification: Required for walking and transforming ASTs to extract source map information. Critical for mapping transformed code back to original source locations.
- Bundle size impact: ~50KB (minified)
- Alternatives considered: Manual AST traversal (more error-prone), but Babel's traverser is battle-tested
3. source-map
- Justification: Industry-standard library for generating and manipulating source maps. Provides the core API for creating valid source map format with proper mappings, sources, and names.
- Bundle size impact: ~15KB (minified)
- Alternatives considered: Custom implementation (higher risk of format errors), but source-map is the de facto standard
4. webpack-sources
- Justification: Provides utilities for working with source content and generating source maps in webpack-compatible environments. Useful for handling file content and line/column calculations.
- Bundle size impact: ~20KB (minified)
- Alternatives considered: Manual content handling, but webpack-sources provides robust cross-platform compatibility
5. lodash
- Justification: Provides utility functions for object manipulation, array operations, and deep cloning that are essential for source map generation logic. Used for safe object merging and data transformation.
- Bundle size impact: ~20KB (minified) - only importing specific methods to minimize impact
- Alternatives considered: Custom utilities (more maintenance burden), but lodash's reliability and performance justify the cost
Alternatives Considered and Rejected
1. Esprima
- Rejected because: While it provides excellent JavaScript parsing, it lacks TypeScript support and doesn't integrate well with the Babel ecosystem. It's also less actively maintained than Babel.
2. Acorn + custom source map generation
- Rejected because: While Acorn is lightweight and fast, building a complete source map generator from scratch would require significant development time and maintenance burden. Babel's ecosystem provides better integration with existing tools.
3. Custom source map implementation
- Rejected because: Source map format is complex and requires strict adherence to specification. Custom implementations are error-prone and would require extensive testing to ensure compliance.
Licensing and Bundle Size Impact Assessment
Bundle size impact: Total third-party dependency footprint is approximately 250-300KB minified (excluding webpack and build tooling). This is acceptable for a source map generator that runs at build time rather than runtime.
Licensing: All selected libraries use permissive MIT or BSD licenses, compatible with both open-source and commercial usage. No restrictive licensing concerns for production deployment.
Build-time vs Runtime: These dependencies are used exclusively during build processes, not in the final runtime bundle, so they don't impact end-user performance or bundle size.
Tree-shaking support: All libraries support tree-shaking where possible, with lodash imported as specific methods rather than the entire library to minimize bundle impact.
15. Open Questions, Risks & Trade-offs
Decisions that need product/engineering alignment
- Source map format version support: Should we support only the latest spec (v3) or maintain backward compatibility with v1/v2? This decision impacts the complexity of our parser and the compatibility matrix with existing tools.
- File size limits: What's the maximum file size we should support for source map generation? Large files can cause memory issues or timeout in the browser.
- Build tool integration: Should we provide first-class support for Webpack, Rollup, or Babel plugins, or focus on CLI/programmatic usage? This affects the scope and complexity of our implementation.
Known trade-offs made in this design
- Memory vs. Performance: We're optimizing for faster generation at the cost of higher memory consumption during processing, particularly for large codebases. This trade-off is acceptable for most use cases but could be problematic in memory-constrained environments.
- Parser vs. AST approach: We're using a direct parser approach rather than leveraging existing AST libraries like Babel or TypeScript's compiler API. This provides more control but increases implementation complexity and maintenance burden.
- Single-threaded vs. multi-threaded: The current design is single-threaded to simplify the architecture and avoid complexity around worker communication. However, this limits scalability for very large source maps.
Risks and mitigations
- Memory exhaustion: Large files or complex code can cause out-of-memory errors. Mitigation: Implement progressive processing with streaming capabilities and memory usage monitoring.
- Parser accuracy: Inaccurate parsing of edge cases in source code could lead to malformed source maps. Mitigation: Comprehensive test suite with edge case coverage and fallback mechanisms.
- Build tool compatibility: Different build tools may have varying expectations about source map generation. Mitigation: Provide plugin adapters for major tools with clear documentation and examples.
- Performance regression: Complex source maps could cause significant performance degradation in the browser. Mitigation: Implement caching and incremental updates where possible.
Technical debt acknowledged
- Limited language support: Currently only supports JavaScript/TypeScript. Adding support for other languages would require significant refactoring of the parser layer.
- No source map validation: We don't validate generated source maps against the spec. This could lead to invalid output that tools might reject.
- Hardcoded assumptions: Some assumptions about source map structure and mapping logic are hardcoded, making it harder to adapt to new requirements or edge cases.
- No incremental build support: The current implementation doesn't support incremental builds where only changed files are processed, which could be a significant performance improvement for large projects.