开发时利用浏览器原生 ESM 按需加载,生产时交给 Rollup 打包。它不像 Webpack 那样在开发阶段先打成一个大包,而是让浏览器直接加载源码模块——项目越大,体验差异越明显。

  • 启动快:不用全量打包,直接提供原生 ESM 模块。
  • HMR 快:热更新只影响变化的模块,不受应用规模影响。
  • 开箱即用:TypeScript、JSX、CSS、静态资源开箱支持。
  • 插件生态清晰:兼容 Rollup 插件生态。

对 Vue、React、Svelte、Solid 等现代框架,Vite 基本已是默认推荐方案。本文统一使用 bun。

创建项目

1
2
3
4
bun create vite@latest my-app -- --template vue-ts   # 也支持 react-ts / vanilla-ts / svelte-ts 等
cd my-app
bun install
bun run dev

常用启动参数:bunx vite --port 3000--host 0.0.0.0--open--https

项目结构

1
2
3
4
5
6
7
8
9
10
my-app/
├── index.html # 入口 HTML,Vite 的构建起点
├── vite.config.ts
├── tsconfig.json
├── public/ # 静态资源,原样拷贝,不参与构建
└── src/ # 源代码,参与构建处理
├── main.ts
├── App.vue
├── components/
└── assets/

index.html 不是单纯的壳子,它是 Vite 的入口之一,模块加载从这里开始。

核心概念

  • 原生 ESM:开发期直接依赖浏览器 ESM,import 按需加载,模块边界清晰。
  • 依赖预构建:用 esbuild 把 CommonJS / 复杂依赖转成浏览器友好的形式。
  • 按需编译:只有被访问的文件才处理,热更新只波及变化模块的最小范围。
  • Rollup 生产构建:开发期追求速度,生产期由 Rollup 做代码分割、压缩和产物输出。

配置文件

vite.config.ts(或 .js.mjs),按需取用:

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
54
55
56
57
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'node:path';

export default defineConfig(({ mode }) => ({
base: '/', // 部署在子目录时改为 '/my-app/'
plugins: [vue()],

resolve: {
alias: { '@': resolve(__dirname, 'src') },
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
},

css: {
modules: { localsConvention: 'camelCaseOnly' },
preprocessorOptions: {
scss: { additionalData: `@use "@/styles/variables.scss" as *;` },
},
devSourcemap: true,
},

server: {
port: 3000,
open: true,
proxy: {
'/api': 'http://localhost:8080', // 字符串简写
'/api/v1': { // 对象配置
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (p) => p.replace(/^\/api\/v1/, ''),
},
'/socket.io': { target: 'ws://localhost:3000', ws: true }, // WebSocket
},
watch: { usePolling: false }, // 热更新不生效时改 true
},

build: {
outDir: 'dist',
sourcemap: false,
minify: 'esbuild', // 'terser' | 'esbuild' | false
assetsInlineLimit: 4096, // 4kb 以下内联 base64
rollupOptions: {
external: ['vue'], // 不打包的依赖
output: {
manualChunks: { vendor: ['vue', 'vue-router', 'pinia'] },
globals: { vue: 'Vue' }, // 配合 external
},
},
},

optimizeDeps: {
include: ['lodash-es', 'axios'],
exclude: ['@vue/compiler-sfc'],
},

envPrefix: 'VITE_',
}));

最常用项:pluginsresolve.aliasserver.proxybuild.outDirbuild.rollupOptions

TypeScript 别名要同步到 tsconfig.json,否则编辑器会报路径找不到:

1
{ "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"] } } }

环境变量

只有 VITE_ 前缀的变量才会暴露到前端代码。dotenv 加载,按模式区分文件:

1
2
3
4
5
# .env               所有环境生效
# .env.development 开发(bun run dev)
# .env.production 生产(bun run build)
# .env.staging 自定义模式(bunx vite build --mode staging)
VITE_API_URL=http://localhost:8080/api
1
2
3
4
import.meta.env.VITE_API_URL;      // 自定义变量
import.meta.env.MODE; // 'development' | 'production'
import.meta.env.DEV; // 开发模式为 true
import.meta.env.PROD; // 生产模式为 true

类型提示(env.d.ts):

1
2
3
4
5
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL: string;
}
interface ImportMeta { readonly env: ImportMetaEnv; }

