Vite 初级指南

Vite 是一个现代前端构建工具,由 Vue 作者尤雨溪创建。它的核心设计目标是解决传统打包工具(如 Webpack)在大型项目中开发体验缓慢的问题。 Webpack 的工作方式是在启动开发服务器前,先把所有模块打包成 bundle,然后再提供服务。随着项目规模增大,这个打包过程会越来越慢,冷启动时间可能长达数十秒甚至数分钟。 Vite 采用了完全不同的策略: Vite 在不同阶段使用不同策略: 这种分离设计保证了开发体验极速,同时生产产物质量有保障。 Vite 5+ 要求 Node.js 版本: 建议使用 LTS 版本。可用 node -v 检查当前版

分享

官方文档:https://cn.vitejs.dev/
适用版本:Vite 5.x(2026-05-07 核实)

1. Vite 是什么

Vite 是一个现代前端构建工具,由 Vue 作者尤雨溪创建。它的核心设计目标是解决传统打包工具(如 Webpack)在大型项目中开发体验缓慢的问题。

和 Webpack 的对比:为什么更快

Webpack 的工作方式是在启动开发服务器前,先把所有模块打包成 bundle,然后再提供服务。随着项目规模增大,这个打包过程会越来越慢,冷启动时间可能长达数十秒甚至数分钟。

Vite 采用了完全不同的策略:

  • 开发阶段:利用浏览器原生 ES Module(ESM)支持,直接按需提供源文件,不需要提前打包。浏览器请求哪个模块,Vite 就处理哪个模块,启动时间极短(通常不超过 1 秒)。
  • 依赖预构建:第三方库(node_modules)用 esbuild 提前处理,esbuild 用 Go 编写,比 JavaScript 工具快 10-100 倍。
  • 热更新(HMR):修改文件后只更新变化的模块,而不是重新打包整个应用,速度极快且不随项目体积增大而变慢。
维度 Webpack Vite
开发启动 打包所有模块后启动 直接启动,按需编译
热更新速度 随项目变大变慢 始终极快(模块级别)
底层工具 JavaScript esbuild(Go)+ Rollup
生产构建 Webpack 打包 Rollup 打包
配置复杂度 较高 较低,约定优于配置

开发服务器 vs 生产构建

Vite 在不同阶段使用不同策略:

  • 开发服务器:基于原生 ESM,浏览器直接处理模块图,Vite 只做必要的转换(如 TypeScript 编译、JSX 转换)
  • 生产构建:使用 Rollup 打包,因为 Rollup 的 tree-shaking 和代码分割能力更成熟,产物更优化

这种分离设计保证了开发体验极速,同时生产产物质量有保障。

适用场景

  • 单页应用(SPA):Vue、React、Svelte 等框架项目
  • 多页应用(MPA):多个独立 HTML 入口
  • 库开发:构建可发布的 JavaScript 库
  • 原型快速搭建:极低的启动成本
  • 不适合:需要兼容 IE11 或极老旧浏览器的场景(可用 @vitejs/plugin-legacy 补救,但有性能代价)

2. 安装与创建项目

Node.js 版本要求

Vite 5+ 要求 Node.js 版本:

  • 18.x(18.0.0+)
  • 20.x(20.19.0+)
  • 22.x(22.12.0+)

建议使用 LTS 版本。可用 node -v 检查当前版本。

使用 npm create vite@latest 创建项目

这是最推荐的方式,会交互式引导选择框架和语言:

npm create vite@latest

也可以直接指定项目名和模板,跳过交互:

# 格式:npm create vite@latest <项目名> -- --template <模板名>
npm create vite@latest my-app -- --template vue
npm create vite@latest my-app -- --template react-ts

等价的其他包管理器写法:

# yarn
yarn create vite my-app --template vue

# pnpm
pnpm create vite my-app --template react

# bun
bun create vite my-app --template svelte

官方模板列表

