Webpack 与 Vite 完全指南

Webpack 与 Vite 完全指南 --- 从原理到实战

一、引言:为什么前端需要构建工具?

1.1 "刀耕火种"的时代

在构建工具普及之前,前端开发是这样写代码的:

xml 复制代码
<script src="https://unpkg.com/jquery@3/dist/jquery.js"></script>
<script src="js/utils.js"></script>
<script src="js/app.js"></script>
<script src="js/components/nav.js"></script>

这种方式有一堆问题:

问题 表现
全局变量污染 所有 script 的变量都在同一作用域,命名冲突是家常便饭
依赖顺序敏感 app.js 必须在 utils.js 之后加载,否则报错
没法用 NPM 包 只能手动下载 JS 文件放到项目里
无法使用高级语法 ES6+、TypeScript、JSX 浏览器不认识
没有模块化 代码组织只能靠文件夹和命名约定
资源优化靠手动 压缩代码、合并文件、图片压缩,全都手动操作

1.2 构建工具解决了什么

构建工具(Build Tool) 是一个"翻译 + 打包 + 优化"的流水线:

复制代码
源代码(.vue / .tsx / .scss ...)
  ↓ 构建工具
转译(TS → JS, SCSS → CSS, JSX → JS...)
  ↓
打包(合并文件、代码分割)
  ↓
优化(压缩、tree-shaking、图片优化...)
  ↓
最终产物(可在浏览器中运行的 .js / .css / .html)

核心价值:

  • 模块化import / export 让代码组织清晰
  • 转译:TypeScript、JSX、Vue SFC 等语法浏览器不认识?构建工具来翻译
  • 优化:代码压缩、Tree Shaking、Code Splitting 自动完成
  • 开发体验:热更新(HMR),改代码即时看到效果

WebpackVite,是目前最主流的两套构建工具。这篇文章会带你深入理解它们的原理,并学会在实战中使用它们。


二、Webpack 核心概念与原理

2.1 核心概念速览

Webpack 有五个核心概念,理解了它们就理解了 Webpack 的骨架:

bash 复制代码
Entry(入口)         指示 Webpack 从哪个文件开始打包
   ↓
Loaders(加载器)     处理非 JS 文件(CSS、图片、TS 等),将它们转为 Webpack 能处理的模块
   ↓
Plugins(插件)       执行范围更广的任务(打包优化、资源管理、环境变量注入)
   ↓
Output(输出)        打包完成后输出到哪里
   ↓
Module(模块)        Webpack 中一切皆模块(JS/CSS/图片/font 都是模块)

819

最小配置示例
javascript 复制代码
// webpack.config.js
const path = require('path')

