前端工程化¶
📚 章节目标¶
本章节将全面介绍前端工程化的核心技术和工具,包括Webpack、Vite、Rollup、模块化等,帮助学习者掌握前端工程化的核心方法。
学习目标¶
- 理解前端工程化的核心概念
- 掌握Webpack的深度配置
- 掌握Vite的构建优化
- 掌握Rollup的打包技术
- 理解模块化规范
- 掌握工程化最佳实践
🏗️ 前端工程化概述¶
1. 什么是前端工程化¶
JavaScript
// 前端工程化的核心要素
// 1. 模块化:将代码拆分成独立的模块
// 2. 组件化:将UI拆分成可复用的组件
// 3. 规范化:制定代码规范和项目规范
// 4. 自动化:自动化构建、测试、部署
// 5. 优化化:性能优化、代码优化
// 示例:模块化
// utils.js
export function formatDate(date) {
return new Date(date).toLocaleDateString();
}
// main.js
import { formatDate } from './utils';
console.log(formatDate(new Date()));
// 示例:组件化
// Button.jsx
export function Button({ children, onClick }) {
return <button onClick={onClick}>{children}</button>;
}
// App.jsx
import { Button } from './Button';
function App() {
return <Button onClick={() => console.log('clicked')}>Click me</Button>;
}
2. 工程化工具链¶
JavaScript
// 前端工程化工具链
// 1. 包管理器:npm, yarn, pnpm
// 2. 构建工具:Webpack, Vite, Rollup
// 3. 代码规范:ESLint, Prettier
// 4. 测试工具:Jest, Cypress, Playwright
// 5. CI/CD:GitHub Actions, GitLab CI, Jenkins
// 示例:使用pnpm
pnpm install
pnpm add react
pnpm add -D typescript
// 示例:使用ESLint
npx eslint src/
npx eslint src/ --fix
// 示例:使用Prettier
npx prettier --write src/
📦 Webpack深度配置¶
1. Webpack基础¶
1.1 基本配置¶
JavaScript
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// 入口文件
entry: './src/index.js',
// 输出配置
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
// 模式
mode: 'production',
// Loader配置
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(png|jpg|jpeg|gif|svg)$/,
type: 'asset/resource',
},
],
},
// 插件配置
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
};
1.2 多入口配置¶
JavaScript
// webpack.config.js
module.exports = {
entry: {
main: './src/index.js',
vendor: './src/vendor.js',
},
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
};
2. Loader配置¶
2.1 JavaScript Loader¶
JavaScript
// babel-loader配置
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react',
'@babel/preset-typescript',
],
plugins: [
'@babel/plugin-transform-runtime',
'@babel/plugin-transform-class-properties',
],
},
},
},
],
},
};
// .babelrc
{
"presets": [
["@babel/preset-env", {
"targets": {
"browsers": ["> 1%", "last 2 versions", "not dead"]
}
}],
"@babel/preset-react",
"@babel/preset-typescript"
],
"plugins": [
"@babel/plugin-transform-runtime",
"@babel/plugin-transform-class-properties"
]
}
2.2 CSS Loader¶
JavaScript
// CSS Loader配置
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
],
},
{
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'sass-loader',
],
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'less-loader',
],
},
],
},
};
// postcss.config.js
module.exports = {
plugins: [
require('autoprefixer'),
require('cssnano'),
],
};
2.3 资源Loader¶
JavaScript
// 资源Loader配置
module.exports = {
module: {
rules: [
// 图片处理
{
test: /\.(png|jpg|jpeg|gif|svg|webp)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024, // 8kb以下转base64
},
},
generator: {
filename: 'images/[name].[hash:6][ext]',
},
},
// 字体处理
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: 'asset/resource',
generator: {
filename: 'fonts/[name].[hash:6][ext]',
},
},
// 媒体文件
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)$/,
type: 'asset/resource',
generator: {
filename: 'media/[name].[hash:6][ext]',
},
},
],
},
};
3. Plugin配置¶
3.1 HTML Plugin¶
JavaScript
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html',
title: 'My App',
minify: {
collapseWhitespace: true,
removeComments: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
useShortDoctype: true,
},
}),
],
};
3.2 清理插件¶
JavaScript
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
plugins: [
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: ['**/*', '!static-files/**'],
}),
],
};
3.3 环境变量插件¶
JavaScript
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
'process.env.API_URL': JSON.stringify(process.env.API_URL),
}),
],
};
4. 优化配置¶
4.1 代码分割¶
JavaScript
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10,
},
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true,
},
},
},
runtimeChunk: {
name: 'runtime',
},
},
};
4.2 Tree Shaking¶
JavaScript
module.exports = {
optimization: {
usedExports: true,
sideEffects: false,
minimize: true,
},
};
// package.json
{
"sideEffects": false
}
4.3 压缩优化¶
JavaScript
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
},
}),
new CssMinimizerPlugin(),
],
},
};
⚡ Vite构建优化¶
1. Vite基础¶
1.1 基本配置¶
JavaScript
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
server: {
port: 3000,
open: true,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
build: {
outDir: 'dist',
assetsDir: 'assets',
sourcemap: false,
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
},
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
router: ['react-router-dom'],
},
},
},
},
});
1.2 环境变量¶
JavaScript
// .env
VITE_API_URL=https://api.example.com
// .env.development
VITE_API_URL=http://localhost:8080
// .env.production
VITE_API_URL=https://api.example.com
// 在代码中使用
const apiUrl = import.meta.env.VITE_API_URL;
2. Vite插件¶
2.1 常用插件¶
JavaScript
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import vitePluginImp from 'vite-plugin-imp';
import legacy from '@vitejs/plugin-legacy';
import viteCompression from 'vite-plugin-compression';
export default defineConfig({
plugins: [
react(),
// 按需引入
vitePluginImp({
libList: [
{
libName: 'antd',
style: (name) => `antd/es/${name}/style`,
},
],
}),
// 兼容旧浏览器
legacy({
targets: ['defaults', 'not IE 11'],
}),
// Gzip压缩
viteCompression({
verbose: true,
disable: false,
threshold: 10240,
algorithm: 'gzip',
ext: '.gz',
}),
],
});
2.2 自定义插件¶
JavaScript
// vite.config.js
import { defineConfig } from 'vite';
function myPlugin() {
return {
name: 'my-plugin',
transform(code, id) {
if (id.endsWith('.js')) {
// 转换代码
return code.replace(/console\.log\(/g, 'logger.log(');
}
},
};
}
export default defineConfig({
plugins: [myPlugin()],
});
3. Vite优化¶
3.1 依赖预构建¶
JavaScript
// vite.config.js
export default defineConfig({
optimizeDeps: {
include: ['react', 'react-dom'],
exclude: ['your-custom-dep'],
},
});
3.2 构建优化¶
JavaScript
// vite.config.js
export default defineConfig({
build: {
// 代码分割
rollupOptions: {
output: {
manualChunks: (id) => {
if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0].toString();
}
},
},
},
// 压缩配置
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
},
// Chunk大小警告
chunkSizeWarningLimit: 1000,
},
});
🔄 Rollup打包技术¶
1. Rollup基础¶
1.1 基本配置¶
JavaScript
// rollup.config.js
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import terser from '@rollup/plugin-terser';
export default {
input: 'src/index.ts',
output: [
{
file: 'dist/bundle.js',
format: 'cjs',
exports: 'auto',
},
{
file: 'dist/bundle.esm.js',
format: 'esm',
},
{
file: 'dist/bundle.umd.js',
format: 'umd',
name: 'MyLibrary',
},
],
plugins: [
resolve(),
commonjs(),
typescript(),
terser(),
],
};
1.2 多入口配置¶
JavaScript
// rollup.config.js
export default {
input: {
main: 'src/index.ts',
utils: 'src/utils/index.ts',
},
output: {
dir: 'dist',
format: 'esm',
entryFileNames: '[name].js',
chunkFileNames: '[name]-[hash].js',
},
};
2. Rollup插件¶
2.1 常用插件¶
JavaScript
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import json from '@rollup/plugin-json';
import babel from '@rollup/plugin-babel';
import replace from '@rollup/plugin-replace';
import alias from '@rollup/plugin-alias';
import postcss from 'rollup-plugin-postcss';
export default {
plugins: [
// 路径别名
alias({
entries: [
{ find: '@', replacement: './src' },
],
}),
// 解析node_modules
resolve({
browser: true,
}),
// 转换CommonJS
commonjs(),
// TypeScript支持
typescript(),
// JSON支持
json(),
// Babel转译
babel({
babelHelpers: 'bundled',
exclude: 'node_modules/**',
}),
// 环境变量替换
replace({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
preventAssignment: true,
}),
// PostCSS
postcss(),
],
};
2.2 自定义插件¶
JavaScript
// rollup.config.js
function myPlugin(options = {}) {
return {
name: 'my-plugin',
transform(code, id) {
if (id.endsWith('.js')) {
// 转换代码
return {
code: code.replace(/console\.log\(/g, 'logger.log('),
map: null,
};
}
},
generateBundle(options, bundle) {
// 生成bundle时执行
console.log('Bundle generated');
},
};
}
export default {
plugins: [myPlugin()],
};
3. Rollup优化¶
3.1 Tree Shaking¶
JavaScript
// rollup.config.js
export default {
treeshake: {
moduleSideEffects: false,
propertyReadSideEffects: false,
unknownGlobalSideEffects: false,
},
};
3.2 代码分割¶
JavaScript
// rollup.config.js
export default {
output: {
manualChunks: (id) => { // 箭头函数:简洁的函数语法
if (id.includes('node_modules')) {
return 'vendor';
}
},
},
};
📦 模块化规范¶
1. CommonJS¶
JavaScript
// 导出
// utils.js
function formatDate(date) {
return new Date(date).toLocaleDateString();
}
function formatNumber(num) {
return num.toLocaleString();
}
module.exports = {
formatDate,
formatNumber,
};
// 或者
exports.formatDate = formatDate;
exports.formatNumber = formatNumber;
// 导入
const { formatDate, formatNumber } = require('./utils'); // 解构赋值:从对象/数组提取值
// 或者
const utils = require('./utils'); // const不可重新赋值;let块级作用域变量
utils.formatDate(new Date());
2. ES Modules¶
JavaScript
// 导出
// utils.js
export function formatDate(date) {
return new Date(date).toLocaleDateString();
}
export function formatNumber(num) {
return num.toLocaleString();
}
// 默认导出
export default function logger(message) {
console.log(message);
}
// 导入
import { formatDate, formatNumber } from './utils';
import logger from './logger';
// 或者
import * as utils from './utils';
utils.formatDate(new Date());
// 动态导入
button.addEventListener('click', async () => { // async定义异步函数;await等待Promise完成
const module = await import('./heavy-module.js'); // await等待异步操作完成
module.doSomething();
});
3. UMD¶
JavaScript
// UMD模块
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['exports'], factory);
} else if (typeof exports === 'object') {
// CommonJS
factory(exports);
} else {
// Browser globals
factory(root.myLibrary = {});
}
}(this, function (exports) {
exports.formatDate = function(date) {
return new Date(date).toLocaleDateString();
};
exports.formatNumber = function(num) {
return num.toLocaleString();
};
}));
🛠️ 工程化最佳实践¶
1. 项目结构¶
Text Only
project/
├── public/ # 静态资源
│ ├── index.html
│ └── favicon.ico
├── src/ # 源代码
│ ├── assets/ # 资源文件
│ ├── components/ # 组件
│ ├── pages/ # 页面
│ ├── services/ # 服务
│ ├── utils/ # 工具函数
│ ├── hooks/ # 自定义Hooks
│ ├── store/ # 状态管理
│ ├── router/ # 路由配置
│ ├── styles/ # 样式文件
│ └── index.js # 入口文件
├── tests/ # 测试文件
├── .eslintrc.js # ESLint配置
├── .prettierrc.js # Prettier配置
├── .gitignore # Git忽略文件
├── package.json # 项目配置
└── README.md # 项目说明
2. 代码规范¶
2.1 ESLint配置¶
JavaScript
// .eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 'latest',
sourceType: 'module',
},
plugins: ['react', '@typescript-eslint', 'prettier'],
rules: {
'react/react-in-jsx-scope': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
'prettier/prettier': 'error',
},
settings: {
react: {
version: 'detect',
},
},
};
2.2 Prettier配置¶
JavaScript
// .prettierrc.js
module.exports = {
semi: true,
singleQuote: true,
tabWidth: 2,
trailingComma: 'es5',
printWidth: 80,
arrowParens: 'always',
endOfLine: 'lf',
};
3. Git工作流¶
Bash
# 分支策略
# main - 主分支
# develop - 开发分支
# feature/* - 功能分支
# hotfix/* - 紧急修复分支
# 工作流程
# 1. 从develop创建功能分支
git checkout -b feature/new-feature
# 2. 开发并提交
git add .
git commit -m "feat: add new feature"
# 3. 推送到远程
git push origin feature/new-feature
# 4. 创建Pull Request
# 5. 代码审查
# 6. 合并到develop
# 7. 定期合并到main
4. CI/CD配置¶
YAML
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Test
run: npm test
- name: Build
run: npm run build
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Deploy to Vercel
uses: amondnet/vercel-action@v20
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.ORG_ID }}
vercel-project-id: ${{ secrets.PROJECT_ID }}
vercel-args: '--prod'
📝 练习题¶
1. 基础题¶
题目1:配置Webpack处理TypeScript
JavaScript
// webpack.config.js
module.exports = {
// 配置Webpack处理TypeScript
module: {
rules: [
// 添加TypeScript loader
],
},
};
2. 进阶题¶
题目2:配置Vite实现按需引入
JavaScript
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
// 配置按需引入插件
],
});
3. 面试题¶
题目3:比较Webpack和Vite的区别
JavaScript
// 答案要点:
// 1. Webpack使用bundle模式,Vite使用ESM模式
// 2. Webpack冷启动慢,Vite冷启动快
// 3. Webpack热更新中等,Vite热更新快
// 4. Webpack配置复杂,Vite配置简单
// 5. Webpack生态成熟,Vite生态快速发展
// 6. Webpack适合所有项目,Vite适合现代项目
🎯 本章总结¶
本章节全面介绍了前端工程化的核心技术和工具,包括Webpack、Vite、Rollup、模块化等。关键要点:
- 工程化概念:理解前端工程化的核心要素
- Webpack:掌握Webpack的深度配置和优化
- Vite:掌握Vite的构建优化和插件
- Rollup:掌握Rollup的打包技术
- 模块化:理解CommonJS、ES Modules、UMD规范
- 最佳实践:掌握项目结构、代码规范、Git工作流、CI/CD配置
下一步将深入学习前端测试技术。