构建工具与工程化完全指南

NOTE

本文档涵盖前端构建工具链、模块系统、包管理器、工程化最佳实践及 CI/CD 集成,是 vibecoding 时代从代码到生产环境的完整工程化指南。


目录

  1. 构建工具生态全景
  2. Vite 深度指南
  3. Webpack 与 Rollup
  4. Node.js 与包管理
  5. Monorepo 工具链
  6. 代码质量与 Linting
  7. CI/CD 与自动化

构建工具生态全景

前端构建工具演进

2010-2015: Grunt — 任务运行器时代
    ↓
2012-2018: Gulp — 流式构建时代
    ↓
2013-至今: Webpack — 模块打包时代
    ↓
2018-至今: Rollup — 库打包标准
    ↓
2020-至今: Vite — 即时服务时代
    ↓
2024-至今: Turbopack — Rust 原生打包

核心概念

构建工具的四大职能:

  1. 模块解析:处理 ES Modules、CommonJS、AMD 等模块系统
  2. 依赖打包:将散落的模块合并为可运行的产物
  3. 转换编译:将 TypeScript、Sass、JSX 转换为浏览器可执行代码
  4. 产物优化:Tree-shaking、代码压缩、懒加载

Vite 深度指南

Vite 架构原理

Vite 的核心创新在于开发环境不打包,而是使用原生 ES Module 直接服务源文件:

传统打包流程(开发时):
  源代码 → Webpack 打包(等待 30s+)→ bundle.js → 浏览器

Vite 开发时:
  源代码 → 浏览器直接请求 ES Module(< 100ms 启动)
  按需编译:访问哪个模块,编译哪个模块

Vite 基础配置

// vite.config.js / vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
 
export default defineConfig({
  // 插件
  plugins: [
    react(),
    // vue:(),
    // svelte:(),
  ],
  
  // 基础路径
  base: '/',
  
  // 路径别名
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
      '@components': path.resolve(__dirname, './src/components'),
    },
  },
  
  // 开发服务器
  server: {
    port: 3000,
    host: true,
    open: true,
    proxy: {
      '/api': {
        target: 'http://localhost:8000',
        changeOrigin: true,
      },
    },
  },
  
  // 构建配置
  build: {
    target: 'esnext',
    outDir: 'dist',
    sourcemap: true,
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
        },
      },
    },
  },
  
  // CSS 配置
  css: {
    modules: {
      localsConvention: 'camelCase',
    },
    preprocessorOptions: {
      scss: {
        additionalData: `@import "@/styles/variables.scss";`,
      },
    },
  },
});

Vite 插件生态

# 常用插件
npm install @vitejs/plugin-react           # React 支持
npm install @vitejs/plugin-vue            # Vue 支持
npm install @sveltejs/vite-plugin-svelte  # Svelte 支持
npm install vite-plugin-pwa               # PWA 支持
npm install vite-plugin-svgr              # SVG 作为 React 组件
npm install vite-plugin-checker           # TypeScript 检查
npm install vite-plugin-mock              # Mock 数据
npm install unplugin-vue-components       # 组件自动导入
npm install unplugin-auto-import          # 自动导入 API

环境变量

# .env                 # 所有环境
# .env.local           # 所有环境,git 忽略
# .env.development     # 仅开发环境
# .env.production      # 仅生产环境
# .env.development.local
# .env.production.local
 
# 命名规则:VITE_ 前缀表示客户端暴露
VITE_API_BASE_URL=https://api.example.com
VITE_APP_TITLE=我的应用
 
# 非 VITE_ 前缀的变量不会暴露给客户端
SECRET_KEY=xxx  # 仅服务端可见
// 使用环境变量
const apiUrl = import.meta.env.VITE_API_BASE_URL;
const appTitle = import.meta.env.VITE_APP_TITLE;
 
// 环境判断
if (import.meta.env.PROD) {
  // 生产环境
}
if (import.meta.env.DEV) {
  // 开发环境
}

Vite 生产构建优化

build: {
  // 代码分割策略
  rollupOptions: {
    output: {
      // 手动分包
      manualChunks: (id) => {
        if (id.includes('node_modules')) {
          if (id.includes('react')) return 'react-vendor';
          if (id.includes('lodash')) return 'lodash-vendor';
          return 'vendor';
        }
      },
      
      // 输出文件名哈希
      entryFileNames: 'assets/[name]-[hash].js',
      chunkFileNames: 'assets/[name]-[hash].js',
      assetFileNames: 'assets/[name]-[hash].[ext]',
    },
  },
  
  // 压缩
  minify: 'terser',  // 或 'esbuild'(更快)
  terserOptions: {
    compress: {
      drop_console: true,  // 移除 console
      drop_debugger: true,
    },
  },
  
  // CSS 代码分割
  cssCodeSplit: true,
  
  // 产物大小报告
  reportCompressedSize: true,
  chunkSizeWarningLimit: 500,  // KB
}

Webpack 与 Rollup

Webpack 核心配置

// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
 
module.exports = (env, argv) => {
  const isProduction = argv.mode === 'production';
  
  return {
    entry: './src/index.js',
    
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: isProduction 
        ? '[name].[contenthash].js' 
        : '[name].js',
      clean: true,
      publicPath: '/',
    },
    
    module: {
      rules: [
        {
          test: /\.tsx?$/,
          use: 'ts-loader',
          exclude: /node_modules/,
        },
        {
          test: /\.css$/,
          use: [
            isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
            'css-loader',
            'postcss-loader',
          ],
        },
        {
          test: /\.(png|jpg|gif|svg)$/,
          type: 'asset/resource',
        },
        {
          test: /\.(woff|woff2|eot|ttf|otf)$/,
          type: 'asset/resource',
        },
      ],
    },
    
    plugins: [
      new HtmlWebpackPlugin({
        template: './src/index.html',
        minify: isProduction,
      }),
    ],
    
    optimization: {
      splitChunks: {
        chunks: 'all',
        cacheGroups: {
          vendor: {
            test: /[\\/]node_modules[\\/]/,
            name: 'vendors',
            chunks: 'all',
          },
        },
      },
    },
    
    devServer: {
      static: './dist',
      hot: true,
      port: 3000,
      proxy: {
        '/api': 'http://localhost:8000',
      },
    },
  };
};

Rollup 基础配置

Rollup 专注于库打包,输出干净、Tree-shakable 的代码:

// rollup.config.js
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import terser from '@rollup/plugin-terser';
import typescript from '@rollup/plugin-typescript';
import dts from 'rollup-plugin-dts';
 
export default [
  // ESM 输出
  {
    input: 'src/index.ts',
    output: {
      file: 'dist/index.mjs',
      format: 'esm',
      sourcemap: true,
    },
    plugins: [
      resolve(),
      commonjs(),
      typescript({ tsconfig: './tsconfig.json' }),
    ],
  },
  
  // CJS 输出(兼容 Node.js)
  {
    input: 'src/index.ts',
    output: {
      file: 'dist/index.cjs',
      format: 'cjs',
      sourcemap: true,
    },
    plugins: [
      resolve(),
      commonjs(),
      typescript({ tsconfig: './tsconfig.json' }),
    ],
  },
  
  // 类型声明
  {
    input: 'src/index.ts',
    output: {
      file: 'dist/index.d.ts',
      format: 'esm',
    },
    plugins: [dts()],
  },
];

Node.js 与包管理

包管理器对比

维度npmyarnpnpmbun
安装速度中等极快极快
磁盘占用
node_modules扁平扁平内容寻址内容寻址
lock 文件package-lock.jsonyarn.lockpnpm-lock.yamlbun.lockb
monorepoworkspacesworkspacesworkspaceworkspace

npm Scripts

{
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
    "lint": "eslint src --ext .ts,.tsx",
    "format": "prettier --write \"src/**/*.{ts,tsx,css}\"",
    "test": "vitest",
    "test:coverage": "vitest run --coverage",
    "typecheck": "tsc --noEmit",
    "prepare": "husky install",
    "commit": "cz"
  }
}

package.json 最佳实践

{
  "name": "my-package",
  "version": "1.0.0",
  "type": "module",
  "description": "Package description",
  "keywords": ["keyword1", "keyword2"],
  "license": "MIT",
  "author": "Author Name",
  "main": "dist/index.cjs",
  "module": "dist/index.mjs",
  "types": "dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs",
      "types": "./dist/index.d.ts"
    }
  },
  "files": ["dist"],
  "sideEffects": false,
  "scripts": { ... },
  "dependencies": { ... },
  "devDependencies": { ... },
  "engines": {
    "node": ">=18.0.0"
  }
}

Monorepo 工具链

工作区配置

// package.json (根目录)
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": [
    "packages/*",
    "apps/*"
  ],
  "scripts": {
    "dev": "pnpm --parallel -r run dev",
    "build": "pnpm --filter ./packages/* build",
    "test": "pnpm --recursive run test"
  }
}

Turborepo 配置

// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "lint": {
      "outputs": []
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"]
    }
  }
}

代码质量与 Linting

ESLint 配置

// .eslintrc.js
module.exports = {
  root: true,
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module',
    ecmaFeatures: { jsx: true },
  },
  plugins: ['@typescript-eslint', 'react-hooks'],
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
    'next/core-web-vitals',  // Next.js 项目
    'prettier',  // 禁用冲突规则
  ],
  rules: {
    'no-console': 'warn',
    'prefer-const': 'error',
    '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
  },
  settings: {
    react: { version: 'detect' },
  },
};

Prettier 配置

// .prettierrc
{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5",
  "printWidth": 100,
  "bracketSpacing": true,
  "arrowParens": "always",
  "endOfLine": "lf",
  "jsxSingleQuote": false,
  "jsxBracketSameLine": false
}

Git Hooks (Husky + lint-staged)

# 安装
npm install -D husky lint-staged
npx husky install
// package.json
{
  "lint-staged": {
    "*.{ts,tsx}": ["eslint --fix", "prettier --write"],
    "*.{css,scss}": ["prettier --write"],
    "*.{json,md}": ["prettier --write"]
  }
}
# 添加 hook
npx husky add .husky/pre-commit "npx lint-staged"

CI/CD 与自动化

GitHub Actions

# .github/workflows/ci.yml
name: CI
 
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
 
jobs:
  lint-and-test:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          
      - run: npm ci
      
      - run: npm run lint
        continue-on-error: true
        
      - run: npm run typecheck
        continue-on-error: true
        
      - run: npm run test -- --coverage
        continue-on-error: true
        
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: test-results
          path: coverage/
 
  build:
    runs-on: ubuntu-latest
    needs: lint-and-test
    
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          
      - run: npm ci
      - run: npm run build
      
      - uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/
 
  deploy:
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main'
    
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          
      - run: npm ci
      
      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v25
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          vercel-args: '--prod'

技术概述与定位

构建工具在前端工程化中的地位

构建工具是前端工程化的核心基础设施,它决定了开发体验(DX)、构建性能和应用性能。现代前端开发几乎离不开构建工具:无论是处理 TypeScript、JSX、Sass,还是打包代码、压缩资源、优化图片,构建工具都是必不可少的一环。

┌─────────────────────────────────────────────────────────────────────┐
│                    前端构建工具生态全景图                              │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  1980s-2000s: 静态 HTML + 少量 JS                                  │
│       ↓                                                              │
│  2006: jQuery 时代 - 手写 JS,简单压缩                               │
│       ↓                                                              │
│  2010: Grunt - 任务自动化                                           │
│       ↓                                                              │
│  2012: Gulp - 流式构建                                              │
│       ↓                                                              │
│  2013: Webpack - 模块打包时代开启                                    │
│       ↓                                                              │
│  2015: Rollup - ES Modules + 库打包                                 │
│       ↓                                                              │
│  2017: Parcel - 零配置打包                                           │
│       ↓                                                              │
│  2020: Vite - 基于 ESM 的极速开发体验                               │
│       ↓                                                              │
│  2022: esbuild - Go 语言极速打包                                     │
│       ↓                                                              │
│  2023: Turbopack - Rust 原生打包(Vercel)                          │
│       ↓                                                              │
│  2024: Rolldown - Rollup 的 Rust 实现                               │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

构建工具的核心职责

职责说明关键技术
模块解析处理各种模块系统ESM, CommonJS, AMD, UMD
依赖管理分析和解析依赖图npm, pnpm, yarn
转译编译TypeScript/JSX/Sass → JS/CSSBabel, esbuild, SWC
代码打包合并模块减少请求Tree-shaking, Code-splitting
资源优化压缩、混淆、图片处理Terser, Cssnano, imagemin
产物生成输出浏览器可用的文件HTML/CSS/JS bundles
开发服务本地开发服务器HMR, Live Reload

构建工具性能对比