module.exports = {
  // 1. 入口
  entry: './src/index.js',

  // 2. 输出
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
    clean: true,     // 每次构建前清理 dist 目录
  },

  // 3. Loader 规则
  module: {
    rules: [
      {
        test: /.css$/,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },

  // 4. 插件
  plugins: [],

  // 5. 模式
  mode: 'development',
}

2.2 打包流程全解析

当你运行 webpack 命令时,内部发生了一件精密的"装配线"作业:

markdown 复制代码
1. 读取配置(webpack.config.js + CLI 参数)
2. 创建 Compiler 对象(webpack 的核心调度器)
3. 运行 Compiler.run(),启动编译
4. 从 Entry 开始,解析入口文件
5. 遇到 import/require → 递归解析依赖 → 构建依赖关系图(Module Graph)
6. 遇到非 JS 文件 → 找匹配的 Loader 处理
7. 遍历完所有依赖 → 得到完整的 Module Graph
8. 根据 Module Graph,将模块合并成 Chunk
9. 将 Chunk 输出为文件(写入 Output 目录)

这个过程有一个很重要的概念叫 AST(抽象语法树)

Loader 的转换链路
xml 复制代码
.css 文件
  ↓ css-loader    解析 @import 和 url(),将 CSS 转为 JS 模块
  ↓ style-loader  将 CSS 注入到页面的 <style> 标签中
  ↓ 最终产物:JS 代码(运行时向 DOM 插入样式)

Loader 的本质是一个函数 ,接收源文件内容,返回转换后的内容。多个 Loader 串联执行,顺序是从右到左,从下到上

javascript 复制代码
// 一个最简单的 Loader
module.exports = function(source) {
  return source.replace(/console.log(.*?)/g, '') // 移除 console.log
}

2.3 Loader 机制详解

Loader 是 Webpack 处理"非 JS 文件"的唯一方式。因为 Webpack 本质上只理解 JavaScript,其他一切文件(CSS、图片、字体、模板)都需要通过 Loader 转换为 JS 模块。

常用 Loader 一览
Loader 用途
babel-loader 将 ES6+ 转译为 ES5
ts-loader 处理 TypeScript
css-loader 解析 CSS 中的 @importurl()
style-loader 将 CSS 注入到 DOM 的 <style> 标签
sass-loader 将 SCSS/SASS 编译为 CSS
less-loader 将 Less 编译为 CSS
postcss-loader 自动添加 CSS 前缀(autoprefixer)
asset/resource 处理图片/字体等文件(Webpack 5 内置)
vue-loader 处理 .vue 单文件组件
html-loader 处理 HTML 中的图片引用
Loader 的执行顺序
javascript 复制代码
module: {
  rules: [
    {
      test: /.scss$/,
      // 执行顺序:从右到左
      // sass-loader → postcss-loader → css-loader → style-loader
      use: [
        'style-loader',
        { loader: 'css-loader', options: { modules: true } },
        'postcss-loader',
        'sass-loader',
      ],
    },
  ],
}

理解顺序:最后一个 Loader 最先处理源文件,然后依次往前传递结果。就像工厂流水线------原料从右端进入,从左端产出成品。

2.4 Plugin 机制:Tapable 钩子系统

如果说 Loader 是处理"文件转换"的,那 Plugin 就是干预构建流程本身的。

Webpack 的构建流程就像一条流水线,不同阶段会触发不同的"钩子(hooks)" 。Plugin 可以在这些钩子上注册自己的逻辑,干预构建过程。

javascript 复制代码
// 一个最简单的 Plugin
class MyPlugin {
  // apply 方法接收 compiler 对象
  apply(compiler) {
    // emit 是 Webpack 的一个钩子------即将输出文件时触发
    compiler.hooks.emit.tap('MyPlugin', (compilation) => {
      console.log('文件即将输出,当前有', Object.keys(compilation.assets).length, '个文件')
    })
  }
}

Webpack 的钩子系统由 Tapable 库实现,这是一套发布-订阅模式,有几十个生命周期钩子:

arduino 复制代码
        开始编译(run)
           ↓
        编译开始(compile)
           ↓
        创建 Compilation(compilation)
           ↓
        make(开始递归构建模块)
           ↓
        build-module(开始构建一个模块)
           ↓
        seal(封装所有模块为 Chunk)
           ↓
        optimize(优化阶段,多个钩子)
           ↓
        emit(输出文件到 output 目录)
           ↓
        done(构建完成)
常用 Plugin 一览
arduino 复制代码
plugins: [
  new HtmlWebpackPlugin({
    template: './public/index.html',
  }),                     // 自动生成 HTML,并注入 JS 引用
  new MiniCssExtractPlugin({
    filename: 'css/[name].[contenthash:8].css',
  }),                     // 将 CSS 提取为独立文件(代替 style-loader)
  new DefinePlugin({
    'process.env.API_URL': JSON.stringify('https://api.example.com'),
  }),                     // 注入全局变量
  new webpack.HotModuleReplacementPlugin(), // 启用 HMR
],

2.5 HMR 热更新原理

HMR(Hot Module Replacement)是 Webpack 最受欢迎的特性之一------修改代码后只替换改动的模块,不刷新整个页面,保留应用状态。

HMR 的工作流程:

arduino 复制代码
你在编辑器中修改了 style.css
  ↓
Webpack Dev Server 监听到文件变化
  ↓
Webpack 重新编译被修改的模块
  ↓
通过 WebSocket 向浏览器发送 "hash" 和 "ok" 消息
  ↓
浏览器收到消息,通过 JSONP 请求新的模块代码
  ↓
HMR Runtime 执行模块热替换逻辑
  ↓
如果是 CSS:直接替换 <style> 标签内容(无需任何额外配置)
如果是 JS:需要模块自身声明 accept 才能热替换

为什么 CSS 默认支持 HMR? 因为 CSS 不涉及应用状态,直接替换样式即可。JS 模块的 HMR 需要开发者显式处理,比如:

javascript 复制代码
if (module.hot) {
  module.hot.accept('./component.js', () => {
    // 重新渲染组件,保留当前状态
    rerender()
  })
}

2.6 动手实战:从零搭建一个 Webpack 项目

前面讲了这么多概念,现在我们来真刀真枪地建一个项目。打开终端,跟着下面的步骤一步步做。


第 1 步:创建项目目录并初始化
perl 复制代码
# 创建项目文件夹
mkdir my-webpack-app
cd my-webpack-app

# 初始化 package.json
npm init -y

执行完 npm init -y 后,项目里会出现一个 package.json 文件。这个文件记录了项目的依赖和脚本。


第 2 步:安装 webpack 核心包
css 复制代码
npm install webpack webpack-cli --save-dev

这条命令干了什么?

安装的包 作用
webpack Webpack 的核心库,负责打包逻辑
webpack-cli 命令行工具,让你能在终端敲 npx webpack 命令

--save-dev 表示这些是开发依赖(devDependencies),生产环境不需要------你的线上代码已经是打包好的成品了。

安装完成后,package.json 里会出现 devDependencies 字段:

json 复制代码
{
  "devDependencies": {
    "webpack": "^5.97.0",
    "webpack-cli": "^6.0.0"
  }
}

第 3 步:创建项目文件结构
arduino 复制代码
# 创建源码目录和 HTML 目录
	mkdir src public

现在创建你的第一个入口文件。新建 src/index.js

javascript 复制代码
// src/index.js
function sayHello(name) {
  return `Hello, ${name}! 这是 Webpack 打包的。`
}

console.log(sayHello('新手'))

再创建 public/index.html------这是你的 HTML 模板:

xml 复制代码
<!-- public/index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>我的第一个 Webpack 项目</title>
</head>
<body>
  <div id="app"></div>
</body>
</html>

此时项目结构如下:

csharp 复制代码
my-webpack-app/
├── node_modules/      # 安装的依赖(自动生成,不用管)
├── public/
│   └── index.html     # HTML 模板
├── src/
│   └── index.js       # JS 入口文件
├── package.json       # 项目配置文件
└── package-lock.json  # 依赖版本锁定文件(自动生成)

第 4 步:添加 npm 脚本

打开 package.json,在 "scripts" 字段中添加 "build" 脚本:

json 复制代码
{
  "scripts": {
    "build": "webpack"
  }
}

此时运行 npm run build 会报错?别急------我们还没告诉 Webpack 怎么打包 呢。Webpack 需要知道三件事:入口在哪输出到哪用哪种模式


第 5 步:创建第一个 webpack.config.js

在项目根目录创建 webpack.config.js

java 复制代码
// webpack.config.js
const path = require('path')

module.exports = {
  // ① 入口:Webpack 从哪个文件开始打包
  entry: './src/index.js',

  // ② 输出:打包好的文件放到哪里
  output: {
    // 输出目录的绝对路径(__dirname 是当前文件所在目录)
    path: path.resolve(__dirname, 'dist'),
    // 输出的文件名
    filename: 'bundle.js',
    // 每次构建前先清空 dist 目录
    clean: true,
  },

  // ③ 模式:development(开发)或 production(生产)
  mode: 'development',
}

最基本的 Webpack 配置只需要这三个字段。现在运行:

arduino 复制代码
npm run build

你会看到类似这样的输出:

less 复制代码
> my-webpack-app@1.0.0 build
> webpack

asset bundle.js 19 KiB [emitted] (name: main)
./src/index.js 79 bytes [built] [code generated]
webpack 5.97.0 compiled successfully in 92 ms

发生了什么事?

arduino 复制代码
src/index.js(你的源代码)
  ↓ Webpack 读取 entry,开始处理
  ↓ ES module 语法(import/export 等)被识别
  ↓ 生成 bundle.js(浏览器能直接运行的纯 JS)
  ↓ 输出到 dist/bundle.js

看看打包产物是什么样的:

bash 复制代码
cat dist/bundle.js

虽然代码看起来经过了转换,但功能是一样的。你可以试试在浏览器中打开 dist/bundle.js 看看------它已经是一个独立的、可以引用的文件了。

👆 你已经成功运行了 Webpack! 只看不练永远觉得难,实际上核心流程就三步:entryoutputmode


第 6 步:添加 HTML 自动注入

目前只是打包了 JS,但 HTML 还需要手动引用 bundle.js。安装一个插件让它自动完成:

css 复制代码
npm install html-webpack-plugin --save-dev

修改 webpack.config.js

javascript 复制代码
// webpack.config.js(新增内容用 👈 标注)
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')  // 👈 引入插件

module.exports = {
  entry: './src/index.js',

  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
    clean: true,
  },

  // 👈 新增:插件配置
  plugins: [
    new HtmlWebpackPlugin({
      // 以 public/index.html 为模板
      template: './public/index.html',
    }),
  ],

  mode: 'development',
}

