← 编程返回顶部

Vite:前端开发服务器与构建工具

Vite 是现代前端项目常用的开发服务器和构建工具。

对于 React + TypeScript + Vite 项目,它主要负责:

  1. 开发阶段:启动开发服务器,提供快速模块加载和热模块替换(HMR)。
  2. 生产构建阶段:将 TypeScript、JSX、CSS 等源码转换、优化并打包为浏览器可以加载的静态资源。

整体流程:

React / TypeScript Source
           │
           ▼
          Vite
           │
     ┌─────┴─────┐
     ▼           ▼
Development   Production
     │           │
 Dev Server    Build
     │           │
     ▼           ▼
 HMR / ESM     dist/
                 │
                 ▼
           Nginx / CDN
                 │
                 ▼
              Browser

对于普通 React SPA,Vite 不需要在生产服务器上持续运行。构建后的 HTML、JavaScript、CSS 等资源可以直接由 Nginx 提供。

1. 开发服务器(Dev Server)

启动命令

pnpm dev

通常对应 package.json:

{
  "scripts": {
    "dev": "vite"
  }
}

默认开发地址通常是:

http://localhost:5173

常见命令:

# 启动开发服务器
pnpm dev

# 指定端口
pnpm dev -- --port 3000

# 允许局域网或容器外访问
pnpm dev -- --host 0.0.0.0

# 强制重新预构建依赖
pnpm dev -- --force

pnpm dev -- 中的 -- 表示将后续参数传递给脚本执行的程序。

开发服务器的工作方式

传统 Bundler 通常需要先构建一部分或全部模块,才能提供开发服务。

Vite 则利用浏览器原生 ES Modules(ESM),按需转换和提供源码模块。

Browser
   │
   ├── GET /src/main.tsx
   │
   ├── GET /src/App.tsx
   │
   └── GET /src/components/Header.tsx
                  │
                  ▼
             Vite Dev Server
                  │
                  ▼
           Transform / Serve

这减少了大型项目启动时必须进行的工作。

不过 Vite 在开发阶段仍然会执行模块转换及依赖预构建,并不是完全不进行任何构建处理。

2. HMR(Hot Module Replacement)

HMR 是 Vite 开发体验中的核心机制之一。

当源码发生变化时:

Modify React Component
          ↓
Vite Detects Changes
          ↓
HMR Update
          ↓
Browser Replaces Module
          ↓
Page Updates

HMR 可以只更新发生变化的模块,而不是重新加载整个页面。

React Fast Refresh

React 项目通常使用:

import react from "@vitejs/plugin-react";

React Fast Refresh 基于 HMR,可以在满足条件时更新组件,并尽可能保留 React State。

例如修改:

export function Counter() {
  const [count, setCount] = useState(0);

  return <button>{count}</button>;
}

组件代码修改后,计数器的状态在某些情况下可以被保留。

注意:

  • HMR 是通用模块热替换机制。
  • Fast Refresh 是专门针对 React 组件的热更新机制。
  • Fast Refresh 不能保证每一次修改都保留组件状态。

3. 依赖预构建(Dependency Pre-Bundling)

Vite 开发阶段通常会预处理部分第三方依赖。

例如:

import React from "react";
import axios from "axios";

这些依赖可能涉及:

  • CommonJS 转 ESM。
  • 大量内部模块。
  • 多层依赖引用。

Vite 会使用依赖优化机制,将适合预构建的依赖提前转换和合并,以提升开发阶段的加载效率。

缓存通常位于:

node_modules/.vite/

optimizeDeps

可以配置:

export default defineConfig({
  optimizeDeps: {
    include: ["react", "react-dom"],
    exclude: [],
  },
});
配置作用
include显式指定需要预构建的依赖
exclude排除某些依赖的预构建
force强制重新优化依赖

通常不需要手动修改 optimizeDeps。

只有遇到特定依赖加载缓慢、CommonJS 兼容性问题或依赖发现异常时,才值得调整。

依赖预构建主要是开发环境的优化机制,不等于生产环境的 Code Splitting。

4. 生产构建(Production Build)

运行:

pnpm build

通常对应:

{
  "scripts": {
    "build": "tsc -b && vite build"
  }
}

执行流程:

TypeScript Type Check
          ↓
       Vite Build
          ↓
    Module Bundling
          ↓
     Tree Shaking
          ↓
      Minification
          ↓
       dist/

