什么是webpack
简单来说,webpack是一个简单的打包工具。可以将互相依赖的html,css,js,以及图片文字字体文件等资源文件,经过处理打包一个可执行的项目文件
开始第一步
我们开始第一步的话 要去配置初始环境 在需要使用webpack文件夹下, 首先去执行npm initial-y去初始化, 其中-y表示所有选项使用的默认
在执行 npm add webpack webpack-cli -- dev 将webpack安装在开发者模式 这个我们就能获得初始的开发环境
新建一个“src”文件夹,然后在里面index.js 编写一个简单的hello 同时添加到index 文件夹中
之后我们就能看到我们的打包文件 dist文件夹 里面的main.js就是打包完成的文件
接下来我们体验一下webpack整合代码的功能
这个就代码整合的过程
配置webpack.config.js
webpack.config.js 这个文件就像webpack 的大心脏 接下来我们就开始吧
初等配置
const path = require("path");
module.exports = {
mode: "development", // 这个是开发者模式 ,便于我们以后的维护
entry: "./src/index.js", // 这个是入口文件
output: {
filename: "dist.js",
path: path.resolve(__dirname,"dist"),
}
}
mode,选择了开发者模式entry,选择了相对于config文件的src目录下的index.js作为入口文件output, 对于输出配置了输出的名字,并且使用了自带的path配置了输出目录
执行npx webpack,可以看到不仅重新输出了dist.js,其中的内容也和之前的有了不一样
output: {
filename: "[name].[contenthash].js",
path: path.resolve(__dirname, "dist"),
},
这样可以避免打包文件更新之后,浏览缓存发生改变 ,可以给文件添加一段随机的字符,每次更新后都会生成新的随机字符,所有webpack.config.js output中设置
打包css文件和图片
要先安装 npm add --dev style-loader css-loader 配置如下:
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: "asset/resource",
},
test 这里面的通过正则来表示解析什么样的文件
所以在入口文件index.js中引入style.css和图片assets/images/avater.jpg即可
使用webpack插件自动生成html文件
还是安装
npm add html-webpack-plugin --dev
然后导入webpack.config.js文件
const HtmlWebpackPlugin = require("html-webpack-plugin")
再进行配置即可
plugins:[
new HtmlWebpackPlugin()
title:'文件名'
],
所以这时候再执行打包命令,可以看到输出文件夹下还多了一个index.html
这个HTML的标题默认为Webpack App
我们也可以对这个进行配置
兼容低版本浏览器
这个可以借助我们的依赖 babel 而webpack也支持相应的loader
所以首先还是安装
npm add --dev babel-loader @babel/core @babel/preset-env
这三个包提供了我们需要的功能
然后再进行下面的配置 这个也是写在 model 中的
{
test: /.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
},
},
},
用于将 ES6+ 的代码转换为向后兼容的 JavaScript 代码,以便在现代浏览器中运行。
压缩打包后的js代码
同样是两步,先安装
npm add --dev terser-webpack-plugin
然后引入和配置
const TerserPlugin = require("terser-webpack-plugin")
optimization: {
minimize: true,
minimizer: [new TerserPlugin()],
},
- TerserWebpackPlugin:
- 代码压缩:TerserWebpackPlugin 使用 Terser 压缩器对 JavaScript 代码进行压缩,包括删除空格、注释和不必要的代码,以及压缩变量名等操作,从而减小文件体积。
- 混淆:该插件可以将 JavaScript 代码进行混淆,使得代码更难以理解,增加了代码的安全性。
- 删除未使用的代码:TerserWebpackPlugin 可以检测并删除未使用的代码,减小最终生成的文件大小,提高性能。
- 配置灵活:插件提供了丰富的配置选项,可以根据项目需求进行定制,例如启用/禁用某些压缩选项、设置压缩的级别等。
无需执行命令自动打包
现在每次修改了代码后都得重新执行打包命令
webpack也提供了一个插件能在保存后自动打包
还是先安装
npm add --dev webpack-dev-server
然后先在webpack.config.js中进行配置
devServer: {
static: "./dist",
},
再在package.json中进行配置
"scripts": {
"start": "webpack serve --open"
},
这样在我们执行npm start的时候就会自动执行webpack serve
并且通过 npm start 打开的网页能在我们修改保存后自动刷新页面
配置可视化打包工具
这是一个帮助分析的工具,它会可视化地展现打包过程中哪个文件占的体积比较大
所以同样的,先是安装这个插件
npm add --dev webpack-bundle-analyzer
接着在配置中引入这个插件
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer");
以及在plugins中添加它
plugins: [
new HtmlWebpackPlugin({
title: "test",
}),
new BundleAnalyzerPlugin.BundleAnalyzerPlugin()
],
所以此时再运行npx webpack
自动跳出了这样的文件分析图
难点 :在webpack打包过程中 文件过大 可以怎么样处理
- 压缩代码:使用 TerserWebpackPlugin 或者其他压缩插件来压缩 JavaScript 代码,减小文件体积。
- 图片优化:对图片文件进行优化,可以使用像 Image-webpack-loader 这样的 loader 来压缩图片,减小图片文件的大小。
- 移除不必要的插件和库:检查项目中是否有不必要的插件和库,尽量只保留需要的部分,减小打包文件的体积。
SplitChunksPlugin
SplitChunksPlugin 是 Webpack 提供的一个用于代码拆分的插件,它可以将公共模块拆分出来,从而减小每个入口文件的体积,提高加载速度。具体来说,SplitChunksPlugin 可以根据配置选项将重复的代码拆分成单独的 chunk,并且在必要时自动创建新的 chunk。
const webpack = require('webpack');
module.exports = {
// 其他配置项...
optimization: {
splitChunks: {
chunks: 'all',
minSize: 20000, // 拆分前的最小文件大小,默认为 30kb
maxSize: 0, // 拆分后的最大文件大小,0 表示不限制
minChunks: 1, // 被拆分模块至少被引用的次数,默认为 1
maxAsyncRequests: 30, // 按需加载时的最大并行请求数,默认为 5
maxInitialRequests: 30, // 入口点的最大并行请求数,默认为 3
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
};
在这个配置中,我们通过 optimization.splitChunks 来配置 SplitChunksPlugin。其中,chunks: 'all' 表示拆分所有类型的 chunk,包括入口 chunk 和异步加载的 chunk。其他配置项用于定义拆分的规则和条件,例如最小/最大文件大小、最小引用次数等。
通过合理地配置 SplitChunksPlugin,可以将公共模块拆分出来,减小打包文件的体积,提高加载速度。
/**
* Webpack 企业级项目配置示例
* 详细说明每个配置项的作用和用法
*/
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const webpack = require('webpack')
// 环境变量
const isProduction = process.env.NODE_ENV === 'production'
const isDevelopment = !isProduction
module.exports = {
// ==================== 1. 入口 (Entry) ====================
/**
* 作用:告诉 webpack 从哪个文件开始打包
* 说明:webpack 会从这个文件开始,找出所有依赖的模块
*
* 示例:
* - 单入口:entry: './src/index.js'
* - 多入口:entry: { main: './src/index.js', admin: './src/admin.js' }
*/
entry: {
main: './src/index.jsx', // 主入口文件
// vendor: './src/vendor.js' // 第三方库入口(可选)
},
// ==================== 2. 输出 (Output) ====================
/**
* 作用:告诉 webpack 打包后的文件输出到哪里,以及如何命名
* 说明:控制打包后文件的路径、文件名、CDN 路径等
*/
output: {
/**
* path: 输出目录的绝对路径
* 作用:指定打包后文件存放的文件夹
* 示例:打包后的文件会放在项目根目录的 build 文件夹中
*/
path: path.resolve(__dirname, 'build'),
/**
* filename: 输出文件的名称
* 作用:控制打包后的 JS 文件命名规则
*
* 占位符说明:
* - [name]: 入口名称(如 main)
* - [hash]: 整个项目的 hash 值(所有文件变化时改变)
* - [chunkhash]: chunk 的 hash 值(只有该 chunk 变化时改变)
* - [contenthash]: 内容的 hash 值(文件内容变化时改变)
*
* 示例:
* - 'bundle.js' → 固定名称
* - '[name].js' → main.js
* - '[name].[hash].js' → main.a1b2c3d4.js(用于缓存)
*/
filename: isProduction
? 'static/js/[name].[contenthash:8].js' // 生产环境:带 hash,便于缓存
: 'static/js/[name].js', // 开发环境:简单名称,便于调试
/**
* chunkFilename: 非入口 chunk 文件的名称
* 作用:控制代码分割后产生的 chunk 文件命名
* 示例:通过 import() 动态导入的模块会使用这个命名规则
*/
chunkFilename: isProduction
? 'static/js/[name].[contenthash:8].chunk.js'
: 'static/js/[name].chunk.js',
/**
* publicPath: 资源的基础路径
* 作用:指定打包后资源(JS、CSS、图片等)的公共路径
*
* 示例:
* - '/' → 相对路径(默认)
* - '/assets/' → 所有资源都在 /assets/ 目录下
* - 'https://cdn.example.com/' → CDN 地址
*
* 实际应用:
* - 开发环境:通常用 '/'
* - 生产环境:如果用 CDN,设置为 CDN 地址
*/
publicPath: '/',
/**
* assetModuleFilename: 静态资源的输出路径和名称
* 作用:控制图片、字体等静态资源的输出位置
*/
assetModuleFilename: 'static/media/[name].[hash:8][ext]',
/**
* clean: 是否在打包前清理输出目录
* 作用:每次打包前自动删除旧的输出文件
*/
clean: true,
},
// ==================== 3. 模式 (Mode) ====================
/**
* 作用:设置 webpack 的运行模式
* 说明:会自动启用相应的优化配置
*
* - 'development': 开发模式,启用开发工具,不压缩代码
* - 'production': 生产模式,启用代码压缩和优化
* - 'none': 不启用任何默认优化
*/
mode: isProduction ? 'production' : 'development',
// ==================== 4. 开发工具 (Devtool) ====================
/**
* 作用:控制如何生成 source map(源码映射)
* 说明:用于调试,将打包后的代码映射回原始源码
*
* 常用选项:
* - 'eval': 最快,但不适合生产环境
* - 'source-map': 最完整,但文件最大
* - 'cheap-module-source-map': 平衡速度和完整性
* - 'hidden-source-map': 生成但不引用(用于错误追踪)
*
* 生产环境建议:'source-map' 或 'hidden-source-map'
* 开发环境建议:'eval-source-map' 或 'cheap-module-eval-source-map'
*/
devtool: isProduction
? 'source-map' // 生产环境:完整的 source map
: 'eval-source-map', // 开发环境:快速构建
// ==================== 5. 模块解析 (Resolve) ====================
/**
* 作用:配置模块如何被解析
* 说明:控制如何查找和解析模块
*/
resolve: {
/**
* extensions: 自动解析的文件扩展名
* 作用:导入模块时可以省略扩展名
* 示例:import Component from './Component' 会自动查找 Component.jsx
*/
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
/**
* alias: 路径别名
* 作用:简化导入路径,避免使用相对路径
*
* 示例:
* - import util from '@/utils/util' 代替 import util from '../../../utils/util'
* - import Component from '@components/Button' 代替长路径
*/
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
'@utils': path.resolve(__dirname, 'src/utils'),
'@services': path.resolve(__dirname, 'src/services'),
'@assets': path.resolve(__dirname, 'src/assets'),
},
/**
* modules: 告诉 webpack 解析模块时应该搜索的目录
* 作用:优先从这些目录查找模块
*/
modules: ['node_modules', path.resolve(__dirname, 'src')],
/**
* mainFields: 指定从 package.json 的哪个字段读取入口文件
* 作用:控制模块的入口文件解析顺序
*/
mainFields: ['browser', 'module', 'main'],
},
// ==================== 6. 模块规则 (Module Rules) ====================
/**
* 作用:配置如何处理不同类型的模块
* 说明:告诉 webpack 遇到不同类型的文件时应该使用什么 loader
*/
module: {
rules: [
// ========== JavaScript/TypeScript 处理 ==========
{
/**
* test: 匹配文件的正则表达式
* 作用:指定哪些文件需要被这个规则处理
*/
test: /\.(js|jsx|ts|tsx)$/,
/**
* exclude: 排除的目录或文件
* 作用:告诉 webpack 不要处理这些文件
* 说明:node_modules 中的文件通常不需要 babel 转译
*/
exclude: /node_modules/,
/**
* use: 使用的 loader
* 作用:指定如何处理匹配的文件
*
* 说明:
* - loader 的执行顺序是从下到上(或从右到左)
* - 先执行 babel-loader,再执行其他处理
*/
use: [
{
loader: 'babel-loader',
options: {
/**
* cacheDirectory: 缓存目录
* 作用:缓存 babel 编译结果,加快构建速度
*/
cacheDirectory: true,
presets: [
['@babel/preset-env', {
useBuiltIns: 'usage', // 按需引入 polyfill
corejs: 3
}],
'@babel/preset-react', // React JSX 支持
'@babel/preset-typescript' // TypeScript 支持
],
plugins: [
'@babel/plugin-proposal-class-properties',
'@babel/plugin-syntax-dynamic-import' // 动态导入支持
]
}
}
]
},
// ========== CSS 处理 ==========
{
test: /\.css$/,
use: [
/**
* 开发环境:使用 style-loader(将 CSS 注入到 <style> 标签)
* 生产环境:使用 MiniCssExtractPlugin.loader(提取 CSS 到独立文件)
*/
isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
{
loader: 'css-loader',
options: {
/**
* modules: 是否启用 CSS Modules
* 作用:将 CSS 类名局部化,避免样式冲突
*/
modules: {
localIdentName: '[local]--[hash:base64:5]' // 类名格式
},
importLoaders: 1 // 在 css-loader 之前应用的 loader 数量
}
},
{
/**
* postcss-loader: CSS 后处理器
* 作用:自动添加浏览器前缀、压缩 CSS 等
*/
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
require('autoprefixer'), // 自动添加浏览器前缀
require('cssnano') // CSS 压缩
]
}
}
}
]
},
// ========== Less/Sass 处理 ==========
{
test: /\.less$/,
use: [
isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
{
loader: 'less-loader',
options: {
lessOptions: {
/**
* modifyVars: 修改 Less 变量
* 作用:覆盖 Less 文件中的变量值
* 示例:可以在配置中统一修改主题色
*/
modifyVars: {
'@primary-color': '#ff7e00',
'@text-color': '#333'
},
javascriptEnabled: true // 允许在 Less 中使用 JavaScript
}
}
}
]
},
// ========== 图片处理 ==========
{
test: /\.(png|jpg|jpeg|gif|svg|webp)$/,
/**
* type: 'asset'
* 作用:webpack 5 的新特性,自动处理静态资源
*
* 说明:
* - 小于 8KB 的图片会转为 base64(内联)
* - 大于 8KB 的图片会作为文件输出
*/
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // 8KB
}
},
generator: {
filename: 'static/images/[name].[hash:8][ext]'
}
},
// ========== 字体处理 ==========
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: 'asset/resource',
generator: {
filename: 'static/fonts/[name].[hash:8][ext]'
}
},
// ========== 其他文件处理 ==========
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)$/,
type: 'asset/resource',
generator: {
filename: 'static/media/[name].[hash:8][ext]'
}
}
]
},
// ==================== 7. 插件 (Plugins) ====================
/**
* 作用:扩展 webpack 功能
* 说明:插件可以执行更广泛的任务,如打包优化、资源管理、环境变量注入等
*/
plugins: [
/**
* HtmlWebpackPlugin: 自动生成 HTML 文件
* 作用:自动将打包后的 JS/CSS 文件注入到 HTML 中
*/
new HtmlWebpackPlugin({
template: './public/index.html', // HTML 模板文件
filename: 'index.html', // 输出文件名
inject: true, // 自动注入 JS/CSS
minify: isProduction ? { // 生产环境压缩 HTML
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true
} : false
}),
/**
* CleanWebpackPlugin: 清理输出目录
* 作用:每次打包前自动删除旧的输出文件
*/
new CleanWebpackPlugin(),
/**
* DefinePlugin: 定义全局常量
* 作用:在代码中使用环境变量
*
* 示例:
* - 代码中:console.log(process.env.NODE_ENV)
* - 会被替换为:console.log('production')
*/
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
'process.env.REACT_APP_API_URL': JSON.stringify(process.env.REACT_APP_API_URL)
}),
/**
* MiniCssExtractPlugin: 提取 CSS 到独立文件
* 作用:将 CSS 从 JS 中分离出来,单独打包
*
* 优势:
* - CSS 可以并行加载
* - CSS 可以被浏览器缓存
* - 减少 JS 文件大小
*/
...(isProduction ? [
new MiniCssExtractPlugin({
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css'
})
] : []),
/**
* IgnorePlugin: 忽略某些模块
* 作用:排除不需要的模块,减小打包体积
*
* 示例:忽略 moment.js 的 locale 文件(只保留英文)
*/
new webpack.IgnorePlugin({
resourceRegExp: /^\.\/locale$/,
contextRegExp: /moment$/
})
],
// ==================== 8. 优化 (Optimization) ====================
/**
* 作用:配置代码分割和压缩优化
* 说明:控制如何优化打包后的代码
*/
optimization: {
/**
* minimize: 是否压缩代码
* 作用:生产环境压缩 JS 和 CSS,减小文件体积
*/
minimize: isProduction,
/**
* minimizer: 压缩器配置
* 作用:指定使用什么工具压缩代码
*/
minimizer: [
/**
* TerserPlugin: JavaScript 压缩器
* 作用:压缩和混淆 JavaScript 代码
*/
new TerserPlugin({
terserOptions: {
compress: {
drop_console: isProduction, // 生产环境移除 console
drop_debugger: isProduction // 生产环境移除 debugger
}
},
extractComments: false // 不提取注释到单独文件
}),
/**
* CssMinimizerPlugin: CSS 压缩器
* 作用:压缩 CSS 代码
*/
new CssMinimizerPlugin()
],
/**
* splitChunks: 代码分割配置
* 作用:将代码拆分成多个 chunk,实现按需加载和缓存优化
*/
splitChunks: {
chunks: 'all', // 对所有类型的 chunk 进行分割
cacheGroups: {
/**
* vendor: 第三方库单独打包
* 作用:将 node_modules 中的代码单独打包
*
* 优势:
* - 第三方库代码变化频率低,可以长期缓存
* - 业务代码和第三方代码分离,便于缓存管理
*/
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10, // 优先级
reuseExistingChunk: true
},
/**
* common: 公共代码单独打包
* 作用:将多个入口共享的代码提取出来
*/
common: {
minChunks: 2, // 至少被 2 个 chunk 引用
priority: 5,
reuseExistingChunk: true
},
/**
* react: React 相关库单独打包
* 作用:React、ReactDOM 等核心库单独打包
*/
react: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: 'react',
priority: 20,
reuseExistingChunk: true
}
}
},
/**
* runtimeChunk: 运行时代码单独打包
* 作用:将 webpack 的运行时代码单独提取
*
* 优势:
* - 运行时代码变化频率低,可以长期缓存
* - 业务代码变化时,运行时代码不需要重新下载
*/
runtimeChunk: {
name: 'runtime'
}
},
// ==================== 9. 开发服务器 (DevServer) ====================
/**
* 作用:配置开发服务器
* 说明:只在开发环境使用,提供热更新、代理等功能
*/
devServer: {
/**
* port: 开发服务器端口
* 作用:指定开发服务器监听的端口号
*/
port: 3000,
/**
* host: 开发服务器主机
* 作用:指定开发服务器的主机地址
* 示例:
* - 'localhost': 只能本机访问
* - '0.0.0.0': 可以通过 IP 地址访问(局域网)
*/
host: 'localhost',
/**
* open: 是否自动打开浏览器
* 作用:启动开发服务器后自动打开浏览器
*/
open: true,
/**
* hot: 是否启用热模块替换 (HMR)
* 作用:修改代码后自动更新,无需刷新页面
*/
hot: true,
/**
* historyApiFallback: SPA 路由支持
* 作用:所有路由都返回 index.html,支持前端路由
*/
historyApiFallback: {
rewrites: [
{ from: /./, to: '/index.html' }
]
},
/**
* proxy: API 代理
* 作用:将 API 请求代理到后端服务器,解决跨域问题
*
* 示例:
* - 前端请求:/api/user/info
* - 实际请求:http://localhost:8080/api/user/info
*/
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true, // 改变请求头中的 origin
pathRewrite: {
'^/api': '' // 重写路径,去掉 /api 前缀
}
},
'/mservice': {
target: 'http://mobileservice.demo.ehi.com.cn',
changeOrigin: true,
pathRewrite: {
'^/mservice': ''
}
}
},
/**
* compress: 是否启用 gzip 压缩
* 作用:压缩响应内容,加快传输速度
*/
compress: true,
/**
* client: 客户端配置
* 作用:控制浏览器中的日志和错误提示
*/
client: {
overlay: {
errors: true, // 显示错误
warnings: false // 不显示警告
},
logging: 'info' // 日志级别
}
},
// ==================== 10. 性能优化 ====================
/**
* 作用:控制 webpack 的性能提示
* 说明:当打包文件过大时给出警告或错误
*/
performance: {
hints: isProduction ? 'warning' : false, // 生产环境显示警告
maxEntrypointSize: 512000, // 入口文件最大 512KB
maxAssetSize: 512000 // 单个资源最大 512KB
},
// ==================== 11. 外部依赖 (Externals) ====================
/**
* 作用:指定哪些模块不需要打包
* 说明:这些模块会在运行时从外部获取(如 CDN)
*
* 使用场景:
* - 使用 CDN 加载大型库(如 React、jQuery)
* - 减小打包体积
* - 利用浏览器缓存
*/
externals: isProduction ? {
// 'react': 'React', // 从 CDN 加载 React
// 'react-dom': 'ReactDOM' // 从 CDN 加载 ReactDOM
} : {},
// ==================== 12. 缓存配置 ====================
/**
* 作用:配置 webpack 的缓存策略
* 说明:缓存编译结果,加快后续构建速度
*/
cache: {
type: 'filesystem', // 使用文件系统缓存
buildDependencies: {
config: [__filename] // 配置文件变化时清除缓存
}
}
}
Webpack 完整指南
📚 目录
Webpack 是什么
定义
Webpack 是一个模块打包器(Module Bundler),它的主要功能是:
- 打包:将多个文件(模块)打包成一个或多个文件
- 转换:将现代 JavaScript、TypeScript、Less/Sass 等转换为浏览器可识别的代码
- 优化:压缩代码、代码分割、按需加载等
- 开发工具:提供开发服务器、热更新等功能
简单理解
想象一下:
- 没有 Webpack:你需要手动管理每个 JS 文件的加载顺序,处理浏览器兼容性,手动压缩代码
- 有 Webpack:你只需要写现代代码,Webpack 自动帮你打包、转换、优化
工作流程
源代码 (src/)
↓
Webpack 处理
├─ 解析依赖关系
├─ 转换代码(Babel、TypeScript、Less)
├─ 优化代码(压缩、分割)
└─ 生成输出文件
↓
打包后的文件 (build/)
为什么需要 Webpack
1. 模块化开发
问题:浏览器原生不支持 ES6 模块、CommonJS 等模块系统
解决:Webpack 可以将模块化代码转换为浏览器可执行的代码
// 源代码(ES6 模块)
import React from 'react'
import { Button } from './components/Button'
import './styles.css'
// Webpack 处理后:所有代码打包成一个文件,浏览器可以直接运行
2. 代码转换
问题:现代 JavaScript(ES6+)、TypeScript、Less/Sass 浏览器不支持
解决:Webpack 通过 Loader 转换代码
// 源代码:TypeScript + Less
const Component: React.FC = () => {
return <div className="container">Hello</div>
}
// Webpack 处理后:转换为 ES5 JavaScript + CSS
3. 性能优化
问题:代码文件太大,加载慢
解决:Webpack 可以:
- 压缩代码(减小体积)
- 代码分割(按需加载)
- Tree Shaking(移除未使用代码)
4. 开发体验
问题:每次修改代码需要手动刷新浏览器
解决:Webpack DevServer 提供:
- 热更新(HMR):修改代码自动更新
- 开发服务器:本地开发环境
- Source Map:方便调试
Webpack 核心概念
1. Entry(入口)
作用:告诉 Webpack 从哪个文件开始打包
// 单入口
entry: './src/index.js'
// 多入口(多页面应用)
entry: {
main: './src/index.js',
admin: './src/admin.js'
}
示例:
// 项目入口文件:src/index.jsx
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(<App />, document.getElementById('root'))
// Webpack 会从这个文件开始,找出所有依赖的模块
2. Output(输出)
作用:告诉 Webpack 打包后的文件输出到哪里
output: {
path: path.resolve(__dirname, 'build'), // 输出目录
filename: 'bundle.js' // 输出文件名
}
示例:
// 打包前
src/
├─ index.jsx
├─ App.jsx
└─ components/Button.jsx
// 打包后
build/
└─ bundle.js // 所有代码打包成一个文件
3. Loader(加载器)
作用:告诉 Webpack 如何处理不同类型的文件
module: {
rules: [
{
test: /\.jsx?$/, // 匹配 .js 和 .jsx 文件
use: 'babel-loader', // 使用 babel-loader 处理
exclude: /node_modules/ // 排除 node_modules
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'] // 从右到左执行
}
]
}
常见 Loader:
| Loader | 作用 | 示例 |
|---|---|---|
babel-loader | 转换 ES6+、JSX | import React from 'react' → ES5 |
css-loader | 处理 CSS | import './style.css' |
less-loader | 处理 Less | import './style.less' |
file-loader | 处理文件 | import logo from './logo.png' |
url-loader | 处理文件(可转 base64) | 小图片转 base64 |
4. Plugin(插件)
作用:扩展 Webpack 功能,执行更广泛的任务
plugins: [
new HtmlWebpackPlugin(), // 自动生成 HTML
new CleanWebpackPlugin(), // 清理输出目录
new MiniCssExtractPlugin() // 提取 CSS
]
常见 Plugin:
| Plugin | 作用 | 示例 |
|---|---|---|
HtmlWebpackPlugin | 自动生成 HTML | 自动注入 JS/CSS 到 HTML |
CleanWebpackPlugin | 清理输出目录 | 每次打包前删除旧文件 |
MiniCssExtractPlugin | 提取 CSS | 将 CSS 从 JS 中分离 |
DefinePlugin | 定义全局变量 | process.env.NODE_ENV |
TerserPlugin | 压缩 JavaScript | 减小文件体积 |
5. Mode(模式)
作用:设置 Webpack 的运行模式,自动启用相应优化
mode: 'production' // 或 'development'
区别:
| 特性 | development | production |
|---|---|---|
| 代码压缩 | ❌ | ✅ |
| Source Map | 完整 | 可选 |
| 构建速度 | 快 | 慢 |
| 文件大小 | 大 | 小 |
| 调试 | 方便 | 困难 |
企业级配置详解
配置结构概览
module.exports = {
entry: {}, // 入口
output: {}, // 输出
mode: '', // 模式
devtool: '', // Source Map
resolve: {}, // 模块解析
module: {}, // 模块规则
plugins: [], // 插件
optimization: {}, // 优化
devServer: {} // 开发服务器
}
1. Entry(入口配置)
作用
指定 Webpack 从哪个文件开始打包,找出所有依赖
配置示例
// 单入口(SPA 应用)
entry: './src/index.jsx'
// 多入口(多页面应用)
entry: {
main: './src/index.jsx',
admin: './src/admin.jsx',
mobile: './src/mobile.jsx'
}
// 项目中的实际用法
entry: {
main: './src/index.jsx' // React 应用入口
}
实际例子
// src/index.jsx
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import './index.css'
ReactDOM.render(<App />, document.getElementById('root'))
// Webpack 会:
// 1. 从 index.jsx 开始
// 2. 找到 App.jsx
// 3. 找到 App.jsx 中导入的所有模块
// 4. 递归处理所有依赖
// 5. 打包成一个或多个文件
2. Output(输出配置)
作用
控制打包后文件的输出位置、文件名、CDN 路径等
配置详解
output: {
// 输出目录(绝对路径)
path: path.resolve(__dirname, 'build'),
// 输出文件名
filename: 'static/js/[name].[contenthash:8].js',
// 公共路径(CDN 地址)
publicPath: '/',
// 清理输出目录
clean: true
}
文件名占位符
| 占位符 | 说明 | 示例 |
|---|---|---|
[name] | 入口名称 | main.js |
[hash] | 整个项目的 hash | main.a1b2c3d4.js |
[chunkhash] | chunk 的 hash | main.b2c3d4e5.js |
[contenthash] | 内容的 hash | main.c3d4e5f6.js |
实际例子
// 开发环境
filename: 'static/js/[name].js'
// 输出:static/js/main.js
// 生产环境(带 hash,便于缓存)
filename: 'static/js/[name].[contenthash:8].js'
// 输出:static/js/main.a1b2c3d4.js
// 优势:文件内容变化时 hash 变化,浏览器会重新下载
// 文件内容不变时 hash 不变,浏览器使用缓存
3. Resolve(模块解析)
作用
配置如何查找和解析模块
配置详解
resolve: {
// 自动解析的扩展名
extensions: ['.js', '.jsx', '.ts', '.tsx'],
// 路径别名
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components')
},
// 模块搜索目录
modules: ['node_modules', path.resolve(__dirname, 'src')]
}
实际例子
// 没有 alias(使用相对路径)
import Button from '../../../components/Button'
import util from '../../utils/util'
// 有 alias(使用别名)
import Button from '@components/Button'
import util from '@/utils/util'
// Webpack 会自动解析:
// @components → src/components
// @ → src
4. Module Rules(模块规则)
作用
告诉 Webpack 如何处理不同类型的文件
JavaScript/TypeScript 处理
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env', // ES6+ → ES5
'@babel/preset-react', // JSX → JavaScript
'@babel/preset-typescript' // TypeScript → JavaScript
]
}
}
}
实际例子:
// 源代码(ES6 + JSX)
const Component = () => {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(count + 1)}>{count}</button>
}
// Babel 转换后(ES5)
var Component = function Component() {
var _useState = useState(0),
count = _useState[0],
setCount = _useState[1];
return React.createElement('button', {
onClick: function onClick() {
return setCount(count + 1);
}
}, count);
};
CSS 处理
{
test: /\.css$/,
use: [
'style-loader', // 将 CSS 注入到 <style> 标签
'css-loader' // 解析 CSS 文件
]
}
实际例子:
// 源代码
import './styles.css'
// Webpack 处理后:
// 1. css-loader 解析 CSS 文件
// 2. style-loader 将 CSS 注入到 <style> 标签
// 3. 页面中自动添加 <style>...</style>
Less/Sass 处理
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
{
loader: 'less-loader',
options: {
lessOptions: {
modifyVars: {
'@primary-color': '#ff7e00' // 覆盖 Less 变量
}
}
}
}
]
}
实际例子:
// 源代码:styles.less
.container {
color: @primary-color; // 使用变量
}
// Less 编译后:styles.css
.container {
color: #ff7e00; // 变量被替换
}
// Webpack 处理后:注入到页面
图片处理
{
test: /\.(png|jpg|jpeg|gif|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // 小于 8KB 转 base64
}
}
}
实际例子:
// 源代码
import logo from './logo.png' // 10KB 的图片
import icon from './icon.png' // 5KB 的图片
// Webpack 处理:
// logo.png → 输出为文件:static/images/logo.a1b2c3d4.png
// icon.png → 转为 base64:data:image/png;base64,iVBORw0KG...
5. Plugins(插件)
HtmlWebpackPlugin
作用:自动生成 HTML 文件,并注入打包后的 JS/CSS
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html',
inject: true // 自动注入 JS/CSS
})
实际例子:
<!-- 模板文件:public/index.html -->
<!DOCTYPE html>
<html>
<head>
<title>一嗨租车</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
<!-- Webpack 处理后:build/index.html -->
<!DOCTYPE html>
<html>
<head>
<title>一嗨租车</title>
<link href="static/css/main.a1b2c3d4.css" rel="stylesheet">
</head>
<body>
<div id="root"></div>
<script src="static/js/main.b2c3d4e5.js"></script>
</body>
</html>
MiniCssExtractPlugin
作用:将 CSS 从 JS 中提取出来,生成独立的 CSS 文件
new MiniCssExtractPlugin({
filename: 'static/css/[name].[contenthash:8].css'
})
实际例子:
// 打包前:CSS 在 JS 中
// bundle.js 包含:
// - JavaScript 代码
// - CSS 代码(通过 style-loader 注入)
// 打包后:CSS 独立文件
// main.js(只包含 JavaScript)
// main.css(独立的 CSS 文件)
// 优势:
// 1. CSS 可以并行加载
// 2. CSS 可以被浏览器缓存
// 3. 减少 JS 文件大小
DefinePlugin
作用:定义全局常量,在代码中使用
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production'),
'process.env.API_URL': JSON.stringify('https://api.example.com')
})
实际例子:
// 源代码
if (process.env.NODE_ENV === 'development') {
console.log('开发环境')
}
// Webpack 替换后
if ('production' === 'development') {
console.log('开发环境')
}
// 生产环境构建时,这段代码会被移除(Tree Shaking)
IgnorePlugin
作用:忽略某些模块,减小打包体积
new webpack.IgnorePlugin({
resourceRegExp: /^\.\/locale$/,
contextRegExp: /moment$/
})
实际例子:
// 源代码
import moment from 'moment'
import 'moment/locale/zh-cn' // 中文语言包
// 使用 IgnorePlugin 后
// moment 的 locale 文件不会被打包
// 打包体积减小:从 200KB → 50KB
// 项目中的实际用法(rsbuild.config.js)
new rspack.IgnorePlugin({
resourceRegExp: /^\.\/locale$/,
contextRegExp: /moment$/
})
6. Optimization(优化配置)
代码压缩
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true, // 移除 console
drop_debugger: true // 移除 debugger
}
}
})
]
}
实际例子:
// 源代码
function add(a, b) {
console.log('计算中...')
return a + b
}
// 压缩后
function add(a,b){return a+b}
// console.log 被移除,代码被压缩
代码分割(Code Splitting)
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10
}
}
}
}
实际例子:
// 打包前:所有代码在一个文件
// bundle.js (2MB)
// - React 代码
// - 业务代码
// - 第三方库代码
// 打包后:代码分割
// vendors.js (500KB) - 第三方库(变化少,可长期缓存)
// main.js (1.5MB) - 业务代码(变化频繁)
// 优势:
// 1. 第三方库可以长期缓存
// 2. 业务代码更新时,用户只需重新下载 main.js
// 3. 可以并行加载多个文件
7. DevServer(开发服务器)
作用
提供开发环境的功能:热更新、代理、开发服务器
配置详解
devServer: {
port: 3000, // 端口
host: 'localhost', // 主机
open: true, // 自动打开浏览器
hot: true, // 热更新
historyApiFallback: true, // SPA 路由支持
proxy: { // API 代理
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
实际例子
热更新(HMR):
// 修改代码
const Component = () => {
return <div>Hello World</div> // 改为 Hello React
}
// 浏览器自动更新,无需刷新页面
// 页面显示:Hello React
API 代理:
// 前端请求
fetch('/api/user/info')
// DevServer 代理后
// 实际请求:http://localhost:8080/api/user/info
// 解决跨域问题
实际应用示例
示例 1:React 项目配置
// webpack.config.js
module.exports = {
entry: './src/index.jsx',
output: {
path: path.resolve(__dirname, 'build'),
filename: 'static/js/[name].[contenthash:8].js'
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: 'babel-loader'
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html'
})
]
}
示例 2:项目中的实际配置(rsbuild)
// rsbuild.config.js(基于 webpack/rspack)
export default defineConfig({
source: {
index: './src/index.jsx' // 入口
},
output: {
distPath: {
root: 'build' // 输出目录
}
},
plugins: [
pluginReact(), // React 支持
pluginLess({ // Less 支持
lessOptions: {
modifyVars: {
'@brand-primary': '#ff7e00' // 主题色
}
}
})
],
server: {
port: 3000,
proxy: serverProxy // API 代理
}
})
总结
Webpack 的核心作用
- 打包:将多个文件打包成少数几个文件
- 转换:将现代代码转换为浏览器可执行的代码
- 优化:压缩、分割、缓存优化
- 开发工具:提供开发服务器和热更新
关键配置项
| 配置项 | 作用 | 示例 |
|---|---|---|
entry | 入口文件 | './src/index.jsx' |
output | 输出配置 | path: 'build' |
module.rules | 文件处理规则 | Babel、CSS Loader |
plugins | 功能扩展 | HtmlWebpackPlugin |
optimization | 代码优化 | 压缩、分割 |
devServer | 开发服务器 | 热更新、代理 |
学习建议
- 理解核心概念:Entry、Output、Loader、Plugin
- 掌握常用配置:开发环境和生产环境的区别
- 实践项目:在实际项目中配置和优化
- 查看文档:遇到问题查阅官方文档
更多详细信息请查看 webpack.config.example.js 文件中的完整配置示例。
Webpack 工作流程示例
📦 打包前后对比
打包前(源代码)
项目结构:
src/
├── index.jsx (入口文件)
├── App.jsx
├── components/
│ ├── Button.jsx
│ └── Header.jsx
├── utils/
│ └── util.js
├── styles/
│ ├── index.css
│ └── app.less
└── assets/
└── logo.png
打包后(输出文件)
build/
├── index.html (自动生成)
├── static/
│ ├── js/
│ │ ├── main.a1b2c3d4.js (业务代码)
│ │ ├── vendors.b2c3d4e5.js (第三方库)
│ │ └── runtime.c3d4e5f6.js (运行时代码)
│ ├── css/
│ │ └── main.d4e5f6a7.css (样式文件)
│ └── images/
│ └── logo.e5f6a7b8.png (图片)
🔄 Webpack 处理流程
步骤 1:解析入口文件
// 入口:src/index.jsx
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import './styles/index.css'
ReactDOM.render(<App />, document.getElementById('root'))
Webpack 分析:
- 发现依赖:
react、react-dom、./App、./styles/index.css - 继续解析这些依赖
步骤 2:解析依赖模块
// src/App.jsx
import Button from './components/Button'
import Header from './components/Header'
import './styles/app.less'
function App() {
return (
<div>
<Header />
<Button />
</div>
)
}
Webpack 分析:
- 发现依赖:
./components/Button、./components/Header、./styles/app.less - 继续解析
步骤 3:构建依赖图
index.jsx
├── react (node_modules)
├── react-dom (node_modules)
├── App.jsx
│ ├── Button.jsx
│ ├── Header.jsx
│ └── app.less
└── index.css
步骤 4:转换代码
JavaScript 转换(Babel)
// 源代码(ES6 + JSX)
const Component = () => {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(count + 1)}>{count}</button>
}
// 转换后(ES5)
var Component = function Component() {
var _useState = useState(0),
count = _useState[0],
setCount = _useState[1];
return React.createElement('button', {
onClick: function onClick() {
return setCount(count + 1);
}
}, count);
};
CSS 处理
/* 源代码:styles/index.css */
.container {
padding: 20px;
background: #f5f5f5;
}
/* Webpack 处理后:注入到页面 */
<style>
.container {
padding: 20px;
background: #f5f5f5;
}
</style>
Less 处理
// 源代码:styles/app.less
@primary-color: #ff7e00;
.button {
background: @primary-color;
color: white;
}
// Less 编译后
.button {
background: #ff7e00;
color: white;
}
// Webpack 处理后:注入到页面或提取为独立文件
步骤 5:代码优化
压缩
// 压缩前
function calculateTotal(items) {
let total = 0
for (let i = 0; i < items.length; i++) {
total += items[i].price
}
return total
}
// 压缩后
function calculateTotal(a){let b=0;for(let c=0;c<a.length;c++)b+=a[c].price;return b}
代码分割
// 打包前:所有代码在一个文件(2MB)
bundle.js
- React (500KB)
- ReactDOM (300KB)
- 业务代码 (1.2MB)
// 打包后:代码分割
vendors.js (800KB) // 第三方库(变化少)
main.js (1.2MB) // 业务代码(变化频繁)
步骤 6:生成输出文件
<!-- 自动生成的 index.html -->
<!DOCTYPE html>
<html>
<head>
<title>一嗨租车</title>
<link href="static/css/main.a1b2c3d4.css" rel="stylesheet">
</head>
<body>
<div id="root"></div>
<script src="static/js/runtime.c3d4e5f6.js"></script>
<script src="static/js/vendors.b2c3d4e5.js"></script>
<script src="static/js/main.a1b2c3d4.js"></script>
</body>
</html>
🎯 实际配置示例
开发环境配置
module.exports = {
mode: 'development',
devtool: 'eval-source-map', // 快速构建,便于调试
entry: './src/index.jsx',
output: {
filename: 'static/js/[name].js', // 简单文件名
path: path.resolve(__dirname, 'build')
},
devServer: {
port: 3000,
hot: true, // 热更新
proxy: {
'/api': 'http://localhost:8080'
}
}
}
特点:
- ✅ 构建速度快
- ✅ 便于调试(Source Map)
- ✅ 热更新
- ❌ 文件体积大
- ❌ 代码未压缩
生产环境配置
module.exports = {
mode: 'production',
devtool: 'source-map', // 完整 Source Map(用于错误追踪)
entry: './src/index.jsx',
output: {
filename: 'static/js/[name].[contenthash:8].js', // 带 hash
path: path.resolve(__dirname, 'build')
},
optimization: {
minimize: true, // 压缩代码
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors'
}
}
}
}
}
特点:
- ✅ 文件体积小(压缩)
- ✅ 代码分割(缓存优化)
- ✅ 生产环境优化
- ❌ 构建速度慢
- ❌ 调试困难
📊 性能对比
打包前
文件数量:100+ 个文件
总大小:未压缩
加载方式:需要手动管理加载顺序
浏览器兼容:需要手动处理
打包后
文件数量:3-5 个文件
总大小:压缩后 500KB - 2MB
加载方式:自动按顺序加载
浏览器兼容:自动处理
实际效果
| 指标 | 打包前 | 打包后 | 提升 |
|---|---|---|---|
| 文件数量 | 100+ | 3-5 | 减少 95% |
| 文件大小 | 5MB | 1MB | 减少 80% |
| 加载时间 | 2-3秒 | 0.5-1秒 | 提升 70% |
| 浏览器兼容 | 手动处理 | 自动处理 | 100% |
🔍 调试技巧
1. 查看打包分析
# 安装分析工具
npm install --save-dev webpack-bundle-analyzer
# 分析打包结果
npx webpack-bundle-analyzer build/static/js/*.js
结果:可视化展示每个模块的大小
2. 查看 Source Map
// 配置 Source Map
devtool: 'source-map'
// 浏览器中:
// 1. 打开开发者工具
// 2. Sources 标签
// 3. 可以看到原始源代码
3. 查看构建信息
# 显示构建详情
npm run build -- --stats verbose
# 输出:
# - 每个模块的大小
# - 构建时间
# - 优化信息
💡 最佳实践
1. 代码分割
// 路由懒加载
const Home = React.lazy(() => import('./pages/Home'))
const About = React.lazy(() => import('./pages/About'))
// Webpack 会自动分割代码
// 访问 /home 时只加载 Home 相关代码
2. 缓存优化
// 使用 contenthash
filename: '[name].[contenthash:8].js'
// 文件内容不变 → hash 不变 → 浏览器使用缓存
// 文件内容变化 → hash 变化 → 浏览器重新下载
3. Tree Shaking
// 只导入需要的部分
import { debounce } from 'lodash' // ✅ 只打包 debounce
// import _ from 'lodash' // ❌ 打包整个 lodash
// Webpack 会自动移除未使用的代码
🎓 总结
Webpack 的核心价值:
- 模块化:支持现代模块系统
- 转换:将现代代码转换为浏览器可执行代码
- 优化:压缩、分割、缓存优化
- 开发体验:热更新、开发服务器
通过合理配置 Webpack,可以:
- ✅ 提升开发效率
- ✅ 优化生产性能
- ✅ 改善用户体验
- ✅ 简化项目维护