再次运行:

arduino 复制代码
npm run build

看看 dist/ 目录下发生了什么变化:

bash 复制代码
dist/
├── bundle.js         # 打包后的 JS
└── index.html        # 自动生成的 HTML(已自动引用 bundle.js)

打开 dist/index.html,你会看到 </body> 前自动插入了:

xml 复制代码
<script defer src="bundle.js"></script>

你不需要手动管理 JS 引用------Webpack 会自动处理好。


第 7 步:处理 CSS 文件

Webpack 默认只认识 JS。要处理 CSS,需要 Loader 来"翻译"。创建 CSS 文件:

bash 复制代码
# 创建样式文件目录
mkdir src/styles
css 复制代码
/* src/styles/main.css */
body {
  background-color: #f0f0f0;
  font-family: 'Arial', sans-serif;
}

#app::after {
  content: '🎉 CSS 已加载成功!';
  display: block;
  margin-top: 20px;
  font-size: 24px;
  color: #42b883;
}

修改 src/index.js,引入 CSS:

javascript 复制代码
// src/index.js
import './styles/main.css'   // 👈 引入 CSS

function sayHello(name) {
  return `Hello, ${name}! 这是 Webpack 打包的。`
}

// 将内容显示到页面上
document.querySelector('#app').textContent = sayHello('新手')