模板名 框架 语言
vanilla 无框架 JavaScript
vanilla-ts 无框架 TypeScript
vue Vue 3 JavaScript
vue-ts Vue 3 TypeScript
react React JavaScript
react-ts React TypeScript
react-swc React(SWC 编译) JavaScript
react-swc-ts React(SWC 编译) TypeScript
preact Preact JavaScript
preact-ts Preact TypeScript
lit Lit(Web Components) JavaScript
lit-ts Lit TypeScript
svelte Svelte JavaScript
svelte-ts Svelte TypeScript
solid Solid.js JavaScript
solid-ts Solid.js TypeScript
qwik Qwik JavaScript
qwik-ts Qwik TypeScript

手动安装方式

如果需要将 Vite 加入已有项目:

# 安装 Vite
npm install -D vite

# 若使用 Vue,还需要安装插件
npm install -D @vitejs/plugin-vue

# 若使用 React,还需要安装插件
npm install -D @vitejs/plugin-react

然后在 package.json 中添加脚本:

{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  }
}

并在项目根目录创建 index.html 作为入口(详见第 3 节)。

创建项目后的步骤

cd my-app
npm install
npm run dev

3. 项目结构

典型目录结构

my-project/
├── public/              # 静态资源(不经过 Vite 处理,直接复制到 dist/)
│   └── favicon.ico
├── src/
│   ├── assets/          # 经过 Vite 处理的资源(图片、字体等)
│   ├── components/      # 组件
│   ├── main.js          # JavaScript 入口文件
│   └── App.vue          # 根组件(Vue 示例)
├── index.html           # HTML 入口(Vite 项目核心)
├── vite.config.js       # Vite 配置文件
├── package.json
└── .env                 # 环境变量文件

index.html 为什么是核心

在 Webpack 项目中,index.html 通常放在 public/ 目录,是一个模板文件,由 html-webpack-plugin 注入打包后的 bundle 路径。真正的入口是 webpack.config.js 里指定的 JS 文件。

Vite 的设计截然不同:index.html 就是项目真正的入口,放在项目根目录。Vite 把 index.html 视为模块图的一部分,直接解析其中的 <script type="module"> 标签,从而找到 JS 入口:

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>My App</title>
  </head>
  <body>
    <div id="app"></div>
    <!-- Vite 从这里找到入口,不需要额外配置 -->
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

这个设计带来的好处:

  • HTML 文件中可以直接使用 <link><script> 引用资源,Vite 会自动处理
  • 多页应用只需要多个 HTML 文件,不需要复杂配置
  • 开发和生产环境的 HTML 处理方式一致

public 目录 vs src/assets 目录

特性 public/ src/assets/
处理方式 直接复制,不做任何处理 经过 Vite 处理(hash 文件名、压缩等)
引用方式 用绝对路径 /favicon.ico import 语句或相对路径
适合内容 不需要处理的文件,如 robots.txt、已压缩的库 需要版本化管理的图片、字体、CSS
缓存控制 无 hash,靠服务器配置缓存 有内容 hash,可长期缓存

4. vite.config.js 基础配置

完整配置示例

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'

export default defineConfig({
  // 项目根目录(index.html 所在位置)
  root: process.cwd(),

  // 部署的基础路径,部署到子路径时修改此项
  base: '/',

  // 静态资源目录
  publicDir: 'public',

  // 插件列表
  plugins: [vue()],

  // 模块解析选项
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
    },
    extensions: ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json'],
  },

  // 全局常量替换
  define: {
    __APP_VERSION__: JSON.stringify('1.0.0'),
  },

  // 环境变量文件目录
  envDir: '.',

  // 暴露给客户端的环境变量前缀
  envPrefix: 'VITE_',

  // 日志级别
  logLevel: 'info',

  // 启动时是否清屏
  clearScreen: true,

  // 应用类型
  appType: 'spa',

  // 开发服务器配置
  server: {
    port: 5173,
    open: true,
  },

  // 构建配置
  build: {
    outDir: 'dist',
    minify: 'esbuild',
  },
})

顶级参数说明

