Skip to content

Build Optimization and Deployment Strategies for Element Plus Applications ​

Overview ​

This guide covers comprehensive build optimization techniques and deployment strategies for Element Plus applications, focusing on performance, scalability, and production readiness.

Build Optimization Strategies ​

Bundle Analysis and Optimization ​

typescript
// vite.config.ts - Advanced build optimization
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
import { visualizer } from 'rollup-plugin-visualizer'
import { compression } from 'vite-plugin-compression'
import { createHtmlPlugin } from 'vite-plugin-html'
import legacy from '@vitejs/plugin-legacy'

export default defineConfig({
  plugins: [
    vue(),
    
    // HTML optimization
    createHtmlPlugin({
      minify: true,
      inject: {
        data: {
          title: 'Element Plus App',
          description: 'Modern Vue 3 application with Element Plus'
        }
      }
    }),
    
    // Legacy browser support
    legacy({
      targets: ['defaults', 'not IE 11']
    }),
    
    // Gzip compression
    compression({
      algorithm: 'gzip',
      ext: '.gz'
    }),
    
    // Brotli compression
    compression({
      algorithm: 'brotliCompress',
      ext: '.br'
    }),
    
    // Bundle analyzer
    visualizer({
      filename: 'dist/stats.html',
      open: true,
      gzipSize: true,
      brotliSize: true
    })
  ],
  
  build: {
    target: 'es2015',
    outDir: 'dist',
    assetsDir: 'assets',
    sourcemap: false,
    
    // Chunk size warnings
    chunkSizeWarningLimit: 1000,
    
    rollupOptions: {
      output: {
        // Manual chunk splitting
        manualChunks: {
          // Vendor chunks
          'vue-vendor': ['vue', 'vue-router', 'pinia'],
          'element-plus': ['element-plus'],
          'element-icons': ['@element-plus/icons-vue'],
          
          // Utility chunks
          'utils': ['lodash-es', 'dayjs', 'axios'],
          'charts': ['echarts', 'vue-echarts'],
          
          // Feature chunks
          'auth': [
            './src/stores/auth.ts',
            './src/composables/useAuth.ts',
            './src/api/auth.ts'
          ]
        },
        
        // Asset naming
        chunkFileNames: (chunkInfo) => {
          const facadeModuleId = chunkInfo.facadeModuleId
          if (facadeModuleId) {
            const fileName = facadeModuleId.split('/').pop()?.replace('.vue', '') || 'chunk'
            return `js/${fileName}-[hash].js`
          }
          return 'js/[name]-[hash].js'
        },
        
        entryFileNames: 'js/[name]-[hash].js',
        assetFileNames: (assetInfo) => {
          const info = assetInfo.name?.split('.') || []
          const ext = info[info.length - 1]
          
          if (/\.(png|jpe?g|svg|gif|tiff|bmp|ico)$/i.test(assetInfo.name || '')) {
            return `images/[name]-[hash].${ext}`
          }
          
          if (/\.(woff2?|eot|ttf|otf)$/i.test(assetInfo.name || '')) {
            return `fonts/[name]-[hash].${ext}`
          }
          
          if (ext === 'css') {
            return `css/[name]-[hash].${ext}`
          }
          
          return `assets/[name]-[hash].${ext}`
        }
      },
      
      // External dependencies (for CDN)
      external: process.env.NODE_ENV === 'production' ? [
        // 'vue',
        // 'vue-router',
        // 'element-plus'
      ] : []
    },
    
    // Minification
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true,
        pure_funcs: ['console.log', 'console.info'],
        passes: 2
      },
      mangle: {
        safari10: true
      },
      format: {
        comments: false
      }
    },
    
    // CSS optimization
    cssCodeSplit: true,
    cssMinify: true
  },
  
  // Dependency optimization
  optimizeDeps: {
    include: [
      'vue',
      'vue-router',
      'pinia',
      'element-plus/es',
      'element-plus/es/components/*/style/index',
      '@element-plus/icons-vue',
      'lodash-es',
      'dayjs'
    ],
    exclude: [
      'vue-demi'
    ]
  }
})

Tree Shaking Optimization ​