安装处理 CSS 所需的 Loader:

css 复制代码
npm install style-loader css-loader --save-dev
Loader 作用
css-loader 解析 CSS 文件中的 @importurl(),把 CSS 变成 JS 能用的模块
style-loader 把 CSS 通过 <style> 标签注入到 HTML 页面中

webpack.config.js 中添加 Loader 规则:

css 复制代码
module.exports = {
  entry: './src/index.js',

  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
    clean: true,
  },

  // 👈 新增:Loader 配置
  module: {
    rules: [
      {
        // 正则匹配 .css 结尾的文件
        test: /.css$/,
        // 使用哪些 Loader(执行顺序:从右到左!)
        use: ['style-loader', 'css-loader'],
      },
    ],
  },

  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],

  mode: 'development',
}

再次构建并查看效果。在浏览器中打开 dist/index.html,你应该能看到样式生效了。

理解 Loader 执行顺序use: ['style-loader', 'css-loader'] 是从 右到左 执行的。

先把 .csscss-loader 解析成 JS 模块,再把结果交给 style-loader 注入到页面。


第 8 步:搭建开发服务器(Dev Server + HMR)

每次改代码都要手动 npm run build 太麻烦了。Webpack 提供了开发服务器,自动监听文件变化,热更新页面

css 复制代码
npm install webpack-dev-server --save-dev

webpack.config.js 末尾添加 devServer 配置:

yaml 复制代码
module.exports = {
  entry: './src/index.js',

  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
    clean: true,
  },

  module: {
    rules: [
      {
        test: /.css$/,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },

  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],

  // 👈 新增:开发服务器配置
  devServer: {
    port: 3000,       // 服务端口
    hot: true,         // 启用 HMR(热更新)
    open: true,        // 启动后自动打开浏览器
  },

  mode: 'development',
}

package.json 中添加 dev 脚本:

json 复制代码
{
  "scripts": {
    "build": "webpack",
    "dev": "webpack serve"    // 👈 新增
  }
}

启动开发服务器:

arduino 复制代码
npm run dev

浏览器会自动打开 http://localhost:3000。现在试试:

  1. 修改 src/index.js 中的文字 → 页面自动更新(不刷新页面哦)
  2. 修改 src/styles/main.css 中的颜色 → 样式即时更新

这就是 HMR(热模块替换) ------改代码即时生效,开发体验直线上升。


第 9 步:处理图片资源

Webpack 5 内置了资源模块(Asset Modules),不需要额外安装 Loader。在 src/ 下创建 images/ 目录,放一张图片进去,然后在 src/index.js 中使用:

javascript 复制代码
// src/index.js
import './styles/main.css'
import logo from './images/logo.png'   // 👈 引入图片

// 使用图片
const img = document.createElement('img')
img.src = logo
img.style.width = '200px'
document.querySelector('#app').appendChild(img)

webpack.config.js 中添加图片处理规则:

javascript 复制代码
module.exports = {
  // ... 前面已有的配置不变

  module: {
    rules: [
      {
        test: /.css$/,
        use: ['style-loader', 'css-loader'],
      },
      // 👈 新增:图片处理
      {
        test: /.(png|jpg|gif|svg)$/,
        type: 'asset',   // 内置资源模块
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1024, // 小于 8KB 的图片转成 base64 内联
          },
        },
      },
    ],
  },

  // ...
}

第 10 步:区分开发和生产环境

开发环境需要 HMR、source map、读着舒服的错误提示。生产环境需要代码压缩、文件名带 hash(方便缓存)、提取 CSS 为独立文件。

javascript 复制代码
// webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

// 👈 判断当前环境
const isDev = process.env.NODE_ENV !== 'production'

module.exports = {
  entry: './src/index.js',

  output: {
    path: path.resolve(__dirname, 'dist'),
    // 开发环境用可读的文件名,生产环境加 contenthash
    filename: isDev ? 'bundle.js' : 'bundle.[contenthash:8].js',
    clean: true,
  },

  module: {
    rules: [
      {
        test: /.css$/,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /.(png|jpg|gif|svg)$/,
        type: 'asset',
        parser: {
          dataUrlCondition: { maxSize: 8 * 1024 },
        },
      },
    ],
  },

  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],

  devServer: {
    port: 3000,
    hot: true,
    open: true,
  },

  // 开发环境启用 source map,方便调试
  devtool: isDev ? 'eval-source-map' : false,

  mode: isDev ? 'development' : 'production',
}