参数 类型 默认值 是否必填 说明
root string process.cwd() 项目根目录,index.html 所在位置,所有相对路径基于此目录解析
base string '/' 开发和生产的公共基础路径。部署到子路径时必须修改,如 '/my-app/'
publicDir string | false 'public' 静态资源目录,其中的文件直接复制到输出目录。设为 false 禁用此功能
plugins Plugin[] [] 插件数组,Vite 和 Rollup 插件均可使用
define Record<string, any> {} 定义全局常量替换。值会在编译时直接替换到代码中
envDir string root 加载 .env 文件的目录
envPrefix string | string[] 'VITE_' 以此前缀开头的环境变量才会暴露给客户端代码
logLevel 'info' | 'warn' | 'error' | 'silent' 'info' 控制台输出级别
clearScreen boolean true 重新打印日志前是否清屏
appType 'spa' | 'mpa' | 'custom' 'spa' 应用类型。mpa 禁用 SPA 回退,custom 禁用 HTML 处理

resolve 参数说明

参数 类型 默认值 是否必填 说明
resolve.alias Record<string, string> | Array {} 路径别名,将 @ 等短路径映射到实际目录,简化 import 路径
resolve.extensions string[] ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json'] 无扩展名 import 时自动尝试的后缀列表,从左到右依次尝试

路径别名配置示例:

import { resolve } from 'path'

export default defineConfig({
  resolve: {
    alias: {
      // 将 @/components/Button 映射到 src/components/Button
      '@': resolve(__dirname, 'src'),
      // 也可以用数组形式,支持正则
      // { find: /^@\//, replacement: resolve(__dirname, 'src') + '/' }
    },
  },
})

使用别名后,import 可以这样写:

// 不用再写 ../../components/Button
import Button from '@/components/Button.vue'

5. 开发服务器

server 参数说明

参数 类型 默认值 是否必填 说明
server.host string | boolean 'localhost' 监听地址。设为 '0.0.0.0'true 可以让局域网内其他设备访问
server.port number 5173 开发服务器端口
server.strictPort boolean false true 时端口被占用直接报错退出,false 时自动尝试下一个可用端口
server.open boolean | string false 启动后自动打开浏览器。传字符串则打开指定路径,如 '/docs'
server.proxy Record<string, ProxyOptions> {} 反向代理配置,将匹配的请求转发到其他服务器
server.cors boolean | CorsOptions 仅允许 localhost 配置跨域资源共享。true 允许所有来源
server.https HttpsServerOptions undefined 启用 TLS。通常配合 @vitejs/plugin-basic-ssl 使用
server.hmr boolean | HmrOptions true 热模块替换配置。false 禁用 HMR

常用 server 配置示例

export default defineConfig({
  server: {
    // 监听所有地址,手机也能访问
    host: '0.0.0.0',
    // 使用 3000 端口
    port: 3000,
    // 端口被占用时报错而非自动换端口
    strictPort: true,
    // 启动后自动打开浏览器
    open: true,
  },
})

代理配置详细示例

开发时前端跑在 localhost:5173,后端 API 跑在 localhost:8080,直接请求会因跨域被浏览器拒绝。用代理可以让 Vite 把 /api 开头的请求转发到后端:

export default defineConfig({
  server: {
    proxy: {
      // 字符串简写:将 /api 开头的请求代理到目标地址
      '/api': 'http://localhost:8080',

      // 对象写法,支持更多选项
      '/api/v2': {
        target: 'http://localhost:8080',
        // 修改请求头中的 Origin 为目标地址(解决某些服务端的 Host 校验)
        changeOrigin: true,
        // 重写路径:去掉 /api 前缀
        // /api/users → /users
        rewrite: (path) => path.replace(/^\/api/, ''),
      },

      // 代理 WebSocket
      '/socket.io': {
        target: 'ws://localhost:8080',
        ws: true,
      },

      // 使用正则匹配
      '^/fallback/.*': {
        target: 'http://jsonplaceholder.typicode.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/fallback/, ''),
      },
    },
  },
})

代理选项说明:

选项 类型 默认值 说明
target string - 转发目标地址
changeOrigin boolean false 是否修改请求头的 Host 为目标域名
rewrite Function - 重写请求路径的函数,参数为原始路径字符串
ws boolean false 是否代理 WebSocket
secure boolean true 目标为 HTTPS 时是否验证证书

6. 环境变量

.env 文件加载顺序

Vite 按以下顺序加载环境变量文件,后面的文件优先级更高(会覆盖前面的同名变量):

