Vite 详解

Vite 详解

1. 什么是 Vite?

Vite(发音为 “veet”)是由 Vue.js 创始人尤雨溪创建的前端构建工具,于 2020 年首次发布。Vite 的核心思想是利用浏览器的原生 ES 模块支持和编译工具的快速冷启动能力,为前端开发提供极速的开发体验。

Vite 的名字来源于法语单词 “vitesse”,意为 “速度”,这也体现了其设计初衷:提供更快的前端开发体验。

1.1 Vite 的诞生背景

在传统的前端构建工具(如 Webpack)中,开发服务器启动时需要打包整个应用,这导致了以下问题:

  • 启动时间长:随着项目规模的增大,打包时间会越来越长,有时甚至需要数分钟。
  • 热更新慢:每次修改代码后,需要重新打包相关模块,导致热更新速度变慢。
  • 开发体验差:长时间的等待会严重影响开发效率和体验。

Vite 正是为了解决这些问题而诞生的,它通过创新的设计理念,彻底改变了前端开发的构建方式。

2. Vite 的核心功能特性

2.1 开发服务器

Vite 的开发服务器采用了创新的架构设计,具有以下特点:

  • 极速冷启动:Vite 在开发模式下不需要打包整个应用,而是使用浏览器的原生 ES 模块支持,直接请求所需的模块。当浏览器请求一个模块时,Vite 会实时编译该模块并返回,大大减少了启动时间。
  • 热模块替换(HMR):Vite 的 HMR 实现非常快速,因为它只需要更新修改的模块,而不是整个应用。Vite 会跟踪模块之间的依赖关系,当一个模块发生变化时,只会更新该模块及其直接依赖的模块。
  • 按需编译:只有当模块被请求时才会进行编译,避免了不必要的编译工作。这意味着即使项目很大,启动时间也不会显著增加。
  • 原生 ES 模块支持:Vite 利用浏览器的原生 ES 模块支持,将开发服务器作为一个模块服务器,直接向浏览器提供 ES 模块。

2.2 构建系统

Vite 的构建系统在生产环境中使用 Rollup 进行打包,具有以下特点:

  • 生产环境优化:在生产环境中,Vite 使用 Rollup 进行打包,利用 Rollup 的 tree-shaking 和代码分割能力,生成优化后的静态资源。Rollup 能够更有效地消除未使用的代码,生成更小的 bundle。
  • 多页面应用支持:Vite 支持多页面应用的构建,通过配置可以生成多个 HTML 入口。这对于需要多个页面的应用非常方便。
  • 库模式:Vite 支持构建库,生成适合发布到 npm 的库文件。在库模式下,Vite 会生成不同格式的输出(如 ESM、CommonJS、UMD 等),以满足不同的使用场景。
  • 预构建依赖:在开发模式下,Vite 会预构建第三方依赖,将它们转换为 ES 模块格式,提高后续请求的速度。

2.3 插件系统

Vite 的插件系统基于 Rollup 插件接口设计并进行了扩展。插件本质是一个包含若干钩子(hooks)函数的对象,在构建流程的不同阶段被 Vite 调用,从而实现代码转换、路径解析、注入内容等功能。

插件 API 架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Vite 插件 = Rollup 插件能力 + Vite 特有扩展

├── Rollup 通用钩子
│ ├── resolveId —— 解析模块路径
│ ├── load —— 加载模块内容
│ ├── transform —— 转换模块代码(核心)
│ ├── moduleParsed —— 模块解析完成
│ └── buildEnd —— 构建结束

├── Rollup 输出阶段钩子
│ ├── renderStart —— 输出开始
│ ├── banner / footer —— 添加文件头尾
│ └── generateBundle —— 生成产物

└── Vite 独有钩子
├── config —— 修改 Vite 配置
├── configResolved —— 配置确认后
├── configureServer —— 配置开发服务器
├── handleHotUpdate —— 自定义 HMR 处理
└── transformIndexHtml —— 转换 index.html

常用钩子详解

**config**:在 Vite 配置解析前调用,可用于修改配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function myPlugin() {
return {
name: 'my-plugin',
config(config, { command, mode }) {
// command: 'serve'(开发)| 'build'(生产)
// mode: 'development' | 'production'
return {
resolve: {
alias: { '@': '/src' },
},
};
},
};
}

**transform**:最常用的钩子,拦截并转换模块代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
function stripConsolePlugin() {
return {
name: 'strip-console',
transform(code, id) {
// id = 模块绝对路径
if (id.endsWith('.vue') || id.includes('node_modules')) return;
return {
code: code.replace(/console\.(log|warn|error)\([^)]*\);?\n?/g, ''),
map: null, // sourcemap
};
},
};
}

**configureServer**:配置开发服务器,添加自定义中间件:

1
2
3
4
5
6
7
8
9
10
11
12
function mockApiPlugin() {
return {
name: 'mock-api',
configureServer(server) {
// server.middlewares 是 Connect 实例
server.middlewares.use('/api/mock', (req, res) => {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ data: 'mock' }));
});
},
};
}

**handleHotUpdate**:自定义 HMR 行为,决定文件变化后如何更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function hmrCustomPlugin() {
return {
name: 'hmr-custom',
handleHotUpdate({ file, server }) {
// file = 变更的文件路径
if (file.endsWith('.scss')) {
// 只更新相关模块,不完全重载页面
const module = server.moduleGraph.getModuleById(file);
if (module) server.reloadModule(module);
return [];
}
},
};
}

**transformIndexHtml**:修改 index.html 输出内容:

1
2
3
4
5
6
7
8
9
10
function injectAnalyticsPlugin() {
return {
name: 'inject-analytics',
transformIndexHtml(html) {
return html.replace('</head>', `
<script>console.log('analytics injected');</script>
</head>`);
},
};
}

插件执行顺序

Vite 插件的执行顺序由两个因素决定:

1
2
3
4
5
6
7
1. 插件在 plugins 数组中的位置
plugins: [pluginA, pluginB] → A 先于 B

2. 钩子的类型
├── 通用(resolveId/transform 等):按顺序执行
├── 输出(generateBundle 等):逆序执行
└── 并行(模块解析相关):不保证顺序

所有插件中,别名和路径解析类插件应尽量靠前,代码转换类插件按依赖关系排列。

编写自定义插件——完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// plugins/vite-plugin-svg-inline.js
import { readFileSync } from 'fs';
import { resolve } from 'path';

export default function svgInlinePlugin(options = {}) {
const { prefix = '?inline' } = options;

return {
name: 'svg-inline', // 插件名(必须,用于错误提示)
enforce: 'pre', // 执行阶段:pre | 默认 | post

// 解析阶段:标记带 ?inline 的 svg 请求
resolveId(source, importer) {
if (source.endsWith(prefix)) {
// 返回一个特殊 ID,跳过后续解析
return '\0' + source.replace(prefix, '') + '.inline.svg';
}
},

// 加载阶段:读取 svg 文件内容
load(id) {
if (id.endsWith('.inline.svg')) {
const filePath = id.replace('\0', '').replace('.inline.svg', '.svg');
const svg = readFileSync(resolve(filePath), 'utf-8');
// 导出为 JS 字符串,让 import 语法合法
return `export default ${JSON.stringify(svg)};`;
}
},

// 开发服务器中间件
configureServer(server) {
server.middlewares.use('/__svg_list', (req, res) => {
res.end('SVG List Endpoint');
});
},
};
}
1
2
3
4
5
6
7
8
9
10
11
12
// vite.config.js —— 使用自定义插件
import svgInline from './plugins/vite-plugin-svg-inline';

export default defineConfig({
plugins: [
vue(),
svgInline({ prefix: '?raw' }),
],
});

// 在代码中使用
import icon from './icons/check.svg?raw'; // 直接得到 SVG 字符串

常用官方与社区插件

插件用途安装量
@vitejs/plugin-vueVue SFC 支持官方
@vitejs/plugin-reactReact JSX + Fast Refresh官方
@vitejs/plugin-legacy传统浏览器兼容(自动 Babel + polyfill)官方
vite-plugin-pwaPWA + Service Worker高频
vite-plugin-svg-iconsSVG 雪碧图高频
vite-plugin-inspect查看插件中间产物调试
vite-plugin-mock本地 mock 数据高频
unplugin-auto-import自动导入 API(ref、computed 等)高频
unplugin-vue-components自动按需引入组件库高频

Rollup 插件兼容性

Vite 的插件系统与 Rollup 兼容,大多数 Rollup 插件可直接在 Vite 中使用:

1
2
3
4
5
6
7
8
9
10
11
12
import rollupPluginCommonjs from '@rollup/plugin-commonjs';
import rollupPluginReplace from '@rollup/plugin-replace';

export default defineConfig({
plugins: [
// Rollup 插件直接使用
rollupPluginCommonjs(),
rollupPluginReplace({
'process.env.NODE_ENV': JSON.stringify('production'),
}),
],
});

注意:部分 Rollup 插件使用了 emitFileresolveFileUrl 等 Vite 不支持的输出钩子,使用前需测试验证。

2.4 类型支持

Vite 对 TypeScript 和 JSX 提供了良好的支持:

  • TypeScript 集成:Vite 内置了对 TypeScript 的支持,无需额外配置。Vite 会自动处理 TypeScript 文件,并在开发模式下提供类型检查。
  • JSX 支持:Vite 支持 JSX 语法,适用于 React 等框架。对于 React 项目,Vite 提供了专门的插件 @vitejs/plugin-react 来优化 JSX 的处理。