更新 package.json 的脚本:

json 复制代码
{
  "scripts": {
    "dev": "webpack serve",
    "build": "NODE_ENV=production webpack",
    "build:win": "set NODE_ENV=production && webpack"  // Windows 用户用这个
  }
}

现在:

arduino 复制代码
npm run dev    # 开发模式 --- 启动服务器,HMR,source map
npm run build  # 生产模式 --- 代码压缩,contenthash,性能最优

目录结构总览

经过这 10 步,你的完整项目结构:

csharp 复制代码
my-webpack-app/
├── node_modules/           # 依赖
├── public/
│   └── index.html          # HTML 模板
├── src/
│   ├── images/
│   │   └── logo.png        # 图片资源
│   ├── styles/
│   │   └── main.css        # 样式文件
│   └── index.js            # JS 入口
├── dist/                   # 构建输出(自动生成)
├── webpack.config.js       # Webpack 配置
└── package.json            # 项目配置

一个功能完整的 Webpack 项目就搭好了!现在你的项目知识体系:

你要做什么 对应 Webpack 知识
告诉 Webpack 从哪开始 entry(入口)
控制最终输出 output(输出)
处理非 JS 文件 Loader(module.rules)
干预构建流程 Plugin(plugins)
开发时自动刷新 Dev Server + HMR
开发 vs 生产 mode + 环境变量
加速路径引用 resolve.alias
代码分割 optimization.splitChunks

💡 回顾一下:最开始你觉得 Webpack 配置复杂。但跟着这 10 步走过来,你会发现每步只学一个新概念,每一步都能实实在在地看到效果。这就是从零搭建的意义。


三、Vite 核心概念与原理

3.1 Vite 的核心理念:基于原生 ESM 的"No-Bundle"方案

Vite 是法语中"快"的意思------它的核心理念就是

要理解 Vite,先要理解一个关键点:现代浏览器已经原生支持 ES Module(ESM)了

xml 复制代码
<!-- 浏览器原生支持这种写法 -->
<script type="module">
  import { createApp } from 'vue'
</script>

Webpack 的做法:开发时也要把所有代码打包成一个 bundle,即使你只改了一行 CSS。

Vite 的做法:开发时根本不打包,直接利用浏览器原生 ESM 加载模块。

这带来了一个质的飞跃:

css 复制代码
Webpack 开发模式:
[所有源代码] → Webpack 打包成一个巨大的 bundle → 浏览器加载并执行

Vite 开发模式:
[浏览器请求 main.js] → Vite Dev Server 实时转换并返回 ESM 模块
[浏览器请求 App.vue] → Vite 将 .vue 编译为 JS 后返回
[浏览器请求 style.css] → Vite 将 CSS 转为 JS 模块后返回

每个模块按需加载,浏览器只转换当前请求的文件。这就是 Vite 快的根本原因。

3.2 依赖预构建:为什么要用 esbuild?

虽然浏览器原生支持 ESM,但有一个实际痛点:node_modules 中的依赖往往不是 ESM 格式

比如 lodash 是 CommonJS 格式,浏览器不认识 require()。Vite 的思路:

arduino 复制代码
Vite Dev Server 启动时
  ↓
用 esbuild(Go 编写,比 JS 快 10-100 倍)扫描 node_modules
  ↓
将所有依赖预打包为 ESM 格式
  ↓
存放到 node_modules/.vite/deps/
  ↓
浏览器请求 'lodash' 时,Vite 直接返回预构建好的 ESM 版本
javascript 复制代码
// 你代码中这样写
import _ from 'lodash'

// 经过 Vite 预构建,实际请求的是
import __vite__cjsImport0_lodash from "/node_modules/.vite/deps/lodash.js?v=abc123"

预构建的三个收益

  1. CommonJS → ESM:让所有依赖统一为 ESM 格式
  2. 请求合并:lodash 有几百个内部文件,预构建后合并为一个请求,避免 HTTP 瀑布
  3. 内容 hash 缓存:依赖不变时,浏览器直接使用缓存

3.3 开发服务器的工作原理

当你访问 http://localhost:5173 时:

bash 复制代码
浏览器请求 /index.html
  ↓
Vite 返回 index.html(内联了 <script type="module" src="/src/main.js">)
  ↓
浏览器解析到 <script type="module">,开始请求 /src/main.js
  ↓
Vite Dev Server 收到请求,进行实时编译:
  ├── .js/.ts 文件 → esbuild 转译(去掉类型注解等)
  ├── .vue 文件   → @vitejs/plugin-vue 编译为 JS
  ├── .css 文件   → 转换为 JS 模块(注入 <style> 或 HMR)
  └── .svg/.png   → 返回 URL 或 data URI
  ↓