.env                  # 所有环境都加载
.env.local            # 所有环境都加载,但不提交到 Git(应加入 .gitignore)
.env.[mode]           # 只在指定 mode 下加载(如 .env.development、.env.production)
.env.[mode].local     # 只在指定 mode 下加载,不提交到 Git

默认 mode:

  • npm run dev → mode 为 development
  • npm run build → mode 为 production

自定义 mode:

# 加载 .env.staging 文件
vite build --mode staging

VITE_ 前缀规则

只有以 VITE_ 开头的变量才会被暴露给客户端代码(打包到最终产物)。没有此前缀的变量只能在 Vite 配置文件(Node.js 环境)中访问,不会泄露给浏览器。

# .env 文件示例

# 这个变量会暴露给客户端,可在浏览器代码中访问
VITE_API_URL=https://api.example.com
VITE_APP_TITLE=My Application

# 这个变量不会暴露给客户端(没有 VITE_ 前缀)
DATABASE_PASSWORD=secret123

import.meta.env 内置属性

属性 类型 说明
import.meta.env.MODE string 当前运行的模式,如 'development''production'
import.meta.env.BASE_URL string 部署的基础 URL,对应 vite.config.js 中的 base 配置
import.meta.env.PROD boolean 是否为生产环境
import.meta.env.DEV boolean 是否为开发环境(与 PROD 相反)
import.meta.env.SSR boolean 是否在服务端渲染环境中运行

在代码中使用:

// 获取自定义环境变量
const apiUrl = import.meta.env.VITE_API_URL

// 根据环境执行不同逻辑
if (import.meta.env.DEV) {
  console.log('当前为开发环境')
}

if (import.meta.env.PROD) {
  // 生产环境才启用统计
  initAnalytics()
}

.env 文件示例

# .env.development
VITE_API_URL=http://localhost:8080/api
VITE_APP_TITLE=My App (Dev)
VITE_ENABLE_MOCK=true
# .env.production
VITE_API_URL=https://api.example.com
VITE_APP_TITLE=My App
VITE_ENABLE_MOCK=false

TypeScript 类型补全

默认情况下 import.meta.env 的自定义变量没有类型提示。在 src/vite-env.d.ts 中扩展 ImportMetaEnv 接口来获得类型提示:

// src/vite-env.d.ts
/// <reference types="vite/client" />

