forked from trent_larson/crowd-funder-for-time-pwa
docs: update build pattern conversion plan with consistent naming and mode handling
- Change build:* naming from hyphen to colon (build:web-dev → build:web:dev) - Add missing build:web:test and build:web:prod scripts - Update build:electron:dev to include electron startup (build + start) - Remove hardcoded --mode electron to allow proper mode override - Add comprehensive mode override behavior documentation - Fix mode conflicts between hardcoded and passed --mode arguments The plan now properly supports: - Development builds with default --mode development - Testing builds with explicit --mode test override - Production builds with explicit --mode production override - Consistent naming across all platforms (web, capacitor, electron)
This commit is contained in:
569
docs/build-pattern-conversion-plan.md
Normal file
569
docs/build-pattern-conversion-plan.md
Normal file
@@ -0,0 +1,569 @@
|
||||
# Build Pattern Conversion Plan
|
||||
|
||||
**Author**: Matthew Raymer
|
||||
**Date**: 2025-07-09
|
||||
**Status**: 🎯 **PLANNING** - Ready for Implementation
|
||||
|
||||
## Overview
|
||||
|
||||
Convert TimeSafari's build instruction pattern from the current script-based
|
||||
approach to a new Vite `mode`-based pattern that provides better environment
|
||||
management and consistency across all build targets.
|
||||
|
||||
## Why Vite Mode Instead of NODE_ENV?
|
||||
|
||||
### ✅ Vite's Native Mode System
|
||||
|
||||
Vite is designed to work with `mode`, which:
|
||||
|
||||
- Determines the `.env` file to load (e.g. `.env.production`, `.env.test`, etc.)
|
||||
- Is passed to `defineConfig(({ mode }) => {...})` in `vite.config.ts`
|
||||
- Is used to set behavior for dev/prod/test at config level
|
||||
- Provides better integration with Vite's build system
|
||||
|
||||
### 🚫 NODE_ENV Limitations
|
||||
|
||||
`NODE_ENV` is legacy from Webpack-era tooling:
|
||||
|
||||
- You can't change `NODE_ENV` manually and expect Vite to adapt
|
||||
- Vite does not map `NODE_ENV` back to `mode`
|
||||
- It's redundant with `mode` and might conflict with assumptions
|
||||
- Limited integration with Vite's environment loading system
|
||||
|
||||
### Usage Pattern
|
||||
|
||||
```bash
|
||||
# ✅ Correct: Use Vite's mode system
|
||||
vite build --mode production
|
||||
vite build --mode development
|
||||
vite build --mode test
|
||||
|
||||
# ⚠️ Only if third-party libraries require NODE_ENV
|
||||
NODE_ENV=production vite build --mode production
|
||||
```
|
||||
|
||||
### Development vs Build Environments
|
||||
|
||||
**Development Environment:**
|
||||
- **Build with defaults**: `npm run build:*` - Uses `--mode development` by default
|
||||
- **Purpose**: Development builds for testing and debugging
|
||||
- **Output**: Bundled files with development optimizations
|
||||
|
||||
**Testing/Production Environments:**
|
||||
- **Build with explicit mode**: `npm run build:* -- --mode test/production`
|
||||
- **Purpose**: Validate and deploy the bundled application
|
||||
- **Output**: Optimized, bundled files for specific environment
|
||||
|
||||
### Mode Override Behavior
|
||||
|
||||
**How `--mode` Override Works:**
|
||||
|
||||
```bash
|
||||
# Base script (no hardcoded mode)
|
||||
"build:electron": "vite build --config vite.config.electron.mts"
|
||||
|
||||
# Development (uses Vite's default: --mode development)
|
||||
npm run build:electron
|
||||
# Executes: vite build --config vite.config.electron.mts
|
||||
|
||||
# Testing (explicitly overrides with --mode test)
|
||||
npm run build:electron -- --mode test
|
||||
# Executes: vite build --config vite.config.electron.mts --mode test
|
||||
|
||||
# Production (explicitly overrides with --mode production)
|
||||
npm run build:electron -- --mode production
|
||||
# Executes: vite build --config vite.config.electron.mts --mode production
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
- Base scripts have **no hardcoded `--mode`** to allow override
|
||||
- `npm run build:electron` defaults to `--mode development`
|
||||
- `npm run build:electron -- --mode test` overrides to `--mode test`
|
||||
- Vite uses the **last `--mode` argument** if multiple are provided
|
||||
|
||||
### Capacitor Platform-Specific Commands
|
||||
|
||||
Capacitor requires platform-specific sync commands after building:
|
||||
|
||||
```bash
|
||||
# General sync (copies web assets to all platforms)
|
||||
npm run build:capacitor && npx cap sync
|
||||
|
||||
# Platform-specific sync
|
||||
npm run build:capacitor && npx cap sync android
|
||||
npm run build:capacitor && npx cap sync ios
|
||||
|
||||
# Environment-specific with platform sync
|
||||
npm run build:capacitor -- --mode production && npx cap sync android
|
||||
npm run build:capacitor -- --mode development && npx cap sync ios
|
||||
```
|
||||
|
||||
### Electron Platform-Specific Commands
|
||||
|
||||
Electron requires platform-specific build commands after the Vite build:
|
||||
|
||||
```bash
|
||||
# General Electron build (Vite build only)
|
||||
npm run build:electron
|
||||
|
||||
# Platform-specific builds
|
||||
npm run build:electron:windows # Windows executable
|
||||
npm run build:electron:mac # macOS app bundle
|
||||
npm run build:electron:linux # Linux executable
|
||||
|
||||
# Package-specific builds
|
||||
npm run build:electron:appimage # Linux AppImage
|
||||
npm run build:electron:dmg # macOS DMG installer
|
||||
|
||||
# Environment-specific builds
|
||||
npm run build:electron -- --mode development
|
||||
npm run build:electron -- --mode test
|
||||
npm run build:electron -- --mode production
|
||||
|
||||
# Environment-specific with platform builds
|
||||
npm run build:electron:windows -- --mode development
|
||||
npm run build:electron:windows -- --mode test
|
||||
npm run build:electron:windows -- --mode production
|
||||
|
||||
npm run build:electron:mac -- --mode development
|
||||
npm run build:electron:mac -- --mode test
|
||||
npm run build:electron:mac -- --mode production
|
||||
|
||||
npm run build:electron:linux -- --mode development
|
||||
npm run build:electron:linux -- --mode test
|
||||
npm run build:electron:linux -- --mode production
|
||||
|
||||
# Environment-specific with package builds
|
||||
npm run build:electron:appimage -- --mode development
|
||||
npm run build:electron:appimage -- --mode test
|
||||
npm run build:electron:appimage -- --mode production
|
||||
|
||||
npm run build:electron:dmg -- --mode development
|
||||
npm run build:electron:dmg -- --mode test
|
||||
npm run build:electron:dmg -- --mode production
|
||||
```
|
||||
|
||||
## Current State Analysis
|
||||
|
||||
### Existing Build Scripts
|
||||
|
||||
- **Web**: `build:web` - Uses vite.config.web.mts
|
||||
- **Capacitor**: `build:capacitor` - Uses vite.config.capacitor.mts
|
||||
- **Android**: `build:android` - Shell script wrapper
|
||||
- **iOS**: `build:ios` - Shell script wrapper
|
||||
- **Electron**: `build:electron` - Uses vite.config.electron.mts
|
||||
- **Windows**: `build:electron:windows` - Windows executable
|
||||
- **macOS**: `build:electron:mac` - macOS app bundle
|
||||
- **Linux**: `build:electron:linux` - Linux executable
|
||||
- **AppImage**: `build:electron:appimage` - Linux AppImage
|
||||
- **DMG**: `build:electron:dmg` - macOS DMG installer
|
||||
|
||||
### Current `package.json` Scripts
|
||||
|
||||
```json
|
||||
{
|
||||
"build:capacitor": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --mode capacitor --config vite.config.capacitor.mts",
|
||||
"build:web": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --config vite.config.web.mts",
|
||||
"build:electron": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --mode electron --config vite.config.electron.mts"
|
||||
}
|
||||
```
|
||||
|
||||
## Target Pattern
|
||||
|
||||
### New Vite Mode-Based Pattern
|
||||
|
||||
```bash
|
||||
# Development builds (defaults to --mode development)
|
||||
npm run build:web-dev
|
||||
npm run build:capacitor-dev
|
||||
npm run build:electron-dev
|
||||
|
||||
# Testing builds (bundle required)
|
||||
npm run build:web -- --mode test
|
||||
npm run build:capacitor -- --mode test && npx cap sync
|
||||
npm run build:electron -- --mode test
|
||||
|
||||
# Production builds (bundle required)
|
||||
npm run build:web -- --mode production
|
||||
npm run build:capacitor -- --mode production && npx cap sync
|
||||
npm run build:electron -- --mode production
|
||||
|
||||
# Docker builds
|
||||
npm run build:web-docker -- --mode test
|
||||
npm run build:web-docker -- --mode production
|
||||
|
||||
# Capacitor platform-specific builds
|
||||
npm run build:capacitor:android -- --mode test
|
||||
npm run build:capacitor:android -- --mode production
|
||||
|
||||
npm run build:capacitor:ios -- --mode test
|
||||
npm run build:capacitor:ios -- --mode production
|
||||
|
||||
# Electron platform-specific builds
|
||||
npm run build:electron:windows -- --mode test
|
||||
npm run build:electron:windows -- --mode production
|
||||
|
||||
npm run build:electron:mac -- --mode test
|
||||
npm run build:electron:mac -- --mode production
|
||||
|
||||
npm run build:electron:linux -- --mode test
|
||||
npm run build:electron:linux -- --mode production
|
||||
|
||||
# Electron package-specific builds
|
||||
npm run build:electron:appimage -- --mode test
|
||||
npm run build:electron:appimage -- --mode production
|
||||
|
||||
npm run build:electron:dmg -- --mode test
|
||||
npm run build:electron:dmg -- --mode production
|
||||
```
|
||||
|
||||
### New `package.json` Scripts Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"build:web": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --config vite.config.web.mts",
|
||||
"build:web:dev": "npm run build:web",
|
||||
"build:web:test": "npm run build:web -- --mode test",
|
||||
"build:web:prod": "npm run build:web -- --mode production"
|
||||
"build:web:docker": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --config vite.config.web.mts",
|
||||
"build:web:docker:test": "npm run build:web:docker -- --mode test",
|
||||
"build:web:docker:prod": "npm run build:web:docker -- --mode production",
|
||||
|
||||
"build:capacitor": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --mode capacitor --config vite.config.capacitor.mts",
|
||||
"build:capacitor-dev": "npm run build:capacitor",
|
||||
"build:capacitor:sync": "npm run build:capacitor && npx cap sync",
|
||||
"build:capacitor:android": "npm run build:capacitor:sync && npx cap sync android",
|
||||
"build:capacitor:ios": "npm run build:capacitor:sync && npx cap sync ios",
|
||||
|
||||
"build:electron": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --config vite.config.electron.mts",
|
||||
"build:electron:dev": "npm run build:electron && cd electron && npm run electron:start",
|
||||
"build:electron:windows": "npm run build:electron && cd electron && npm run build:windows",
|
||||
"build:electron:mac": "npm run build:electron && cd electron && npm run build:mac",
|
||||
"build:electron:linux": "npm run build:electron && cd electron && npm run build:linux",
|
||||
"build:electron:appimage": "npm run build:electron:linux && cd electron && npm run build:appimage",
|
||||
"build:electron:dmg": "npm run build:electron:mac && cd electron && npm run build:dmg"
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Environment Configuration (Day 1)
|
||||
|
||||
#### 1.1 Update Vite Configurations
|
||||
|
||||
- [ ] **vite.config.web.mts**: Add mode-based configuration
|
||||
- [ ] **vite.config.capacitor.mts**: Add mode-based configuration
|
||||
- [ ] **vite.config.electron.mts**: Add mode-based configuration
|
||||
- [ ] **vite.config.common.mts**: Add environment-specific variables
|
||||
|
||||
#### 1.2 Environment Variables Setup
|
||||
|
||||
- [ ] Create `.env.development` file for development settings
|
||||
- [ ] Create `.env.test` file for testing settings
|
||||
- [ ] Create `.env.production` file for production settings
|
||||
- [ ] Update `.env.example` with new pattern
|
||||
|
||||
#### 1.3 Environment Detection Logic
|
||||
|
||||
```typescript
|
||||
// vite.config.common.mts
|
||||
export default defineConfig(({ mode }) => {
|
||||
const getEnvironmentConfig = (mode: string) => {
|
||||
switch (mode) {
|
||||
case 'production':
|
||||
return { /* production settings */ };
|
||||
case 'test':
|
||||
return { /* testing settings */ };
|
||||
default:
|
||||
return { /* development settings */ };
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
define: {
|
||||
__DEV__: mode === 'development',
|
||||
__TEST__: mode === 'test',
|
||||
__PROD__: mode === 'production'
|
||||
},
|
||||
// ... other config
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
### Phase 2: Package.json Scripts Update (Day 1)
|
||||
|
||||
#### 2.1 Web Build Scripts
|
||||
|
||||
```json
|
||||
{
|
||||
"build:web": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --config vite.config.web.mts",
|
||||
"build:web-dev": "npm run build:web",
|
||||
"build:web-test": "npm run build:web -- --mode test",
|
||||
"build:web-prod": "npm run build:web -- --mode production"
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 Capacitor Build Scripts
|
||||
|
||||
```json
|
||||
{
|
||||
"build:capacitor": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --mode capacitor --config vite.config.capacitor.mts",
|
||||
"build:capacitor-dev": "npm run build:capacitor",
|
||||
"build:capacitor:sync": "npm run build:capacitor && npx cap sync",
|
||||
"build:capacitor:android": "npm run build:capacitor:sync && npx cap sync android",
|
||||
"build:capacitor:ios": "npm run build:capacitor:sync && npx cap sync ios",
|
||||
"build:capacitor-test": "npm run build:capacitor -- --mode test && npx cap sync",
|
||||
"build:capacitor-prod": "npm run build:capacitor -- --mode production && npx cap sync",
|
||||
"build:capacitor:android-test": "npm run build:capacitor -- --mode test && npx cap sync android",
|
||||
"build:capacitor:android-prod": "npm run build:capacitor -- --mode production && npx cap sync android",
|
||||
"build:capacitor:ios-test": "npm run build:capacitor -- --mode test && npx cap sync ios",
|
||||
"build:capacitor:ios-prod": "npm run build:capacitor -- --mode production && npx cap sync ios"
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 Electron Build Scripts
|
||||
|
||||
```json
|
||||
{
|
||||
"build:electron": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --config vite.config.electron.mts",
|
||||
"build:electron-dev": "npm run build:electron",
|
||||
"build:electron:windows": "npm run build:electron && cd electron && npm run build:windows",
|
||||
"build:electron:mac": "npm run build:electron && cd electron && npm run build:mac",
|
||||
"build:electron:linux": "npm run build:electron && cd electron && npm run build:linux",
|
||||
"build:electron:appimage": "npm run build:electron:linux && cd electron && npm run build:appimage",
|
||||
"build:electron:dmg": "npm run build:electron:mac && cd electron && npm run build:dmg",
|
||||
"build:electron-test": "npm run build:electron -- --mode test",
|
||||
"build:electron-prod": "npm run build:electron -- --mode production",
|
||||
"build:electron:windows-test": "npm run build:electron -- --mode test && cd electron && npm run build:windows",
|
||||
"build:electron:windows-prod": "npm run build:electron -- --mode production && cd electron && npm run build:windows",
|
||||
"build:electron:mac-dev": "npm run build:electron -- --mode development && cd electron && npm run build:mac",
|
||||
"build:electron:mac-test": "npm run build:electron -- --mode test && cd electron && npm run build:mac",
|
||||
"build:electron:mac-prod": "npm run build:electron -- --mode production && cd electron && npm run build:mac",
|
||||
"build:electron:linux-test": "npm run build:electron -- --mode test && cd electron && npm run build:linux",
|
||||
"build:electron:linux-prod": "npm run build:electron -- --mode production && cd electron && npm run build:linux"
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.4 Docker Build Scripts
|
||||
|
||||
```json
|
||||
{
|
||||
"build:web-docker": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --config vite.config.web.mts",
|
||||
"build:web-docker-test": "npm run build:web-docker -- --mode test",
|
||||
"build:web-docker-prod": "npm run build:web-docker -- --mode production"
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 3: Shell Script Updates (Day 2)
|
||||
|
||||
#### 3.1 Update build-electron.sh
|
||||
|
||||
- [ ] Add mode-based environment support
|
||||
- [ ] Update environment loading logic
|
||||
- [ ] Add environment-specific build paths
|
||||
- [ ] Update logging to show environment
|
||||
|
||||
#### 3.2 Update build-android.sh
|
||||
|
||||
- [ ] Add mode-based environment support
|
||||
- [ ] Update environment detection
|
||||
- [ ] Add environment-specific configurations
|
||||
|
||||
#### 3.3 Update build-ios.sh
|
||||
|
||||
- [ ] Add mode-based environment support
|
||||
- [ ] Update environment detection
|
||||
- [ ] Add environment-specific configurations
|
||||
|
||||
### Phase 4: Documentation Updates (Day 2)
|
||||
|
||||
#### 4.1 Update BUILDING.md
|
||||
|
||||
- [ ] Document new Vite mode-based pattern
|
||||
- [ ] Update build instructions
|
||||
- [ ] Add environment-specific examples
|
||||
- [ ] Update troubleshooting section
|
||||
|
||||
#### 4.2 Update scripts/README.md
|
||||
|
||||
- [ ] Document new Vite mode-based build patterns
|
||||
- [ ] Update usage examples
|
||||
- [ ] Add environment configuration guide
|
||||
|
||||
#### 4.3 Update CI/CD Documentation
|
||||
|
||||
- [ ] Update GitHub Actions workflows
|
||||
- [ ] Update Docker build instructions
|
||||
- [ ] Update deployment guides
|
||||
|
||||
### Phase 5: Testing & Validation (Day 3)
|
||||
|
||||
#### 5.1 Environment Testing
|
||||
|
||||
- [ ] Test dev environment builds
|
||||
- [ ] Test test environment builds
|
||||
- [ ] Test prod environment builds
|
||||
- [ ] Validate environment variables
|
||||
|
||||
#### 5.2 Platform Testing
|
||||
|
||||
- [ ] Test web builds across environments
|
||||
- [ ] Test capacitor builds across environments
|
||||
- [ ] Test capacitor android sync across environments
|
||||
- [ ] Test capacitor ios sync across environments
|
||||
- [ ] Test electron builds across environments
|
||||
- [ ] Test electron windows builds across environments
|
||||
- [ ] Test electron mac builds across environments
|
||||
- [ ] Test electron linux builds across environments
|
||||
- [ ] Test electron appimage builds across environments
|
||||
- [ ] Test electron dmg builds across environments
|
||||
- [ ] Test docker builds across environments
|
||||
|
||||
#### 5.3 Integration Testing
|
||||
|
||||
- [ ] Test with existing CI/CD pipelines
|
||||
- [ ] Test with existing deployment scripts
|
||||
- [ ] Test with existing development workflows
|
||||
|
||||
## Environment-Specific Configurations
|
||||
|
||||
### Development Environment (--mode development)
|
||||
|
||||
```typescript
|
||||
{
|
||||
VITE_API_URL: 'http://localhost:3000',
|
||||
VITE_DEBUG: 'true',
|
||||
VITE_LOG_LEVEL: 'debug',
|
||||
VITE_ENABLE_DEV_TOOLS: 'true'
|
||||
}
|
||||
```
|
||||
|
||||
### Testing Environment (--mode test)
|
||||
|
||||
```typescript
|
||||
{
|
||||
VITE_API_URL: 'https://test-api.timesafari.com',
|
||||
VITE_DEBUG: 'false',
|
||||
VITE_LOG_LEVEL: 'info',
|
||||
VITE_ENABLE_DEV_TOOLS: 'false'
|
||||
}
|
||||
```
|
||||
|
||||
### Production Environment (--mode production)
|
||||
|
||||
```typescript
|
||||
{
|
||||
VITE_API_URL: 'https://api.timesafari.com',
|
||||
VITE_DEBUG: 'false',
|
||||
VITE_LOG_LEVEL: 'warn',
|
||||
VITE_ENABLE_DEV_TOOLS: 'false'
|
||||
}
|
||||
```
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
### Backward Compatibility
|
||||
|
||||
- [ ] Keep existing script names as aliases
|
||||
- [ ] Add deprecation warnings for old scripts
|
||||
- [ ] Maintain existing CI/CD compatibility
|
||||
- [ ] Provide migration guide for users
|
||||
|
||||
### Gradual Rollout
|
||||
|
||||
1. **Week 1**: Implement new scripts alongside existing ones
|
||||
2. **Week 2**: Update CI/CD to use new pattern
|
||||
3. **Week 3**: Update documentation and guides
|
||||
4. **Week 4**: Deprecate old scripts with warnings
|
||||
|
||||
## Success Metrics
|
||||
|
||||
### Technical Metrics
|
||||
|
||||
- [ ] All builds work with Vite mode-based pattern
|
||||
- [ ] Environment variables properly loaded
|
||||
- [ ] Build artifacts correctly generated
|
||||
- [ ] No regression in existing functionality
|
||||
|
||||
### Process Metrics
|
||||
|
||||
- [ ] Reduced build script complexity
|
||||
- [ ] Improved environment management
|
||||
- [ ] Better developer experience
|
||||
- [ ] Consistent build patterns
|
||||
|
||||
## Risk Assessment
|
||||
|
||||
### Low Risk
|
||||
|
||||
- [ ] Environment variable changes
|
||||
- [ ] Package.json script updates
|
||||
- [ ] Documentation updates
|
||||
|
||||
### Medium Risk
|
||||
|
||||
- [ ] Vite configuration changes (mode-based)
|
||||
- [ ] Shell script modifications
|
||||
- [ ] CI/CD pipeline updates
|
||||
|
||||
### High Risk
|
||||
|
||||
- [ ] Breaking existing build processes
|
||||
- [ ] Environment-specific bugs
|
||||
- [ ] Deployment failures
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
### Immediate Rollback
|
||||
|
||||
- [ ] Revert package.json changes
|
||||
- [ ] Restore original vite configs
|
||||
- [ ] Restore original shell scripts
|
||||
|
||||
### Gradual Rollback
|
||||
|
||||
- [ ] Keep old scripts as primary
|
||||
- [ ] Use new scripts as experimental
|
||||
- [ ] Gather feedback before full migration
|
||||
|
||||
## Timeline
|
||||
|
||||
### Day 1: Foundation
|
||||
|
||||
- [ ] Environment configuration setup
|
||||
- [ ] Package.json script updates
|
||||
- [ ] Basic testing
|
||||
|
||||
### Day 2: Integration
|
||||
|
||||
- [ ] Shell script updates
|
||||
- [ ] Documentation updates
|
||||
- [ ] Integration testing
|
||||
|
||||
### Day 3: Validation
|
||||
|
||||
- [ ] Comprehensive testing
|
||||
- [ ] Performance validation
|
||||
- [ ] Documentation review
|
||||
|
||||
### Day 4: Deployment
|
||||
|
||||
- [ ] CI/CD updates
|
||||
- [ ] Production validation
|
||||
- [ ] User communication
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Review and approve plan**
|
||||
2. **Set up development environment**
|
||||
3. **Begin Phase 1 implementation**
|
||||
4. **Create test cases**
|
||||
5. **Start implementation**
|
||||
|
||||
---
|
||||
|
||||
**Status**: Ready for implementation
|
||||
**Priority**: Medium
|
||||
**Estimated Effort**: 3-4 days
|
||||
**Dependencies**: None
|
||||
**Stakeholders**: Development team, DevOps team
|
||||
662
docs/lazy-loading-patterns.md
Normal file
662
docs/lazy-loading-patterns.md
Normal file
@@ -0,0 +1,662 @@
|
||||
# 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<UserConfig> {
|
||||
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: '<div class="loading">Loading...</div>'
|
||||
},
|
||||
errorComponent: {
|
||||
template: '<div class="error">Failed to load</div>'
|
||||
},
|
||||
delay: 200,
|
||||
timeout: 10000
|
||||
})
|
||||
}
|
||||
})
|
||||
export default class LazyLoadingExample extends Vue {
|
||||
// Component logic
|
||||
}
|
||||
```
|
||||
|
||||
#### Advanced Pattern with Suspense
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<Suspense>
|
||||
<template #default>
|
||||
<LazyHeavyComponent v-if="showComponent" />
|
||||
</template>
|
||||
<template #fallback>
|
||||
<div class="loading-fallback">
|
||||
<div class="spinner"></div>
|
||||
<p>Loading component...</p>
|
||||
</div>
|
||||
</template>
|
||||
</Suspense>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 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<void> {
|
||||
this.showHeavyComponent = !this.showHeavyComponent;
|
||||
|
||||
if (this.showHeavyComponent) {
|
||||
// Preload component for better UX
|
||||
await this.preloadComponent(() => import('./HeavyComponent.vue'));
|
||||
}
|
||||
}
|
||||
|
||||
private async preloadComponent(loader: () => Promise<any>): Promise<void> {
|
||||
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<void> {
|
||||
if (!this.threeJS) {
|
||||
// Lazy load ThreeJS only when needed
|
||||
this.threeJS = await import('three');
|
||||
console.log('ThreeJS loaded successfully');
|
||||
}
|
||||
}
|
||||
|
||||
async initialize3DScene(): Promise<void> {
|
||||
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<void> {
|
||||
// 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<void> {
|
||||
// Initialize camera only when component is mounted
|
||||
await this.initializeCamera();
|
||||
}
|
||||
|
||||
async initializeCamera(): Promise<void> {
|
||||
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<void> {
|
||||
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<string, any> = new Map();
|
||||
|
||||
async loadLibrary(name: string): Promise<any> {
|
||||
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<void> {
|
||||
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
|
||||
<template>
|
||||
<Suspense>
|
||||
<template #default>
|
||||
<LazyComponent />
|
||||
</template>
|
||||
<template #fallback>
|
||||
<div class="loading-state">
|
||||
<div class="spinner"></div>
|
||||
<p>Loading {{ componentName }}...</p>
|
||||
<p class="loading-tip">{{ loadingTip }}</p>
|
||||
</div>
|
||||
</template>
|
||||
</Suspense>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 2. Error Handling
|
||||
|
||||
Implement comprehensive error handling:
|
||||
|
||||
```typescript
|
||||
const LazyComponent = defineAsyncComponent({
|
||||
loader: () => import('./HeavyComponent.vue'),
|
||||
errorComponent: {
|
||||
template: `
|
||||
<div class="error-state">
|
||||
<h3>Failed to load component</h3>
|
||||
<p>{{ error.message }}</p>
|
||||
<button @click="retry">Retry</button>
|
||||
</div>
|
||||
`,
|
||||
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<any>> = [];
|
||||
private isPreloading = false;
|
||||
|
||||
// Preload based on user behavior
|
||||
onUserHover(componentLoader: () => Promise<any>): void {
|
||||
this.preloadQueue.push(componentLoader);
|
||||
this.processPreloadQueue();
|
||||
}
|
||||
|
||||
private async processPreloadQueue(): Promise<void> {
|
||||
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
|
||||
<template>
|
||||
<LazyComponent v-if="show" />
|
||||
</template>
|
||||
```
|
||||
|
||||
✅ **Do provide loading feedback:**
|
||||
```vue
|
||||
<template>
|
||||
<Suspense>
|
||||
<template #default>
|
||||
<LazyComponent v-if="show" />
|
||||
</template>
|
||||
<template #fallback>
|
||||
<LoadingSpinner />
|
||||
</template>
|
||||
</Suspense>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 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
|
||||
<template>
|
||||
<div class="app">
|
||||
<h1>Lazy Loading Demo</h1>
|
||||
|
||||
<LazyLoadingExample
|
||||
:initial-load-heavy="false"
|
||||
@qr-detected="handleQRCode"
|
||||
@model-loaded="handleModelLoaded"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-facing-decorator';
|
||||
import LazyLoadingExample from '@/components/LazyLoadingExample.vue';
|
||||
|
||||
@Component({
|
||||
name: 'App',
|
||||
components: {
|
||||
LazyLoadingExample
|
||||
}
|
||||
})
|
||||
export default class App extends Vue {
|
||||
handleQRCode(data: string): void {
|
||||
console.log('QR code detected:', data);
|
||||
}
|
||||
|
||||
handleModelLoaded(info: any): void {
|
||||
console.log('3D model loaded:', info);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## 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).
|
||||
Reference in New Issue
Block a user