浏览器接收编译后的 ESM 模块,执行

Vite 对请求的拦截和处理:

javascript 复制代码
// Vite 内部做的核心工作(示意)
// 当浏览器请求 /src/main.ts 时
// Vite 的 transform 中间件会:
// 1. 读取文件内容
// 2. 抹除 TypeScript 类型注解
// 3. 将裸模块导入('vue')转为浏览器可用的 URL
// 4. 返回编译后的内容

// 你的代码:
import { createApp } from 'vue'
import App from './App.vue'

// 浏览器实际收到:
import { createApp } from '/node_modules/.vite/deps/vue.js?v=abc123'
import App from '/src/App.vue'

3.4 HMR:基于 ESM 的模块热更新

Vite 的 HMR 比 Webpack 快,根本原因是范围不同

markdown 复制代码
修改一个文件时:

Webpack:
  1. 重新打包这个模块及其依赖链
  2. 通过 WebSocket 发送整个更新 chunk
  3. 浏览器执行 chunk

Vite:
  1. Vite Dev Server 通过 WebSocket 通知浏览器:"xxx 文件变了"
  2. 浏览器直接请求这个文件的最新 ESM 模块
  3. 浏览器原生切换为新模块(精确到单个文件)

关键区别:Webpack 的 HMR 需要重新打包并传输更新 chunk ,Vite 只需要浏览器重新请求被修改的文件。因为浏览器原生 ESM 可以做到"只更新这一个模块"。

javascript 复制代码
// Vite 的 HMR API(框架插件已封装,一般无需手写)
if (import.meta.hot) {
  import.meta.hot.accept('./render.js', (newModule) => {
    // 只替换 render.js 模块,页面不刷新
    newModule.render()
  })
}

3.5 生产构建:为什么最终还是用 Rollup?

你可能想问:Vite 开发环境那么快,为什么生产构建不用 esbuild,而是用 Rollup?

方面 esbuild Rollup
速度 极快(Go 编写) 中等(JS 编写)
代码分割(Code Splitting) 有限支持 完善支持
CSS 处理 基础支持 完善(PostCSS 集成)
插件生态 小而精 丰富成熟
Tree Shaking 基础 深入且可配置
产物体积优化 一般 更好(更小的 bundle)

Vite 的选择策略 :在开发时用 esbuild 追求速度 ,在构建时用 Rollup 追求质量和生态

markdown 复制代码
开发环境(dev):esbuild 转译 + 浏览器原生 ESM
    ↓
生产构建(build):Rollup 打包优化
    ↓
产出:经过 Tree Shaking、代码分割、压缩的优化代码

3.6 实战:一个完整的 Vite 配置

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: 'https://api.example.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^/api/, ''),
      },
    },
  },

  // 构建配置
  build: {
    outDir: 'dist',
    sourcemap: false,
    chunkSizeWarningLimit: 500,

    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
          ui: ['antd', '@ant-design/icons'],
        },
      },
    },
  },

  // CSS 配置
  css: {
    modules: {
      localsConvention: 'camelCase',
    },
    preprocessorOptions: {
      scss: {
        additionalData: `@import "@/styles/variables.scss";`,
      },
    },
  },
})

对比 Webpack,Vite 的配置明显更简洁------很多"标配"功能(如 HMR、TypeScript 支持、CSS 处理)都开箱即用,不需要手写 Loader。


四、Webpack vs Vite 核心对比

4.1 开发体验对比

维度 Webpack Vite
启动速度 慢(需打包整个应用后才启动 dev server) 极快(启动 dev server 几乎是瞬时的)
HMR 速度 随项目增大而变慢 恒快(只处理修改的文件)
配置复杂度 较复杂(需要明确配置 Loader/Plugin) 简洁(大部分功能开箱即用)
TypeScript 支持 需配置 babel-loader / ts-loader 原生支持(esbuild 转译,无需额外配置)
CSS 处理 需配置多个 loader 内置支持(CSS Modules、PostCSS)
调试体验 source map 配置较复杂 source map 默认配置良好

4.2 生产构建对比

维度 Webpack Vite (Rollup)
产物体积 优化成熟,插件丰富 通常更小(Rollup 的 Tree Shaking 更彻底)
代码分割 支持完善(SplitChunksPlugin) 支持完善(Rollup 原生 + manualChunks)
CSS 提取 MiniCssExtractPlugin 内置支持
Tree Shaking 依赖 sideEffects 配置 Rollup 更彻底(基于 ESM 静态分析)
构建速度 项目越大越慢 通常更快
兼容性 自动 polyfill 需要额外配置 polyfill