注意:vite build 本身主要负责构建,不会像 tsc --noEmit 一样执行完整的 TypeScript 语义类型检查。

因此 React + TypeScript 项目通常会单独运行:

tsc -b

构建产物

通常生成:

dist/
├── index.html
└── assets/
    ├── index-A1b2C3.js
    ├── index-D4e5F6.css
    ├── MapPage-K7m8N9.js
    └── logo-P1q2R3.svg

这些文件就是部署到 Nginx 或 CDN 的静态资源。

build 配置

export default defineConfig({
  build: {
    outDir: "dist",
    sourcemap: false,
    emptyOutDir: true,
    chunkSizeWarningLimit: 500,
  },
});
配置作用
outDir构建结果输出目录
sourcemap是否生成 Source Map
emptyOutDir构建前是否清理输出目录
chunkSizeWarningLimitChunk 大小警告阈值
target构建目标浏览器环境
minify是否及如何压缩代码

chunkSizeWarningLimit 只控制警告阈值,并不会实际降低 Bundle 大小。

5. Preview:预览生产构建

pnpm build
pnpm preview

通常对应:

{
  "scripts": {
    "preview": "vite preview"
  }
}

作用:

dist/
  ↓
Vite Preview Server
  ↓
Browser

可以用来检查:

  • 构建后的页面是否正常。
  • 动态导入是否正确。
  • 静态资源路径是否有效。
  • 页面路由是否可以访问。

vite preview 主要用于本地预览,不建议将它作为正式生产服务器。

6. vite.config.ts

Vite 的主要配置文件通常是:

vite.config.ts

React + TypeScript + Tailwind CSS 项目示例:

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";

export default defineConfig({
  plugins: [react(), tailwindcss()],

  server: {
    host: "127.0.0.1",
    port: 5173,

    proxy: {
      "/api": {
        target: "http://localhost:8000",
        changeOrigin: true,
      },
    },
  },

  build: {
    outDir: "dist",
    sourcemap: false,
  },

  resolve: {
    tsconfigPaths: true,
  },

  base: "/",
});

这里的 resolve.tsconfigPaths 适用于支持该选项的较新版本 Vite;旧版本可以继续使用 resolve.alias。

常用配置分类

配置作用
plugins加载插件
server开发服务器配置
server.proxy开发环境反向代理
build生产构建
resolve模块路径解析
base资源公共路径
define定义编译时常量
cssCSS 处理
preview预览服务器配置
optimizeDeps开发依赖优化

7. 路径别名(Alias)

假设代码结构:

src/
├── components/
│   └── Button.tsx
├── pages/
└── main.tsx

如果不使用 Alias:

import { Button } from "../../components/Button";

使用 Alias:

import { Button } from "@/components/Button";

TypeScript 配置

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

Vite 配置

方式一:使用 resolve.alias。

import { fileURLToPath, URL } from "node:url";

export default defineConfig({
  resolve: {
    alias: {
      "@": fileURLToPath(new URL("./src", import.meta.url)),
    },
  },
});

方式二:较新版本 Vite 可使用:

resolve: {
  tsconfigPaths: true,
}

使 Vite 根据匹配的 tsconfig.json 解析路径别名。

注意:这项功能可能带来一定解析开销,且不能解决所有 CSS 或第三方工具中的路径解析问题。

8. 开发代理(server.proxy)

开发中,React 和 Django 可能运行在不同端口:

React → localhost:5173
Django → localhost:8000

可以配置:

server: {
  proxy: {
    "/api": {
      target: "http://localhost:8000",
      changeOrigin: true,
    },
  },
},

浏览器请求:

GET http://localhost:5173/api/v1/pois/

Vite 将请求转发至:

GET http://localhost:8000/api/v1/pois/

这样前端代码可以使用相对地址:

axios.get("/api/v1/pois/");

不需要在开发环境中直接请求另一个 Origin。

生产环境

server.proxy 只用于 Vite 开发服务器。

生产时通常由 Nginx 负责:

Browser
   ↓
Nginx
   ├── /assets/ → Static Files
   ├── /api/    → Django
   └── /        → React SPA

9. 环境变量(Environment Variables)

Vite 使用 .env 文件管理不同环境的配置。

常见文件:

.env
.env.local
.env.development
.env.production
.env.staging