前端环境变量不是秘密管理系统——进了前端包就等于公开,密钥、token 不要放进来。

静态资源

src 里的资源参与构建(哈希命名、引用分析),public 里的资源原样拷贝、不参与模块解析。

1
2
3
4
import logo from './assets/logo.png';         // 转为 URL
import raw from './data.txt?raw'; // 原始内容
import url from './large.png?url'; // 强制 URL(不内联)
const logoUrl = new URL('./logo.png', import.meta.url).href;

小于 assetsInlineLimit(默认 4kb)的资源自动内联为 base64。需要处理和哈希的资源放 src,纯静态文件放 public

插件

官方:@vitejs/plugin-vue@vitejs/plugin-react@vitejs/plugin-legacy 等。常用社区插件:

1
2
3
4
import { visualizer } from 'rollup-plugin-visualizer';   // 包体积分析
import compression from 'vite-plugin-compression'; // gzip/brotli 压缩
import AutoImport from 'unplugin-auto-import/vite'; // 自动导入 API
import Components from 'unplugin-vue-components/vite'; // 自动导入组件

自定义插件就是实现几个 Rollup 式钩子:

1
2
3
4
5
6
7
8
9
10
11
12
13
import type { Plugin } from 'vite';

function myPlugin(): Plugin {
return {
name: 'my-plugin',
transform(code, id) { // 转换模块
if (id.endsWith('.vue')) return { code: code.replace(/console\.log\(.*?\);?/g, ''), map: null };
},
transformIndexHtml(html) { // 转换 HTML
return html.replace('</body>', '<script>console.log("hi")</script></body>');
},
};
}

多页面与库模式

多页面:在 build.rollupOptions.input 注册多个入口,每个对应一个 .html

1
2
3
4
5
6
7
8
build: {
rollupOptions: {
input: {
index: resolve(__dirname, 'src/main.ts'),
admin: resolve(__dirname, 'src/admin/main.ts'),
},
},
}

库模式:把项目打包成可发布的库,external 外部化 peer 依赖。

1
2
3
4
build: {
lib: { entry: resolve(__dirname, 'src/index.ts'), name: 'MyLibrary', formats: ['es', 'umd', 'iife'] },
rollupOptions: { external: ['vue'], output: { globals: { vue: 'Vue' } } },
}

性能优化

1
2
3
4
5
// 路由级懒加载——最划算的优化
const routes = [{ path: '/', component: () => import('./views/Home.vue') }];

// 依赖预构建,加速开发服务器
optimizeDeps: { include: ['lodash-es', 'axios', 'dayjs'] }

构建优化手段:manualChunks 拆第三方包、动态导入拆路由和大组件、减少大型工具库、用 visualizer 找体积大头。开发服务器慢时清缓存 rm -rf node_modules/.vite 或设 optimizeDeps.force: true

生产构建

1
2
bun run build      # 生成产物到 dist
bun run preview # 本地预览构建结果,接近真实部署环境

常见坑

  • 环境变量读不到:忘了 VITE_ 前缀,或没重启服务器。
  • 敏感信息泄露:进了前端包就等于公开,密钥别放 VITE_ 变量。
  • 别名只配一边:Vite 和 tsconfig.json 的别名要同步。
  • public 当源码用public 不参与模块解析,引用方式不同(用绝对路径 /xxx)。
  • 插件装太多:会拖慢启动和构建,按需安装。
  • 构建后路径错误:部署在子目录要配 base: '/my-app/'
  • 热更新不生效:检查路径,必要时 server.watch.usePolling: true

起步模板

1
2
3
4
5
6
bun create vite@latest my-app -- --template vue-ts
cd my-app
bun install
bun add vue-router pinia
bun add -D vitest eslint prettier @vitejs/plugin-vue
bun run dev

Vue 3 + TypeScript 稳妥组合:Vite + Vue Router + Pinia + Vitest + ESLint。后续补齐路由懒加载、API 封装、环境变量分层、构建分析、CI 接入 lint/测试即可。

何时不用 Vite

依赖老旧构建链迁移成本高、有高度定制的复杂编译流程、或做的不是浏览器前端打包——这几种情况需谨慎。对绝大多数 SPA、组件库、后台系统、内容站点,Vite 都是稳的默认选择。