4.3 生态对比

维度 Webpack Vite
插件数量 极其丰富(十年积累) 快速增长中
社区资源 教程、问答最多 已很成熟,官方文档优秀
SSR 支持 Next.js、Gatsby 等基于 Webpack Nuxt 3、Astro、SvelteKit 等已转向 Vite
微前端 方案成熟(Module Federation) 正在发展中
企业级项目 大量现有项目使用 新项目越来越多选择 Vite

4.4 何时选择哪个?

sql 复制代码
选 Webpack:
├── 维护现有的大型 Webpack 项目(没有必要迁移)
├── 需要 Module Federation 做微前端
├── 依赖一些 Webpack 独有的插件
└── 团队对 Webpack 生态非常熟悉

选 Vite:
├── 新建项目(强烈推荐)
├── 追求开发体验和构建速度
├── 项目会不断增长,需要可维护性
└── 使用 Vue / React / Svelte 等现代框架

五、常见场景配置示例

场景 1:React + TypeScript 项目

Webpack 配置(需要自行配置 babel):

javascript 复制代码
// webpack.config.js --- 针对 React + TS
module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /.(ts|tsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              '@babel/preset-env',
              ['@babel/preset-react', { runtime: 'automatic' }],
              '@babel/preset-typescript',
            ],
          },
        },
      },
    ],
  },
}

Vite 配置(一行插件搞定):

javascript 复制代码
// vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],  // 自动处理 JSX、TS、HMR
})

场景 2:Vue 3 项目

Webpack

javascript 复制代码
// webpack.config.js
const { VueLoaderPlugin } = require('vue-loader')

module.exports = {
  module: {
    rules: [
      {
        test: /.vue$/,
        loader: 'vue-loader',
      },
    ],
  },
  plugins: [
    new VueLoaderPlugin(),
  ],
}

Vite(Vue 生态首选):

javascript 复制代码
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
})

场景 3:环境变量与多环境配置

Webpack (使用 webpack.DefinePlugindotenv):

javascript 复制代码
// webpack.config.js
const webpack = require('webpack')
require('dotenv').config({ path: `.env.${process.env.NODE_ENV}` })

module.exports = {
  plugins: [
    new webpack.DefinePlugin({
      'process.env.API_URL': JSON.stringify(process.env.API_URL),
      'process.env.APP_TITLE': JSON.stringify(process.env.APP_TITLE),
    }),
  ],
}

Vite (内置环境变量支持,import.meta.env):

ini 复制代码
# .env.development
VITE_API_URL=https://dev-api.example.com
VITE_APP_TITLE=My App (Dev)

# .env.production
VITE_API_URL=https://api.example.com
VITE_APP_TITLE=My App
javascript 复制代码
// 代码中使用
const apiUrl = import.meta.env.VITE_API_URL

// vite.config.js 中也可读取
import { defineConfig, loadEnv } from 'vite'

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd())
  return {
    define: {
      __APP_TITLE__: JSON.stringify(env.VITE_APP_TITLE),
    },
  }
})

Vite 约定 :只有 VITE_ 开头的变量才会暴露给客户端,防止意外泄露敏感信息。

场景 4:代码分割与懒加载

两者语法一致(基于动态 import()),但配置不同:

javascript 复制代码
// 懒加载组件(Webpack 和 Vite 写法相同)
const Dashboard = () => import('./pages/Dashboard.vue')
const Settings = () => import('./pages/Settings.vue')

Webpack 的代码分割配置

yaml 复制代码
// webpack.config.js
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\/]node_modules[\/]/,
          name: 'vendor',
          chunks: 'all',
        },
        common: {
          minChunks: 2,
          minSize: 0,
          name: 'common',
        },
      },
    },
  },
}

Vite 的代码分割配置

php 复制代码
// vite.config.js
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
          utils: ['lodash-es', 'dayjs'],
        },
      },
    },
  },
})

场景 5:CSS 预处理(Sass/PostCSS)

Webpack

复制代码
npm install sass-loader sass postcss-loader autoprefixer -D
java 复制代码
// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /.scss$/,
        use: [
          'style-loader',
          { loader: 'css-loader', options: { modules: true } },
          'postcss-loader',
          'sass-loader',
        ],
      },
    ],
  },
}

Vite

bash 复制代码
npm install sass -D   # 只需安装 sass,Vite 内置支持
php 复制代码
// vite.config.js(PostCSS 基于 postcss.config.js 自动加载)
export default defineConfig({
  css: {
    modules: {
      localsConvention: 'camelCase',
    },
    preprocessorOptions: {
      scss: {
        additionalData: `@import "@/styles/variables.scss";`,
      },
    },
  },
})
css 复制代码
// postcss.config.js(Vite 自动读取,无需在 vite.config.js 中配置)
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