2.5 其他特性

Vite 还提供了许多其他实用特性:

  • CSS 预处理:内置支持 CSS 预处理器,如 Sass、Less、Stylus。只需安装相应的预处理器依赖,Vite 就会自动处理。
  • 环境变量:支持 .env 文件和环境变量的管理。Vite 会根据当前环境加载对应的 .env 文件,并将环境变量注入到代码中。
  • 动态导入:支持动态导入语法,实现代码分割。这对于大型应用的性能优化非常重要。
  • 模块热替换:除了 HMR 外,Vite 还支持模块热替换,允许在不刷新页面的情况下更新模块。
  • 静态资源处理:Vite 对图片、字体、JSON 等静态资源的处理非常方便,可以直接导入使用。

3. Vite 的基本用法

3.1 初始化项目

使用 npm create vite 命令初始化一个新的 Vite 项目:

1
2
3
4
5
6
7
8
# 使用 npm
npm create vite@latest my-vite-app

# 使用 yarn
yarn create vite my-vite-app

# 使用 pnpm
pnpm create vite my-vite-app

初始化过程中,Vite 会提示选择框架(Vue、React、Svelte 等)和变体(JavaScript 或 TypeScript)。例如:

1
2
3
4
5
6
7
8
9
10
11
? Select a framework: › - Use arrow-keys. Return to submit.
Vanilla
Vue
React
Preact
Lit
Svelte

? Select a variant: › - Use arrow-keys. Return to submit.
JavaScript
TypeScript

选择完成后,Vite 会生成相应的项目结构和配置文件。

3.2 项目结构

一个典型的 Vite 项目结构如下:

1
2
3
4
5
6
7
8
9
10
11
my-vite-app/
├── index.html # 入口 HTML 文件
├── package.json # 项目配置文件
├── vite.config.js # Vite 配置文件
├── public/ # 静态资源目录(不会被处理)
└── src/ # 源代码目录
├── main.js # 应用入口文件
├── App.vue # 根组件(Vue 项目)
├── components/ # 组件目录
├── assets/ # 资源目录
└── style.css # 全局样式文件