工具构建速度热更新速度输出质量生态
Vite快 (esbuild)极快 (<100ms)优秀极大
Webpack慢 (~1-5s)优秀极大
Rollup中等中等最佳 (库)
esbuild极快 (10-100x)极快良好增长中
swc极快 (20x)良好增长中
Parcel良好中等
Turbopack极快极快优秀新兴

完整安装与配置

Vite 完整配置指南

基础项目初始化

# 创建 React + TypeScript 项目
npm create vite@latest my-app -- --template react-ts
 
# 创建 Vue + TypeScript 项目
npm create vite@latest my-app -- --template vue-ts
 
# 创建 Svelte 项目
npm create vite@latest my-app -- --template svelte-ts
 
# 创建 vanilla 项目
npm create vite@latest my-app -- --template vanilla-ts

完整 Vite 配置

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import vue from '@vitejs/plugin-vue';
import svelte from '@sveltejs/vite-plugin-svelte';
import path from 'path';
 
// https://vitejs.dev/config/
export default defineConfig({
  // ============================================
  // 第一部分:基础配置
  // ============================================
  
  // 基础路径
  // - '/' 用于 SPA
  // - '/app/' 用于多页应用子路径
  base: '/',
  
  // 环境文件
  envDir: './env',
  envPrefix: ['VITE_', 'PUBLIC_'],
  
  // ============================================
  // 第二部分:路径别名
  // ============================================
  
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
      '@components': path.resolve(__dirname, './src/components'),
      '@views': path.resolve(__dirname, './src/views'),
      '@utils': path.resolve(__dirname, './src/utils'),
      '@stores': path.resolve(__dirname, './src/stores'),
      '@api': path.resolve(__dirname, './src/api'),
      '@assets': path.resolve(__dirname, './src/assets'),
      '@types': path.resolve(__dirname, './src/types'),
    },
    
    // 文件扩展名优先级
    extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json'],
    
    // 手动忽略的模块
    noExternal: ['some-package'],
    external: ['only-cjs-package'],
  },
  
  // ============================================
  // 第三部分:插件配置
  // ============================================
  
  plugins: [
    // React 支持
    react({
      // JSX 配置
      babel: {
        parserOpts: {
          plugins: ['decorators-legacy'],
        },
      },
      // 自动导入 React(可选)
      reactReactRefreshEntrypoints: ['src/main.tsx'],
    }),
    
    // Vue 支持
    vue({
      template: {
        compilerOptions: {
          // Vue 3 模板配置
        },
      },
    }),
    
    // Svelte 支持
    svelte({
      compilerOptions: {
        dev: process.env.NODE_ENV !== 'production',
      },
    }),
  ],
  
  // ============================================
  // 第四部分:开发服务器
  // ============================================
  
  server: {
    // 端口
    port: 3000,
    
    // 主机
    host: true, // 0.0.0.0
    // 或指定域名
    // host: 'my-site.local',
    
    // 自动打开浏览器
    open: true,
    // 或指定路径
    // open: '/docs',
    
    // 代理配置
    proxy: {
      // 代理 API 请求
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        // 重写路径
        // rewrite: (path) => path.replace(/^\/api/, ''),
      },
      // 代理 WebSocket
      '/ws': {
        target: 'ws://localhost:8080',
        ws: true,
      },
    },
    
    // CORS 配置
    cors: true,
    
    // 自定义服务
    middlewareMode: false,
    
    // 端口监听失败时自动换端口
    strictPort: false,
    
    // HMR 配置
    hmr: {
      overlay: true,
    },
    
    // 请求超时
    timeout: 30000,
    
    // 监听文件变化
    watch: {
      ignored: ['**/node_modules/**', '**/dist/**'],
    },
  },
  
  // ============================================
  // 第五部分:预览服务器
  // ============================================
  
  preview: {
    port: 4173,
    host: true,
    open: false,
    proxy: {
      '/api': 'http://localhost:8080',
    },
  },
  
  // ============================================
  // 第六部分:构建配置
  // ============================================
  
  build: {
    // 构建目标
    target: 'esnext',
    // 或指定浏览器范围
    // target: ['es2020', 'edge88', 'firefox78', 'chrome87', 'safari14'],
    
    // 输出目录
    outDir: 'dist',
    
    // 生成 sourcemap
    sourcemap: false,
    // 或指定类型
    // sourcemap: 'hidden', // 生产隐藏 sourcemap
    
    // 清理输出目录
    cleanOutDir: true,
    
    // 单文件低于此阈值内联为 base64
    assetsInlineLimit: 4096, // bytes
    
    // CSS 代码分割
    cssCodeSplit: true,
    
    // 构建体积报告
    reportCompressedSize: true,
    chunkSizeWarningLimit: 500, // KB
    
    // 压缩引擎
    minify: 'terser', // 或 'esbuild' (更快)
    
    // Terser 配置
    terserOptions: {
      compress: {
        drop_console: true, // 移除 console
        drop_debugger: true, // 移除 debugger
        pure_funcs: ['console.log', 'console.info'],
      },
      format: {
        comments: false, // 移除注释
      },
    },
    
    // Rollup 配置
    rollupOptions: {
      // 输入文件
      input: {
        main: path.resolve(__dirname, 'index.html'),
        // 多入口
        // nested: path.resolve(__dirname, 'nested/index.html'),
      },
      
      // 输出配置
      output: {
        // 输出文件名(支持 hash)
        entryFileNames: 'assets/[name]-[hash].js',
        chunkFileNames: 'assets/[name]-[hash].js',
        assetFileNames: 'assets/[name]-[hash].[ext]',
        
        // 分包策略
        manualChunks: (id) => {
          // 第三方库分包
          if (id.includes('node_modules')) {
            // React 生态
            if (id.includes('react') || id.includes('react-dom')) {
              return 'react-vendor';
            }
            // Vue 生态
            if (id.includes('vue')) {
              return 'vue-vendor';
            }
            // 大型工具库
            if (id.includes('lodash') || id.includes('date-fns')) {
              return 'utils-vendor';
            }
            // 其他第三方库
            return 'vendor';
          }
        },
        
        // 块分割
        // manualChunks: 'all',
        
        // 块保留
        // preserveEntrySignatures: 'strict',
        
        // 导出格式
        // format: 'es',
        
        // 导出文件名
        // exports: 'named',
        
        // 折叠模块
        // inlineDynamicImports: true,
        
        // 外部链接
        // external: ['react', 'react-dom'],
      },
      
      // 缓存
      cache: true,
      
      // 输出警告
      onwarn(warning, warn) {
        // 忽略循环依赖警告
        if (warning.code === 'CIRCULAR_DEPENDENCY') {
          return;
        }
        warn(warning);
      },
    },
    
    // 库模式
    // lib: {
    //   entry: path.resolve(__dirname, 'lib/index.ts'),
    //   name: 'MyLib',
    //   formats: ['es', 'umd', 'iife'],
    //   fileName: (format) => `my-lib.${format}.js`,
    // },
  },
  
  // ============================================
  // 第七部分:CSS 配置
  // ============================================
  
  css: {
    // CSS Modules 配置
    modules: {
      // 类名生成规则
      generateScopedName: '[name]__[local]___[hash:base64:5]',
      // 本地标识符规则
      localsConvention: 'camelCase',
    },
    
    // Preprocessor 配置
    preprocessorOptions: {
      // SCSS
      scss: {
        // 注入全局变量
        additionalData: `
          @import "@/styles/variables.scss";
          @import "@/styles/mixins.scss";
        `,
        // API 选项
        api: 'modern-compiler', // 或 'modern' 或 'legacy'
      },
      
      // Less
      less: {
        math: 'always',
        javascriptEnabled: true,
      },
      
      // Stylus
      stylus: {
        imports: [path.resolve(__dirname, './src/styles/variables.styl')],
      },
    },
    
    // CSS 声明
    devSourcemap: true,
  },
  
  // ============================================
  // 第八部分:优化配置
  // ============================================
  
  optimizeDeps: {
    // 预构建依赖
    include: ['react', 'react-dom', 'lodash'],
    
    // 排除依赖
    exclude: ['@vite/client'],
    
    // 强制预构建
    force: false,
    
    // 入口点
    entries: ['index.html'],
  },
  
  // ============================================
  // 第九部分:SSR 配置
  // ============================================
  
  ssr: {
    // SSR 入口
    noExternal: ['vue', '@vue/'],
    
    // 外部包
    external: ['vite-plugin-node-polyfills'],
  },
});