六、从 Webpack 迁移到 Vite

如果你有一个现有 Webpack 项目,迁移到 Vite 的核心步骤:

6.1 配置映射对照表

Webpack Vite 对应
webpack.config.js vite.config.js
entry 约定 index.html 在根目录
output build.outDir(默认 dist)
resolve.alias resolve.alias
module.rulestest: /.css$/ 内置支持
module.rulesbabel-loader 插件 @vitejs/plugin-react
DefinePlugin define 配置
HtmlWebpackPlugin 内置(index.html 在根目录)
MiniCssExtractPlugin 内置
CopyWebpackPlugin 插件 vite-plugin-static-copy
ProvidePlugin 不推荐,用 ESM import 替代
optimization.splitChunks build.rollupOptions.output.manualChunks
devServer.proxy server.proxy
public/ 目录 public/ 目录(功能相同)

6.2 常见迁移踩坑点

  1. process.env 不可用

    • Webpack 中:process.env.NODE_ENV
    • Vite 中:改用 import.meta.env.MODE(或以 VITE_ 开头的环境变量)
  2. require 不再可用

    • Vite 在 ESM 模式下不支持 require()
    • 改为 import 动态导入:const module = await import('/path')
  3. 图片路径处理

    • Webpack:require('./logo.png') 或配置 loader
    • Vite:直接 import logo from './logo.png'(内置支持)
  4. 全局样式引入

    • Vite 中需要在 main.ts 中使用 import './styles/global.scss',或在 vite.config.js 中配置 css.preprocessorOptions
  5. 动态 import 的变量路径

    • Webpack 支持完全变量化的 import(path)(context module)
    • Vite/Rollup 要求路径至少有一部分是静态的:import('./pages/' + pageName + '.vue')
javascript 复制代码
// ❌ 不兼容 Vite
const module = await import(path)

// ✅ 兼容 Vite(路径前缀静态)
const module = await import(`./pages/${pageName}.vue`)

6.3 快速迁移清单

shell 复制代码
# 1. 安装 Vite 和相关插件
npm install -D vite @vitejs/plugin-react  # 或 @vitejs/plugin-vue

# 2. 创建 vite.config.js(参考上文的配置映射)

# 3. 将 index.html 从 public/ 移到项目根目录
#    并在其中添加 <script type="module" src="/src/main.tsx">

# 4. 将 tsconfig.json 中的 "module" 改为 "ESNext"
#    添加 "types": ["vite/client"]

# 5. 替换代码中的 process.env 为 import.meta.env

# 6. 运行测试
npx vite          # 开发模式
npx vite build    # 生产构建
npx vite preview  # 预览构建结果

七、总结与选型建议

7.1 核心观点回顾

arduino 复制代码
Webpack:
  └── 打包 → 启动 Dev Server → 等 bundle 完成 → 浏览器加载
  └── "先打包,再服务"

Vite:
  └── 启动 Dev Server → 浏览器请求什么就编译什么
  └── "先服务,按需编译"

这看似简单的顺序变化,带来了开发体验的质的飞跃。

7.2 给初学者的建议

  1. 学 Vite 从新项目开始 :用 npm create vite@latest 创建项目,体验零配置开发的快感
  2. 学 Webpack 理解"底层" :Webpack 虽然配置繁琐,但它让你真正理解构建工具的运作机制
  3. 不必非此即彼:Vite 项目也可能用到 Webpack 的知识(Plugin/Loader 的概念是共通的)
  4. 遇到问题先查官方文档:Vite 的文档非常优秀,Webpack 的文档虽全但需要耐心

7.3 一句话总结

Webpack 是一个打包器,它把一切打包好再交给浏览器;Vite 是一个翻译器,它在浏览器需要的时候才翻译。两者目标相同------让前端开发更高效------但走了完全不同的路。


参考

相关推荐
灏仟亿前端技术团队1 小时前
B 端多弹窗越来越难维护?试试把弹窗交互 Promise 化
前端
奇奇怪怪的1 小时前
向量数据库选型与生产级实战
前端
徐小夕2 小时前
jitword 协同文档3.2发布:打造浏览器中最强word编辑器
前端·架构·github
纯爱掌门人4 小时前
干了这么多年前端,聊聊 2026 年我们到底还值不值钱
前端·程序员
houhou4 小时前
Monaco Editor 集成指南:从配置到优化
前端
hunterandroid4 小时前
[Android 从零到一] Custom View 自定义绘制:从 onDraw 到完整交互
前端
李明卫杭州4 小时前
Vue3 v-memo 指令详解:让你的列表渲染性能翻倍 🚀
前端
梨子同志4 小时前
Monorepo
前端
lihaozecq4 小时前
继 Web Coding Agent 后,我做了一个本地优先的桌面 AI Agent
前端·agent