重要文件说明

  • index.html:Vite 项目的入口 HTML 文件,与传统项目不同,它位于项目根目录,而不是 public 目录。
  • vite.config.js:Vite 的配置文件,用于配置 Vite 的各种选项。
  • **public/**:存放静态资源的目录,这些资源不会被 Vite 处理,会直接复制到构建输出目录。
  • **src/**:存放源代码的目录,包括 JavaScript/TypeScript 文件、Vue 组件、样式文件等。

3.3 开发服务器

启动开发服务器:

1
2
3
4
5
6
7
8
# 使用 npm
npm run dev

# 使用 yarn
yarn dev

# 使用 pnpm
pnpm dev

开发服务器默认运行在 http://localhost:5173。启动后,Vite 会在终端显示开发服务器的地址和其他信息:

1
2
3
4
5
6
7
> vite

VITE v5.0.0 ready in 500 ms

➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h to show help

3.4 构建生产版本

构建生产版本:

1
2
3
4
5
6
7
8
# 使用 npm
npm run build

# 使用 yarn
yarn build

# 使用 pnpm
pnpm build

构建结果会输出到 dist 目录。构建完成后,Vite 会显示构建结果的统计信息:

1
2
3
4
5
6
7
8
9
> vite build

vite v5.0.0 building for production...
done in 1.2s

✓ 3 modules transformed.
dist/index.html 0.50 kB
dist/assets/index-123456.js 1.20 kB
dist/assets/index-123456.css 0.30 kB

3.5 预览生产版本

构建完成后,可以使用 Vite 的预览命令来预览生产版本:

1
2
3
4
5
6
7
8
# 使用 npm
npm run preview

# 使用 yarn
yarn preview

# 使用 pnpm
pnpm preview

预览服务器默认运行在 http://localhost:4173。

4. Vite 的常用操作

4.1 配置 Vite

Vite 的配置文件是 vite.config.js,可以在其中配置各种选项。以下是一些常用的配置选项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'

export default defineConfig({
// 插件配置
plugins: [vue()],

// 开发服务器配置
server: {
port: 3000, // 服务器端口
open: true, // 自动打开浏览器
proxy: { // 代理配置
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
},

// 构建配置
build: {
outDir: 'dist', // 构建输出目录
minify: 'terser', // 代码压缩工具
sourcemap: true, // 生成 sourcemap
rollupOptions: { // Rollup 配置
output: {
manualChunks: {
vendor: ['vue'],
common: ['lodash']
}
}
}
},

// 解析配置
resolve: {
alias: {
'@': resolve(__dirname, 'src') // 路径别名
}
},

// CSS 配置
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "./src/styles/variables.scss";`
}
}
}
})

4.2 使用插件

Vite 的插件系统非常强大,可以通过插件扩展其功能。以下是一些常用的 Vite 插件:

  • @vitejs/plugin-vue:Vue 官方插件,用于处理 Vue 单文件组件。
  • @vitejs/plugin-react:React 官方插件,用于处理 React 组件和 JSX。
  • @vitejs/plugin-legacy:用于生成传统浏览器兼容的代码。
  • vite-plugin-pwa:用于添加 PWA 支持。
  • vite-plugin-svg-icons:用于处理 SVG 图标。

安装并使用插件的示例:

1
2
3
4
5
6
7
8
9
10
11
# 安装插件
npm install @vitejs/plugin-react

# 在配置文件中使用
// vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
plugins: [react()]
})

4.3 处理静态资源

Vite 对静态资源的处理非常方便,支持多种静态资源类型:

  • 图片:可以直接导入图片文件,Vite 会自动处理。对于小图片,Vite 会将其转换为 Base64 编码,减少网络请求。
    1
    2
    3
    4
    5
    import logo from './assets/logo.png'

    function App() {
    return <img src={logo} alt="Logo" />
    }
  • 字体:可以直接导入字体文件。
    1
    import './assets/fonts/my-font.css'
  • JSON:可以直接导入 JSON 文件,Vite 会将其转换为 JavaScript 对象。
    1
    2
    import config from './config.json'
    console.log(config.apiBaseUrl)
  • CSS:可以直接导入 CSS 文件,Vite 会将其注入到页面中。
    1
    import './style.css'

    4.4 环境变量

Vite 支持环境变量,通过 .env 文件定义。Vite 会根据当前环境加载对应的 .env 文件:

  • .env:所有环境通用的配置
  • .env.local:所有环境通用的本地配置(不会被 git 跟踪)
  • .env.development:开发环境配置
  • .env.production:生产环境配置

环境变量的定义:

1
2
3
# .env
VITE_API_BASE_URL=https://api.example.com
VITE_APP_TITLE=My App

在代码中使用环境变量:

1
2
3
4
5
6
7
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL
const appTitle = import.meta.env.VITE_APP_TITLE

// 内置环境变量
const isDev = import.meta.env.DEV // 开发环境为 true
const isProd = import.meta.env.PROD // 生产环境为 true
const baseUrl = import.meta.env.BASE_URL // 应用的基础路径

4.5 代码分割

Vite 支持动态导入实现代码分割,这对于大型应用的性能优化非常重要:

1
2
3
4
5
6
7
8
9
10
11
// 动态导入
async function loadComponent() {
const module = await import('./HeavyComponent.vue')
return module.default
}

// 在需要时加载
button.addEventListener('click', async () => {
const HeavyComponent = await loadComponent()
// 使用 HeavyComponent
})

Vite 还支持在路由层面进行代码分割,例如在 Vue Router 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
{
path: '/',
component: () => import('../views/Home.vue')
},
{
path: '/about',
component: () => import('../views/About.vue')
}
]

const router = createRouter({
history: createWebHistory(),
routes
})

export default router

5. Vite 的工作原理

5.1 开发模式

在开发模式下,Vite 的工作原理如下:

  1. 启动开发服务器:Vite 启动一个开发服务器,监听特定端口。
  2. 处理 HTML 请求:当浏览器请求 HTML 文件时,Vite 会读取根目录下的 index.html 文件,并对其进行处理。
  3. 处理模块请求:当浏览器请求 JavaScript 模块时,Vite 会:
    • 检查模块是否是第三方依赖
    • 如果是第三方依赖,使用预构建的版本
    • 如果是本地模块,实时编译并返回
  4. 热模块替换:当文件发生变化时,Vite 会:
    • 重新编译修改的模块
    • 通过 WebSocket 通知浏览器更新
    • 浏览器只更新变化的模块,而不是整个应用

5.2 生产模式

在生产模式下,Vite 的工作原理如下:

  1. 预构建依赖:Vite 会预构建第三方依赖,将它们转换为 ES 模块格式。
  2. 打包应用代码:Vite 使用 Rollup 打包应用代码,进行 tree-shaking 和代码分割。
  3. 生成静态资源:Vite 会生成优化后的静态资源,包括 JavaScript、CSS、图片等。
  4. 生成 HTML 文件:Vite 会生成 HTML 文件,并注入打包后的静态资源。

6. Vite 与 Webpack 的对比分析

6.1 核心差异

特性ViteWebpack
开发模式使用浏览器原生 ES 模块,按需编译打包整个应用
冷启动速度极快(毫秒级)较慢(秒级,大型项目可能需要数分钟)
HMR 速度极快(即时)较慢(随着项目规模增大而变慢)
生产构建使用 Rollup使用内置打包器
配置复杂度简单(默认配置即可满足大部分需求)复杂(需要配置多个 loader 和 plugin)
生态系统正在发展中(插件数量快速增长)成熟(拥有大量插件和 loader)
适用场景现代前端项目,对开发体验要求高各种前端项目,特别是需要复杂构建配置的项目

6.2 性能对比

6.2.1 开发启动时间

Vite 的开发启动时间通常比 Webpack 快 10-100 倍,特别是对于大型项目。这是因为:

  • Vite 不需要打包整个应用,而是按需编译
  • Vite 利用浏览器的原生 ES 模块支持,减少了构建步骤
  • Vite 会预构建第三方依赖,提高后续请求的速度

6.2.2 热更新速度

Vite 的 HMR 速度几乎是即时的,而 Webpack 的 HMR 速度会随着项目规模的增大而变慢。这是因为:

  • Vite 只需要更新修改的模块,而不是整个应用
  • Vite 会跟踪模块之间的依赖关系,只更新受影响的模块
  • Vite 的 HMR 实现更加高效,不需要重新打包整个应用

6.2.3 生产构建速度

两者在生产构建速度上相差不大,Vite 使用 Rollup 可能会略快一些。这是因为:

  • Vite 使用 Rollup 进行生产构建,Rollup 的 tree-shaking 能力更强
  • Vite 的构建配置更加优化,默认情况下就有较好的性能

6.3 适用场景

6.3.1 Vite 适用场景

  • 快速原型开发:Vite 的极速启动速度非常适合快速原型开发,可以立即看到修改效果。
  • 中小型项目:对于中小型项目,Vite 可以提供更好的开发体验。
  • 对开发体验要求高的项目:如果团队非常注重开发体验和效率,Vite 是一个很好的选择。
  • 使用现代前端框架的项目:Vite 对 Vue、React、Svelte 等现代前端框架有很好的支持。
  • 使用 ES 模块的项目:Vite 原生支持 ES 模块,对于使用 ES 模块的项目非常友好。

6.3.2 Webpack 适用场景

  • 大型项目:Webpack 在处理大型项目时更加稳定,拥有更多的优化选项。
  • 需要复杂构建配置的项目:如果项目需要复杂的构建配置,Webpack 提供了更多的灵活性。
  • 对兼容性要求高的项目:Webpack 可以通过各种 loader 和 plugin 处理各种兼容性问题。
  • 已有 Webpack 配置的项目:如果项目已经有了完整的 Webpack 配置,迁移到 Vite 可能需要一定的成本。
  • 需要特殊构建功能的项目:Webpack 拥有更多的插件和 loader,可以满足各种特殊的构建需求。

6.4 迁移建议

如果您正在考虑从 Webpack 迁移到 Vite,可以按照以下步骤进行:

  1. 评估项目需求:首先评估您的项目是否适合使用 Vite,特别是是否需要 Vite 不支持的功能。

  2. 创建测试项目:在正式迁移之前,可以创建一个测试项目,尝试使用 Vite 构建您的应用,看看是否能够满足需求。

  3. 逐步迁移:可以先在小项目或新功能中尝试 Vite,积累经验后再逐步迁移整个项目。

  4. 调整配置:Vite 的配置与 Webpack 有所不同,需要进行适当调整。例如:

    • Vite 使用 vite.config.js 而不是 webpack.config.js
    • Vite 的插件系统与 Webpack 不同
    • Vite 的路径解析规则与 Webpack 有所不同
  5. 处理依赖问题:某些依赖可能与 Vite 不兼容,需要寻找替代方案或进行适当的配置。

  6. 测试:迁移后需要进行充分的测试,确保功能正常,特别是在生产环境中。

6.5 混合使用

在某些情况下,您可能需要混合使用 Vite 和 Webpack:

  • 开发环境使用 Vite:利用 Vite 的极速开发体验
  • 生产环境使用 Webpack:利用 Webpack 的成熟生态和复杂配置能力

这种方式可以结合两者的优势,但需要维护两套构建配置,增加了维护成本。

7. Vite 的最佳实践

7.1 项目配置

  • 合理使用路径别名:使用路径别名可以简化导入路径,提高代码可读性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // vite.config.js
    import { defineConfig } from 'vite'
    import { resolve } from 'path'

    export default defineConfig({
    resolve: {
    alias: {
    '@': resolve(__dirname, 'src')
    }
    }
    })
  • 配置代理:在开发环境中配置代理,解决跨域问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // vite.config.js
    export default defineConfig({
    server: {
    proxy: {
    '/api': {
    target: 'http://localhost:8080',
    changeOrigin: true
    }
    }
    }
    })
  • 优化构建配置:根据项目需求优化构建配置,提高构建效率和产出质量。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // vite.config.js
    export default defineConfig({
    build: {
    rollupOptions: {
    output: {
    manualChunks: {
    vendor: ['vue', 'vue-router'],
    common: ['lodash', 'axios']
    }
    }
    }
    }
    })

    7.2 代码组织

  • 合理使用动态导入:对于大型组件或第三方库,使用动态导入实现代码分割,提高首屏加载速度。

  • 优化资源加载:对于图片等静态资源,合理使用导入方式,小图片使用 Base64 编码,大图片使用正常导入。

  • 模块化组织代码:将代码按照功能模块进行组织,提高代码的可维护性。

7.3 性能优化

  • 使用生产模式构建:在部署前,确保使用 npm run build 构建生产版本,Vite 会自动进行各种优化。

  • 启用 gzip 压缩:在服务器端启用 gzip 压缩,减少传输体积。

  • 使用 CDN:对于第三方库,可以考虑使用 CDN 加载,减少打包体积。

  • 优化图片:对图片进行压缩和优化,减少图片体积。

7.4 开发技巧

  • 使用 Vite 的 HMR:充分利用 Vite 的热模块替换功能,提高开发效率。

  • 使用环境变量:通过环境变量管理不同环境的配置,避免硬编码。

  • 使用插件扩展功能:根据项目需求,使用合适的 Vite 插件扩展功能。

  • 利用 Vite 的调试工具:Vite 提供了一些调试工具,如 vite debug 命令,可以帮助排查问题。

8. 总结

Vite 是一款现代化的前端构建工具,通过利用浏览器的原生 ES 模块支持和快速的编译能力,为前端开发提供了极速的开发体验。它的核心优势在于:

  • 极速的开发服务器启动速度:Vite 的冷启动速度通常比传统构建工具快 10-100 倍,大大提高了开发效率。
  • 快速的热模块替换:Vite 的 HMR 速度几乎是即时的,让开发者可以立即看到修改效果。
  • 简单易用的配置:Vite 的配置非常简单,默认配置即可满足大部分需求,减少了配置的复杂性。
  • 与现代前端框架的良好集成:Vite 对 Vue、React、Svelte 等现代前端框架有很好的支持,提供了专门的插件。
  • 强大的构建能力:在生产环境中,Vite 使用 Rollup 进行打包,生成优化后的静态资源。

虽然 Vite 的生态系统还在发展中,但它已经成为许多前端开发者的首选构建工具,特别是对于使用现代前端框架的项目。随着 Vite 的不断发展和完善,它有望成为前端构建工具的主流选择。

与 Webpack 相比,Vite 在开发体验上有明显的优势,但 Webpack 在某些复杂场景下仍然具有不可替代的地位。开发者可以根据项目的具体需求选择合适的构建工具,或者在不同的场景下使用不同的工具。

无论选择哪种构建工具,最重要的是它能够满足项目的需求,提高开发效率,保证应用的性能和质量。

9. 答疑解惑

9.1 浏览器的 ESM 支持

现代浏览器(2017 年以后)原生支持 ES Module(ESM)规范。

  • 支持情况:Chrome 61+、Firefox 60+、Safari 16.4+、Edge 79+ 都已经原生支持 <script type="module"> 语法。
  • 支持的内容:浏览器可以原生解析 import / export 语句,能识别 import { foo } from './bar.js' 这种路径,并能发起 HTTP 请求去加载依赖的模块。
  • 重要澄清:浏览器支持的是 ESM 的加载机制(模块化规范),而不是支持所有 ES6+ 新语法(如箭头函数、可选链、装饰器等)。后者依然需要 Babel 或 esbuild 转译。

你困惑的“浏览器能支持 ES6 吗”其实混淆了两个概念:语法特性(如箭头函数)vs 模块加载机制(如 import/export)。浏览器原生支持的是后者,前者需要按需转译。


既然浏览器原生支持 ESM,为什么 Webpack 不直接用?

因为 Webpack 诞生于 2012 年,而浏览器原生 ESM 在 2017 年才开始普及。

Webpack 的设计前提是:浏览器没有模块系统,所以它必须在构建时把所有模块打包成一个(或几个)bundle 文件,让浏览器一次性加载。

但更关键的是,就算现在浏览器支持了 ESM,Webpack 也不能简单切换,原因在于“生态依赖”

维度Webpack 的依赖为什么不能直接用浏览器 ESM
非 JS 资源需要处理 CSS、图片、字体等浏览器 ESM 只能加载 JS,而 Webpack 的 loader 体系(如 css-loader)是在构建时把 CSS 转为 JS 模块。如果用浏览器原生加载,这些非 JS 资源无法处理。
Node.js 模块大量包使用 require()module.exports浏览器不认识 CommonJS。Webpack 通过静态分析将所有 CJS 转为 ESM 语法,这个过程必须在构建时完成,浏览器做不到。
代码分割策略Webpack 有自己的 import() 动态加载逻辑虽然浏览器也支持动态 import(),但 Webpack 的拆分策略(如 splitChunks)极其精细,需要构建时计算最优切割点,无法在浏览器运行时动态决策。

那 Vite 为什么能在开发模式下用浏览器原生 ESM?

Vite 的策略是“开发态”和“生产态”完全分开

开发模式(dev server):

  • 不打包:Vite 启动一个开发服务器,浏览器直接请求源码文件(.vue.tsx 等)。
  • 按需转译:Vite 使用 esbuild(极快的转译器)在请求到来时即时将 TypeScript、JSX 转为纯 JS,但保留 import/export 语法不打包
  • 依赖预构建:首次启动时,Vite 会用 esbuild 将 node_modules 中的 CJS 包(如 lodash)预转为 ESM 格式并缓存,这样浏览器就能直接加载了。
  • 结果:浏览器通过 <script type="module"> 加载入口文件,然后递归发起 HTTP 请求加载所有依赖。这个过程没有“打包”动作,所以启动极快。

生产模式(build):

  • 依然使用 Rollup 打包:Vite 在生产环境下会像 Webpack 一样做完整的打包、Tree Shaking、代码分割。因为生产环境要兼容老旧浏览器(需要转译到 ES5),且要合并请求减少 HTTP 数量。

为什么 Webpack 不学 Vite 这样做?

不是因为技术做不到,而是因为设计理念和生态包袱不同:

对比维度WebpackVite
诞生年份2012 年(彼时无 ESM)2020 年(ESM 已成标准)
核心设计一切资源都是模块,全量打包开发用原生 ESM,生产用 Rollup
启动速度需要全量编译,项目越大越慢无需打包,只编译请求的文件
历史包袱海量的 loader/plugin 生态都基于“构建时”设计无包袱,可激进使用新特性
运行时依赖依赖自己的 runtime 代码(处理 CJS 模拟等)完全依赖浏览器原生能力

Webpack 如果要改成 Vite 的模式,相当于重构整个架构,且会破坏数百万项目的兼容性。所以 Webpack 选择了在 5.0 版本中优化缓存和持久化构建来提速,而非改变底层范式。


一张图总结差异

1
2
3
4
5
6
Webpack 开发模式:
源码 → 全量打包(bundle) → 启动服务器 → 浏览器加载大文件

Vite 开发模式:
启动服务器 → 浏览器请求入口 → 按需转译并返回 → 递归加载依赖
(省去了“全量打包”这一步)

Q:开发模式下 Vite 用浏览器 ESM,那如何处理 CSS / 图片?
A:Vite 会把 CSS 转为 JS 模块,注入 <style> 标签;图片会返回 URL 字符串,这些都是通过插件在请求时即时转换的。

Q:生产环境用原生 ESM 不是更快吗?为什么还要打包?
A:因为生产环境要优化:合并请求(减少 200 个文件请求为 1 个)、压缩代码、Tree Shaking、兼容旧浏览器。打包是“性能优化”手段,而不是“模块加载”必需品。


9.2 Vite 为什么选择 esbuild?

选择 esbuild 而非 Babel,核心就是一个字:。而且这个转译功能确实是 Vite 默认自带的,开箱即用。

Vite 选择 esbuild,是基于 “性能优先于功能” 的务实考量。

  • 极致的速度是根本原因:esbuild 用 Go 语言编写,直接编译成机器码执行;而 Babel 是 JavaScript 编写的,需要解释执行。在处理 JSX、TypeScript 等文件的转译时,esbuild 比 Babel 快 10 到 100 倍。这种速度差异,让 Vite 能够在开发服务器启动和热更新时,提供近乎即时的响应。

  • 性能与灵活性的取舍:Babel 的强大之处在于其海量的插件生态,能做到精细化的语法转换和 Polyfill 注入。而 esbuild 的设计哲学非常纯粹:只做标准且通用的语法转译,追求极致性能,不提供扩展性强的插件系统。这种取舍让 esbuild 无法像 Babel 那样支持 Vue 的 v-model 等框架特定语法,但换来了绝大部分场景下都能胜任的转译速度。

  • 日常开发够用:对于 Vite 默认支持的现代浏览器(如 Chrome >=111, Edge >=111, Firefox >=114, Safari >=16.4),esbuild 默认或通过简单配置(如 build.target)就能将代码转译到兼容版本。它的能力足以覆盖绝大多数常规项目需求,也因此被 Vite 内部大量用于依赖预打包、TS/JSX 转译和生产环境代码压缩等多个环节。

转译是默认自带的吗?

是的,完全默认自带,无需额外配置。

Vite 内置了 esbuild 作为其转译引擎。你可以通过 vite.config.js 中的 build.target 选项来精细控制转译的目标环境。

  • **默认目标 ('modules')**:Vite 会假设你的代码运行在支持原生 ES Module 的现代浏览器上,并据此进行转译。
  • 自定义目标:如果需要兼容更低版本的浏览器,你可以将 build.target 设置为 es2015 或更具体的浏览器版本(如 chrome64)。

需要留意的是:Vite 默认 只处理语法转译,不包含 Polyfill。如果需要兼容非常老的浏览器(如 IE11),并注入 Polyfill,官方推荐使用 @vitejs/plugin-legacy 插件,该插件内部会调用 Babel 来完成更复杂的兼容工作。

9.3 原生 JS 导入能力

这个问题问得非常棒,直击了 “原生 ESM”和“Vite 增强能力” 之间的核心边界。

先给你最直接的答案:JS 原生模块(ESM)确实不能直接 import 图片、CSS 等非 JS 资源,浏览器会报错。Vite 的“方便处理”本质上是把这些非 JS 资源“伪装”成 JS 模块,让浏览器能正常加载。

根据 ES Module 规范,import 语句只能导入 JavaScript 模块.js.mjs 文件)。

  • ✅ 可以导入
    1
    2
    import { foo } from './bar.js';    // 正常
    import lodash from 'lodash'; // 正常(指向 package.json 的 main 字段)
  • ❌ 直接导入会报错
    1
    2
    3
    import logo from './logo.png';     // ❌ 浏览器报错:Cannot load image
    import './style.css'; // ❌ 浏览器报错:Cannot load CSS
    import data from './data.json'; // ❌ 浏览器报错:Cannot load JSON
    浏览器遇到非 JS 文件时,会抛出类似 Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "image/png" 的错误,因为服务器返回的 MIME 类型不是 application/javascript

Vite 是如何“处理”这些资源的?

Vite 在开发服务器中做了“拦截和转换”,把非 JS 资源变成一个可被浏览器执行的 JS 模块

举例 1:图片导入

源码

1
import logo from './logo.png';

Vite 在开发模式下实际返回给浏览器的 JS 内容(经过转换):

1
2
// 实际返回的是 JS 代码,不是图片二进制
export default "/src/assets/logo.png" // 一个 URL 字符串

浏览器执行的是一段 JS,它导出了一个字符串(图片的访问路径),而不是图片本身。这样 import 语法就合法了。

举例 2:CSS 导入

源码

1
import './style.css';

Vite 在开发模式下实际返回的 JS 内容

1
2
3
4
5
// 动态创建 <style> 标签,将 CSS 内容注入到页面
const style = document.createElement('style');
style.textContent = `/* 这里是你 CSS 文件的内容 */`;
document.head.appendChild(style);
export default {}; // 空对象,满足模块导出

浏览器执行这段 JS 后,CSS 就被动态注入到页面了。

举例 3:JSON 导入

源码

1
import data from './data.json';

Vite 在开发模式下实际返回的 JS 内容

1
2
3
4
5
// 把 JSON 对象直接作为默认导出
export default {
"name": "Vite",
"version": "5.0.0"
};

这背后 Vite 做了什么?

Vite 的开发服务器(基于 Connect)会拦截所有 .png.css.json 等文件的 HTTP 请求:

  1. 浏览器请求GET /src/assets/logo.png
  2. Vite 拦截:识别到请求的是非 JS 文件
  3. 即时转换:读取文件内容,根据文件类型生成对应的 JS 代码
  4. 返回 JS:将转换后的 JS 代码返回给浏览器(Content-Type 设为 application/javascript

整个过程是即时、按需的,你不需要任何配置。


为什么 Vite 能做到而原生 ESM 不行?

维度原生 ESMVite
加载方式浏览器直接请求文件浏览器请求 → Vite 服务器拦截 → 动态转换 → 返回 JS
处理能力只能执行 .js / .mjs能处理图片、CSS、JSON、WASM 等任意资源
实现原理依赖浏览器原生实现依赖开发服务器的 HTTP 拦截和动态代码生成

Vite 的本质是一个 “开发服务器 + 按需转换器”,它利用浏览器的原生 ESM 加载机制,但在服务器端做了“魔法转换”,让非 JS 资源也能以 JS 模块的形式被浏览器消费。


生产环境呢?

生产构建(vite build)时,Vite 会用 Rollup 做静态分析和打包:

  • 图片:会被复制到输出目录,文件名带 hash,import 返回的是最终 URL。
  • CSS:会被提取成独立的 .css 文件(或基于配置内联)。
  • JSON:会被直接打包进 JS bundle 中。

但无论哪种方式,最终产物都是浏览器能正确执行的代码,这是构建工具的核心职责。


总结一句话

JS 原生模块只能导入 JS,不能导入图片/CSS/JSON。Vite 的“方便处理”是通过开发服务器拦截请求,动态把非 JS 资源“翻译”成 JS 模块,让浏览器愉快地执行,从而让你写 import 时感觉“天然支持”。

10. 参考资料