typescript
// src/plugins/element-plus.ts - Optimized Element Plus setup
import type { App } from 'vue'

// Import only required components
const elementPlusComponents = {
  // Form components
  ElForm: () => import('element-plus/es/components/form'),
  ElFormItem: () => import('element-plus/es/components/form-item'),
  ElInput: () => import('element-plus/es/components/input'),
  ElSelect: () => import('element-plus/es/components/select'),
  ElOption: () => import('element-plus/es/components/option'),
  ElButton: () => import('element-plus/es/components/button'),
  
  // Data display
  ElTable: () => import('element-plus/es/components/table'),
  ElTableColumn: () => import('element-plus/es/components/table-column'),
  ElPagination: () => import('element-plus/es/components/pagination'),
  ElCard: () => import('element-plus/es/components/card'),
  ElTag: () => import('element-plus/es/components/tag'),
  
  // Navigation
  ElMenu: () => import('element-plus/es/components/menu'),
  ElMenuItem: () => import('element-plus/es/components/menu-item'),
  ElSubmenu: () => import('element-plus/es/components/sub-menu'),
  ElBreadcrumb: () => import('element-plus/es/components/breadcrumb'),
  ElBreadcrumbItem: () => import('element-plus/es/components/breadcrumb-item'),
  
  // Feedback
  ElDialog: () => import('element-plus/es/components/dialog'),
  ElDrawer: () => import('element-plus/es/components/drawer'),
  ElPopover: () => import('element-plus/es/components/popover'),
  ElTooltip: () => import('element-plus/es/components/tooltip')
}

// Import only required icons
const elementPlusIcons = {
  Search: () => import('@element-plus/icons-vue/dist/components/Search'),
  Edit: () => import('@element-plus/icons-vue/dist/components/Edit'),
  Delete: () => import('@element-plus/icons-vue/dist/components/Delete'),
  Plus: () => import('@element-plus/icons-vue/dist/components/Plus'),
  Download: () => import('@element-plus/icons-vue/dist/components/Download'),
  Upload: () => import('@element-plus/icons-vue/dist/components/Upload'),
  User: () => import('@element-plus/icons-vue/dist/components/User'),
  Setting: () => import('@element-plus/icons-vue/dist/components/Setting')
}

// Lazy component registration
export async function setupElementPlus(app: App) {
  // Register components lazily
  for (const [name, importFn] of Object.entries(elementPlusComponents)) {
    app.component(name, defineAsyncComponent(importFn))
  }
  
  // Register icons lazily
  for (const [name, importFn] of Object.entries(elementPlusIcons)) {
    app.component(name, defineAsyncComponent(importFn))
  }
  
  // Import and setup global services
  const [ElMessage, ElMessageBox, ElNotification, ElLoading] = await Promise.all([
    import('element-plus/es/components/message'),
    import('element-plus/es/components/message-box'),
    import('element-plus/es/components/notification'),
    import('element-plus/es/components/loading')
  ])
  
  // Global properties
  app.config.globalProperties.$message = ElMessage.default
  app.config.globalProperties.$msgbox = ElMessageBox.default
  app.config.globalProperties.$notify = ElNotification.default
  app.config.globalProperties.$loading = ElLoading.default.service
}

Code Splitting Strategies ​

typescript
// src/router/index.ts - Advanced route-based code splitting
import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'

// Lazy loading with error boundaries
const createAsyncComponent = (importFn: () => Promise<any>, chunkName?: string) => {
  return defineAsyncComponent({
    loader: importFn,
    loadingComponent: () => import('@/components/common/Loading.vue'),
    errorComponent: () => import('@/components/common/ErrorBoundary.vue'),
    delay: 200,
    timeout: 10000
  })
}