环境变量文件

# .env                 # 所有环境
# .env.local          # 所有环境,git 忽略
# .env.development     # 仅开发
# .env.production      # 仅生产
# .env.development.local
# .env.production.local
 
# 命名规则
# VITE_ 开头的变量会暴露给客户端
VITE_API_BASE_URL=https://api.example.com
VITE_APP_TITLE=我的应用
VITE_UPLOAD_LIMIT=10485760
 
# 非 VITE_ 开头的变量不会暴露给客户端
NODE_ENV=development
SECRET_KEY=xxx
// src/utils/env.ts
// 环境变量类型定义
interface ImportMetaEnv {
  readonly VITE_API_BASE_URL: string;
  readonly VITE_APP_TITLE: string;
  readonly VITE_UPLOAD_LIMIT: string;
}
 
interface ImportMeta {
  readonly env: ImportMetaEnv;
}
 
// 使用
const apiUrl = import.meta.env.VITE_API_BASE_URL;
const appTitle = import.meta.env.VITE_APP_TITLE;
 
// 环境判断
if (import.meta.env.PROD) {
  // 生产环境
}
 
if (import.meta.env.DEV) {
  // 开发环境
}
 
if (import.meta.env.MODE === 'staging') {
  // 预发布环境
}

Webpack 5 完整配置

基础 Webpack 配置

// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const { VueLoaderPlugin } = require('vue-loader');
 
module.exports = (env, argv) => {
  const isProduction = argv.mode === 'production';
  
  return {
    // 入口
    entry: './src/index.ts',
    
    // 输出
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: isProduction 
        ? 'js/[name].[contenthash:8].js' 
        : 'js/[name].js',
      chunkFilename: isProduction 
        ? 'js/[name].[contenthash:8].chunk.js' 
        : 'js/[name].chunk.js',
      assetModuleFilename: 'assets/[name].[hash:8][ext]',
      clean: true,
      publicPath: '/',
    },
    
    // 模式
    mode: isProduction ? 'production' : 'development',
    
    // 源码映射
    devtool: isProduction ? 'hidden-source-map' : 'eval-source-map',
    
    // 解析
    resolve: {
      extensions: ['.tsx', '.ts', '.jsx', '.js', '.json'],
      alias: {
        '@': path.resolve(__dirname, 'src'),
        '@components': path.resolve(__dirname, 'src/components'),
        '@utils': path.resolve(__dirname, 'src/utils'),
      },
    },
    
    // 模块
    module: {
      rules: [
        // TypeScript
        {
          test: /\.tsx?$/,
          use: 'ts-loader',
          exclude: /node_modules/,
        },
        
        // Vue
        {
          test: /\.vue$/,
          loader: 'vue-loader',
        },
        
        // CSS
        {
          test: /\.css$/,
          use: [
            isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
            'css-loader',
            'postcss-loader',
          ],
        },
        
        // SCSS
        {
          test: /\.scss$/,
          use: [
            isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
            'css-loader',
            'postcss-loader',
            'sass-loader',
          ],
        },
        
        // 图片
        {
          test: /\.(png|jpe?g|gif|svg|webp|ico)$/i,
          type: 'asset/resource',
          generator: {
            filename: 'images/[name].[hash:8][ext]',
          },
        },
        
        // 字体
        {
          test: /\.(woff2?|eot|ttf|otf)$/i,
          type: 'asset/resource',
          generator: {
            filename: 'fonts/[name].[hash:8][ext]',
          },
        },
        
        // 音频/视频
        {
          test: /\.(mp3|wav|ogg|mp4|webm)$/i,
          type: 'asset/resource',
          generator: {
            filename: 'media/[name].[hash:8][ext]',
          },
        },
        
        // JSON
        {
          test: /\.json$/i,
          type: 'json',
        },
        
        // CSV/XML
        {
          test: /\.(csv|xml)$/i,
          type: 'asset/resource',
        },
        
        // 替换
        {
          test: /\.(txt)$/i,
          type: 'raw',
        },
      ],
    },
    
    // 插件
    plugins: [
      new VueLoaderPlugin(),
      
      new HtmlWebpackPlugin({
        template: './public/index.html',
        filename: 'index.html',
        inject: 'body',
        minify: isProduction ? {
          removeComments: true,
          collapseWhitespace: true,
          removeAttributeQuotes: true,
        } : false,
      }),
      
      isProduction && new MiniCssExtractPlugin({
        filename: 'css/[name].[contenthash:8].css',
        chunkFilename: 'css/[name].[contenthash:8].chunk.css',
      }),
    ].filter(Boolean),
    
    // 优化
    optimization: {
      minimize: isProduction,
      minimizer: [
        new TerserPlugin({
          terserOptions: {
            compress: {
              drop_console: true,
              drop_debugger: true,
            },
          },
        }),
        new CssMinimizerPlugin(),
      ],
      splitChunks: {
        chunks: 'all',
        cacheGroups: {
          defaultVendors: {
            test: /[\\/]node_modules[\\/]/,
            name: 'vendors',
            chunks: 'all',
            priority: -10,
          },
          react: {
            test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
            name: 'react',
            chunks: 'all',
          },
          common: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true,
          },
        },
      },
      runtimeChunk: 'single',
      moduleIds: 'deterministic',
      chunkIds: 'deterministic',
    },
    
    // 开发服务器
    devServer: {
      static: {
        directory: path.join(__dirname, 'public'),
      },
      port: 3000,
      hot: true,
      open: true,
      historyApiFallback: true,
      proxy: [
        {
          context: ['/api'],
          target: 'http://localhost:8080',
          changeOrigin: true,
        },
      ],
    },
    
    // 缓存
    cache: {
      type: 'filesystem',
      buildDependencies: {
        config: [__filename],
      },
    },
    
    // 性能
    performance: {
      hints: isProduction ? 'warning' : false,
      maxEntrypointSize: 512000,
      maxAssetSize: 512000,
    },
    
    // 统计
    stats: {
      colors: true,
      modules: false,
      children: false,
      chunks: false,
      chunkModules: false,
    },
  };
};

