# 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).