示例

.env.development:

VITE_API_URL=http://localhost:8000

.env.production:

VITE_API_URL=https://api.example.com

代码中读取:

const apiUrl = import.meta.env.VITE_API_URL;

内置环境变量

变量作用
import.meta.env.MODE当前 Vite Mode
import.meta.env.DEV是否为开发模式
import.meta.env.PROD是否为生产模式
import.meta.env.BASE_URL资源基础路径
import.meta.env.SSR是否处于 SSR 构建环境

例如:

if (import.meta.env.DEV) {
  console.log("Development");
}

生产构建时可以通过静态替换和 Tree Shaking 删除部分开发专用代码。

环境变量安全

默认情况下,只有 VITE_ 前缀的自定义环境变量会暴露到客户端代码。

但需要特别注意:

所有暴露给浏览器的环境变量都应当视为公开信息。

不能放入:

  • 数据库密码。
  • Django SECRET_KEY。
  • 私有 API Token。
  • 服务器访问凭据。

Build-time 与 Runtime

Vite 的客户端环境变量通常是在构建时确定的。

.env.production
       ↓
    vite build
       ↓
  JavaScript Bundle
       ↓
    Nginx

如果构建后修改服务器上的 .env.production,已经生成的 JS 文件不会自动改变。

如果需要运行时动态配置,需要额外的配置文件或配置接口机制。

10. 静态资源处理

Vite 主要有两种静态资源管理方式。

src/assets

src/assets/logo.png

使用:

import logo from "@/assets/logo.png";

function Logo() {
  return <img src={logo} alt="Logo" />;
}

Vite 构建时会处理资源路径,并可能根据资源内容、大小及配置进行内联或生成带哈希文件。

例如:

/assets/logo-Cx7Ab91.png

public

public/favicon.ico

通常会原样复制到:

dist/favicon.ico

使用:

<img src="/favicon.ico" alt="Favicon" />

两者区别

特性src/assetspublic
经过构建资源处理是通常否
自动 Hash通常支持不自动添加
可以通过 import 引用是通常不推荐
固定 URL不一定是
适用场景应用资源favicon、固定路径文件

普通应用图片、字体等资源通常优先放在 src/assets。

需要保持固定文件名或固定 URL 的资源可以放在 public。

11. Code Splitting(代码拆分)

Code Splitting 用于将 JavaScript 拆分成多个 Chunk,避免用户首次访问时必须下载全部应用代码。

Dynamic Import

import { lazy, Suspense } from "react";

const MapPage = lazy(() => import("./pages/MapPage"));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <MapPage />
    </Suspense>
  );
}

Vite 在生产构建时可以将动态导入的模块拆成独立 Chunk。

例如:

dist/assets/
├── index-A1b2.js
├── MapPage-C3d4.js
└── maplibre-E5f6.js

实际拆分结果取决于模块依赖关系与构建配置。

Lazy Loading 与 Code Splitting

两者相关,但并不完全相同。

  • Code Splitting:将代码分成多个可独立加载的文件。
  • Lazy Loading:推迟加载代码或资源,直到需要使用时。

典型流程:

User Opens Homepage
         ↓
      main.js
         ↓
User Opens Map Page
         ↓
   MapPage Chunk
         ↓
  MapLibre Chunk

如果 MapPage 在首页就被立即渲染,那么即使使用了 lazy(),相关模块也可能在首页加载。

真正的页面级懒加载需要与路由或条件渲染配合。

12. Tree Shaking

Tree Shaking 用于移除构建过程中确定未被使用的代码。

例如:

export function a() {}
export function b() {}
export function c() {}

只引用:

import { a } from "./utils";

在满足静态分析和副作用处理条件时,Vite 可以在生产构建中移除没有使用的 b() 和 c()。

局限性

Tree Shaking 可能受到以下因素影响:

  • CommonJS。
  • 动态引用。
  • 模块副作用。
  • 第三方依赖打包方式。
  • 构建器无法证明代码未使用。

因此,使用 ES Modules 有助于构建工具进行静态分析,但不保证所有未使用代码都能被删除。

13. Minification(代码压缩)

生产构建通常会对 JavaScript 和 CSS 进行压缩。

例如源码:

function calculateTotal(price: number, quantity: number) {
  return price * quantity;
}

压缩后可能变成:

function t(n, r) {
  return n * r;
}

Minification 主要包括:

  • 删除不必要的空白和注释。
  • 缩短局部标识符。
  • 简化表达式。
  • 删除可证明无用的代码。

Minification 与 gzip/Brotli

它们解决的问题不同:

技术作用阶段核心作用
MinificationVite 构建减少代码本身的体积
gzipHTTP 传输压缩网络传输数据
BrotliHTTP 传输通常提供更高压缩率
Lazy Loading浏览器加载推迟不需要的代码下载

典型过程:

Source Code
     ↓
Vite Minification
     ↓
Minified JavaScript
     ↓
Nginx Brotli / gzip
     ↓
Network Transfer
     ↓
Browser Decompression
     ↓
Parse / Compile / Execute

gzip/Brotli 不会减少浏览器解压后需要处理的 JavaScript 代码量。

14. Bundle 分析与优化

当依赖体积较大,例如 MapLibre 时,可以分析构建产物。

常见优化方式

  1. 使用页面级 Lazy Loading。
  2. 避免在入口文件直接引入大型依赖。
  3. 采用 Dynamic Import。
  4. 利用 Tree Shaking。
  5. 合理拆分 Chunk。
  6. 启用 HTTP 压缩与缓存。

manualChunks

某些 Vite 版本和底层构建器支持手动分包配置,例如:

build: {
  rollupOptions: {
    output: {
      manualChunks: {
        map: ["maplibre-gl"],
      },
    },
  },
},

注意:这是基于传统 Rollup 配置的示例。较新版本 Vite 的底层构建器和对应配置 API 可能不同,使用时应确认版本兼容性。

而且手动 Chunk 拆分不一定能缩小总代码体积,也不保证相关 Chunk 不会在首屏加载。

一般建议先使用动态导入和默认代码拆分策略,只有经过 Bundle 分析后才手动调整 Chunk。

构建体积警告

Some chunks are larger than 500 kB

这是构建工具提醒某些 Chunk 较大,不代表构建失败,也不意味着页面一定存在严重性能问题。

需要同时考虑:

  • 网络压缩后的大小。
  • 是否属于首屏资源。
  • 是否长期缓存。
  • JavaScript 解析和执行开销。
  • 实际设备性能。

15. Source Map

生产代码通常经过 Minification,因此错误堆栈可能不容易阅读。

例如:

function a(e) {
  return e + 1;
}

Source Map 可以帮助将构建后的代码位置映射回原始源码。

Production Error
       ↓
Minified JS Position
       ↓
Source Map
       ↓
src/components/Map.tsx:32

配置:

build: {
  sourcemap: true,
},

常见选项

配置作用
false不生成
true生成独立 Source Map
"inline"内嵌到构建文件
"hidden"生成独立 Source Map,但不添加 Source Mapping URL 注释

对于 Sentry 等错误监控服务,可以考虑使用 hidden 并上传 Source Map。

需要注意:Source Map 可能暴露源码结构,因此不一定适合直接公开部署。

16. Base:部署基础路径

base 用于配置应用公开访问时的资源基础路径。

部署在根路径

https://example.com/

配置:

base: "/",

部署在子路径

https://example.com/my-app/

配置:

base: "/my-app/",

Vite 构建时会根据 base 调整:

  • JS 资源引用。
  • CSS 资源引用。
  • 静态资源路径。
  • HTML 中相关资源地址。

注意:base 不会自动配置 React Router。

如果 React Router 使用 Browser History,并部署在子路径下,还需要正确配置 Router 的 basename。

17. Plugin:Vite 插件系统

Vite 通过插件扩展功能。

常见插件:

Plugin作用
@vitejs/plugin-reactReact 支持和 Fast Refresh
@tailwindcss/viteTailwind CSS 集成
vite-plugin-svgr将 SVG 作为 React 组件
vite-plugin-pwaPWA 支持
Bundle Analyzer 类插件分析构建产物
TypeScript/ESLint 检查插件辅助开发检查

例如:

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

Vite 的插件机制与 Rollup 插件体系有兼容关系,但 Vite 还提供面向开发服务器和模块转换的专有能力。

18. Vite 与 Nginx 生产部署

Vite 负责构建静态资源,Nginx 负责生产环境的 HTTP 服务。

React Source
     ↓
  Vite Build
     ↓
    dist/
     ↓