PostCSS 配置

// postcss.config.js
module.exports = {
  plugins: {
    // Tailwind CSS
    tailwindcss: {},
    
    // Autoprefixer
    autoprefixer: {
      grid: 'autoplace',
      flexbox: 'no-2009',
    },
  },
};
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ['./src/**/*.{js,jsx,ts,tsx}'],
  darkMode: 'class',
  theme: {
    extend: {},
  },
  plugins: [],
};

核心概念详解

核心概念一:模块系统演进

CommonJS vs ES Modules

┌─────────────────────────────────────────────────────────────────────┐
│                    模块系统对比                                       │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  CommonJS (Node.js)              ES Modules (ES6+)                  │
│  ══════════════════              ══════════════════                │
│                                                                      │
│  // 导出                                                              │
│  module.exports = {              // 导出                              │
│    add,                         export const add = (...) => {...};  │
│    multiply                     export default function(...) {...}  │
│  };                             export { add, multiply };          │
│                                                                      │
│  // 导入                                                              │
│  const { add } = require('./');  import { add } from './';           │
│  const utils = require('./');    import utils from './';             │
│                                                                      │
│  // 同步加载                   // 异步/静态解析                      │
│  // 运行时                    // 编译时                             │
│  // 可以动态                   // 必须顶层静态                       │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

模块打包原理

┌─────────────────────────────────────────────────────────────────────┐
│                    模块打包过程                                       │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  源码                                                                 │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │  import React from 'react';                                  │    │
│  │  import { Button } from './Button';                          │    │
│  │  import styles from './Button.css';                          │    │
│  │                                                              │    │
│  │  export default function Button() {                         │    │
│  │    return <button className={styles.button}>Click</button>; │    │
│  │  }                                                           │    │
│  └─────────────────────────────────────────────────────────────┘    │
│                            ↓                                         │
│  依赖图构建                                                          │
│                            ↓                                         │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │  Button.tsx                                                 │    │
│  │    ├── react                                                │    │
│  │    ├── react-dom                                            │    │
│  │    ├── ./Button.tsx                                         │    │
│  │    └── ./Button.css                                         │    │
│  │                                                              │    │
│  │  (递归解析所有依赖)                                          │    │
│  └─────────────────────────────────────────────────────────────┘    │
│                            ↓                                         │
│  依赖图 + 模块 ID                                                   │
│                            ↓                                         │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │  Entry: src/index.tsx (id: 0)                               │    │
│  │  Module 1: node_modules/react/index.js                      │    │
│  │  Module 2: node_modules/react-dom/index.js                  │    │
│  │  Module 3: src/components/Button.tsx (id: 3)                │    │
│  │  Module 4: src/components/Button.css (id: 4)                │    │
│  └─────────────────────────────────────────────────────────────┘    │
│                            ↓                                         │
│  转换与打包                                                          │
│                            ↓                                         │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │  (1) TypeScript → JavaScript                               │    │
│  │  (2) JSX → React.createElement                             │    │
│  │  (3) CSS Modules → 唯一类名                                │    │
│  │  (4) ESM → 兼容格式 (可选)                                  │    │
│  └─────────────────────────────────────────────────────────────┘    │
│                            ↓                                         │
│  输出                                                                 │
│                            ↓                                         │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │  dist/                                                      │    │
│  │  ├── index.html                                             │    │
│  │  ├── assets/                                                │    │
│  │  │   ├── index-a1b2c3d4.js     (主包)                      │    │
│  │  │   ├── vendor-c5d6e7f8.js    (第三方库)                  │    │
│  │  │   └── Button-g9h0i1j2.css   (组件样式)                  │    │
│  │  └── static/                                                │    │
│  └─────────────────────────────────────────────────────────────┘    │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

核心概念二:代码分割策略

为什么要代码分割

┌─────────────────────────────────────────────────────────────────────┐
│                    代码分割的价值                                     │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  未分割:                                                             │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │  bundle.js (单文件,2MB)                                    │    │
│  │                                                              │    │
│  │  ████████████████████████████████████████████████████████  │    │
│  │  0%                               100%                      │    │
│  │                                                              │    │
│  │  问题:需要等待整个文件下载完成才开始执行                       │    │
│  │  用户体验:白屏时间长,首次交互延迟高                          │    │
│  └─────────────────────────────────────────────────────────────┘    │
│                                                                      │
│  分割后:                                                             │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │  vendor.js (400KB)      main.js (300KB)    charts.js (500KB)│    │
│  │  ████████████          ██████████         ████████████████ │    │
│  │                                                              │    │
│  │  ↓                    ↓                   ↓                  │    │
│  │  立即加载            首屏必需           按需加载              │    │
│  │  (缓存复用)          关键路径           (懒加载)             │    │
│  │                                                              │    │
│  │  优势:并行下载、按需执行、更好的缓存策略                      │    │
│  └─────────────────────────────────────────────────────────────┘    │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

