mirror of
https://github.com/Silentely/eSIM-Tools.git
synced 2026-09-03 06:24:20 +08:00
- Add INTEGRATION_GUIDE.md with real-world examples - Add IMPROVEMENTS_SUMMARY.md with complete overview - Include migration checklist and best practices - Document all performance improvements and metrics Co-authored-by: Silentely <22141172+Silentely@users.noreply.github.com>
13 KiB
13 KiB
Integration Guide: Using the New Modules
This guide shows how to integrate the new performance and utility modules into your application.
Quick Start
1. Basic Setup
// main.js or your entry point
import performanceMonitor from '@modules/performance-monitor';
import resourceHints from '@modules/resource-hints';
import appConfig from '@modules/app-config';
// Initialize monitoring (automatic)
// performanceMonitor is already initialized
// Initialize resource hints (automatic)
// resourceHints is already initialized
console.log('App environment:', appConfig.isDevelopment() ? 'development' : 'production');
2. Using API Service
Replace direct fetch calls with the API service:
Before:
async function fetchUserData() {
try {
const response = await fetch('/api/user');
if (!response.ok) throw new Error('Failed');
return await response.json();
} catch (error) {
console.error(error);
}
}
After:
import { giffgaffAPI } from '@modules/api-service';
async function fetchUserData() {
// Automatic retry, caching, and error handling
return await giffgaffAPI.get('/giffgaff-graphql');
}
3. Performance Monitoring Integration
Track Custom Operations:
import performanceMonitor from '@modules/performance-monitor';
async function performExpensiveOperation() {
performanceMonitor.startMark('expensive-operation');
// Your code here
await someAsyncWork();
performanceMonitor.endMark('expensive-operation');
}
View Performance Report:
// In development, check performance metrics
if (appConfig.isDevelopment()) {
window.showPerformanceReport = () => {
const report = performanceMonitor.generateReport();
console.table(report.metrics);
};
}
4. Resource Optimization
Preload Critical Resources:
import resourceHints from '@modules/resource-hints';
// Preload the next page users are likely to visit
resourceHints.addPrefetch('/giffgaff');
// Preconnect to API early
resourceHints.addPreconnect('https://api.giffgaff.com');
Real-World Examples
Example 1: Optimized Form Submission
import { giffgaffAPI } from '@modules/api-service';
import { debounce } from '@utils';
import performanceMonitor from '@modules/performance-monitor';
class OptimizedForm {
constructor(formElement) {
this.form = formElement;
this.setupEventListeners();
}
setupEventListeners() {
// Debounce search input
const searchInput = this.form.querySelector('#search');
if (searchInput) {
searchInput.addEventListener('input',
debounce((e) => this.handleSearch(e.target.value), 300)
);
}
// Track form submission
this.form.addEventListener('submit', (e) => this.handleSubmit(e));
}
async handleSearch(query) {
if (!query) return;
performanceMonitor.startMark('search-request');
try {
// API call with automatic caching
const results = await giffgaffAPI.get(`/search?q=${encodeURIComponent(query)}`);
this.displayResults(results);
} catch (error) {
this.showError('Search failed');
} finally {
performanceMonitor.endMark('search-request');
}
}
async handleSubmit(e) {
e.preventDefault();
performanceMonitor.startMark('form-submit');
const formData = new FormData(this.form);
const data = Object.fromEntries(formData.entries());
try {
const result = await giffgaffAPI.post('/submit', data);
this.showSuccess(result);
} catch (error) {
this.showError('Submission failed');
} finally {
performanceMonitor.endMark('form-submit');
}
}
displayResults(results) {
// Implementation
}
showSuccess(message) {
// Implementation
}
showError(message) {
// Implementation
}
}
// Initialize
document.addEventListener('DOMContentLoaded', () => {
const form = document.querySelector('#my-form');
if (form) {
new OptimizedForm(form);
}
});
Example 2: Optimized Image Gallery
import resourceHints from '@modules/resource-hints';
import { throttle } from '@utils';
class ImageGallery {
constructor(containerElement) {
this.container = containerElement;
this.images = [];
this.setupIntersectionObserver();
this.setupPrefetching();
}
setupIntersectionObserver() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.loadImage(entry.target);
observer.unobserve(entry.target);
}
});
}, {
rootMargin: '50px' // Load before entering viewport
});
this.container.querySelectorAll('img[data-src]').forEach(img => {
observer.observe(img);
});
}
setupPrefetching() {
// Prefetch full-size images when thumbnails are hovered
this.container.addEventListener('mouseover',
throttle((e) => {
if (e.target.tagName === 'IMG') {
const fullSizeUrl = e.target.dataset.fullsize;
if (fullSizeUrl) {
resourceHints.addPrefetch(fullSizeUrl, 'image');
}
}
}, 200)
);
}
loadImage(img) {
const src = img.dataset.src;
if (src) {
img.src = src;
img.classList.add('loaded');
img.removeAttribute('data-src');
}
}
}
// Initialize
document.addEventListener('DOMContentLoaded', () => {
const gallery = document.querySelector('.image-gallery');
if (gallery) {
new ImageGallery(gallery);
}
});
Example 3: Configuration-Based Feature Toggles
import appConfig from '@modules/app-config';
class AnalyticsService {
constructor() {
this.enabled = appConfig.isFeatureEnabled('analytics');
if (this.enabled) {
this.initialize();
}
}
initialize() {
// Only load analytics in production
if (appConfig.isProduction()) {
this.loadAnalyticsScript();
}
}
trackEvent(eventName, data) {
if (!this.enabled) return;
console.log('Track event:', eventName, data);
// Send to analytics service
}
trackPageView(path) {
if (!this.enabled) return;
console.log('Track page view:', path);
// Send to analytics service
}
loadAnalyticsScript() {
// Load analytics script dynamically
}
}
class ErrorReportingService {
constructor() {
this.enabled = appConfig.isFeatureEnabled('errorReporting');
if (this.enabled) {
this.initialize();
}
}
initialize() {
window.addEventListener('error', (e) => this.handleError(e));
window.addEventListener('unhandledrejection', (e) => this.handleRejection(e));
}
handleError(error) {
if (!this.enabled) return;
console.error('Captured error:', error);
// Send to error reporting service (e.g., Sentry)
}
handleRejection(event) {
if (!this.enabled) return;
console.error('Unhandled rejection:', event.reason);
// Send to error reporting service
}
}
// Export services
export const analytics = new AnalyticsService();
export const errorReporting = new ErrorReportingService();
Example 4: Advanced API Usage with Retry
import { retry } from '@utils';
import { giffgaffAPI } from '@modules/api-service';
import performanceMonitor from '@modules/performance-monitor';
class ESIMActivationService {
async activateESIM(data) {
performanceMonitor.startMark('esim-activation');
try {
// Retry up to 5 times with exponential backoff
const result = await retry(
() => giffgaffAPI.post('/giffgaff-mfa-challenge', data),
5, // max retries
2000 // initial delay (2s)
);
performanceMonitor.endMark('esim-activation');
return result;
} catch (error) {
performanceMonitor.endMark('esim-activation');
throw new Error(`Activation failed after retries: ${error.message}`);
}
}
async checkStatus(activationId) {
// Use cached response if available
return await giffgaffAPI.get(
`/status?id=${activationId}`,
{ cacheTime: 5000 } // Cache for 5 seconds
);
}
}
export default new ESIMActivationService();
Performance Monitoring Dashboard
Add a simple dashboard to view metrics in development:
// performance-dashboard.js
import performanceMonitor from '@modules/performance-monitor';
import appConfig from '@modules/app-config';
if (appConfig.isDevelopment()) {
class PerformanceDashboard {
constructor() {
this.createDashboard();
this.updateInterval = setInterval(() => this.update(), 1000);
}
createDashboard() {
const dashboard = document.createElement('div');
dashboard.id = 'perf-dashboard';
dashboard.style.cssText = `
position: fixed;
bottom: 10px;
right: 10px;
background: rgba(0, 0, 0, 0.9);
color: white;
padding: 10px;
border-radius: 5px;
font-family: monospace;
font-size: 12px;
z-index: 10000;
max-width: 300px;
`;
document.body.appendChild(dashboard);
this.dashboard = dashboard;
}
update() {
const metrics = performanceMonitor.getMetrics();
let html = '<strong>Performance Metrics</strong><br>';
for (const [name, data] of Object.entries(metrics)) {
if (data.value !== undefined) {
const color = this.getColor(data.rating);
html += `${name}: <span style="color: ${color}">${Math.round(data.value)}ms</span><br>`;
}
}
this.dashboard.innerHTML = html;
}
getColor(rating) {
const colors = {
'good': '#4caf50',
'needs-improvement': '#ff9800',
'poor': '#f44336'
};
return colors[rating] || '#fff';
}
destroy() {
clearInterval(this.updateInterval);
if (this.dashboard) {
this.dashboard.remove();
}
}
}
// Initialize dashboard
window.perfDashboard = new PerformanceDashboard();
}
Testing Integration
Example test using the new modules:
// __tests__/api-service.test.js
import APIService from '../src/js/modules/api-service';
describe('APIService', () => {
let api;
beforeEach(() => {
api = new APIService({ baseURL: 'https://api.example.com' });
});
afterEach(() => {
api.clearCache();
});
test('should cache GET requests', async () => {
global.fetch = jest.fn()
.mockResolvedValueOnce({
ok: true,
json: async () => ({ data: 'test' })
});
// First call
await api.get('/data');
// Second call (should use cache)
const result = await api.get('/data');
// Fetch should only be called once
expect(global.fetch).toHaveBeenCalledTimes(1);
expect(result).toEqual({ data: 'test' });
});
test('should retry on failure', async () => {
global.fetch = jest.fn()
.mockRejectedValueOnce(new Error('Network error'))
.mockRejectedValueOnce(new Error('Network error'))
.mockResolvedValueOnce({
ok: true,
json: async () => ({ data: 'success' })
});
const result = await api.get('/data');
expect(global.fetch).toHaveBeenCalledTimes(3);
expect(result).toEqual({ data: 'success' });
});
});
Migration Checklist
When integrating these modules into an existing codebase:
- Replace direct
fetchcalls with API service - Add performance marks for critical operations
- Configure resource hints for your application
- Set up feature flags in app-config
- Add debounce/throttle to event handlers
- Implement retry logic for critical API calls
- Set up error boundaries
- Add performance monitoring dashboard (dev only)
- Update tests to use new utilities
- Configure webpack aliases
- Update documentation
Best Practices Summary
- Always use the API service instead of direct fetch
- Monitor critical paths with performance marks
- Debounce user inputs (especially search)
- Throttle scroll handlers with RAF throttle
- Prefetch likely next pages for better UX
- Use feature flags for gradual rollouts
- Implement retry logic for important operations
- Clean up observers to prevent memory leaks
- Cache wisely - use appropriate cache times
- Test with new utilities for better maintainability
Troubleshooting
Module not found
Make sure webpack aliases are configured in webpack.config.js:
resolve: {
alias: {
'@modules': path.resolve(__dirname, 'src/js/modules'),
'@utils': path.resolve(__dirname, 'src/js/modules/utils')
}
}
Performance metrics not showing
Check that the PerformanceObserver API is supported:
if ('PerformanceObserver' in window) {
// Supported
}
Cache not working
Ensure you're using GET requests for cacheable endpoints:
// Cacheable
await api.get('/data');
// Not cacheable (by default)
await api.post('/data', body);
Further Reading
- docs/ARCHITECTURE.md - High-level architecture
- src/js/modules/README.md - Module documentation
- Web Vitals - Google's Core Web Vitals
- Resource Hints - W3C Specification