diff --git a/BUILDING.md b/BUILDING.md index 2acdb7a0..ffba0457 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -2030,8 +2030,11 @@ share_target: { ### B.6 Additional Vite Configurations -**vite.config.ts**: Legacy configuration file (minimal) -**vite.config.mts**: Main configuration entry point +**vite.config.web.mts**: Web platform configuration (used by build-web.sh) +**vite.config.electron.mts**: Electron platform configuration (used by build-electron.sh) +**vite.config.capacitor.mts**: Capacitor mobile configuration (used by npm run build:capacitor) +**vite.config.common.mts**: Shared configuration utilities +**vite.config.utils.mts**: Configuration utility functions **vite.config.dev.mts**: Development-specific configuration **vite.config.optimized.mts**: Optimized build configuration diff --git a/docs/lazy-loading-patterns.md b/docs/lazy-loading-patterns.md deleted file mode 100644 index e1e3ba99..00000000 --- a/docs/lazy-loading-patterns.md +++ /dev/null @@ -1,662 +0,0 @@ -# Vue 3 + Vite + vue-facing-decorator: Lazy Loading Patterns & Best Practices - -## Overview - -This document provides comprehensive guidance on implementing lazy loading and code splitting in Vue 3 applications using Vite and `vue-facing-decorator`. The patterns demonstrated here optimize bundle size, improve initial load times, and enhance user experience through progressive loading. - -**Author:** Matthew Raymer -**Version:** 1.0.0 -**Last Updated:** 2024 - -## Table of Contents - -1. [Why Lazy Loading Matters](#why-lazy-loading-matters) -2. [Vite Configuration for Optimal Code Splitting](#vite-configuration-for-optimal-code-splitting) -3. [Lazy Loading Patterns](#lazy-loading-patterns) -4. [Component-Level Lazy Loading](#component-level-lazy-loading) -5. [Route-Level Lazy Loading](#route-level-lazy-loading) -6. [Library-Level Lazy Loading](#library-level-lazy-loading) -7. [Performance Monitoring](#performance-monitoring) -8. [Best Practices](#best-practices) -9. [Common Pitfalls](#common-pitfalls) -10. [Examples](#examples) - -## Why Lazy Loading Matters - -### Performance Benefits - -- **Faster Initial Load**: Only load code needed for the current view -- **Reduced Bundle Size**: Split large applications into smaller chunks -- **Better Caching**: Independent chunks can be cached separately -- **Improved User Experience**: Progressive loading with loading states - -### When to Use Lazy Loading - -✅ **Good Candidates for Lazy Loading:** -- Heavy components (data processing, 3D rendering) -- Feature-specific components (QR scanner, file uploader) -- Route-based components -- Large third-party libraries (ThreeJS, Chart.js) -- Components with conditional rendering - -❌ **Avoid Lazy Loading:** -- Core UI components used everywhere -- Small utility components -- Components needed for initial render -- Components with frequent usage patterns - -## Vite Configuration for Optimal Code Splitting - -### Enhanced Build Configuration - -```typescript -// vite.config.optimized.mts -export async function createOptimizedBuildConfig(mode: string): Promise { - return { - build: { - rollupOptions: { - output: { - // Enhanced manual chunks for better code splitting - manualChunks: { - // Vendor chunks for better caching - 'vue-vendor': ['vue', 'vue-router', 'pinia'], - 'ui-vendor': ['@fortawesome/fontawesome-svg-core', '@fortawesome/vue-fontawesome'], - 'crypto-vendor': ['@ethersproject/wallet', '@ethersproject/hdnode'], - 'sql-vendor': ['@jlongster/sql.js', 'absurd-sql', 'dexie'], - 'qr-vendor': ['qrcode', 'jsqr', 'vue-qrcode-reader'], - 'three-vendor': ['three', '@tweenjs/tween.js'], - 'utils-vendor': ['luxon', 'ramda', 'zod', 'axios'], - // Platform-specific chunks - ...(isCapacitor && { - 'capacitor-vendor': ['@capacitor/core', '@capacitor/app'] - }) - } - } - } - } - }; -} -``` - -### Key Configuration Features - -1. **Manual Chunks**: Group related dependencies for better caching -2. **Platform-Specific Chunks**: Separate native and web dependencies -3. **Vendor Separation**: Keep third-party libraries separate from app code -4. **Dynamic Imports**: Enable automatic code splitting for dynamic imports - -## Lazy Loading Patterns - -### 1. Component-Level Lazy Loading - -#### Basic Pattern with defineAsyncComponent - -```typescript -import { Component, Vue } from 'vue-facing-decorator'; -import { defineAsyncComponent } from 'vue'; - -@Component({ - name: 'LazyLoadingExample', - components: { - LazyHeavyComponent: defineAsyncComponent({ - loader: () => import('./sub-components/HeavyComponent.vue'), - loadingComponent: { - template: '
Loading...
' - }, - errorComponent: { - template: '
Failed to load
' - }, - delay: 200, - timeout: 10000 - }) - } -}) -export default class LazyLoadingExample extends Vue { - // Component logic -} -``` - -#### Advanced Pattern with Suspense - -```vue - -``` - -### 2. Conditional Loading - -```typescript -@Component({ - name: 'ConditionalLazyLoading' -}) -export default class ConditionalLazyLoading extends Vue { - showHeavyComponent = false; - showQRScanner = false; - - // Lazy load based on user interaction - async toggleHeavyComponent(): Promise { - this.showHeavyComponent = !this.showHeavyComponent; - - if (this.showHeavyComponent) { - // Preload component for better UX - await this.preloadComponent(() => import('./HeavyComponent.vue')); - } - } - - private async preloadComponent(loader: () => Promise): Promise { - try { - await loader(); - } catch (error) { - console.warn('Preload failed:', error); - } - } -} -``` - -### 3. Library-Level Lazy Loading - -```typescript -@Component({ - name: 'LibraryLazyLoading' -}) -export default class LibraryLazyLoading extends Vue { - private threeJS: any = null; - - async loadThreeJS(): Promise { - if (!this.threeJS) { - // Lazy load ThreeJS only when needed - this.threeJS = await import('three'); - console.log('ThreeJS loaded successfully'); - } - } - - async initialize3DScene(): Promise { - await this.loadThreeJS(); - - // Use ThreeJS after loading - const scene = new this.threeJS.Scene(); - // ... rest of 3D setup - } -} -``` - -## Component-Level Lazy Loading - -### Heavy Data Processing Component - -```typescript -// HeavyComponent.vue -@Component({ - name: 'HeavyComponent' -}) -export default class HeavyComponent extends Vue { - @Prop({ required: true }) readonly data!: any; - - async processData(): Promise { - // Process data in batches to avoid blocking UI - const batchSize = 10; - const items = this.data.items; - - for (let i = 0; i < items.length; i += batchSize) { - const batch = items.slice(i, i + batchSize); - await this.processBatch(batch); - - // Allow UI to update - await this.$nextTick(); - await new Promise(resolve => setTimeout(resolve, 10)); - } - } -} -``` - -### Camera-Dependent Component - -```typescript -// QRScannerComponent.vue -@Component({ - name: 'QRScannerComponent' -}) -export default class QRScannerComponent extends Vue { - async mounted(): Promise { - // Initialize camera only when component is mounted - await this.initializeCamera(); - } - - async initializeCamera(): Promise { - try { - const devices = await navigator.mediaDevices.enumerateDevices(); - this.cameras = devices.filter(device => device.kind === 'videoinput'); - this.hasCamera = this.cameras.length > 0; - } catch (error) { - console.error('Camera initialization failed:', error); - this.hasCamera = false; - } - } -} -``` - -## Route-Level Lazy Loading - -### Vue Router Configuration - -```typescript -// router/index.ts -import { createRouter, createWebHistory } from 'vue-router'; - -const routes = [ - { - path: '/', - name: 'Home', - component: () => import('@/views/HomeView.vue') - }, - { - path: '/heavy-feature', - name: 'HeavyFeature', - component: () => import('@/views/HeavyFeatureView.vue'), - // Preload on hover for better UX - beforeEnter: (to, from, next) => { - if (from.name) { - // Preload component when navigating from another route - import('@/views/HeavyFeatureView.vue'); - } - next(); - } - } -]; - -export default createRouter({ - history: createWebHistory(), - routes -}); -``` - -### Route Guards with Lazy Loading - -```typescript -// router/guards.ts -export async function lazyLoadGuard(to: any, from: any, next: any): Promise { - if (to.meta.requiresHeavyFeature) { - try { - // Preload heavy feature before navigation - await import('@/components/HeavyFeature.vue'); - next(); - } catch (error) { - console.error('Failed to load heavy feature:', error); - next('/error'); - } - } else { - next(); - } -} -``` - -## Library-Level Lazy Loading - -### Dynamic Library Loading - -```typescript -@Component({ - name: 'DynamicLibraryLoader' -}) -export default class DynamicLibraryLoader extends Vue { - private libraries: Map = new Map(); - - async loadLibrary(name: string): Promise { - if (this.libraries.has(name)) { - return this.libraries.get(name); - } - - let library: any; - - switch (name) { - case 'three': - library = await import('three'); - break; - case 'chart': - library = await import('chart.js'); - break; - case 'qr': - library = await import('jsqr'); - break; - default: - throw new Error(`Unknown library: ${name}`); - } - - this.libraries.set(name, library); - return library; - } -} -``` - -### Conditional Library Loading - -```typescript -@Component({ - name: 'ConditionalLibraryLoader' -}) -export default class ConditionalLibraryLoader extends Vue { - async loadPlatformSpecificLibrary(): Promise { - if (process.env.VITE_PLATFORM === 'capacitor') { - // Load Capacitor-specific libraries - await import('@capacitor/camera'); - await import('@capacitor/filesystem'); - } else { - // Load web-specific libraries - await import('file-saver'); - await import('jszip'); - } - } -} -``` - -## Performance Monitoring - -### Bundle Analysis - -```bash -# Analyze bundle size -npm run build -npx vite-bundle-analyzer dist - -# Monitor chunk sizes -npx vite-bundle-analyzer dist --mode=treemap -``` - -### Runtime Performance Monitoring - -```typescript -@Component({ - name: 'PerformanceMonitor' -}) -export default class PerformanceMonitor extends Vue { - private performanceMetrics = { - componentLoadTime: 0, - renderTime: 0, - memoryUsage: 0 - }; - - private measureComponentLoad(componentName: string): void { - const startTime = performance.now(); - - return () => { - const loadTime = performance.now() - startTime; - this.performanceMetrics.componentLoadTime = loadTime; - - console.log(`${componentName} loaded in ${loadTime.toFixed(2)}ms`); - - // Send to analytics - this.trackPerformance(componentName, loadTime); - }; - } - - private trackPerformance(component: string, loadTime: number): void { - // Send to analytics service - if (window.gtag) { - window.gtag('event', 'component_load', { - component_name: component, - load_time: loadTime - }); - } - } -} -``` - -## Best Practices - -### 1. Loading States - -Always provide meaningful loading states: - -```vue - -``` - -### 2. Error Handling - -Implement comprehensive error handling: - -```typescript -const LazyComponent = defineAsyncComponent({ - loader: () => import('./HeavyComponent.vue'), - errorComponent: { - template: ` -
-

Failed to load component

-

{{ error.message }}

- -
- `, - props: ['error'], - methods: { - retry() { - this.$emit('retry'); - } - } - }, - onError(error, retry, fail, attempts) { - if (attempts <= 3) { - console.warn(`Retrying component load (attempt ${attempts})`); - retry(); - } else { - console.error('Component failed to load after 3 attempts'); - fail(); - } - } -}); -``` - -### 3. Preloading Strategy - -Implement intelligent preloading: - -```typescript -@Component({ - name: 'SmartPreloader' -}) -export default class SmartPreloader extends Vue { - private preloadQueue: Array<() => Promise> = []; - private isPreloading = false; - - // Preload based on user behavior - onUserHover(componentLoader: () => Promise): void { - this.preloadQueue.push(componentLoader); - this.processPreloadQueue(); - } - - private async processPreloadQueue(): Promise { - if (this.isPreloading || this.preloadQueue.length === 0) return; - - this.isPreloading = true; - - while (this.preloadQueue.length > 0) { - const loader = this.preloadQueue.shift(); - if (loader) { - try { - await loader(); - } catch (error) { - console.warn('Preload failed:', error); - } - } - - // Small delay between preloads - await new Promise(resolve => setTimeout(resolve, 100)); - } - - this.isPreloading = false; - } -} -``` - -### 4. Bundle Optimization - -Optimize bundle splitting: - -```typescript -// vite.config.ts -export default defineConfig({ - build: { - rollupOptions: { - output: { - manualChunks: (id) => { - // Group by feature - if (id.includes('qr')) return 'qr-feature'; - if (id.includes('three')) return '3d-feature'; - if (id.includes('chart')) return 'chart-feature'; - - // Group by vendor - if (id.includes('node_modules')) { - if (id.includes('vue')) return 'vue-vendor'; - if (id.includes('lodash')) return 'utils-vendor'; - return 'vendor'; - } - } - } - } - } -}); -``` - -## Common Pitfalls - -### 1. Over-Lazy Loading - -❌ **Don't lazy load everything:** -```typescript -// Bad: Lazy loading small components -const SmallButton = defineAsyncComponent(() => import('./SmallButton.vue')); -``` - -✅ **Do lazy load strategically:** -```typescript -// Good: Lazy load heavy components only -const HeavyDataProcessor = defineAsyncComponent(() => import('./HeavyDataProcessor.vue')); -``` - -### 2. Missing Loading States - -❌ **Don't leave users hanging:** -```vue - -``` - -✅ **Do provide loading feedback:** -```vue - -``` - -### 3. Ignoring Error States - -❌ **Don't ignore loading failures:** -```typescript -const LazyComponent = defineAsyncComponent(() => import('./Component.vue')); -``` - -✅ **Do handle errors gracefully:** -```typescript -const LazyComponent = defineAsyncComponent({ - loader: () => import('./Component.vue'), - errorComponent: ErrorFallback, - onError: (error, retry, fail) => { - console.error('Component load failed:', error); - // Retry once, then fail - retry(); - } -}); -``` - -## Examples - -### Complete Lazy Loading Example - -See the following files for complete working examples: - -1. **`src/components/LazyLoadingExample.vue`** - Main example component -2. **`src/components/sub-components/HeavyComponent.vue`** - Data processing component -3. **`src/components/sub-components/QRScannerComponent.vue`** - Camera-dependent component -4. **`src/components/sub-components/ThreeJSViewer.vue`** - 3D rendering component -5. **`vite.config.optimized.mts`** - Optimized Vite configuration - -### Usage Example - -```vue - - - -``` - -## Conclusion - -Lazy loading with Vue 3 + Vite + `vue-facing-decorator` provides powerful tools for optimizing application performance. By implementing these patterns strategically, you can significantly improve initial load times while maintaining excellent user experience. - -Remember to: -- Use lazy loading for heavy components and features -- Provide meaningful loading and error states -- Monitor performance and bundle sizes -- Implement intelligent preloading strategies -- Handle errors gracefully - -For more information, see the Vue 3 documentation on [Async Components](https://vuejs.org/guide/components/async.html) and the Vite documentation on [Code Splitting](https://vitejs.dev/guide/features.html#code-splitting). \ No newline at end of file diff --git a/docs/migration-assessment-2025-07-16.md b/docs/migration-assessment-2025-07-16.md index 67b6d4f3..524ea367 100644 --- a/docs/migration-assessment-2025-07-16.md +++ b/docs/migration-assessment-2025-07-16.md @@ -60,7 +60,6 @@ These components are static UI elements, help pages, or simple components that d - **Help pages**: `HelpNotificationTypesView.vue`, `HelpOnboardingView.vue` - **Static views**: `StatisticsView.vue`, `QuickActionBvcView.vue` - **UI components**: `ChoiceButtonDialog.vue`, `EntitySummaryButton.vue` -- **Sub-components**: `HeavyComponent.vue`, `QRScannerComponent.vue`, `ThreeJSViewer.vue` - **Utility components**: `PWAInstallPrompt.vue`, `HiddenDidDialog.vue` #### 🔄 **Remaining Legacy Patterns (4 files)** diff --git a/src/components/sub-components/HeavyComponent.vue b/src/components/sub-components/HeavyComponent.vue deleted file mode 100644 index 6fcd1ad0..00000000 --- a/src/components/sub-components/HeavyComponent.vue +++ /dev/null @@ -1,554 +0,0 @@ - - - - - diff --git a/src/components/sub-components/QRScannerComponent.vue b/src/components/sub-components/QRScannerComponent.vue deleted file mode 100644 index 01301572..00000000 --- a/src/components/sub-components/QRScannerComponent.vue +++ /dev/null @@ -1,721 +0,0 @@ - - - - - diff --git a/src/components/sub-components/ThreeJSViewer.vue b/src/components/sub-components/ThreeJSViewer.vue deleted file mode 100644 index 7080d0bd..00000000 --- a/src/components/sub-components/ThreeJSViewer.vue +++ /dev/null @@ -1,667 +0,0 @@ - - - - - diff --git a/vite.config.mts b/vite.config.mts deleted file mode 100644 index 06baf870..00000000 --- a/vite.config.mts +++ /dev/null @@ -1,70 +0,0 @@ -import { defineConfig } from "vite"; -import vue from "@vitejs/plugin-vue"; -import path from "path"; -import { fileURLToPath } from 'url'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - -export default defineConfig({ - plugins: [vue()], - resolve: { - alias: { - '@': path.resolve(__dirname, 'src'), - '@nostr/tools': path.resolve(__dirname, 'node_modules/@nostr/tools'), - '@nostr/tools/nip06': path.resolve(__dirname, 'node_modules/@nostr/tools/nip06'), - stream: 'stream-browserify', - util: 'util', - crypto: 'crypto-browserify', - assert: 'assert/', - http: 'stream-http', - https: 'https-browserify', - url: 'url/', - zlib: 'browserify-zlib', - path: 'path-browserify', - fs: false, - tty: 'tty-browserify', - net: false, - dns: false, - child_process: false, - os: false - }, - mainFields: ['module', 'jsnext:main', 'jsnext', 'main'], - }, - optimizeDeps: { - include: ['@nostr/tools', '@nostr/tools/nip06', '@jlongster/sql.js'], - esbuildOptions: { - define: { - global: 'globalThis' - } - } - }, - build: { - sourcemap: true, - target: 'esnext', - chunkSizeWarningLimit: 1000, - commonjsOptions: { - include: [/node_modules/], - transformMixedEsModules: true - }, - rollupOptions: { - external: [ - 'stream', 'util', 'crypto', 'http', 'https', 'url', 'zlib', - 'path', 'fs', 'tty', 'assert', 'net', 'dns', 'child_process', 'os' - ], - output: { - globals: { - stream: 'stream', - util: 'util', - crypto: 'crypto', - http: 'http', - https: 'https', - url: 'url', - zlib: 'zlib', - path: 'path', - assert: 'assert', - tty: 'tty' - } - } - } - } -}); \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts deleted file mode 100644 index 56462de4..00000000 --- a/vite.config.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { defineConfig } from "vite"; -import vue from "@vitejs/plugin-vue"; -import path from "path"; - -export default defineConfig({ - plugins: [vue()], - // CORS headers removed to allow images from any domain - resolve: { - alias: { - '@': path.resolve(__dirname, 'src'), - '@nostr/tools': path.resolve(__dirname, 'node_modules/@nostr/tools'), - '@nostr/tools/nip06': path.resolve(__dirname, 'node_modules/@nostr/tools/nip06'), - stream: 'stream-browserify', - util: 'util', - crypto: 'crypto-browserify' - }, - mainFields: ['module', 'jsnext:main', 'jsnext', 'main'], - }, - optimizeDeps: { - include: ['@nostr/tools', '@nostr/tools/nip06', '@jlongster/sql.js'], - esbuildOptions: { - define: { - global: 'globalThis' - } - } - }, - build: { - sourcemap: true, - target: 'esnext', - chunkSizeWarningLimit: 1000, - commonjsOptions: { - include: [/node_modules/], - transformMixedEsModules: true - }, - rollupOptions: { - external: ['stream', 'util', 'crypto'], - output: { - globals: { - stream: 'stream', - util: 'util', - crypto: 'crypto' - } - } - } - }, - assetsInclude: ['**/*.wasm'] -}); \ No newline at end of file