// Feature-based route grouping
const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'Home',
    component: createAsyncComponent(
      () => import(/* webpackChunkName: "home" */ '@/views/Home.vue'),
      'home'
    )
  },
  
  // Dashboard module
  {
    path: '/dashboard',
    component: createAsyncComponent(
      () => import(/* webpackChunkName: "dashboard" */ '@/layouts/DashboardLayout.vue'),
      'dashboard'
    ),
    children: [
      {
        path: '',
        name: 'Dashboard',
        component: createAsyncComponent(
          () => import(/* webpackChunkName: "dashboard-home" */ '@/views/dashboard/Home.vue')
        )
      },
      {
        path: 'analytics',
        name: 'Analytics',
        component: createAsyncComponent(
          () => import(/* webpackChunkName: "dashboard-analytics" */ '@/views/dashboard/Analytics.vue')
        )
      },
      {
        path: 'reports',
        name: 'Reports',
        component: createAsyncComponent(
          () => import(/* webpackChunkName: "dashboard-reports" */ '@/views/dashboard/Reports.vue')
        )
      }
    ]
  },
  
  // Admin module (role-based loading)
  {
    path: '/admin',
    component: createAsyncComponent(
      () => import(/* webpackChunkName: "admin" */ '@/layouts/AdminLayout.vue'),
      'admin'
    ),
    meta: { requiresAuth: true, roles: ['admin'] },
    children: [
      {
        path: 'users',
        name: 'UserManagement',
        component: createAsyncComponent(
          () => import(/* webpackChunkName: "admin-users" */ '@/views/admin/Users.vue')
        )
      },
      {
        path: 'settings',
        name: 'SystemSettings',
        component: createAsyncComponent(
          () => import(/* webpackChunkName: "admin-settings" */ '@/views/admin/Settings.vue')
        )
      }
    ]
  }
]

// Dynamic route loading based on user permissions
export const loadUserRoutes = async (userRoles: string[]) => {
  const dynamicRoutes: RouteRecordRaw[] = []
  
  // Load admin routes only for admin users
  if (userRoles.includes('admin')) {
    const adminModule = await import(
      /* webpackChunkName: "admin-routes" */ './modules/admin'
    )
    dynamicRoutes.push(...adminModule.routes)
  }
  
  // Load manager routes for managers
  if (userRoles.includes('manager')) {
    const managerModule = await import(
      /* webpackChunkName: "manager-routes" */ './modules/manager'
    )
    dynamicRoutes.push(...managerModule.routes)
  }
  
  return dynamicRoutes
}

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

Asset Optimization ​

typescript
// scripts/optimize-assets.ts
import { readdir, readFile, writeFile, stat } from 'fs/promises'
import { join, extname } from 'path'
import sharp from 'sharp'
import { optimize } from 'svgo'

interface OptimizationOptions {
  inputDir: string
  outputDir: string
  quality: number
  formats: string[]
}

class AssetOptimizer {
  constructor(private options: OptimizationOptions) {}
  
  async optimizeImages() {
    const { inputDir, outputDir, quality, formats } = this.options
    
    const files = await this.getImageFiles(inputDir)
    
    for (const file of files) {
      const inputPath = join(inputDir, file)
      const ext = extname(file).toLowerCase()
      const baseName = file.replace(ext, '')
      
      if (['.jpg', '.jpeg', '.png'].includes(ext)) {
        // Generate multiple formats
        for (const format of formats) {
          const outputPath = join(outputDir, `${baseName}.${format}`)
          
          await sharp(inputPath)
            .resize(1920, 1080, { fit: 'inside', withoutEnlargement: true })
            .toFormat(format as any, { quality })
            .toFile(outputPath)
          
          console.log(`✅ Optimized: ${file} -> ${baseName}.${format}`)
        }
        
        // Generate responsive images
        const sizes = [480, 768, 1024, 1920]
        for (const size of sizes) {
          const outputPath = join(outputDir, `${baseName}-${size}w.webp`)
          
          await sharp(inputPath)
            .resize(size, null, { withoutEnlargement: true })
            .webp({ quality })
            .toFile(outputPath)
        }
      } else if (ext === '.svg') {
        await this.optimizeSvg(inputPath, join(outputDir, file))
      }
    }
  }
  
  private async optimizeSvg(inputPath: string, outputPath: string) {
    const svgContent = await readFile(inputPath, 'utf-8')
    
    const result = optimize(svgContent, {
      plugins: [
        'preset-default',
        'removeDimensions',
        'removeViewBox',
        'cleanupIDs'
      ]
    })
    
    await writeFile(outputPath, result.data)
    console.log(`✅ Optimized SVG: ${inputPath}`)
  }
  