interface ImportMetaEnv {
  // 在这里声明自定义环境变量的类型
  readonly VITE_API_URL: string
  readonly VITE_APP_TITLE: string
  readonly VITE_ENABLE_MOCK: string  // .env 中的值都是字符串
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

7. 静态资源处理

import 资源文件

Vite 支持直接用 import 导入图片、字体等静态资源,返回处理后的资源 URL:

// 导入图片,imgUrl 是处理后的 URL(生产环境会带 hash)
import imgUrl from './assets/logo.png'

// 在代码中使用
const img = document.createElement('img')
img.src = imgUrl
document.body.appendChild(img)

在 Vue 单文件组件中:

<template>
  <!-- 模板中可以直接使用相对路径,Vite 自动处理 -->
  <img src="./assets/logo.png" alt="logo" />
</template>

<script setup>
import logoUrl from './assets/logo.png'
</script>

查询参数修饰符

导入资源时可以加查询参数改变 Vite 的处理方式:

查询参数 说明 使用场景
?url 强制返回 URL 字符串(即使文件很小也不内联) 需要明确 URL 的情况
?raw 返回文件内容的字符串 读取 GLSL shader、文本文件内容
?inline 强制内联为 base64 URL(即使文件较大) 确保资源内联避免额外请求
// ?url:始终获取 URL,不内联
import iconUrl from './icon.svg?url'

// ?raw:获取文件的原始文本内容
import shaderSource from './shader.glsl?raw'
console.log(shaderSource) // 文件的字符串内容

// ?inline:强制 base64 内联
import smallIcon from './icon.png?inline'
// smallIcon 为 'data:image/png;base64,...'

public 目录 vs src/assets 目录的区别

详细比较:

特性 public/ src/assets/
处理方式 原样复制,不做任何转换 Vite 处理:压缩、添加 hash 等
引用方式 HTML/CSS/JS 中用绝对路径如 /logo.png import 或相对路径
能否用 import 不能 可以
文件名是否带 hash 否,文件名不变 是,如 logo.a1b2c3.png
浏览器缓存 需要服务器配置 Cache-Control 可长期缓存(hash 变化即更新)
适合什么文件 robots.txt、favicon、已处理的第三方 JS 项目自己的图片、字体、图标
<!-- public/ 中的文件,用绝对路径引用 -->
<link rel="icon" href="/favicon.ico" />
<img src="/images/hero.jpg" />
// src/assets/ 中的文件,用 import
import heroImage from './assets/images/hero.jpg'

assetsInlineLimit

小于此阈值的资源会被内联为 base64 URL,避免额外的 HTTP 请求。大于此阈值的资源会生成独立文件。

默认值为 4096 字节(4KB)。在 vite.config.js 中配置:

export default defineConfig({
  build: {
    // 8KB 以下的资源内联,设为 0 禁用内联
    assetsInlineLimit: 8192,
  },
})

8. 常用插件

官方插件

插件 安装命令 用途
@vitejs/plugin-vue npm i -D @vitejs/plugin-vue Vue 3 单文件组件(.vue)支持
@vitejs/plugin-vue-jsx npm i -D @vitejs/plugin-vue-jsx Vue 3 JSX/TSX 支持
@vitejs/plugin-react npm i -D @vitejs/plugin-react React 支持(使用 Babel)
@vitejs/plugin-react-swc npm i -D @vitejs/plugin-react-swc React 支持(使用 SWC,速度更快)
@vitejs/plugin-legacy npm i -D @vitejs/plugin-legacy 兼容旧版浏览器(自动注入 polyfill)

社区常用插件

插件 安装命令 用途
vite-plugin-checker npm i -D vite-plugin-checker 在开发服务器中运行 TypeScript/ESLint 检查并显示错误
unplugin-auto-import npm i -D unplugin-auto-import 自动导入 Vue/React API,无需手动 import
unplugin-vue-components npm i -D unplugin-vue-components Vue 组件自动按需导入

@vitejs/plugin-vue 配置示例

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [
    vue({
      // 包含 Vue 的自定义块(如 <docs>)的文件
      include: [/\.vue$/],
      // 启用响应式语法糖(实验性)
      // reactivityTransform: true,
    }),
  ],
})

@vitejs/plugin-react 配置示例

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [
    react({
      // 包含 JSX 的文件(默认只处理 .jsx/.tsx)
      include: '**/*.{jsx,tsx}',
      // Babel 插件,用于添加装饰器等特性
      babel: {
        plugins: ['@babel/plugin-proposal-decorators'],
      },
    }),
  ],
})

@vitejs/plugin-react-swc 配置示例

SWC 是用 Rust 编写的编译器,比 Babel 快很多,推荐新项目使用:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'

export default defineConfig({
  plugins: [
    react({
      // 启用 TypeScript 装饰器支持
      // tsDecorators: true,
    }),
  ],
})

@vitejs/plugin-legacy 配置示例

用于支持不支持原生 ESM 的旧浏览器(如 IE11 时代的浏览器):

# 需要同时安装 terser
npm install -D @vitejs/plugin-legacy terser
import { defineConfig } from 'vite'
import legacy from '@vitejs/plugin-legacy'

export default defineConfig({
  plugins: [
    legacy({
      // 目标浏览器,使用 browserslist 语法
      targets: ['defaults', 'not IE 11'],
      // 额外的 polyfill
      additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
    }),
  ],
})

vite-plugin-checker 配置示例

在浏览器中显示 TypeScript 编译错误(而不只是在终端):

npm install -D vite-plugin-checker
import { defineConfig } from 'vite'
import checker from 'vite-plugin-checker'

export default defineConfig({
  plugins: [
    checker({
      // 启用 TypeScript 检查
      typescript: true,
      // 启用 ESLint 检查
      eslint: {
        lintCommand: 'eslint "./src/**/*.{ts,tsx,vue}"',
      },
    }),
  ],
})

unplugin-auto-import 配置示例

自动导入常用 API,无需每个文件都写 import { ref, computed } from 'vue'

npm install -D unplugin-auto-import
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'

