一、先认识它们
Webpack
Webpack 是 2014 年诞生的模块打包器(Module Bundler),核心思路是:把所有资源(JS、CSS、图片、字体等)都视为模块,通过 Loader 和 Plugin 统一处理后打包成浏览器可运行的静态文件。经历了近十年的发展,拥有最庞大的生态和最成熟的解决方案。
Vite
Vite 是 2020 年由 Vue 作者尤雨溪发起的新型构建 工具,由两部分组成:
- 开发环境:基于浏览器原生 ES Modules,使用 esbuild 做预构建,实现"按需编译、即时热更新"
- 生产环境:使用 Rollup 打包,追求最优的输出体积
核心差异一句话:Webpack 是"先打包再启动",Vite 是"先启动,用到再编译"。
二、架构原理深度对比
2.1 Webpack:全量打包模型
markdown
源代码 ──→ Entry 入口 ──→ 模块依赖分析 ──→ Loader 转换 ──→ 生成 Chunk ──→ 输出 Bundle
↑ │
└──────────── Plugin 贯穿全流程 ─────────────────────┘
关键机制
1. 递归构建依赖图(Dependency Graph)
Webpack 从 entry 出发,递归解析所有 import/require 语句,构建完整的模块依赖图。即使你只改了 1 行代码,它也需要重新走一遍整个图的"构建-打包"流程(HMR 优化后有所缓解,但依然偏重)。
2. Chunk 与 Code Splitting 的核心实现
javascript
// Webpack 的分包策略依赖于 SplitChunksPlugin
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vender',
priority: 10,
},
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true,
},
},
},
},
};
Webpack 基于引用次数、体积阈值、优先级 等规则对模块进行分组,生成多个 chunk。这种静态分析方式可控性强,但配置较为复杂,需要开发者精确理解 cacheGroups、priority、chunks 等参数的含义。
3. Loader 管道机制
Loader 按从右到左、从下到上的顺序组成管道:
javascript
// 执行顺序:sass-loader → css-loader → style-loader(最左)
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
}
每个 Loader 本质是一个接收 source 并返回转换后结果的高阶函数,通过 loader-utils 获取配置上下文。这种管道模型让扩展变得非常灵活,你可以编写自定义 Loader 在任何环节介入处理。
4. Plugin 钩子系统
Webpack 的核心是一个基于 Tapable 的发布订阅系统,Plugin 通过钩子(Hooks)介入构建全流程:
| 阶段 | 关键钩子 | 典型用途 |
|---|---|---|
| 初始化 | initialize |
读取配置 |
| 编译前 | beforeRun、run |
清理目录 |
| 模块构建 | compilation、make、buildModule |
处理单个模块 |
| 优化 | optimize、optimizeTree |
Tree Shaking、压缩 |
| 输出 | emit、afterEmit |
生成文件、上传 CDN |
javascript
// Plugin 本质是一个带有 apply 方法的类
class MyPlugin {
apply(compiler) {
compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
// 在资源输出到目录前进行操作
const assets = compilation.assets;
// ... 处理资源文件
callback();
});
}
}
2.2 Vite:原生 ESM + 预构建模型
arduino
┌─── 请求 /src/main.js ───→ Vite Dev Server
│ │
浏览器 ←─┤ ① 用 esbuild 预构建 node_modules
│ ② 将源码转为 ESM
│ │
└─── 按需发起 import 请求 ─────→ 返回对应模块
(只编译被请求的模块)
关键机制
1. 预构建(Pre-bundling)
Vite 在首次启动时使用 esbuild(Go 语言编写,速度是 JS 工具的 10-100 倍)对依赖做两件事:
- 将非 ESM 格式的依赖转为 ESM(如
module.exports→export default) - 将内部有大量子模块的包打包成单个模块(如
lodash-es的 600+ 个文件合并为一个)
这解决了两个核心问题:CommonJS 兼容性 和请求瀑布流。
2. 按需编译(On-demand Compilation)
bash
# 浏览器发送的典型请求序列
GET / → index.html
GET /src/main.js → Vite 即时编译并返回
GET /src/components/Header.vue → 按需编译
GET /src/utils/api.js → 按需编译
GET /node_modules/.vite/deps/react.js → 直接返回缓存
只有被浏览器实际 import 的文件才会被编译。对于大型项目,1000 个页面路由你只访问其中 1 个,Vite 只需要编译那 1 个页面的相关模块。
3. HMR 实现
Vite 的热更新基于原生 ESM,粒度天然就是模块级别:
arduino
┌─────────────────────────────────────────────┐
│ 修改文件 → WebSocket 通知 → 浏览器 │
│ 发送 import请求 → 服务端返回新模块 → 局部替换 │
└─────────────────────────────────────────────┘
不需要像 Webpack 那样先重新构建整个依赖图的受影响部分再推送给浏览器。对于 .vue 单文件组件,HMR 速度通常可以做到 <10ms。
三、性能数据对比
冷启动时间
| 场景 | Webpack 5 | Vite |
|---|---|---|
| 小型项目(~50 模块) | 2-4s | <1s |
| 中型项目(~500 模块) | 10-30s | 2-5s |
| 大型项目(~3000+ 模块) | 60-120s+ | 10-25s |
Vite 启动快的原因:不对源码做完整的依赖分析,直接用 esbuild 预构建 node_modules,然后用浏览器原生的 ESM import 机制按需编译。
HMR 热更新时间
| 场景 | Webpack 5(HMR) | Vite |
|---|---|---|
| 修改单个 .vue 文件 | 200-800ms | <10ms |
| 修改公共 utils | 1-3s(级联更新) | <100ms |
| 修改 node_modules 依赖 | 需重启 | 需重启/设置 optimizeDeps |
Vite HMR 快的原因:只发变更模块,浏览器用
import()重新拉取,不重新分析依赖图。
四、配置复杂度对比
Webpack:自由度高但心智负担重
javascript
// 一个典型的中等复杂 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 CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = (env, argv) => {
const isProd = argv.mode === 'production';
return {
mode: isProd ? 'production' : 'development',
entry: {
main: './src/index.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: isProd ? 'js/[name].[contenthash:8].js' : 'js/[name].js',
publicPath: '/',
},
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
alias: { '@': path.resolve(__dirname, 'src') },
},
module: {
rules: [
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: ['@babel/plugin-transform-runtime'],
},
},
},
{
test: /\.css$/,
use: [isProd ? MiniCssExtractPlugin.loader : 'style-loader', 'css-loader'],
},
{
test: /\.less$/,
use: [
isProd ? MiniCssExtractPlugin.loader : 'style-loader',
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]__[local]--[hash:base64:5]',
},
},
},
'less-loader',
],
},
{
test: /\.(png|jpe?g|gif|svg)$/,
type: 'asset',
parser: { dataUrlCondition: { maxSize: 8 * 1024 } },
generator: { filename: 'images/[name].[hash:8][ext]' },
},
],
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({ template: './public/index.html' }),
new MiniCssExtractPlugin({ filename: 'css/[name].[contenthash:8].css' }),
new CopyWebpackPlugin({ patterns: [{ from: 'public', to: '.', globOptions: { ignore: ['**/index.html'] } }] }),
],
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: { test: /[\\/]node_modules[\\/]/, name: 'vendor', priority: 10 },
},
},
minimizer: isProd ? [new CssMinimizerPlugin(), new TerserPlugin()] : [],
},
devServer: {
port: 3000,
hot: true,
historyApiFallback: true,
proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } },
},
devtool: isProd ? 'source-map' : 'eval-cheap-module-source-map',
};
};
痛点 :开发/生产环境行为不一致(style-loader vs MiniCssExtractPlugin.loader)、Source Map 选型需理解不同模式的差异、缓存策略需要手动优化等。
Vite:开箱即用,约定大于配置
javascript
// vite.config.js - 零配置即可运行
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
resolve: {
alias: { '@': '/src' },
},
server: {
port: 3000,
proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } },
},
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
},
},
},
},
});
优势 :CSS Modules、PostCSS、TypeScript、JSX、静态资源导入等全部零配置支持,不需要 babel-loader、css-loader、style-loader 等一堆 loader 链。
五、生产构建对比
| 维度 | Webpack | Vite(Rollup) |
|---|---|---|
| Tree Shaking | 基于 usedExports + Terser 死代码消除 |
Rollup 的静态分析 Tree Shaking 更彻底 |
| 代码分割 | SplitChunksPlugin 灵活但复杂 |
manualChunks + 自动提取,API 更简洁 |
| 输出体积 | 相当(取决于配置) | 通常略优(Rollup 的 ESM 优化更激进) |
| 插件质量 | 生态成熟,插件丰富 | Rollup 插件生态稍弱但日常够用 |
| CSS 处理 | 需手动配置插件链 | 原生支持 CSS 拆分、@import 内联 |
| 构建速度 | 中等(JS 实现) | 略快(Rollup 对 ESM 有天然优势) |
关键差异 :Webpack 生产构建使用自己的打包引擎(JS 编写),Vite 使用 Rollup(也是 JS 编写但专为 ESM 优化)。实际上生产环境两者速度差异远小于开发环境,Vite 的真正优势在于开发体验。
六、生态系统
Webpack 生态
| 类别 | 代表 |
|---|---|
| Loader | babel-loader、ts-loader、css-loader、sass-loader、vue-loader、file-loader |
| Plugin | HtmlWebpackPlugin、MiniCssExtractPlugin、TerserPlugin、CompressionPlugin、BundleAnalyzerPlugin |
| 框架封装 | create-react-app、vue-cli、next.js(12 之前) |
| 特色能力 | Module Federation(微前端) |
Module Federation 是 Webpack 5 的独家杀手锏:
javascript
// 微前端:运行时加载远程模块
new ModuleFederationPlugin({
name: 'app1',
filename: 'remoteEntry.js',
exposes: {
'./Header': './src/components/Header',
},
shared: ['react', 'react-dom'],
});
这允许不同构建产出的应用在浏览器端动态共享依赖,是 Vite 目前无法原生覆盖的场景。
Vite 生态
| 类别 | 代表 |
|---|---|
| 官方插件 | @vitejs/plugin-vue、@vitejs/plugin-react、@vitejs/plugin-legacy |
| 社区插件 | vite-plugin-compression、vite-plugin-imagemin、vite-plugin-svg-icons |
| 框架封装 | Nuxt 3、SvelteKit、Astro、Vitepress |
| 测试 | Vitest(与 Vite 配置共享,速度快) |
Vite 独有的优势:框架作者从 CLI 向 Vite 生态大迁徙,新一代元框架几乎全部基于 Vite 构建。
七、浏览器兼容性
Webpack
通过 babel-loader + @babel/preset-env + core-js 的 polyfill 机制,可以覆盖 IE11 甚至更老的浏览器:
javascript
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [['@babel/preset-env', { targets: '> 0.25%, not dead, ie >= 11' }]],
},
},
}
Vite
默认 target 是 modules (即支持原生 ESM 的浏览器,Chrome 63+ / Edge 79+ / Safari 12.1+)。如果需要兼容旧浏览器,需要使用 @vitejs/plugin-legacy:
javascript
import legacy from '@vitejs/plugin-legacy';
export default defineConfig({
plugins: [
legacy({
targets: ['defaults', 'not IE 11'], // 不支持 IE
}),
],
});
注意:即使使用 legacy 插件,Vite 也无法支持 IE11,因为底层依赖的 esbuild 不支持 IE11。
八、何时选择什么?
选择 Webpack 的场景
| 场景 | 原因 |
|---|---|
| 需要 Module Federation 微前端 | Vite 原生不支持,生态方案不成熟 |
| 需要兼容 IE11 或极旧浏览器 | Vite 底层不支持 |
| 已有庞大的 Webpack 定制体系 | 迁移成本过高,ROI 未必划算 |
| 使用 Next.js 12 及以下 | 但 Next.js 13+ 已迁移到 Turbopack |
| 有极其复杂的构建流程 | Webpack Plugin 的 Tapable 钩子系统更灵活 |
| 团队对 Webpack 极度熟悉 | 切换工具链有学习成本 |
选择 Vite 的场景
| 场景 | 原因 |
|---|---|
| 新项目,浏览器要求现代 | 开发体验提升 10 倍+ |
| Vue 3 / React + TS 项目 | 官方推荐方案,零配置支持 |
| 大型项目急需改善启动速度 | 冷启动从分钟级降至秒级 |
| 使用 Nuxt 3 / SvelteKit / Astro | 框架底层就是 Vite |
| 需要统一的开发/测试工具链 | Vite + Vitest 配置共享 |
| 团队追求现代化工具链 | 未来趋势明确 |
九、迁移策略:从 Webpack 到 Vite
如果你决定迁移,推荐渐进式策略:
第一步:用 @originjs/vite-plugin-commonjs 处理 CommonJS 依赖
很多老项目有 CommonJS 的内部模块,这个插件可以自动转换。
第二步:用 @nabla/vite-plugin-eslint 保留 ESLint 流程
Vite 不会在编译时跑 ESLint,需要额外引入。
第三步:别名和路径映射
javascript
// webpack 写法
resolve: { alias: { '@': path.resolve(__dirname, 'src') } }
// vite 写法(也需同步 tsconfig.json)
resolve: { alias: { '@': '/src' } }
第四步:环境变量替换
javascript
// Webpack: process.env.VUE_APP_API_BASE
// Vite: import.meta.env.VITE_API_BASE
全局替换 process.env.VUE_APP_ / process.env.REACT_APP_ 为 import.meta.env.VITE_。
第五步:require() 全部改为 import
Vite 不支持运行时的 require(),所有动态引用需改为 import() 或 glob import:
javascript
// 之前(Webpack)
require(`./locales/${lang}.json`);
// 之后(Vite)
import.meta.glob('./locales/*.json');
十、技术演进趋势
| 时间线 | 事件 |
|---|---|
| 2020 | Vite 1.0 发布,仅支持 Vue |
| 2021 | Vite 2.0 发布,框架无关,生态爆发 |
| 2022 | Vite 3.0 发布,Rollup 3 支持,Vitest 1.0 |
| 2023 | Vite 4.0-5.0,成为 npm 周下载量第二的构建工具 |
| 2024 | Vite 6.0,Environment API 实验性支持 |
同时 Webpack 也没有停止进化:Rspack(字节跳动的 Rust 版 Webpack 兼容替代品)和 Turbopack(Vercel 的 Rust 构建工具)都试图用系统级语言重写打包引擎,让 Webpack 生态重获新生。
个人判断:
- Vite 已经是新项目的事实标准
- Webpack 在大型存量项目和微前端场景中仍然不可替代
- Rust 系工具(Rspack / Turbopack)可能在未来 2-3 年改变格局
总结
| 维度 | Webpack | Vite |
|---|---|---|
| 开发冷启动 | ⭐⭐ 慢,全量打包 | ⭐⭐⭐⭐⭐ 秒级启动 |
| 热更新速度 | ⭐⭐⭐ 还行 | ⭐⭐⭐⭐⭐ 毫秒级 |
| 配置复杂度 | ⭐⭐ 上手难 | ⭐⭐⭐⭐⭐ 零配置可用 |
| 生产构建 | ⭐⭐⭐⭐ 成熟稳定 | ⭐⭐⭐⭐ Rollup 打包,相当 |
| 生态丰富度 | ⭐⭐⭐⭐⭐ 最庞大 | ⭐⭐⭐⭐ 快速增长中 |
| 浏览器兼容 | ⭐⭐⭐⭐⭐ 支持 IE11 | ⭐⭐⭐ 需 legacy 插件 |
| 微前端 | ⭐⭐⭐⭐⭐ Module Federation | ⭐⭐ 需第三方方案 |
| 学习曲线 | ⭐⭐ 陡峭 | ⭐⭐⭐⭐ 平缓 |
一句话:如果你在 2024 年后开新项目,默认选 Vite;如果你维护的是复杂的企业级 Webpack 项目且没有严重性能瓶颈,暂时不需要迁移。