  private async getImageFiles(dir: string): Promise<string[]> {
    const files = await readdir(dir)
    const imageFiles: string[] = []
    
    for (const file of files) {
      const filePath = join(dir, file)
      const stats = await stat(filePath)
      
      if (stats.isFile()) {
        const ext = extname(file).toLowerCase()
        if (['.jpg', '.jpeg', '.png', '.svg', '.webp'].includes(ext)) {
          imageFiles.push(file)
        }
      }
    }
    
    return imageFiles
  }
}

// Usage
const optimizer = new AssetOptimizer({
  inputDir: 'src/assets/images',
  outputDir: 'dist/assets/images',
  quality: 80,
  formats: ['webp', 'avif']
})

optimizer.optimizeImages().catch(console.error)

Deployment Strategies ​

Multi-Environment Configuration ​

typescript
// config/environments.ts
export interface EnvironmentConfig {
  apiBaseUrl: string
  cdnUrl: string
  enableAnalytics: boolean
  enableErrorReporting: boolean
  logLevel: 'debug' | 'info' | 'warn' | 'error'
  features: {
    enablePWA: boolean
    enableOfflineMode: boolean
    enablePushNotifications: boolean
  }
}

const environments: Record<string, EnvironmentConfig> = {
  development: {
    apiBaseUrl: 'http://localhost:8080/api',
    cdnUrl: '',
    enableAnalytics: false,
    enableErrorReporting: false,
    logLevel: 'debug',
    features: {
      enablePWA: false,
      enableOfflineMode: false,
      enablePushNotifications: false
    }
  },
  
  staging: {
    apiBaseUrl: 'https://staging-api.example.com/api',
    cdnUrl: 'https://staging-cdn.example.com',
    enableAnalytics: true,
    enableErrorReporting: true,
    logLevel: 'info',
    features: {
      enablePWA: true,
      enableOfflineMode: true,
      enablePushNotifications: false
    }
  },
  
  production: {
    apiBaseUrl: 'https://api.example.com/api',
    cdnUrl: 'https://cdn.example.com',
    enableAnalytics: true,
    enableErrorReporting: true,
    logLevel: 'error',
    features: {
      enablePWA: true,
      enableOfflineMode: true,
      enablePushNotifications: true
    }
  }
}

export const getEnvironmentConfig = (): EnvironmentConfig => {
  const env = import.meta.env.MODE || 'development'
  return environments[env] || environments.development
}

Docker Deployment ​

dockerfile
# Dockerfile.production
# Multi-stage build for production
FROM node:18-alpine as build-stage

# Set working directory
WORKDIR /app

# Copy package files
COPY package*.json ./
COPY pnpm-lock.yaml ./

# Install pnpm and dependencies
RUN npm install -g pnpm
RUN pnpm install --frozen-lockfile

# Copy source code
COPY . .

# Build application
ARG BUILD_ENV=production
ENV NODE_ENV=production
RUN pnpm run build:${BUILD_ENV}

# Production stage
FROM nginx:alpine as production-stage

# Install security updates
RUN apk update && apk upgrade

# Copy built assets
COPY --from=build-stage /app/dist /usr/share/nginx/html

# Copy nginx configuration
COPY nginx/nginx.conf /etc/nginx/nginx.conf
COPY nginx/default.conf /etc/nginx/conf.d/default.conf

# Add health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost/ || exit 1

# Create non-root user
RUN addgroup -g 1001 -S nginx && \
    adduser -S -D -H -u 1001 -h /var/cache/nginx -s /sbin/nologin -G nginx -g nginx nginx

# Set ownership
RUN chown -R nginx:nginx /usr/share/nginx/html
RUN chown -R nginx:nginx /var/cache/nginx