export default defineConfig({
  plugins: [
    AutoImport({
      // 自动导入的来源
      imports: [
        'vue',          // ref, computed, watch 等
        'vue-router',   // useRouter, useRoute 等
        'pinia',        // defineStore, storeToRefs 等
      ],
      // 生成类型声明文件(TypeScript 项目推荐)
      dts: 'src/auto-imports.d.ts',
    }),
  ],
})

unplugin-vue-components 配置示例

Vue 组件自动按需导入,无需手动注册组件:

npm install -D unplugin-vue-components
import { defineConfig } from 'vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  plugins: [
    Components({
      // 组件目录(默认为 src/components)
      dirs: ['src/components'],
      // 使用 Element Plus 自动按需导入
      resolvers: [ElementPlusResolver()],
      // 生成类型声明文件
      dts: 'src/components.d.ts',
    }),
  ],
})

9. 生产构建基础

构建命令

# 构建生产版本,输出到 dist/ 目录
npm run build
# 等价于
npx vite build

# 预览构建产物(本地启动一个静态文件服务器)
npm run preview
# 等价于
npx vite preview

vite preview 是用来验证构建产物是否正常的工具,它会启动一个简单的 HTTP 服务器提供 dist/ 目录的文件,用于在部署前本地测试。它不是用于生产部署的服务器。

build 参数说明

参数 类型 默认值 是否必填 说明
build.outDir string 'dist' 构建输出目录,相对于 root
build.assetsDir string 'assets' 静态资源的存放子目录(在 outDir 内)
build.target string | string[] 'baseline-widely-available' 构建产物的浏览器兼容目标。baseline-widely-available 约等于 Chrome 107+
build.minify 'esbuild' | 'terser' | false 'esbuild' 代码压缩工具。esbuild 速度快,terser 压缩率更高,false 不压缩
build.sourcemap boolean | 'inline' | 'hidden' false 是否生成 source map。true 生成独立 .map 文件,'inline' 内联到产物,'hidden' 生成但不在产物中引用
build.assetsInlineLimit number 4096 小于此字节数的资源内联为 base64,减少 HTTP 请求

完整 build 配置示例

export default defineConfig({
  build: {
    // 输出到 dist 目录
    outDir: 'dist',

    // 静态资源放到 dist/assets/ 下
    assetsDir: 'assets',

    // 目标浏览器:支持现代浏览器
    target: 'baseline-widely-available',

    // 使用 esbuild 压缩(更快)
    minify: 'esbuild',

    // 生成 source map(调试生产问题时有用)
    sourcemap: false,

    // 小于 4KB 的资源内联
    assetsInlineLimit: 4096,

    // Rollup 打包选项
    rollupOptions: {
      output: {
        // 手动分包:将第三方库单独打包
        manualChunks: {
          vendor: ['vue', 'vue-router', 'pinia'],
        },
      },
    },
  },
})

构建产物结构

dist/
├── index.html
└── assets/
    ├── index-a1b2c3.js      # 主 JS(带内容 hash)
    ├── vendor-d4e5f6.js     # 第三方库(如果有 manualChunks 配置)
    ├── index-g7h8i9.css     # 样式文件
    └── logo-j1k2l3.png      # 图片资源

10. 常见问题与踩坑

process.env.NODE_ENV 不可用

Webpack 项目中常用 process.env.NODE_ENV 判断环境,但在 Vite 项目中直接使用会报错(浏览器没有 process 对象)。

正确做法是使用 import.meta.env

// 错误写法(Vite 项目中不可用)
if (process.env.NODE_ENV === 'production') {
  // ...
}

// 正确写法
if (import.meta.env.PROD) {
  // ...
}

// 或者
if (import.meta.env.MODE === 'production') {
  // ...
}

如果引用的第三方库内部使用了 process.env.NODE_ENV,可以在 vite.config.js 中用 define 手动注入:

export default defineConfig({
  define: {
    'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
  },
})

部署后资源 404:忘记配置 base