Vite 代码分割配置

// vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        // 手动分包
        manualChunks: (id) => {
          // 框架
          if (id.includes('node_modules')) {
            if (id.includes('react')) {
              return 'react-vendor';
            }
            if (id.includes('vue')) {
              return 'vue-vendor';
            }
            if (id.includes('@mui') || id.includes('antd')) {
              return 'ui-vendor';
            }
            if (id.includes('date-fns') || id.includes('dayjs')) {
              return 'date-vendor';
            }
            if (id.includes('lodash') || id.includes('ramda')) {
              return 'utils-vendor';
            }
            if (id.includes('echarts') || id.includes('chart')) {
              return 'charts-vendor';
            }
            if (id.includes('three')) {
              return 'three-vendor';
            }
            return 'vendor';
          }
        },
        
        // 块保留策略
        preserveEntrySignatures: 'strict', // 或 'allow-extension' 或 false
      },
    },
    
    // 自动分包(实验性)
    // rollupOptions: {
    //   output: {
    //     manualChunks: undefined,
    //   },
    // },
  },
});

React 懒加载示例

import { lazy, Suspense } from 'react';
 
// 方式一:React.lazy 懒加载组件
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
const Profile = lazy(() => import('./pages/Profile'));
 
// 方式二:带预加载的懒加载
const DashboardPromise = import('./pages/Dashboard');
export const DashboardLazy = lazy(() => DashboardPromise);
 
// 预加载(在鼠标悬停时)
const handleMouseEnter = () => {
  DashboardPromise; // 开始加载
};
 
// 使用
function App() {
  return (
    <Suspense fallback={<Loading />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
        <Route path="/profile" element={<Profile />} />
      </Routes>
    </Suspense>
  );
}
 
// 方式三:路由级别的懒加载
const routes = [
  {
    path: '/dashboard',
    component: lazy(() => import('./pages/Dashboard')),
  },
  {
    path: '/settings',
    component: lazy(() => import('./pages/Settings')),
  },
];

核心概念三:Tree Shaking 原理

Tree Shaking 工作机制

┌─────────────────────────────────────────────────────────────────────┐
│                    Tree Shaking 工作原理                              │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  源码                                                                 │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │  // utils.js                                                 │    │
│  │  export function add(a, b) { return a + b; }               │    │
│  │  export function multiply(a, b) { return a * b; }          │    │
│  │  export function subtract(a, b) { return a - b; }          │    │
│  │                                                              │    │
│  │  // main.js                                                  │    │
│  │  import { add } from './utils';                             │    │
│  │  console.log(add(1, 2));                                   │    │
│  └─────────────────────────────────────────────────────────────┘    │
│                            ↓                                         │
│  分析(标记)                                                          │
│                            ↓                                         │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │  ✓ add - 被使用,保留                                        │    │
│  │  ✗ multiply - 未使用,标记为 dead code                       │    │
│  │  ✗ subtract - 未使用,标记为 dead code                       │    │
│  └─────────────────────────────────────────────────────────────┘    │
│                            ↓                                         │
│  移除                                                                 │
│                            ↓                                         │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │  // 输出                                                    │    │
│  │  function add(a, b) { return a + b; }                     │    │
│  │  export { add };                                           │    │
│  │  console.log(add(1, 2));                                   │    │
│  └─────────────────────────────────────────────────────────────┘    │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

Tree Shaking 最佳实践

// 1. 使用 ES Modules(静态导入)
import { add } from './utils'; // ✓ 可 Tree Shaking
 
// 2. 避免 CommonJS(动态导入)
const utils = require('./utils'); // ✗ 不可 Tree Shaking
const { add } = utils;
 
// 3. 避免副作用
// package.json 中声明 sideEffects
{
  "sideEffects": [
    "*.css",
    "*.scss",
    "./src/polyfills.js"
  ]
}
 
// 4. 使用纯净函数
// 好的例子
export const add = (a, b) => a + b;
 
// 不好的例子(有副作用)
let result = 0;
export const add = (a, b) => {
  result = a + b; // 副作用
  return result;
};
 
// 5. 条件导出时使用显式导出
// 好的例子
export { add } from './utils';
export { multiply } from './math-utils';
export { subtract } from './math-utils';

核心概念四:开发服务器与 HMR

Vite 开发服务器原理

┌─────────────────────────────────────────────────────────────────────┐
│                    Vite 开发服务器架构                                │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  浏览器                                                               │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │  ES Modules 请求                                           │    │
│  │  <script type="module" src="/src/main.ts"></script>       │    │
│  └─────────────────────────────────────────────────────────────┘    │
│                            ↓                                         │
│                      HTTP/2 或 HTTP/1.1                             │
│                            ↓                                         │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │                    Vite Dev Server                         │    │
│  │                                                              │    │
│  │  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐   │    │
│  │  │   静态文件   │    │  Transform  │    │    HMR      │   │    │
│  │  │   服务       │    │   中间件     │    │   Engine    │   │    │
│  │  └─────────────┘    └─────────────┘    └─────────────┘   │    │
│  │         ↓                  ↓                  ↓           │    │
│  │  ┌─────────────────────────────────────────────────────┐  │    │
│  │  │                    esbuild                          │  │    │
│  │  │  - TypeScript/JSX → JS (30ms)                      │  │    │
│  │  │  - 单文件转换,不打包整个项目                         │  │    │
│  │  │  - 编译后的模块直接返回给浏览器                       │  │    │
│  │  └─────────────────────────────────────────────────────┘  │    │
│  │                                                              │    │
│  └─────────────────────────────────────────────────────────────┘    │
│                                                                      │
│  对比 Webpack:                                                       │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │  Webpack Dev Server                                        │    │
│  │  - 启动时打包整个项目 (30s+)                                │    │
│  │  - HMR 时重新打包相关模块 (1-5s)                           │    │
│  │  - 返回打包后的完整 bundle                                  │    │
│  └─────────────────────────────────────────────────────────────┘    │
│                                                                      │
│  Vite: 即时启动 + 即时 HMR                                           │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

HMR 边界与处理

// HMR 边界示例
// 当这些模块变化时,整个模块及其依赖都需要重新加载
 
// 1. 状态管理(Redux/Zustand)
if (import.meta.hot) {
  import.meta.hot.accept(['./store'], (newModule) => {
    // 重新初始化 store
  });
}
 
// 2. 路由组件
// 路由组件变化通常需要完整重载页面
// Vite 会自动处理
 
// 3. 全局样式
// 样式变化通常不影响 JS 逻辑
 
// 4. Context 提供者
// Context 变化需要重新渲染子树
function App() {
  const [count, setCount] = useState(0);
  
  return (
    <CountContext.Provider value={{ count, setCount }}>
      <Child />
    </CountContext.Provider>
  );
}

常用命令与操作

Vite CLI

# 创建项目
npm create vite@latest my-app -- --template react-ts
 
# 开发服务器
npm run dev
 
# 开发服务器(指定端口)
npm run dev -- --port 5173
 
# 开发服务器(打开浏览器)
npm run dev -- --open
 
# 生产构建
npm run build
 
# 预览构建结果
npm run preview
 
# 预览(指定端口)
npm run preview -- --port 4173
 
# 类型检查
npm run typecheck
 
# Lint
npm run lint

Webpack CLI

# 开发服务器
npx webpack serve
 
# 指定配置
npx webpack serve --config webpack.prod.config.js
 
# 生产构建
npx webpack --mode production
 
# 开发构建
npx webpack --mode development
 
# 监听模式
npx webpack --watch
 
# 分析 bundle
npx webpack --profile --json > stats.json

Rollup CLI

# 构建
npx rollup -c
 
# 监听模式
npx rollup -c -w
 
# 指定输入输出
npx rollup src/main.js --file dist/bundle.js --format iife
 
# 指定格式
npx rollup src/main.js --format es --file dist/es.js
npx rollup src/main.js --format cjs --file dist/cjs.js
npx rollup src/main.js --format umd --name MyLib --file dist/umd.js

pnpm/npm Scripts

{
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc --noEmit && vite build",
    "preview": "vite preview",
    "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
    "format": "prettier --write src/**/*.{vue,js,jsx,ts,tsx}",
    "typecheck": "vue-tsc --noEmit",
    "test": "vitest",
    "test:coverage": "vitest run --coverage",
    "test:e2e": "playwright test",
    "prepare": "husky install",
    "storybook": "storybook dev -p 6006",
    "build-storybook": "storybook build"
  }
}