# Switch to non-root user
USER nginx

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
nginx
# nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
    use epoll;
    multi_accept on;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    
    # Logging
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';
    
    access_log /var/log/nginx/access.log main;
    
    # Performance
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    
    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types
        text/plain
        text/css
        text/xml
        text/javascript
        application/json
        application/javascript
        application/xml+rss
        application/atom+xml
        image/svg+xml;
    
    # Brotli compression (if module available)
    # brotli on;
    # brotli_comp_level 6;
    # brotli_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
    
    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https:; media-src 'self'; object-src 'none'; child-src 'self'; frame-ancestors 'self'; form-action 'self'; base-uri 'self';" always;
    
    include /etc/nginx/conf.d/*.conf;
}
nginx
# nginx/default.conf
server {
    listen 80;
    server_name localhost;
    root /usr/share/nginx/html;
    index index.html;
    
    # Enable HTTP/2 Server Push
    location = /index.html {
        add_header Link "</css/app.css>; rel=preload; as=style";
        add_header Link "</js/app.js>; rel=preload; as=script";
    }
    
    # Handle client-side routing
    location / {
        try_files $uri $uri/ /index.html;
        
        # Cache control for HTML files
        location ~* \.html$ {
            expires -1;
            add_header Cache-Control "no-cache, no-store, must-revalidate";
        }
    }
    
    # Static assets with long-term caching
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        
        # Enable CORS for fonts
        location ~* \.(woff|woff2|ttf|eot)$ {
            add_header Access-Control-Allow-Origin "*";
        }
    }
    
    # API proxy (if needed)
    location /api/ {
        proxy_pass http://backend:8080/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # Timeouts
        proxy_connect_timeout 30s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;
    }
    
    # Health check endpoint
    location /health {
        access_log off;
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }
    
    # Security
    location ~ /\. {
        deny all;
    }
    
    location ~ ~$ {
        deny all;
    }
}

Kubernetes Deployment ​

yaml
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: element-plus-app
  labels:
    app: element-plus-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: element-plus-app
  template:
    metadata:
      labels:
        app: element-plus-app
    spec:
      containers:
      - name: element-plus-app
        image: your-registry/element-plus-app:latest
        ports:
        - containerPort: 80
        resources:
          requests:
            memory: "64Mi"
            cpu: "50m"
          limits:
            memory: "128Mi"
            cpu: "100m"
        livenessProbe:
          httpGet:
            path: /health
            port: 80
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5
        env:
        - name: NODE_ENV
          value: "production"
---
apiVersion: v1
kind: Service
metadata:
  name: element-plus-app-service
spec:
  selector:
    app: element-plus-app
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: element-plus-app-ingress
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
  tls:
  - hosts:
    - your-domain.com
    secretName: element-plus-app-tls
  rules:
  - host: your-domain.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: element-plus-app-service
            port:
              number: 80

CI/CD Pipeline ​

yaml
# .github/workflows/deploy.yml
name: Build and Deploy

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

env:
  NODE_VERSION: '18'
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: ${{ env.NODE_VERSION }}
        cache: 'pnpm'
    
    - name: Install pnpm
      run: npm install -g pnpm
    
    - name: Install dependencies
      run: pnpm install --frozen-lockfile
    
    - name: Run linting
      run: pnpm run lint
    
    - name: Run type checking
      run: pnpm run type-check
    
    - name: Run unit tests
      run: pnpm run test:unit
    
    - name: Run e2e tests
      run: pnpm run test:e2e
    
    - name: Upload coverage reports
      uses: codecov/codecov-action@v3
      with:
        file: ./coverage/lcov.info

  build:
    needs: test
    runs-on: ubuntu-latest
    if: github.event_name == 'push'
    
    outputs:
      image: ${{ steps.image.outputs.image }}
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: ${{ env.NODE_VERSION }}
        cache: 'pnpm'
    
    - name: Install pnpm
      run: npm install -g pnpm
    
    - name: Install dependencies
      run: pnpm install --frozen-lockfile
    
    - name: Build application
      run: |
        if [[ ${{ github.ref }} == 'refs/heads/main' ]]; then
          pnpm run build:production
        else
          pnpm run build:staging
        fi
    
    - name: Log in to Container Registry
      uses: docker/login-action@v2
      with:
        registry: ${{ env.REGISTRY }}
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}
    
    - name: Extract metadata
      id: meta
      uses: docker/metadata-action@v4
      with:
        images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
        tags: |
          type=ref,event=branch
          type=ref,event=pr
          type=sha,prefix={{branch}}-
    
    - name: Build and push Docker image
      uses: docker/build-push-action@v4
      with:
        context: .
        file: ./Dockerfile.production
        push: true
        tags: ${{ steps.meta.outputs.tags }}
        labels: ${{ steps.meta.outputs.labels }}
    
    - name: Output image
      id: image
      run: echo "image=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}" >> $GITHUB_OUTPUT

  deploy-staging:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/develop'
    environment: staging
    
    steps:
    - name: Deploy to staging
      run: |
        echo "Deploying ${{ needs.build.outputs.image }} to staging"
        # Add your staging deployment commands here

  deploy-production:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    environment: production
    
    steps:
    - name: Deploy to production
      run: |
        echo "Deploying ${{ needs.build.outputs.image }} to production"
        # Add your production deployment commands here

Performance Monitoring ​

typescript
// src/utils/performance.ts
export class PerformanceMonitor {
  private static instance: PerformanceMonitor
  private metrics: Map<string, number[]> = new Map()
  
  static getInstance(): PerformanceMonitor {
    if (!PerformanceMonitor.instance) {
      PerformanceMonitor.instance = new PerformanceMonitor()
    }
    return PerformanceMonitor.instance
  }
  
  // Measure Core Web Vitals
  measureCoreWebVitals() {
    // Largest Contentful Paint
    new PerformanceObserver((list) => {
      const entries = list.getEntries()
      const lastEntry = entries[entries.length - 1]
      this.recordMetric('LCP', lastEntry.startTime)
    }).observe({ entryTypes: ['largest-contentful-paint'] })
    
    // First Input Delay
    new PerformanceObserver((list) => {
      const entries = list.getEntries()
      entries.forEach((entry: any) => {
        this.recordMetric('FID', entry.processingStart - entry.startTime)
      })
    }).observe({ entryTypes: ['first-input'] })
    
    // Cumulative Layout Shift
    new PerformanceObserver((list) => {
      let clsValue = 0
      const entries = list.getEntries()
      
      entries.forEach((entry: any) => {
        if (!entry.hadRecentInput) {
          clsValue += entry.value
        }
      })
      
      this.recordMetric('CLS', clsValue)
    }).observe({ entryTypes: ['layout-shift'] })
  }
  
  // Measure custom metrics
  measureRouteChange(routeName: string) {
    const startTime = performance.now()
    
    return () => {
      const endTime = performance.now()
      const duration = endTime - startTime
      this.recordMetric(`route-${routeName}`, duration)
    }
  }
  
  measureComponentRender(componentName: string) {
    const startTime = performance.now()
    
    return () => {
      const endTime = performance.now()
      const duration = endTime - startTime
      this.recordMetric(`component-${componentName}`, duration)
    }
  }
  
  private recordMetric(name: string, value: number) {
    if (!this.metrics.has(name)) {
      this.metrics.set(name, [])
    }
    
    const values = this.metrics.get(name)!
    values.push(value)
    
    // Keep only last 100 measurements
    if (values.length > 100) {
      values.shift()
    }
    
    // Send to analytics service
    this.sendToAnalytics(name, value)
  }
  
  private sendToAnalytics(metric: string, value: number) {
    // Send to your analytics service
    if (typeof gtag !== 'undefined') {
      gtag('event', 'performance_metric', {
        metric_name: metric,
        metric_value: Math.round(value),
        custom_parameter: 'element_plus_app'
      })
    }
  }
  
  getMetrics() {
    const summary = new Map()
    
    for (const [name, values] of this.metrics) {
      const avg = values.reduce((a, b) => a + b, 0) / values.length
      const min = Math.min(...values)
      const max = Math.max(...values)
      
      summary.set(name, { avg, min, max, count: values.length })
    }
    
    return summary
  }
}

// Usage in main.ts
const monitor = PerformanceMonitor.getInstance()
monitor.measureCoreWebVitals()

This comprehensive guide provides advanced build optimization techniques and deployment strategies for Element Plus applications, ensuring optimal performance and scalable deployment across different environments.

Element Plus Study Guide