将项目部署到子路径(如 https://example.com/my-app/)时,如果没有配置 base,构建产物中的资源路径仍然是根路径 /,导致 404。

// 错误:部署到子路径时没有配置 base
export default defineConfig({
  // 没有 base 配置,默认 '/'
})

// 正确:配置 base 为子路径
export default defineConfig({
  base: '/my-app/', // 必须以 / 结尾
})

部署到 GitHub Pages 时通常需要将 base 设为仓库名:

export default defineConfig({
  base: '/repo-name/',
})

public 里的文件不能用 import 引用

public/ 目录中的文件不经过 Vite 处理,因此不能用 ES import 语句引用,必须用绝对路径字符串。

// 错误:public 目录的文件不能用 import
import logo from '/public/logo.png'  // 报错

// 正确:用绝对路径字符串
const logo = '/logo.png'  // public/ 中的文件用根路径
img.src = logo

// 如果需要用 import,应把文件放在 src/assets/ 中
import logoUrl from './assets/logo.png'  // 正确

在 HTML 中引用 public 文件:

<!-- 正确:绝对路径 -->
<img src="/logo.png" />

<!-- 错误:不要写成 /public/logo.png -->
<img src="/public/logo.png" />

HMR 失效的常见原因

热更新不生效时,可以从以下方面排查:

  1. 文件不在 Vite 监听范围内:默认监听 root 目录。node_modules 中的文件变化不触发 HMR。

  2. 导出方式问题:组件没有默认导出,或导出方式不标准。

  3. 副作用阻止了 HMR:模块有不可恢复的副作用(如修改了全局状态),Vite 会回退到全页刷新。

  4. 网络问题:HMR 通过 WebSocket 通信,如果 WebSocket 连接失败,HMR 就无法工作。可以在 server.hmr 中配置:

export default defineConfig({
  server: {
    hmr: {
      // 手动指定 HMR 通信用的 host 和 port
      host: 'localhost',
      port: 5173,
      // 显示 HMR 连接错误
      overlay: true,
    },
  },
})
  1. 在生产构建后测试 HMR:vite preview 不支持 HMR,HMR 只在开发模式(vite)下有效。

依赖预构建缓存问题

Vite 会把 node_modules 中的依赖预构建后缓存到 node_modules/.vite/deps/ 目录。有时候这个缓存会过期或损坏,导致奇怪的问题(如模块找不到、版本不一致等)。

解决方法:

# 方法一:启动时强制重新预构建(推荐)
npx vite --force

# 方法二:手动删除缓存目录
rm -rf node_modules/.vite

# 方法三:重新安装依赖
rm -rf node_modules
npm install

也可以在配置中控制哪些依赖需要预构建:

export default defineConfig({
  optimizeDeps: {
    // 强制预构建这些依赖(即使 Vite 自动检测时没发现)
    include: ['some-package'],
    // 排除某些依赖不进行预构建
    exclude: ['another-package'],
  },
})

import.meta.env 变量只能用完整变量名

Vite 在构建时通过静态替换来注入环境变量,因此不能用动态方式访问:

// 错误:动态访问,Vite 无法在构建时替换
const key = 'VITE_API_URL'
const value = import.meta.env[key]  // 生产构建后可能为 undefined

// 正确:用完整的变量名
const value = import.meta.env.VITE_API_URL


最佳实践

始终通过 import.meta.env.VITE_xxx 访问环境变量,不拼接变量名:Vite 在构建时静态替换 import.meta.env 中的变量,若用变量名拼接(如 `VITE_${key}`),构建时无法识别,运行时返回 undefined

生产构建前运行 vite preview 验证产物dev 模式和 build 产物的行为可能不同(如路径别名、资源引用),用 preview 在本地模拟生产环境,比直接部署再排查问题高效。

合理配置 optimizeDeps.include 加速冷启动:对于较大的 CommonJS 依赖(如某些 UI 库),预构建默认有时无法自动检测到,手动加入 include 列表可避免首次访问时再编译的延迟。

使用路径别名(@src/)替代相对路径导入:深层嵌套的 ../../../components/xxx 难以维护,配置 resolve.alias 后统一用 @/components/xxx,移动文件时只需修改别名配置一处。

// vite.config.ts
import { resolve } from 'path'
export default defineConfig({
  resolve: {
    alias: { '@': resolve(__dirname, 'src') }
  }
})

常见陷阱

陷阱:修改 vite.config.ts 后开发服务器未重启,配置未生效

现象: 修改了 vite.config.tsserver.portresolve.alias,但行为没有变化。
原因: Vite 开发服务器在启动时读取配置,配置变更后需要重启服务器才生效;部分配置(如插件列表)不支持热更新。
解决: 停止 vite 进程后重新启动;或检查 Vite 版本是否支持配置热重载(--config flag 变更会自动重启)。

陷阱:import 图片路径在构建后变为 undefined

现象: 开发环境图片正常显示,vite build 后图片加载 404 或 srcundefined
原因: 用字符串拼接动态路径(如 `/assets/${name}.png`)无法被 Vite 静态分析,不会被打包处理;Vite 只处理静态 importnew URL(path, import.meta.url) 形式。
解决:new URL(./assets/${name}.png, import.meta.url).href 动态引用资源;或将动态图片放到 public/ 目录(不经过 Vite 处理,路径保持不变)。

陷阱:process.env.NODE_ENV 在 Vite 项目中无法访问

现象: 代码中使用 process.env.NODE_ENV,浏览器报 process is not defined
原因: Vite 不自动注入 process 全局变量(这是 Webpack 的行为);Vite 使用 import.meta.env.MODE 代替。
解决:process.env.NODE_ENV 替换为 import.meta.env.MODE;或在 vite.config.ts 中配置 define: { 'process.env.NODE_ENV': JSON.stringify(mode) } 手动注入。


参见

阅读更多

Web 安全基础

1. HTML 转义(服务端渲染必须): 2. CSP(Content Security Policy): 3. HttpOnly Cookie:防止 JS 读取会话 Cookie: 4. 前端框架防护: 攻击者在第三方网站构造一个表单,诱导已登录用户提交,浏览器会自动携带目标站的 Cookie。 触发条件: 1. 用户已登录目标网站(Cookie 有效) 2. 目标 API 仅凭 Cookie 识别用户身份 3. 请求来源未验证 1. CSRF Token(推荐): 2. SameSite Cookie: 3. 验证 Origin/Referer 头:

By yellowdog

HTTP 协议深度指南

HTTP(HyperText Transfer Protocol)是 Web 的基础传输协议,基于 TCP/IP,采用请求/响应模型。 相关文档:Web安全基础(/web-an-quan-ji-chu/) FastAPI完全指南(/fastapi-wan-quan-zhi-nan/) Nginx完全指南(/nginx-wan-quan-zhi-nan/) 幂等性:多次执行相同请求,服务器状态结果相同。PUT /users/1 多次执行结果一致;POST /users 每次创建新资源,非幂等。 浏览器直接从本地缓存读取,不向服务器发送请求。 缓存命中时,状

By yellowdog

系统设计基础

SLA 对照表: 选择建议:无状态服务(Web 层、API 层)优先水平扩展;数据库初期垂直扩展,达到瓶颈后考虑分库分表或读写分离。 缓存穿透(查询不存在的 key,每次都打到 DB): 缓存击穿(热点 key 过期,瞬间大量请求打到 DB): 缓存雪崩(大量 key 同时过期,或缓存服务宕机): 令牌桶 Python 实现: Redis 实现分布式限流(滑动窗口): URL 命名规则: Cursor 分页响应格式: 雪花算法结构(64 bit): 定义:分布式系统不能同时满足以下三个特性: 在分布式环境中 P 是必须保证的,所以实际是 CP vs AP

By yellowdog

算法思路与模板

二分查找要求序列有序,每次将搜索范围缩减一半,时间复杂度 O(log n)。 两个指针从两端向中间收缩,常用于有序数组。 滑动窗口维护一个满足条件的区间 left, right,right 不断向右扩张,条件不满足时收缩 left。 滑动窗口通用框架: 1. 确定"子问题":原问题可以分解为哪些规模更小的同类问题 2. 定义 dpi 或 dpij 的含义,要足够清晰 3. 推导状态转移方程 4. 确定初始状态(边界条件) 5. 确定计算顺序(确保依赖的子问题先计算) 每件物品最多选一次。dpj = 容量为 j 时的最大价值,逆序遍历容量防止重复选取。 每

By yellowdog