构建工具
现代前端开发离不开构建工具,它们负责代码转换、打包、优化等任务,是前端工程化的核心。
Webpack
基础配置
javascript
1// webpack.config.js2const path = require('path');3const HtmlWebpackPlugin = require('html-webpack-plugin');4const MiniCssExtractPlugin = require('mini-css-extract-plugin');56module.exports = {7 entry: './src/index.js',8 output: {9 path: path.resolve(__dirname, 'dist'),10 filename: '[name].[contenthash].js',11 clean: true,12 publicPath: '/'13 },14 module: {15 rules: [16 {17 test: /\.js$/,18 exclude: /node_modules/,19 use: {20 loader: 'babel-loader',21 options: {22 presets: ['@babel/preset-env', '@babel/preset-react']23 }24 }25 },26 {27 test: /\.css$/,28 use: [29 process.env.NODE_ENV === 'production' 30 ? MiniCssExtractPlugin.loader 31 : 'style-loader',32 'css-loader',33 'postcss-loader'34 ]35 },36 {37 test: /\.(png|svg|jpg|jpeg|gif)$/i,38 type: 'asset/resource'39 },40 {41 test: /\.(woff|woff2|eot|ttf|otf)$/i,42 type: 'asset/resource'43 }44 ]45 },46 plugins: [47 new HtmlWebpackPlugin({48 template: './public/index.html'49 }),50 new MiniCssExtractPlugin({51 filename: '[name].[contenthash].css'52 })53 ],54 optimization: {55 splitChunks: {56 chunks: 'all',57 cacheGroups: {58 vendor: {59 test: /[\\/]node_modules[\\/]/,60 name: 'vendors',61 chunks: 'all'62 }63 }64 }65 },66 devServer: {67 contentBase: path.join(__dirname, 'dist'),68 compress: true,69 port: 3000,70 hot: true,71 historyApiFallback: true72 },73 mode: process.env.NODE_ENV || 'development'74};高级配置
javascript
1// webpack.prod.js - 生产环境配置2const { merge } = require('webpack-merge');3const common = require('./webpack.common.js');4const TerserPlugin = require('terser-webpack-plugin');5const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');6const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');78module.exports = merge(common, {9 mode: 'production',10 devtool: 'source-map',11 optimization: {12 minimize: true,13 minimizer: [14 new TerserPlugin({15 terserOptions: {16 compress: {17 drop_console: true,18 drop_debugger: true19 }20 }21 }),22 new CssMinimizerPlugin()23 ],24 splitChunks: {25 chunks: 'all',26 cacheGroups: {27 default: {28 minChunks: 2,29 priority: -20,30 reuseExistingChunk: true31 },32 vendor: {33 test: /[\\/]node_modules[\\/]/,34 name: 'vendors',35 priority: -10,36 chunks: 'all'37 },38 react: {39 test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,40 name: 'react',41 chunks: 'all'42 }43 }44 }45 },46 plugins: [47 new BundleAnalyzerPlugin({48 analyzerMode: 'static',49 openAnalyzer: false50 })51 ]52});Vite
Vite是新一代前端构建工具,以其快速的开发服务器和优化的构建性能而闻名。
基础配置
javascript
1// vite.config.js2import { defineConfig } from 'vite';3import react from '@vitejs/plugin-react';4import { resolve } from 'path';56export default defineConfig({7 plugins: [react()],8 resolve: {9 alias: {10 '@': resolve(__dirname, 'src'),11 '@components': resolve(__dirname, 'src/components'),12 '@utils': resolve(__dirname, 'src/utils')13 }14 },15 css: {16 preprocessorOptions: {17 scss: {18 additionalData: `@import "@/styles/variables.scss";`19 }20 },21 modules: {22 localsConvention: 'camelCase'23 }24 },25 build: {26 outDir: 'dist',27 sourcemap: true,28 rollupOptions: {29 output: {30 manualChunks: {31 vendor: ['react', 'react-dom'],32 router: ['react-router-dom']33 }34 }35 }36 },37 server: {38 port: 3000,39 open: true,40 proxy: {41 '/api': {42 target: 'http://localhost:8080',43 changeOrigin: true,44 rewrite: (path) => path.replace(/^\/api/, '')45 }46 }47 }48});环境配置
javascript
1// vite.config.ts - TypeScript配置2import { defineConfig, loadEnv } from 'vite';3import react from '@vitejs/plugin-react';4import { resolve } from 'path';56export default defineConfig(({ command, mode }) => {7 const env = loadEnv(mode, process.cwd(), '');8 9 return {10 plugins: [11 react({12 jsxImportSource: '@emotion/react',13 babel: {14 plugins: ['@emotion/babel-plugin']15 }16 })17 ],18 define: {19 __APP_ENV__: JSON.stringify(env.APP_ENV)20 },21 resolve: {22 alias: {23 '@': resolve(__dirname, 'src')24 }25 },26 build: {27 target: 'es2015',28 minify: 'terser',29 terserOptions: {30 compress: {31 drop_console: command === 'build',32 drop_debugger: command === 'build'33 }34 }35 }36 };37});Rollup
Rollup专注于ES模块打包,特别适合库的构建。
基础配置
javascript
1// rollup.config.js2import resolve from '@rollup/plugin-node-resolve';3import commonjs from '@rollup/plugin-commonjs';4import babel from '@rollup/plugin-babel';5import { terser } from 'rollup-plugin-terser';6import peerDepsExternal from 'rollup-plugin-peer-deps-external';78export default {9 input: 'src/index.js',10 output: [11 {12 file: 'dist/index.cjs.js',13 format: 'cjs',14 sourcemap: true15 },16 {17 file: 'dist/index.esm.js',18 format: 'esm',19 sourcemap: true20 },21 {22 file: 'dist/index.umd.js',23 format: 'umd',24 name: 'MyLibrary',25 sourcemap: true26 }27 ],28 plugins: [29 peerDepsExternal(),30 resolve({31 browser: true32 }),33 commonjs(),34 babel({35 babelHelpers: 'bundled',36 exclude: 'node_modules/**',37 presets: ['@babel/preset-env', '@babel/preset-react']38 }),39 terser()40 ],41 external: ['react', 'react-dom']42};esbuild
esbuild是用Go编写的极快的JavaScript打包器和压缩器。
基础使用
javascript
1// build.js2const esbuild = require('esbuild');34esbuild.build({5 entryPoints: ['src/index.js'],6 bundle: true,7 outfile: 'dist/bundle.js',8 minify: true,9 sourcemap: true,10 target: ['es2015'],11 loader: {12 '.png': 'dataurl',13 '.svg': 'text'14 },15 define: {16 'process.env.NODE_ENV': '"production"'17 }18}).catch(() => process.exit(1));开发服务器
javascript
1// dev-server.js2const esbuild = require('esbuild');34esbuild.serve({5 servedir: 'public',6 port: 30007}, {8 entryPoints: ['src/index.js'],9 bundle: true,10 outdir: 'public/dist',11 loader: {12 '.js': 'jsx'13 }14}).then(server => {15 console.log(`Server running at http://localhost:${server.port}`);16});Parcel
Parcel是零配置的Web应用打包器。
基础使用
json
1{2 "name": "my-app",3 "scripts": {4 "start": "parcel src/index.html",5 "build": "parcel build src/index.html"6 },7 "devDependencies": {8 "parcel": "^2.0.0"9 }10}配置文件
json
1// .parcelrc2{3 "extends": "@parcel/config-default",4 "transformers": {5 "*.{ts,tsx}": ["@parcel/transformer-typescript-tsc"]6 },7 "resolvers": ["@parcel/resolver-default"],8 "bundler": "@parcel/bundler-default",9 "namers": ["@parcel/namer-default"],10 "runtimes": ["@parcel/runtime-js", "@parcel/runtime-browser-hmr"],11 "optimizers": {12 "*.js": ["@parcel/optimizer-terser"],13 "*.css": ["@parcel/optimizer-cssnano"]14 },15 "packagers": {16 "*.html": "@parcel/packager-html",17 "*.js": "@parcel/packager-js",18 "*.css": "@parcel/packager-css"19 },20 "compressors": {21 "*.{html,css,js,svg,map}": ["@parcel/compressor-gzip"]22 }23}构建优化策略
1. 代码分割
javascript
1// 动态导入实现代码分割2const LazyComponent = React.lazy(() => import('./LazyComponent'));34// Webpack魔法注释5const LazyComponent = React.lazy(() => 6 import(/* webpackChunkName: "lazy-component" */ './LazyComponent')7);89// 路由级别分割10const routes = [11 {12 path: '/',13 component: React.lazy(() => import('./pages/Home'))14 },15 {16 path: '/about',17 component: React.lazy(() => import('./pages/About'))18 }19];2. Tree Shaking
javascript
1// 确保使用ES模块2import { debounce } from 'lodash-es'; // ✅ 支持tree shaking3// import _ from 'lodash'; // ❌ 不支持tree shaking45// package.json中标记副作用6{7 "sideEffects": false, // 或者 ["*.css", "*.scss"]8}910// 使用具名导出11export const utils = {12 debounce,13 throttle14};1516// 而不是默认导出17export default {18 debounce,19 throttle20};3. 缓存策略
javascript
1// Webpack缓存配置2module.exports = {3 cache: {4 type: 'filesystem',5 buildDependencies: {6 config: [__filename]7 }8 },9 output: {10 filename: '[name].[contenthash].js',11 chunkFilename: '[name].[contenthash].chunk.js'12 }13};4. 性能监控
javascript
1// webpack-bundle-analyzer2const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;34module.exports = {5 plugins: [6 new BundleAnalyzerPlugin({7 analyzerMode: 'static',8 openAnalyzer: false,9 reportFilename: 'bundle-report.html'10 })11 ]12};1314// 性能预算15module.exports = {16 performance: {17 maxAssetSize: 250000,18 maxEntrypointSize: 250000,19 hints: 'warning'20 }21};构建工具对比
| 特性 | Webpack | Vite | Rollup | esbuild | Parcel |
|---|---|---|---|---|---|
| 配置复杂度 | 高 | 低 | 中 | 低 | 极低 |
| 构建速度 | 中 | 快 | 中 | 极快 | 快 |
| 开发服务器 | 支持 | 极快 | 需插件 | 支持 | 支持 |
| 代码分割 | 强大 | 支持 | 支持 | 支持 | 支持 |
| 插件生态 | 丰富 | 快速增长 | 丰富 | 有限 | 中等 |
| 学习成本 | 高 | 低 | 中 | 低 | 极低 |
| 适用场景 | 大型项目 | 现代项目 | 库开发 | 快速构建 | 快速原型 |
最佳实践
1. 选择合适的工具
- 大型复杂项目:Webpack + 详细配置
- 现代React/Vue项目:Vite
- 库开发:Rollup
- 快速原型:Parcel
- 性能要求极高:esbuild
2. 优化构建性能
javascript
1// 并行处理2const TerserPlugin = require('terser-webpack-plugin');34module.exports = {5 optimization: {6 minimizer: [7 new TerserPlugin({8 parallel: true, // 启用并行压缩9 cache: true // 启用缓存10 })11 ]12 }13};1415// 减少解析范围16module.exports = {17 resolve: {18 modules: [path.resolve(__dirname, 'src'), 'node_modules'],19 extensions: ['.js', '.jsx', '.ts', '.tsx']20 },21 module: {22 rules: [23 {24 test: /\.js$/,25 include: path.resolve(__dirname, 'src'), // 只处理src目录26 exclude: /node_modules/27 }28 ]29 }30};3. 环境区分
javascript
1// 开发环境2const devConfig = {3 mode: 'development',4 devtool: 'eval-source-map',5 devServer: {6 hot: true,7 open: true8 }9};1011// 生产环境12const prodConfig = {13 mode: 'production',14 devtool: 'source-map',15 optimization: {16 minimize: true,17 splitChunks: {18 chunks: 'all'19 }20 }21};通过合理选择和配置构建工具,可以显著提升开发效率和应用性能。
常用Loader
javascript
1// webpack.config.js2module.exports = {3 module: {4 rules: [5 // JavaScript处理6 {7 test: /\.js$/,8 exclude: /node_modules/,9 use: 'babel-loader'10 },11 12 // TypeScript处理13 {14 test: /\.ts$/,15 use: 'ts-loader'16 },17 18 // CSS处理19 {20 test: /\.css$/,21 use: ['style-loader', 'css-loader']22 },23 24 // SCSS处理25 {26 test: /\.scss$/,27 use: ['style-loader', 'css-loader', 'sass-loader']28 },29 30 // 图片处理31 {32 test: /\.(png|jpg|gif|svg)$/,33 type: 'asset/resource'34 },35 36 // 字体处理37 {38 test: /\.(woff|woff2|eot|ttf|otf)$/,39 type: 'asset/resource'40 }41 ]42 }43};常用Plugin
javascript
1// webpack.config.js2const HtmlWebpackPlugin = require('html-webpack-plugin');3const MiniCssExtractPlugin = require('mini-css-extract-plugin');4const { CleanWebpackPlugin } = require('clean-webpack-plugin');56module.exports = {7 plugins: [8 // 生成HTML文件9 new HtmlWebpackPlugin({10 template: './src/index.html',11 filename: 'index.html'12 }),13 14 // 提取CSS到单独文件15 new MiniCssExtractPlugin({16 filename: '[name].[contenthash].css'17 }),18 19 // 清理构建目录20 new CleanWebpackPlugin()21 ]22};开发服务器
javascript
1// webpack.config.js2module.exports = {3 devServer: {4 static: './dist',5 hot: true,6 port: 3000,7 open: true,8 historyApiFallback: true9 }10};Vite
基础配置
javascript
1// vite.config.js2import { defineConfig } from 'vite';3import react from '@vitejs/plugin-react';45export default defineConfig({6 plugins: [react()],7 root: './src',8 build: {9 outDir: '../dist',10 rollupOptions: {11 input: {12 main: './src/index.html'13 }14 }15 },16 server: {17 port: 3000,18 open: true19 }20});插件配置
javascript
1// vite.config.js2import { defineConfig } from 'vite';3import react from '@vitejs/plugin-react';4import legacy from '@vitejs/plugin-legacy';56export default defineConfig({7 plugins: [8 react(),9 legacy({10 targets: ['defaults', 'not IE 11']11 })12 ],13 css: {14 preprocessorOptions: {15 scss: {16 additionalData: `@import "@/styles/variables.scss";`17 }18 }19 }20});环境变量
javascript
1// vite.config.js2export default defineConfig(({ command, mode }) => {3 const env = loadEnv(mode, process.cwd(), '');4 5 return {6 define: {7 __APP_VERSION__: JSON.stringify(process.env.npm_package_version)8 },9 server: {10 port: env.VITE_PORT || 300011 }12 };13});Rollup
基础配置
javascript
1// rollup.config.js2import resolve from '@rollup/plugin-node-resolve';3import commonjs from '@rollup/plugin-commonjs';4import babel from '@rollup/plugin-babel';56export default {7 input: 'src/index.js',8 output: [9 {10 file: 'dist/bundle.js',11 format: 'cjs'12 },13 {14 file: 'dist/bundle.esm.js',15 format: 'es'16 }17 ],18 plugins: [19 resolve(),20 commonjs(),21 babel({22 exclude: 'node_modules/**'23 })24 ],25 external: ['lodash']26};库打包配置
javascript
1// rollup.config.js2import resolve from '@rollup/plugin-node-resolve';3import commonjs from '@rollup/plugin-commonjs';4import typescript from '@rollup/plugin-typescript';56export default {7 input: 'src/index.ts',8 output: [9 {10 file: 'dist/index.js',11 format: 'cjs',12 sourcemap: true13 },14 {15 file: 'dist/index.esm.js',16 format: 'es',17 sourcemap: true18 }19 ],20 plugins: [21 resolve(),22 commonjs(),23 typescript({24 tsconfig: './tsconfig.json'25 })26 ],27 external: ['react', 'react-dom']28};Parcel
零配置使用
bash
1# 安装Parcel2npm install --save-dev parcel34# 开发模式5npx parcel src/index.html67# 构建8npx parcel build src/index.html配置文件
json
1// .parcelrc2{3 "extends": "@parcel/config-default",4 "transformers": {5 "*.{jpg,jpeg,png,gif,svg}": ["@parcel/transformer-image"]6 }7}工具对比
性能对比
| 工具 | 启动速度 | 热更新 | 构建速度 | 配置复杂度 |
|---|---|---|---|---|
| Webpack | 慢 | 中等 | 慢 | 高 |
| Vite | 快 | 快 | 快 | 中等 |
| Rollup | 中等 | 无 | 快 | 中等 |
| Parcel | 快 | 快 | 中等 | 低 |
适用场景
- Webpack:大型项目,需要复杂配置
- Vite:现代项目,快速开发体验
- Rollup:库开发,ES模块输出
- Parcel:简单项目,零配置需求
优化策略
代码分割
javascript
1// webpack.config.js2module.exports = {3 optimization: {4 splitChunks: {5 chunks: 'all',6 cacheGroups: {7 vendor: {8 test: /[\\/]node_modules[\\/]/,9 name: 'vendors',10 chunks: 'all'11 }12 }13 }14 }15};缓存优化
javascript
1// webpack.config.js2module.exports = {3 output: {4 filename: '[name].[contenthash].js',5 chunkFilename: '[name].[contenthash].js'6 },7 optimization: {8 moduleIds: 'deterministic',9 chunkIds: 'deterministic'10 }11};压缩优化
javascript
1// webpack.config.js2const TerserPlugin = require('terser-webpack-plugin');34module.exports = {5 optimization: {6 minimize: true,7 minimizer: [8 new TerserPlugin({9 terserOptions: {10 compress: {11 drop_console: true12 }13 }14 })15 ]16 }17};现代构建工具
ESBuild
javascript
1// esbuild.config.js2const esbuild = require('esbuild');34esbuild.build({5 entryPoints: ['src/index.js'],6 bundle: true,7 outfile: 'dist/bundle.js',8 minify: true,9 sourcemap: true,10 target: ['es2020']11}).catch(() => process.exit(1));SWC
javascript
1// .swcrc2{3 "jsc": {4 "parser": {5 "syntax": "ecmascript",6 "jsx": true7 },8 "transform": {9 "react": {10 "runtime": "automatic"11 }12 }13 }14}最佳实践
环境配置
javascript
1// webpack.config.js2const isProduction = process.env.NODE_ENV === 'production';34module.exports = {5 mode: isProduction ? 'production' : 'development',6 devtool: isProduction ? 'source-map' : 'eval-cheap-module-source-map',7 optimization: {8 minimize: isProduction9 }10};多环境配置
javascript
1// webpack.config.js2const configs = {3 development: {4 mode: 'development',5 devtool: 'eval-source-map'6 },7 production: {8 mode: 'production',9 devtool: 'source-map'10 }11};1213module.exports = (env, argv) => {14 const mode = argv.mode || 'development';15 return {16 ...configs[mode],17 entry: './src/index.js',18 output: {19 path: path.resolve(__dirname, 'dist'),20 filename: 'bundle.js'21 }22 };23};
参与讨论