Docker Image
     ↓
   Nginx
     ↓
  Browser

Docker 多阶段构建

# Stage 1: Build
FROM node:24-alpine AS build

WORKDIR /app

COPY package.json pnpm-lock.yaml ./

RUN corepack enable && pnpm install --frozen-lockfile

COPY . .

RUN pnpm build

# Stage 2: Serve
FROM nginx:alpine

COPY --from=build /app/dist /usr/share/nginx/html

COPY nginx.conf /etc/nginx/conf.d/default.conf

这样最终运行的镜像不需要包含 Node.js 开发依赖。

Nginx SPA 配置

server {
    listen 80;

    root /usr/share/nginx/html;
    index index.html;

    location /assets/ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    location / {
        try_files $uri $uri/ /index.html;
    }

    location = /index.html {
        add_header Cache-Control "no-cache";
    }
}

这里假设 Vite 默认使用 /assets/ 输出带哈希的静态资源。

为什么 index.html 不能长期缓存?

Vite 构建文件通常包含内容哈希:

index-A1b2C3.js

代码更新后:

index-X9y8Z7.js

因此带哈希的文件可以长期缓存。

但 index.html 必须能够及时发现最新构建版本,因为它负责引用新的 JS/CSS 文件。

推荐:

index.html
    ↓
no-cache(使用前重新验证)

assets/*.js
assets/*.css
    ↓
Long-term Cache

注意:对于 SPA,还应确保不存在的静态资源不会错误地回退成 index.html,否则可能产生 JavaScript MIME Type 错误。

19. Vite、Nginx 与浏览器的职责

技术主要职责
ReactUI 组件和应用状态
TypeScript静态类型检查
Vite Dev Server开发服务和 HMR
Vite Build模块打包、代码转换和优化
NginxHTTP 服务、反向代理、压缩、缓存
Browser下载、缓存、解析和执行资源

典型生产优化:

React.lazy()
      ↓
Dynamic Import
      ↓
Vite Code Splitting
      ↓
Hashed Static Assets
      ↓
Nginx gzip / Brotli
      ↓
Cache-Control
      ↓
Browser

这是一套适用于 React SPA 的常见生产部署方式。

20. Vite 常见问题

Q1:Vite 是 Web Framework 吗?

不是。

Vite 主要是开发服务器和构建工具。

React 负责 UI;React Router 负责客户端路由;Vite 负责开发和构建。

Q2:Vite 生产环境需要运行 Node.js 吗?

对于普通静态 React SPA,不需要。

Vite 构建生成的静态资源可以直接由 Nginx 或 CDN 提供。

但 SSR 等服务端渲染场景可能需要 Node.js 或其他服务器运行时。

Q3:Vite 会自动开启 gzip 吗?

不会自动让生产服务器启用 gzip。

构建输出中显示的 gzip 体积通常只是压缩体积信息,不代表 Nginx 已启用 gzip。

Q4:Vite 可以自动进行 Code Splitting 吗?

可以。

Vite 的生产构建器会根据模块关系及动态导入等信息生成 Chunk。

但实际拆分结果受到构建器、插件和配置影响。

Q5:Vite 和 Webpack 有什么区别?

两者都能完成前端构建。

Vite 的主要特点是利用原生 ESM 提供快速开发服务,并使用生产构建器优化输出资源。

Webpack 则提供高度可配置的模块打包机制。

二者的实际性能取决于项目规模、配置及所用版本,不能简单认为某个工具在所有情况下都更快。

总结

Vite 的核心知识可以分为四个部分:

分类重点
开发机制Dev Server、ESM、HMR、Fast Refresh、Dependency Pre-Bundling
构建优化Build、Code Splitting、Tree Shaking、Minification
项目配置vite.config.ts、Alias、Proxy、Env、Base、Plugin
生产部署dist、Hashed Assets、Source Map、Nginx、Cache-Control

对于 React + TypeScript SPA 项目,最需要掌握的是:

  1. 开发服务器与 HMR 的工作方式。
  2. vite.config.ts 的常用配置。
  3. 开发代理与生产反向代理的区别。
  4. 环境变量的构建时注入机制。
  5. Dynamic Import 与 Code Splitting。
  6. 静态资源 Hash 和浏览器缓存。
  7. Vite Build 与 TypeScript 类型检查的区别。

参考文档