高级配置与技巧

Vite 插件开发

插件结构

// my-vite-plugin.ts
import type { Plugin } from 'vite';
 
export default function myPlugin(options = {}): Plugin {
  return {
    name: 'my-plugin',
    
    // 配置阶段
    config(config) {
      // 修改配置
    },
    
    // 配置解析后
    configResolved(resolvedConfig) {
      // 读取已解析的配置
    },
    
    // 解析 ID
    resolveId(source, importer, options) {
      // 拦截导入
      if (source === 'virtual-module') {
        return source; // 返回 ID 表示已处理
      }
      return null; // 继续处理
    },
    
    // 加载模块
    load(id) {
      if (id === 'virtual-module') {
        return `export const msg = "virtual module"`;
      }
    },
    
    // 转换代码
    transform(code, id) {
      if (id.endsWith('.vue')) {
        // 转换 Vue 文件
        return {
          code: transformedCode,
          map: null, // 或 source map
        };
      }
    },
    
    // 转换模块内容后
    transformIndexHtml(html) {
      return html;
    },
    
    // 构建阶段
    buildStart() {
      // 开始构建
    },
    
    // 生成资源
    generateBundle(options, bundle) {
      // 修改产物
      const asset = bundle['asset.js'];
      if (asset) {
        asset.source = 'modified content';
      }
    },
    
    // 构建结束
    closeBundle() {
      // 构建结束
    },
  };
}

常用 Vite 插件

# UI 框架
npm install @vitejs/plugin-react        # React
npm install @vitejs/plugin-vue          # Vue
npm install @sveltejs/vite-plugin-svelte # Svelte
 
# Tailwind
npm install @tailwindcss/vite           # Tailwind CSS 4.0
npm install -D tailwindcss postcss autoprefixer  # Tailwind CSS 3.x
 
# 工具
npm install vite-plugin-mock           # Mock 数据
npm install vite-plugin-pwa            # PWA
npm install vite-plugin-svgr           # SVG 组件化
npm install vite-plugin-checker        # TypeScript 检查
npm install vite-plugin-inspect        # 调试插件
npm install unplugin-vue-components     # 组件自动导入
npm install unplugin-auto-import       # 自动导入 API
 
# SSR
npm install vite-plugin-ssr            # 服务端渲染
npm install @originjs/vite-plugin-ssg  # 静态站点生成

Bundle 分析

Vite Bundle 分析

# 使用 vite-bundle-analyzer
npm install -D rollup-plugin-visualizer
 
# vite.config.ts
import { visualizer } from 'rollup-plugin-visualizer';
 
export default defineConfig({
  plugins: [
    visualizer({
      filename: './dist/stats.html',
      open: true,
      gzipSize: true,
      brotliSize: true,
    }),
  ],
});

Webpack Bundle 分析

# 使用 webpack-bundle-analyzer
npm install -D webpack-bundle-analyzer
 
# webpack.config.js
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
 
module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      reportFilename: 'bundle-report.html',
      openAnalyzer: true,
    }),
  ],
};

Monorepo 配置

pnpm Workspace 配置

# pnpm-workspace.yaml
packages:
  - 'apps/*'
  - 'packages/*'
  - 'tools/*'
// packages/shared/package.json
{
  "name": "@my/shared",
  "version": "1.0.0",
  "main": "./dist/index.js",
  "module": "./dist/index.mjs",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.js",
      "types": "./dist/index.d.ts"
    }
  },
  "files": ["dist"],
  "scripts": {
    "build": "tsup",
    "dev": "tsup --watch"
  }
}

Turborepo 配置

// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": ["**/.env.*local"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**", "!.next/cache/**"],
      "inputs": ["src/**", "package.json", "tsconfig.json"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "lint": {
      "outputs": []
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"],
      "inputs": ["src/**", "test/**"]
    },
    "typecheck": {
      "dependsOn": ["^build"],
      "outputs": []
    }
  }
}

与同类技术对比

构建工具横向对比

维度ViteWebpackRollupesbuildTurbopack
核心语言TypeScriptJavaScriptJavaScriptGoRust
启动速度秒级分钟级中等毫秒级毫秒级
HMR 速度<100ms1-5s中等<50ms
配置复杂度
插件生态丰富极丰富丰富新兴新兴
Tree Shaking支持支持最佳支持支持
CSS Modules原生需配置插件原生原生
SSR支持支持支持不支持支持
库模式支持支持最佳支持支持

