构建工具与工程化完全指南
NOTE
本文档涵盖前端构建工具链、模块系统、包管理器、工程化最佳实践及 CI/CD 集成,是 vibecoding 时代从代码到生产环境的完整工程化指南。
目录
构建工具生态全景
前端构建工具演进
2010-2015: Grunt — 任务运行器时代
↓
2012-2018: Gulp — 流式构建时代
↓
2013-至今: Webpack — 模块打包时代
↓
2018-至今: Rollup — 库打包标准
↓
2020-至今: Vite — 即时服务时代
↓
2024-至今: Turbopack — Rust 原生打包
核心概念
构建工具的四大职能:
- 模块解析:处理 ES Modules、CommonJS、AMD 等模块系统
- 依赖打包:将散落的模块合并为可运行的产物
- 转换编译:将 TypeScript、Sass、JSX 转换为浏览器可执行代码
- 产物优化: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 与包管理
包管理器对比
| 维度 | npm | yarn | pnpm | bun |
|---|---|---|---|---|
| 安装速度 | 中等 | 快 | 极快 | 极快 |
| 磁盘占用 | 大 | 中 | 小 | 小 |
| node_modules | 扁平 | 扁平 | 内容寻址 | 内容寻址 |
| lock 文件 | package-lock.json | yarn.lock | pnpm-lock.yaml | bun.lockb |
| monorepo | workspaces | workspaces | workspace | workspace |
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/CSS | Babel, 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 lintWebpack 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.jsonRollup 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.jspnpm/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": []
}
}
}与同类技术对比
构建工具横向对比
| 维度 | Vite | Webpack | Rollup | esbuild | Turbopack |
|---|---|---|---|---|---|
| 核心语言 | TypeScript | JavaScript | JavaScript | Go | Rust |
| 启动速度 | 秒级 | 分钟级 | 中等 | 毫秒级 | 毫秒级 |
| HMR 速度 | <100ms | 1-5s | 中等 | 快 | <50ms |
| 配置复杂度 | 低 | 高 | 中 | 无 | 低 |
| 插件生态 | 丰富 | 极丰富 | 丰富 | 新兴 | 新兴 |
| Tree Shaking | 支持 | 支持 | 最佳 | 支持 | 支持 |
| CSS Modules | 原生 | 需配置 | 插件 | 原生 | 原生 |
| SSR | 支持 | 支持 | 支持 | 不支持 | 支持 |
| 库模式 | 支持 | 支持 | 最佳 | 支持 | 支持 |
使用场景建议
┌─────────────────────────────────────────────────────────────────────┐
│ 构建工具选型决策树 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 项目类型? │
│ │ │
│ ├── 业务应用(React/Vue/Svelte) │
│ │ ├── 新项目 → Vite │
│ │ ├── 已有 Webpack 配置 → 继续用或迁移到 Vite │
│ │ └── 追求极致性能 → Turbopack │
│ │ │
│ ├── 库/组件发布 │
│ │ ├── 推荐 Rollup │
│ │ └── 或 Vite 库模式 │
│ │ │
│ ├── Node.js 服务 │
│ │ └── esbuild / tsup │
│ │ │
│ └── 简单静态页面 │
│ └── Parcel / Vite / esbuild │
│ │
└─────────────────────────────────────────────────────────────────────┘
常见问题与解决方案
问题一:构建产物过大
诊断步骤:
- 分析 Bundle
- 识别大依赖
- 检查 Tree Shaking
- 优化分包
解决方案:
// 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'],
},
});问题二:开发服务器启动慢
诊断步骤:
- 检查 node_modules 依赖
- 检查 optimizeDeps 配置
- 考虑迁移到 Vite
解决方案:
// vite.config.ts
export default defineConfig({
optimizeDeps: {
// 强制预构建
include: [
'react',
'react-dom',
'lodash',
// 其他常用依赖
],
},
server: {
// 预构建依赖
preTransformRequests: true,
},
});问题三:HMR 不生效
诊断步骤:
- 检查文件是否在 HMR 边界内
- 检查是否使用了不支持 HMR 的特性
解决方案:
// 检查组件是否有 state
// 有 state 的组件 HMR 更可靠
// 使用 defineConfig 确保正确配置
export default defineConfig({
plugins: [
react({
// 确保 React Refresh
exclude: [/node_modules/],
}),
],
});问题四:静态资源引用错误
诊断步骤:
- 检查 base 配置
- 检查 public 目录
- 检查相对路径
解决方案:
export default defineConfig({
// 基础路径
base: '/my-app/',
// 公共资源(会复制到输出目录)
publicDir: 'public',
// 资源引用
assetsInclude: ['**/*.glb'], // 包含额外的资源类型
});问题五:第三方库打包问题
常见问题库:
| 库 | 问题 | 解决方案 |
|---|---|---|
| node-fetch | Node 特有的 polyfill | 使用 cross-fetch |
| Buffer | Node 全局变量 | 配置 define |
| path | Node 模块 | 使用 path-browserify |
| crypto | Node 模块 | 使用 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 等核心概念。掌握这些知识,可以帮助你构建更高效、更快速、更可维护的前端项目。选择合适的工具,持续优化构建流程,让开发体验达到最佳。