使用场景建议

┌─────────────────────────────────────────────────────────────────────┐
│                    构建工具选型决策树                                  │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  项目类型?                                                           │
│  │                                                                   │
│  ├── 业务应用(React/Vue/Svelte)                                   │
│  │   ├── 新项目 → Vite                                             │
│  │   ├── 已有 Webpack 配置 → 继续用或迁移到 Vite                     │
│  │   └── 追求极致性能 → Turbopack                                    │
│  │                                                                   │
│  ├── 库/组件发布                                                     │
│  │   ├── 推荐 Rollup                                                │
│  │   └── 或 Vite 库模式                                             │
│  │                                                                   │
│  ├── Node.js 服务                                                   │
│  │   └── esbuild / tsup                                            │
│  │                                                                   │
│  └── 简单静态页面                                                    │
│      └── Parcel / Vite / esbuild                                   │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

常见问题与解决方案

问题一:构建产物过大

诊断步骤:

  1. 分析 Bundle
  2. 识别大依赖
  3. 检查 Tree Shaking
  4. 优化分包

解决方案:

// vite.config.ts
export default defineConfig({
  build: {
    // 分析
    reportCompressedSize: true,
    chunkSizeWarningLimit: 500,
    
    // 优化分包
    rollupOptions: {
      output: {
        manualChunks: (id) => {
          // 分包大依赖
          if (id.includes('echarts')) return 'echarts';
          if (id.includes('three')) return 'three';
          if (id.includes('pdfjs')) return 'pdf';
        },
      },
    },
  },
  
  // 优化依赖预构建
  optimizeDeps: {
    include: ['react', 'react-dom'],
  },
});

问题二:开发服务器启动慢

诊断步骤:

  1. 检查 node_modules 依赖
  2. 检查 optimizeDeps 配置
  3. 考虑迁移到 Vite

解决方案:

// vite.config.ts
export default defineConfig({
  optimizeDeps: {
    // 强制预构建
    include: [
      'react',
      'react-dom',
      'lodash',
      // 其他常用依赖
    ],
  },
  
  server: {
    // 预构建依赖
    preTransformRequests: true,
  },
});

问题三:HMR 不生效

诊断步骤:

  1. 检查文件是否在 HMR 边界内
  2. 检查是否使用了不支持 HMR 的特性

解决方案:

// 检查组件是否有 state
// 有 state 的组件 HMR 更可靠
 
// 使用 defineConfig 确保正确配置
export default defineConfig({
  plugins: [
    react({
      // 确保 React Refresh
      exclude: [/node_modules/],
    }),
  ],
});

问题四:静态资源引用错误

诊断步骤:

  1. 检查 base 配置
  2. 检查 public 目录
  3. 检查相对路径

解决方案:

export default defineConfig({
  // 基础路径
  base: '/my-app/',
  
  // 公共资源(会复制到输出目录)
  publicDir: 'public',
  
  // 资源引用
  assetsInclude: ['**/*.glb'], // 包含额外的资源类型
});

问题五:第三方库打包问题

常见问题库:

问题解决方案
node-fetchNode 特有的 polyfill使用 cross-fetch
BufferNode 全局变量配置 define
pathNode 模块使用 path-browserify
cryptoNode 模块使用 crypto-browserify

解决方案:

// vite.config.ts
export default defineConfig({
  define: {
    // Node 全局变量
    'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
    global: 'globalThis',
  },
  
  resolve: {
    alias: {
      // Node 模块替换
      path: 'path-browserify',
      crypto: 'crypto-browserify',
      buffer: 'buffer',
    },
  },
  
  optimizeDeps: {
    esbuildOptions: {
      define: {
        global: 'globalThis',
      },
    },
  },
});

实战项目示例

示例一:完整的 Vite + React + TypeScript 项目

project/
├── public/
│   └── favicon.ico
├── src/
│   ├── assets/
│   │   ├── images/
│   │   └── styles/
│   ├── components/
│   │   ├── Button/
│   │   │   ├── Button.tsx
│   │   │   ├── Button.module.scss
│   │   │   └── index.ts
│   │   └── ...
│   ├── pages/
│   │   ├── Home/
│   │   └── About/
│   ├── hooks/
│   ├── utils/
│   ├── stores/
│   ├── api/
│   ├── types/
│   ├── App.tsx
│   ├── main.tsx
│   └── vite-env.d.ts
├── env/
│   ├── .env
│   ├── .env.development
│   └── .env.production
├── index.html
├── package.json
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
├── postcss.config.js
├── tailwind.config.js
├── .eslintrc.cjs
├── .prettierrc.js
└── .gitignore
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
 
export default defineConfig({
  plugins: [
    react(),
    
    // 自动导入 API
    AutoImport({
      imports: ['react', 'react-router-dom'],
      dts: 'src/auto-imports.d.ts',
    }),
    
    // 自动导入组件
    Components({
      dts: 'src/components.d.ts',
    }),
  ],
  
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
  
  server: {
    port: 3000,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
      },
    },
  },
  
  build: {
    target: 'esnext',
    rollupOptions: {
      output: {
        manualChunks: {
          'react-vendor': ['react', 'react-dom', 'react-router-dom'],
        },
      },
    },
  },
});

示例二:Monorepo 项目配置

my-monorepo/
├── apps/
│   ├── web/                 # 主应用
│   │   ├── package.json
│   │   ├── vite.config.ts
│   │   └── src/
│   └── admin/               # 管理后台
│       ├── package.json
│       └── src/
├── packages/
│   ├── ui/                  # UI 组件库
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   └── src/
│   ├── utils/               # 工具函数
│   │   ├── package.json
│   │   └── src/
│   └── hooks/               # 自定义 Hooks
│       ├── package.json
│       └── src/
├── tools/
│   ├── eslint-config/
│   └── tsconfig/
├── pnpm-workspace.yaml
├── turbo.json
├── package.json
└── tsconfig.base.json
// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "lint": {
      "outputs": []
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"]
    }
  }
}

SUCCESS

构建工具是前端工程化的基石。本文档全面覆盖了 Vite、Webpack、Rollup 等主流构建工具的配置与使用,以及代码分割、Tree Shaking、HMR 等核心概念。掌握这些知识,可以帮助你构建更高效、更快速、更可维护的前端项目。选择合适的工具,持续优化构建流程,让开发体验达到最佳。