重新学习前端之Vite

2 阅读17分钟

Vite

一、核心原理

1. Vite 是什么?

定义: Vite 是一个现代化的前端构建工具,由 Vue 作者尤雨溪开发,旨在提供更快、更高效的前端开发体验。

原理: Vite 的名字来源于法语"快"的意思。它充分利用了浏览器原生 ES Modules 的能力,在开发环境下跳过打包步骤,实现按需编译和加载。

工作方式:

  • 开发环境:基于浏览器原生 ESM,实现按需编译
  • 生产环境:使用 Rollup 进行打包,确保生产代码的优化

示例:

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

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

常见误区:

  • 误区:Vite 只适用于 Vue 项目
  • 正解:Vite 支持 Vue、React、Svelte、Preact 等多种框架

2. Vite 原理

定义: Vite 的核心原理是基于浏览器原生 ES Modules 实现开发环境下的按需编译,生产环境使用 Rollup 打包。

原理详解:

  1. 开发环境工作流程:

    • 启动时只处理入口文件,不打包整个项目
    • 浏览器请求模块时,Vite 拦截请求并实时编译
    • 利用浏览器原生 ESM 能力,按模块逐个加载
    • 只有被请求的模块才会被编译
  2. 模块解析流程:

    浏览器请求 → Vite Dev Server 拦截 → 模块转换 → 返回编译结果
    

示例流程:

// 源码 main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

// Vite 转换后的结果(浏览器实际加载的)
import { createApp } from '/@modules/vue.js'
import App from '/src/App.vue?t=1631234567'
createApp(App).mount('#app')

代码说明:

  • /@modules/vue 被转换为 /@modules/vue.js(依赖解析)
  • ./App.vue 被转换为 /src/App.vue?t=1631234567(添加时间戳用于缓存控制)

常见误区:

  • 误区:Vite 开发环境也需要打包
  • 正解:Vite 开发环境不打包,而是按需编译和加载

3. Vite 为什么快?

定义: Vite 的快速来源于其独特的开发环境架构设计,避免了传统打包工具的冷启动和全量编译。

核心原因:

  1. 无需打包: 开发环境跳过打包环节,直接利用浏览器 ESM
  2. 按需编译: 只有被请求的模块才会被编译
  3. 原生 ESM: 浏览器原生支持,无需额外的模块加载器
  4. 高效缓存: 依赖预构建结果被缓存,减少重复工作

对比说明:

传统打包工具(Webpack)启动流程:
项目启动 → 分析依赖树 → 转换所有模块 → 打包输出 → 启动服务器
                                    ↑ 耗时主要在这里

Vite 启动流程:
项目启动 → 预构建依赖 → 启动服务器 → 按需编译模块
                            ↑ 只处理一次,后续按需加载

性能对比示例:

// Webpack 启动时需要编译所有模块
// 1000个模块 → 全部编译 → 打包 → 启动(可能需要10-30秒)

// Vite 启动时只处理入口和预构建依赖
// 1000个模块 → 只预构建依赖 → 启动(通常1-3秒)
// 后续访问模块时才编译(每个模块几十毫秒)

常见误区:

  • 误区:Vite 快只是因为缓存
  • 正解:Vite 快的核心原因是开发环境不打包+按需编译

4. ES Modules 与原生 ESM

定义: ES Modules 是 JavaScript 官方的模块化规范。原生 ESM 指浏览器原生支持该规范,无需额外工具。

ESM 语法:

// 导出 - export.js
export const name = 'Vite'
export function greet() {
  return `Hello ${name}`
}
export default { name, greet }

// 导入 - main.js
import myModule, { name, greet } from './export.js'
console.log(name)
console.log(greet())

浏览器原生 ESM 使用:

<!-- 必须添加 type="module" -->
<script type="module">
  import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
  createApp({
    template: '<div>Hello ESM</div>'
  }).mount('#app')
</script>

ESM 核心特性:

// 1. 静态分析 - 编译时确定依赖关系
import { foo } from './bar.js' // 可以在编译时确定依赖

// 2. 严格模式 - ESM 默认启用严格模式
'use strict' // 隐式启用

// 3. 模块级作用域 - 变量不会污染全局
const modulePrivate = 'not global'

// 4. 顶层 await - 支持顶层异步操作
const data = await fetch('/api/data').then(r => r.json())

// 5. 单例模式 - 模块只执行一次
let count = 0
export function getCount() {
  count++
  return count
}

浏览器支持情况:

// 现代浏览器都支持原生 ESM
// Chrome 61+, Firefox 60+, Safari 11+, Edge 16+

// 查看当前环境是否支持 ESM
const supportsESM = (() => {
  try {
    new Function('import("")')
    return true
  } catch {
    return false
  }
})()

常见误区:

  • 误区:ESM 只能用于浏览器
  • 正解:Node.js 12.17+ 也支持原生 ESM(需要 .mjs 扩展名或 package.json 中设置 type: "module")
  • 误区:import 语句可以在条件语句中使用
  • 正解:import 是静态的,不能在 if/for 等动态语句中使用(需要使用动态 import())

5. Vite 启动原理

定义: Vite 启动时只处理入口文件和依赖预构建,不会打包整个应用。

启动流程:

/**
 * Vite 启动流程:
 * 
 * 1. 读取配置文件 (vite.config.js)
 * 2. 创建 Dev Server
 * 3. 依赖预构建 (optimizeDeps)
 * 4. 启动 HTTP 服务器
 * 5. 等待浏览器请求
 * 6. 按需编译返回模块
 */

// 伪代码模拟启动流程
async function startViteDevServer() {
  // 1. 加载配置
  const config = await loadConfig('vite.config.js')
  
  // 2. 扫描入口文件,发现依赖
  const deps = scanDependencies(config.root)
  
  // 3. 预构建依赖(esbuild)
  await preBundleDeps(deps) // 使用 esbuild,非常快
  
  // 4. 启动开发服务器
  const server = createDevServer(config)
  
  // 5. 监听请求,按需转换模块
  server.on('request', async (ctx) => {
    if (ctx.path.startsWith('/@modules/')) {
      // 预构建的依赖
      return servePreBundledModule(ctx.path)
    } else if (ctx.path.endsWith('.vue')) {
      // Vue 单文件组件,需要转换
      return await transformVueSFC(ctx.path)
    } else if (ctx.path.endsWith('.ts')) {
      // TypeScript,需要转换
      return await transformTypeScript(ctx.path)
    }
    // 其他文件直接返回
    return serveFile(ctx.path)
  })
  
  return server
}

启动时只处理入口文件:

// index.html(入口)
<script type="module" src="/src/main.js"></script>

// Vite 启动时:
// 1. 解析 index.html,发现 /src/main.js
// 2. 预构建 node_modules 中的依赖
// 3. 启动服务器,等待请求
// 4. 当浏览器请求 main.js 时,才编译 main.js
// 5. 当 main.js 中 import 了其他模块,浏览器再次请求时才编译那些模块

常见误区:

  • 误区:Vite 启动时会编译所有源码文件
  • 正解:Vite 启动时只预构建依赖,源码文件按需编译

6. Vite 模块解析

定义: Vite 模块解析是指 Vite 如何处理和转换各种类型的模块导入。

模块解析规则:

// vite.config.js 中的 resolve 配置
export default defineConfig({
  resolve: {
    // 别名配置
    alias: {
      '@': '/src',
      '~': path.resolve(__dirname, 'src/assets')
    },
    // 扩展名解析
    extensions: ['.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
    // 主字段解析顺序
    mainFields: ['module', 'jsnext:main', 'main']
  }
})

解析优先级:

// 1. 绝对路径
import utils from '/src/utils/index.js'

// 2. 相对路径
import utils from './utils/index.js'
import utils from '../utils/index.js'

// 3. 别名路径(需要在配置中定义)
import utils from '@/utils/index.js'

// 4. npm 包(从 node_modules 解析)
import vue from 'vue'
import lodash from 'lodash-es'

特殊模块转换:

// 裸模块导入会被转换为 /@modules/ 路径
import { createApp } from 'vue'
// 转换为: import { createApp } from '/@modules/vue.js'

// 带条件路径的导入
import 'my-lib/sub/module.js'
// 根据 package.json 的 exports 字段解析

常见误区:

  • 误区:Vite 的别名配置和 Webpack 完全一样
  • 正解:Vite 的别名使用 resolve.alias,配置方式与 Webpack 类似,但底层解析逻辑不同

二、热更新/HMR

7. Vite 快速启动

定义: Vite 的快速启动体现在开发服务器的冷启动时间极短,通常 1-3 秒即可启动。

快速启动的原因:

  1. 不打包源码: 跳过整个应用的打包环节
  2. esbuild 预构建依赖: 使用 Go 编写的 esbuild,速度极快
  3. 按需编译: 只编译被请求的模块

启动速度对比:

Webpack(大型项目):
- 冷启动:15-30秒
- 需要分析所有依赖,转换所有模块,打包输出

Vite(同等规模项目):
- 冷启动:1-3秒
- 只预构建依赖,源码按需编译

配置优化启动速度:

// vite.config.js
export default defineConfig({
  optimizeDeps: {
    // 预构建时包含的依赖
    include: ['vue', 'vue-router', 'pinia'],
    // 排除不需要预构建的依赖
    exclude: ['some-local-package']
  },
  server: {
    // 预构建完成后自动打开浏览器
    open: true
  }
})

8. Vite 热更新(HMR)

定义: HMR(Hot Module Replacement)热模块替换,指在开发过程中修改代码后,只更新修改的模块,而无需刷新整个页面。

HMR 核心优势:

  • 保持应用状态(如表单输入、Vuex/Redux 状态)
  • 节省重新加载的时间
  • 提升开发体验

HMR 工作流程:

/**
 * HMR 工作流程:
 * 
 * 1. 文件发生变化
 * 2. Vite 监听到文件变更
 * 3. 确定受影响的模块
 * 4. 通过 WebSocket 通知浏览器
 * 5. 浏览器请求更新后的模块
 * 6. 替换旧模块,执行更新回调
 * 7. 页面局部更新,保持状态
 */

// Vite 的 HMR 使用 WebSocket 通信
// 服务器 → 浏览器:发送更新通知
// 浏览器 → 服务器:请求更新后的模块内容

示例:

// counter.js
let count = 0

export function increment() {
  count++
  console.log('Count:', count)
}

// HMR 处理
if (import.meta.hot) {
  import.meta.hot.accept((newModule) => {
    // 模块更新后的处理
    console.log('Counter module updated')
    // 保留原有状态
    newModule.increment()
  })
}

9. HMR 原理

定义: Vite 的 HMR 原理是基于文件监听 + WebSocket + 模块图实现的高效模块替换机制。

原理详解:

/**
 * Vite HMR 原理:
 * 
 * 1. 文件监听(基于 chokidar)
 *    - 监听项目文件变化
 *    - 记录模块依赖图
 * 
 * 2. WebSocket 通信
 *    - 服务器与浏览器建立 WebSocket 连接
 *    - 发送更新事件和模块路径
 * 
 * 3. 模块边界计算
 *    - 确定受影响的模块链
 *    - 找到最近的 HMR 边界
 * 
 * 4. 按需更新
 *    - 只请求和更新变更的模块
 *    - 执行模块的 accept 回调
 */

// Vite 模块图结构
const moduleGraph = {
  '/src/main.js': {
    importers: [],              // 导入当前模块的模块
    importedModules: [          // 当前模块导入的模块
      '/src/App.vue',
      '/@modules/vue.js'
    ],
    isSelfAccepting: false      // 是否接受自身更新
  },
  '/src/App.vue': {
    importers: ['/src/main.js'],
    importedModules: ['/src/components/Hello.vue'],
    isSelfAccepting: true       // Vue SFC 通常支持自更新
  }
}

HMR 通信流程代码模拟:

// 服务器端(Vite Dev Server)
const watcher = chokidar.watch(root)

watcher.on('change', async (file) => {
  // 1. 找到受影响的模块
  const affectedModules = moduleGraph.getModulesByFile(file)
  
  // 2. 通过 WebSocket 发送更新通知
  ws.send(JSON.stringify({
    type: 'update',
    updates: affectedModules.map(mod => ({
      type: mod.isSelfAccepting ? 'js-update' : 'full-reload',
      path: mod.url,
      acceptedPath: mod.url,
      timestamp: Date.now()
    }))
  }))
})

// 客户端(Vite HMR Client)
const socket = new WebSocket('ws://localhost:3000')

socket.onmessage = async ({ data }) => {
  const message = JSON.parse(data)
  
  if (message.type === 'update') {
    for (const update of message.updates) {
      if (update.type === 'js-update') {
        // 动态导入更新后的模块
        const newModule = await import(update.path + '?t=' + update.timestamp)
        // 执行 accept 回调
        hotModulesMap.get(update.path).callbacks.forEach(cb => cb(newModule))
      } else {
        // 全量刷新
        location.reload()
      }
    }
  }
}

常见误区:

  • 误区:HMR 就是自动刷新页面
  • 正解:HMR 是模块级别的热替换,不是页面刷新。只有当无法找到 HMR 边界时才会全量刷新

10. Vite HMR API

定义: Vite 提供了 import.meta.hot API,允许开发者自定义模块更新行为。

HMR API 方法:

// 1. accept - 接受模块更新
import.meta.hot.accept((newModule) => {
  // 模块更新后的回调
  console.log('Module updated:', newModule)
})

// 2. accept 带路径 - 接受特定依赖的更新
import.meta.hot.accept('./dep.js', (newDep) => {
  console.log('Dependency updated:', newDep)
})

// 3. dispose - 清理旧模块的资源
import.meta.hot.dispose((data) => {
  // 模块被替换前执行
  // data 可以传递给新模块
  data.cleanup = 'some state'
})

// 4. invalidate - 标记模块无效,触发全量刷新
import.meta.hot.invalidate('Reason for invalidation')

// 5. prune - 模块不再被导入时的回调
import.meta.hot.prune(() => {
  // 清理不再需要的资源
})

// 6. decline - 拒绝更新,触发全量刷新
import.meta.hot.decline()

完整 HMR 处理示例:

// store.js - 一个简单的状态管理
let state = { count: 0 }
const listeners = new Set()

export function getState() {
  return state
}

export function setState(newState) {
  state = { ...state, ...newState }
  listeners.forEach(fn => fn(state))
}

export function subscribe(fn) {
  listeners.add(fn)
  return () => listeners.delete(fn)
}

// HMR 处理
if (import.meta.hot) {
  import.meta.hot.accept((newModule) => {
    // 保持原有状态
    const currentState = state
    // 如果需要,可以迁移状态
    console.log('Store module hot updated')
  })
  
  import.meta.hot.dispose(() => {
    // 清理监听器
    listeners.clear()
  })
}

11. 热更新边界

定义: 热更新边界是指模块能够接受自身或依赖更新的最小范围。当文件变化时,Vite 会向上查找直到找到能接受更新的模块。

边界查找规则:

/**
 * 热更新边界查找:
 * 
 * 1. 检查变更模块是否接受自身更新 (self-accepting)
 * 2. 如果不接受,检查导入它的模块是否接受它的更新
 * 3. 递归向上查找,直到找到接受更新的模块
 * 4. 如果到达根模块仍未找到,则全量刷新
 */

// 示例模块依赖图:
// main.js → App.vue → Header.vue → Logo.vue
//                              → Nav.vue

// 场景1:Logo.vue 变更
// - Logo.vue 接受自身更新 → 只更新 Logo.vue

// 场景2:Header.vue 变更
// - Header.vue 接受自身更新 → 更新 Header.vue 及其子组件

// 场景3:App.vue 变更
// - App.vue 接受自身更新 → 更新整个应用

// 场景4:main.js 变更
// - 到达根模块,全量刷新

框架的 HMR 边界处理:

// Vue SFC 插件自动处理 HMR
// @vitejs/plugin-vue 会为每个 .vue 文件注入 HMR 代码

// 转换后的 .vue 文件包含:
if (import.meta.hot) {
  import.meta.hot.accept((newModule) => {
    // Vue 组件的 HMR 处理
    __VUE_HMR_RUNTIME__.rerender(componentId, newModule.render)
    __VUE_HMR_RUNTIME__.reload(componentId, newModule.default)
  })
}

// React 通过 @vitejs/plugin-react 实现
// 使用 Fast Refresh 技术

常见误区:

  • 误区:所有文件变更都会触发热更新
  • 正解:只有支持 HMR 的文件类型才会热更新,如 .vue、.jsx、.css 等。配置文件变更需要重启服务器

三、依赖预构建

12. Vite 依赖预构建

定义: Vite 在启动时会对 node_modules 中的依赖进行预构建,将 CommonJS/UMD 格式的依赖转换为 ESM 格式。

预构建原因:

  1. CommonJS/UMD 兼容: 浏览器原生 ESM 不支持 CommonJS,需要转换
  2. 性能优化: 将多个内部模块合并为单个文件,减少请求数量
  3. 统一入口: 确保依赖以标准 ESM 格式提供

预构建流程图:

node_modules 中的依赖
       ↓
    扫描发现依赖
       ↓
  esbuild 预构建(转换 + 合并)
       ↓
  缓存到 node_modules/.vite
       ↓
  开发环境从缓存加载

13. optimizeDeps 配置

定义: optimizeDeps 是 Vite 配置中用于控制依赖预构建行为的选项。

配置项详解:

// vite.config.js
export default defineConfig({
  optimizeDeps: {
    // 强制预构建的依赖(即使未被直接导入)
    include: [
      'lodash-es',
      'axios'
    ],
    
    // 排除预构建的依赖
    exclude: [
      'some-local-package',
      'custom-lib'
    ],
    
    // esbuild 转换选项
    esbuildOptions: {
      target: 'esnext',
      supported: {
        'top-level-await': true
      }
    },
    
    // 强制重新预构建
    force: true
  }
})

include 配置示例:

// 场景:某些依赖未被直接导入,但需要在预构建中包含
export default defineConfig({
  optimizeDeps: {
    include: [
      // 直接导入的依赖会自动预构建
      'vue',
      'vue-router',
      
      // 动态导入或间接依赖需要手动指定
      'element-plus/es',
      'lodash-es/merge.js'
    ]
  }
})

exclude 配置示例:

// 场景:某些本地开发的包不需要预构建
export default defineConfig({
  optimizeDeps: {
    exclude: [
      // 本地 link 的包
      'my-local-package',
      // 使用 ES Modules 的包
      'some-esm-only-package'
    ]
  }
})

14. 预构建的目的

定义: 预构建的主要目的是解决 CommonJS 兼容性和性能优化问题。

具体目的:

  1. 格式转换:
// 转换前(CommonJS)
// node_modules/lodash/index.js
module.exports = {
  merge: function() { /* ... */ },
  debounce: function() { /* ... */ }
}

// 转换后(ESM)
// node_modules/.vite/deps/lodash.js
export { merge, debounce }
// 包装为标准 ESM 格式,浏览器可直接使用
  1. 模块合并:
// 转换前:lodash-es 包含 600+ 个文件
// 用户可能只用了其中几个函数

// 转换后:合并为单个文件
// node_modules/.vite/deps/lodash-es.js
// 减少 HTTP 请求数量
  1. 统一依赖入口:
// 不同包可能导出方式不同
// 预构建确保统一为标准 ESM

常见误区:

  • 误区:预构建会减小依赖体积
  • 正解:预构建主要解决格式兼容和减少请求数量,不一定会减小体积

15. 预构建缓存

定义: Vite 将预构建结果缓存到 node_modules/.vite 目录,避免重复构建。

缓存机制:

// 缓存位置:node_modules/.vite/deps/

// 缓存失效条件:
// 1. package.json 变更
// 2. lockfile 变更(package-lock.json, yarn.lock, pnpm-lock.yaml)
// 3. optimizeDeps 配置变更
// 4. 使用 --force 参数启动

// 缓存结构:
// node_modules/.vite/
// ├── deps/                    // 预构建的依赖
// │   ├── vue.js
// │   ├── vue-router.js
// │   └── _metadata.json       // 缓存元数据
// └── deps_cache/              // esbuild 缓存

// _metadata.json 示例
{
  "hash": "abc123",
  "browserHash": "def456",
  "optimized": {
    "vue": {
      "file": "vue.js",
      "src": "vue/dist/vue.runtime.esm-bundler.js",
      "needsInterop": false
    }
  }
}

强制重新预构建:

# 方式1:使用 --force 参数
vite --force

# 方式2:删除缓存目录
rm -rf node_modules/.vite

# 方式3:配置中设置
export default defineConfig({
  optimizeDeps: {
    force: true
  }
})

四、插件系统

16. Vite 插件

定义: Vite 插件系统基于 Rollup 插件设计,扩展了开发环境特有的钩子。

插件基础结构:

// 一个简单的 Vite 插件
export function myPlugin(options = {}) {
  return {
    name: 'my-plugin', // 插件名称(必需)
    
    // 配置钩子
    config(config, { command, mode }) {
      // 修改 Vite 配置
      return {
        resolve: {
          alias: {
            '~': options.basePath || '/src'
          }
        }
      }
    },
    
    // 配置解析后钩子
    configResolved(config) {
      // 获取最终配置
      console.log('Resolved config:', config)
    },
    
    // 转换钩子
    transform(code, id) {
      // 转换模块内容
      if (id.endsWith('.md')) {
        return transformMarkdown(code)
      }
      return null // 不处理则返回 null
    }
  }
}

常用内置插件:

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

export default defineConfig({
  plugins: [
    // Vue 支持
    vue({
      template: {
        compilerOptions: {
          // 自定义元素配置
          isCustomElement: (tag) => tag.startsWith('custom-')
        }
      }
    }),
    
    // React 支持
    react({
      fastRefresh: true,
      babel: {
        // 自定义 Babel 配置
      }
    })
  ]
})

17. Vite 插件钩子

定义: Vite 插件钩子分为两类:Rollup 构建钩子和 Vite 特有钩子。

Rollup 构建钩子(通用):

export default {
  name: 'rollup-hooks-demo',
  
  // 构建开始
  buildStart(options) {
    console.log('Build starting')
  },
  
  // 解析导入
  resolveId(source, importer, options) {
    // 自定义模块解析
    if (source.startsWith('virtual:')) {
      return '\0' + source // \0 前缀表示虚拟模块
    }
    return null // 使用默认解析
  },
  
  // 加载模块
  load(id) {
    // 自定义模块加载
    if (id.startsWith('\0virtual:')) {
      return 'export default "virtual module content"'
    }
    return null
  },
  
  // 转换模块
  transform(code, id) {
    // 自定义模块转换
    return null
  },
  
  // 构建结束
  buildEnd(error) {
    console.log('Build finished')
  }
}

Vite 特有钩子:

export default {
  name: 'vite-hooks-demo',
  
  // 修改配置
  config(config, env) {
    return {
      define: {
        __CUSTOM_DEFINE__: JSON.stringify('custom value')
      }
    }
  },
  
  // 配置解析后
  configResolved(config) {
    // 可以访问完整解析后的配置
  },
  
  // 配置服务器
  configureServer(server) {
    // 添加自定义中间件
    server.middlewares.use((req, res, next) => {
      if (req.url.startsWith('/api')) {
        // 处理 API 请求
        res.end(JSON.stringify({ message: 'Hello' }))
      } else {
        next()
      }
    })
  },
  
  // 转换 HTML
  transformIndexHtml(html) {
    return html.replace(
      '</head>',
      '<script src="/inject.js"></script></head>'
    )
  },
  
  // 热更新处理
  handleHotUpdate({ file, server, modules, read }) {
    // 自定义 HMR 处理
    return modules
  }
}

钩子执行顺序:

/**
 * 开发环境钩子执行顺序:
 * 1. config
 * 2. configResolved
 * 3. configureServer
 * 4. transformIndexHtml
 * 5. resolveId(每次模块请求)
 * 6. load(每次模块请求)
 * 7. transform(每次模块请求)
 * 8. handleHotUpdate(文件变更时)
 * 
 * 生产环境钩子执行顺序:
 * 1. config
 * 2. configResolved
 * 3. buildStart
 * 4. resolveId
 * 5. load
 * 6. transform
 * 7. buildEnd
 * 8. closeBundle
 */

18. 自定义 Vite 插件

定义: 自定义 Vite 插件可以满足特定的项目需求,如自定义文件处理、代码生成等。

实战案例1:Markdown 转 Vue 组件

// vite-plugin-markdown-vue.js
import { marked } from 'marked'

export function markdownToVue() {
  return {
    name: 'markdown-to-vue',
    
    transform(code, id) {
      if (id.endsWith('.md')) {
        // 将 Markdown 转换为 HTML
        const html = marked(code)
        
        // 包装为 Vue 组件
        const vueComponent = `
          <template>
            <article class="markdown-body">
              ${html}
            </article>
          </template>
          
          <script>
          export default {
            name: 'MarkdownDoc'
          }
          </script>
        `
        
        return vueComponent
      }
      return null
    }
  }
}

// 使用
// vite.config.js
import { markdownToVue } from './vite-plugin-markdown-vue'

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

// 在代码中使用
import Doc from './docs/guide.md'

实战案例2:虚拟模块插件

// vite-plugin-virtual-module.js
export function virtualModule() {
  const virtualModuleId = 'virtual:app-config'
  const resolvedVirtualModuleId = '\0' + virtualModuleId
  
  return {
    name: 'virtual-module',
    
    resolveId(id) {
      if (id === virtualModuleId) {
        return resolvedVirtualModuleId
      }
      return null
    },
    
    load(id) {
      if (id === resolvedVirtualModuleId) {
        // 动态生成模块内容
        return `
          export const APP_NAME = 'My App'
          export const VERSION = '${process.env.npm_package_version}'
          export const BUILD_TIME = '${new Date().toISOString()}'
        `
      }
      return null
    }
  }
}

// 使用
import { APP_NAME, VERSION } from 'virtual:app-config'
console.log(`${APP_NAME} v${VERSION}`)

实战案例3:图片压缩插件

// vite-plugin-imagemin 类似功能
import imagemin from 'imagemin'
import imageminPngquant from 'imagemin-pngquant'

export function imageMin(options = {}) {
  return {
    name: 'image-min',
    apply: 'build', // 仅在生产构建时生效
    
    async transform(code, id) {
      if (/\.(png|jpg|jpeg|gif|webp)$/.test(id)) {
        const result = await imagemin.buffer(code, {
          plugins: [
            imageminPngquant({
              quality: [0.6, 0.8]
            })
          ]
        })
        return { code: Buffer.from(result).toString('base64') }
      }
      return null
    }
  }
}

19. Vite 插件与 Rollup 插件

定义: Vite 插件基于 Rollup 插件设计,但增加了开发环境特有的钩子。

关系说明:

/**
 * Vite 插件 vs Rollup 插件:
 * 
 * 相同点:
 * - 使用相同的插件接口(name, resolveId, load, transform 等)
 * - Rollup 插件可以在 Vite 中使用(大部分)
 * - 都支持钩子机制
 * 
 * 不同点:
 * - Vite 增加了开发环境特有的钩子(configureServer, handleHotUpdate 等)
 * - Vite 插件可以在开发环境和生产环境有不同行为
 * - Vite 插件支持 apply 选项控制生效环境
 */

// apply 选项
export default {
  name: 'conditional-plugin',
  
  // 仅在开发环境生效
  apply: 'serve',
  
  // 或仅在生产环境生效
  // apply: 'build',
  
  // 或自定义条件
  // apply(config, { command }) {
  //   return command === 'build' && config.mode === 'production'
  // },
  
  transform(code, id) {
    // 根据环境不同行为
    return code
  }
}

// 常用第三方插件
// - vite-plugin-pwa: PWA 支持
// - vite-plugin-imagemin: 图片压缩
// - unplugin-auto-import: 自动导入 API
// - vite-plugin-compression: Gzip/Brotli 压缩
// - vite-plugin-html: HTML 处理

常见误区:

  • 误区:所有 Rollup 插件都能直接在 Vite 中使用
  • 正解:大部分 Rollup 插件可以直接使用,但涉及文件系统操作的插件可能需要调整

20. 常用 Vite 插件

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { viteStaticCopy } from 'vite-plugin-static-copy'
import { compression } from 'vite-plugin-compression2'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'

export default defineConfig({
  plugins: [
    // 1. 框架支持
    vue(),
    
    // 2. 自动导入
    AutoImport({
      imports: ['vue', 'vue-router', 'pinia'],
      dts: 'src/auto-imports.d.ts'
    }),
    
    // 3. 组件自动注册
    Components({
      dirs: ['src/components'],
      extensions: ['vue'],
      dts: 'src/components.d.ts'
    }),
    
    // 4. 静态资源复制
    viteStaticCopy({
      targets: [
        {
          src: 'public/robots.txt',
          dest: ''
        }
      ]
    }),
    
    // 5. Gzip 压缩
    compression({
      algorithm: 'gzip'
    })
  ]
})

五、构建优化

21. Vite 构建优化

定义: Vite 生产构建使用 Rollup 打包,提供多种优化策略来提升生产环境的加载性能。

优化策略总览:

// vite.config.js
export default defineConfig({
  build: {
    // 1. 代码分割
    rollupOptions: {
      output: {
        manualChunks(id) {
          // 将 node_modules 中的包分割为单独的文件
          if (id.includes('node_modules')) {
            // 第三方库单独打包
            if (id.includes('vue')) return 'vue-vendor'
            if (id.includes('lodash')) return 'lodash-vendor'
            return 'vendor'
          }
          // 大型组件单独打包
          if (id.includes('components/Chart')) return 'chart'
          if (id.includes('components/Editor')) return 'editor'
        }
      }
    },
    
    // 2. 压缩
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,  // 移除 console.log
        drop_debugger: true  // 移除 debugger
      }
    },
    
    // 3. CSS 代码分割
    cssCodeSplit: true,
    
    // 4. 静态资源处理
    assetsInlineLimit: 4096, // 小于 4KB 的资源内联为 base64
    
    // 5. Source Map
    sourcemap: false, // 生产环境默认关闭
  }
})

22. 代码分割

定义: 代码分割是将打包后的代码拆分为多个较小的文件,实现按需加载。

配置方式:

// vite.config.js
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        // 方式1:函数方式(灵活控制)
        manualChunks(id, { getModuleInfo }) {
          if (id.includes('node_modules')) {
            // 按库分类
            if (id.includes('vue')) return 'vue-vendor'
            if (id.includes('react')) return 'react-vendor'
            if (id.includes('element-plus')) return 'element-vendor'
            return 'vendor'
          }
          // 按功能模块分割
          if (id.includes('/pages/')) {
            const match = id.match(/\/pages\/([^/]+)/)
            if (match) return `page-${match[1]}`
          }
        },
        
        // 方式2:对象方式(简单配置)
        // manualChunks: {
        //   vue: ['vue', 'vue-router', 'pinia'],
        //   ui: ['element-plus'],
        //   utils: ['lodash-es', 'dayjs']
        // }
      }
    }
  }
})

动态导入实现按需加载:

// 路由级别代码分割
const routes = [
  {
    path: '/dashboard',
    // 动态导入,自动创建独立的 chunk
    component: () => import('@/pages/Dashboard.vue')
  },
  {
    path: '/settings',
    component: () => import('@/pages/Settings.vue')
  }
]

// 组件级别代码分割
<template>
  <div>
    <button @click="loadChart">加载图表</button>
    <component v-if="ChartComponent" :is="ChartComponent" />
  </div>
</template>

<script setup>
import { ref } from 'vue'

const ChartComponent = ref(null)

const loadChart = async () => {
  ChartComponent.value = (await import('@/components/Chart.vue')).default
}
</script>

23. Tree Shaking

定义: Tree Shaking 是指在打包过程中移除未使用的代码,减小最终打包体积。

原理: 基于 ES Modules 的静态分析特性,在编译时确定哪些导出被使用,移除未使用的代码。

示例:

// utils.js
export function used() {
  return 'I am used'
}

export function unused() {
  return 'I am unused' // 这个函数会被 Tree Shaking 移除
}

export const constant = 'constant value'

// main.js
import { used } from './utils.js'
console.log(used())
// unused 函数不会被打包,因为没有被导入使用

// 使用 Vite 构建后(简化)
function used() {
  return 'I am used'
}
console.log(used())
// unused 函数不存在于打包结果中

Tree Shaking 生效条件:

// 1. 使用 ESM 格式
// ✅ 正确 - 支持 Tree Shaking
import { foo } from 'lodash-es'

// ❌ 错误 - CommonJS 不支持 Tree Shaking
const { foo } = require('lodash')

// 2. 避免副作用
// ✅ 正确 - 无副作用
export function pureFunction() {
  return 42
}

// ❌ 错误 - 有副作用,可能无法被正确 Tree Shaking
export function sideEffectFunction() {
  window.myGlobal = {} // 修改全局状态
  return 42
}

// 3. package.json 中标注副作用
// package.json
{
  "sideEffects": false  // 声明所有文件无副作用
  // 或
  "sideEffects": ["*.css"] // 只有 CSS 文件有副作用
}

最佳实践:

// 1. 使用 ESM 版本的库
// ✅ 推荐
import merge from 'lodash-es/merge.js'

// ❌ 不推荐
import merge from 'lodash/merge.js'

// 2. 按需导入
// ✅ 推荐
import { debounce } from 'lodash-es'

// ❌ 不推荐(会导入整个库)
import _ from 'lodash-es'

// 3. 确保模块无副作用声明正确
// 在 package.json 中正确配置 sideEffects

24. 压缩代码

定义: 压缩代码是指移除代码中的空格、注释,缩短变量名等操作,减小文件体积。

压缩配置:

// vite.config.js
export default defineConfig({
  build: {
    // 压缩方式选择
    minify: 'esbuild',  // 默认,速度最快
    // minify: 'terser', // 更细粒度控制
    
    // Terser 配置
    terserOptions: {
      compress: {
        // 生产环境移除 console
        drop_console: true,
        drop_debugger: true,
        // 移除纯函数调用
        pure_funcs: ['console.log', 'console.info'],
        // 优化
        passes: 2
      },
      format: {
        // 移除注释
        comments: false
      },
      mangle: {
        // 混淆变量名(默认启用)
        safari10: true // 兼容 Safari 10
      }
    }
  }
})

esbuild vs Terser:

// esbuild 压缩(默认)
// - 速度极快(Go 编写)
// - 移除空格、注释
// - 缩短变量名
// - 不支持移除 console

// Terser 压缩
// - 速度较慢(JavaScript 编写)
// - 支持移除 console.log
// - 支持更细粒度的控制
// - 支持压缩 Passes(多次压缩优化)

25. CSS 代码分割

定义: CSS 代码分割将 CSS 提取为独立文件,支持按需加载对应的 CSS。

配置:

// vite.config.js
export default defineConfig({
  build: {
    // 启用 CSS 代码分割(默认 true)
    cssCodeSplit: true,
    
    rollupOptions: {
      output: {
        manualChunks(id) {
          if (id.includes('node_modules')) {
            return 'vendor'
          }
        },
        // CSS 文件名格式
        assetFileNames: (assetInfo) => {
          if (assetInfo.name.endsWith('.css')) {
            return 'css/[name]-[hash][extname]'
          }
          return 'assets/[name]-[hash][extname]'
        }
      }
    }
  }
})

效果说明:

/* cssCodeSplit: true 时 */
/* 动态导入的组件会提取对应的 CSS */
/* 按需加载 */

<!-- 页面加载 -->
<link rel="stylesheet" href="/css/index-abc123.css">

<!-- 异步组件加载时 -->
<link rel="stylesheet" href="/css/AsyncComponent-def456.css">

/* cssCodeSplit: false 时 */
/* 所有 CSS 合并为单个文件 */
<link rel="stylesheet" href="/css/style-xyz789.css">

六、生产环境构建

26. Vite 生产构建(vite build)

定义: vite build 命令用于将项目构建为生产环境可用的静态文件。

构建流程:

/**
 * vite build 流程:
 * 
 * 1. 加载配置(生产环境配置)
 * 2. 使用 Rollup 打包
 * 3. 执行 Tree Shaking
 * 4. 代码分割
 * 5. 压缩代码
 * 6. 处理静态资源
 * 7. 输出到 dist 目录
 */

构建命令:

# 基础构建
vite build

# 指定模式
vite build --mode staging

# 详细日志
vite build --debug

# 指定输出目录
vite build --outDir custom-dist

# 空输出目录前是否清空
vite build --emptyOutDir

构建配置:

// vite.config.js
export default defineConfig({
  build: {
    // 输出目录
    outDir: 'dist',
    
    // 静态资源目录名
    assetsDir: 'assets',
    
    // 静态资源内联阈值(字节)
    assetsInlineLimit: 4096,
    
    // 生成 source map
    sourcemap: false,
    
    // 自定义 Rollup 配置
    rollupOptions: {
      input: {
        main: 'index.html',
        admin: 'admin.html'
      },
      output: {
        // 文件命名规则
        entryFileNames: 'js/[name]-[hash].js',
        chunkFileNames: 'js/[name]-[hash].js',
        assetFileNames: '[ext]/[name]-[hash].[ext]'
      }
    },
    
    // chunk 大小警告阈值(kb)
    chunkSizeWarningLimit: 500,
    
    // 构建前是否清空 outDir
    emptyOutDir: true,
    
    // 启用 CSS source map
    cssCodeSplit: true,
    
    // 报告压缩后文件大小
    reportCompressedSize: true,
  }
})

多页面应用构建:

// vite.config.js
import { resolve } from 'path'

export default defineConfig({
  build: {
    rollupOptions: {
      input: {
        main: resolve(__dirname, 'index.html'),
        admin: resolve(__dirname, 'admin.html'),
        mobile: resolve(__dirname, 'mobile.html')
      }
    }
  }
})

27. Vite preview

定义: vite preview 命令用于本地预览生产构建结果。

使用方式:

# 构建后预览
vite build
vite preview

# 指定端口
vite preview --port 4173

# 指定 host
vite preview --host 0.0.0.0

# 打开浏览器
vite preview --open

配置预览服务器:

// vite.config.js
export default defineConfig({
  preview: {
    port: 4173,
    strictPort: true, // 端口被占用时报错,不自动切换
    host: '0.0.0.0',
    open: true,
    cors: true,
    headers: {
      'Cache-Control': 'no-cache'
    }
  }
})

预览与开发服务器的区别:

/**
 * vite preview vs vite dev:
 * 
 * vite dev(开发服务器):
 * - 按需编译,不打包
 * - 支持 HMR
 * - 使用原生 ESM
 * 
 * vite preview(预览服务器):
 * - 提供静态文件服务
 * - 不支持 HMR
 * - 模拟生产环境
 * - 用于验证构建结果
 */

28. Rollup 打包

定义: Vite 生产环境使用 Rollup 进行打包,利用其优秀的 Tree Shaking 和代码分割能力。

Rollup 在 Vite 中的作用:

/**
 * Vite 为什么生产环境使用 Rollup:
 * 
 * 1. Rollup 专注于库/应用打包
 * 2. 优秀的 Tree Shaking 能力
 * 3. 成熟的代码分割策略
 * 4. 丰富的插件生态
 * 5. 输出格式多样(ESM, CommonJS, IIFE, UMD)
 */

// Vite 内部使用 Rollup 配置示例
// 可以通过 build.rollupOptions 自定义
export default defineConfig({
  build: {
    rollupOptions: {
      // 外部依赖(不从 node_modules 打包)
      external: ['vue', 'vue-router'],
      
      // 插件
      plugins: [
        // 自定义 Rollup 插件
      ],
      
      // 输出配置
      output: {
        format: 'es', // 输出格式
        dir: 'dist',
        manualChunks(id) {
          // 手动分割 chunk
        }
      }
    }
  }
})

七、多框架支持

29. Vite 多框架支持

定义: Vite 通过官方插件支持多种前端框架,核心能力与框架无关。

支持的框架:

// Vue 3
import vue from '@vitejs/plugin-vue'

// React
import react from '@vitejs/plugin-react'

// Svelte
import { svelte } from '@sveltejs/vite-plugin-svelte'

// Preact
import preact from '@preact/preset-vite'

// Vue 2
import { createVuePlugin } from 'vite-plugin-vue2'

原理说明:

/**
 * Vite 多框架支持原理:
 * 
 * 1. 核心能力与框架无关
 *    - 模块解析、依赖预构建、HMR 等是通用的
 * 
 * 2. 框架特定处理通过插件实现
 *    - .vue 文件转换 → @vitejs/plugin-vue
 *    - JSX/TSX 转换 → @vitejs/plugin-react
 *    - .svelte 文件转换 → @sveltejs/vite-plugin-svelte
 * 
 * 3. 框架的 HMR 通过插件注入
 *    - 每个框架插件处理各自的 HMR 逻辑
 */

30. Vite 与 Vue

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

export default defineConfig({
  plugins: [
    vue({
      // Vue 编译器选项
      template: {
        compilerOptions: {
          // 自定义元素
          isCustomElement: (tag) => tag.startsWith('custom-')
        }
      },
      
      // Vue SFC 选项
      script: {
        // 定义宏处理
        defineModel: true
      },
      
      // 响应性转换
      reactivityTransform: true
    })
  ]
})

31. Vite 与 React

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

export default defineConfig({
  plugins: [
    react({
      // Fast Refresh(热更新)
      fastRefresh: true,
      
      // 使用 Babel
      babel: {
        plugins: [
          // 自定义 Babel 插件
          ['babel-plugin-macros', { /* options */ }]
        ]
      },
      
      // 或使用 SWC(更快)
      // 使用 @vitejs/plugin-react-swc
    })
  ]
})

React SWC 插件(更快):

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

export default defineConfig({
  plugins: [react()],
  // SWC 比 Babel 快 20x
})

32. Vite 与 Svelte

// vite.config.js
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'

export default defineConfig({
  plugins: [
    svelte({
      // Svelte 编译器选项
      compilerOptions: {
        dev: process.env.NODE_ENV !== 'production'
      }
    })
  ]
})

33. Vite 与 Preact

// vite.config.js
import { defineConfig } from 'vite'
import preact from '@preact/preset-vite'

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

八、与 Webpack 对比

34. Vite 与 Webpack 的区别

定义: Vite 和 Webpack 都是前端构建工具,但设计理念和工作方式有本质区别。

对比表格:

对比维度ViteWebpack
核心理念开发环境不打包,按需编译开发环境打包整个应用
开发环境原生 ESM + 按需编译Bundle + Dev Server
生产环境Rollup 打包Webpack 打包
启动速度极快(1-3秒)较慢(10-30秒)
HMR快速、稳定较慢,项目大时可能失效
打包工具esbuild(开发)+ Rollup(生产)Webpack(全部)
配置复杂度简单较复杂
插件生态成长中非常成熟
兼容性现代浏览器支持所有浏览器
代码分割优秀优秀
Tree Shaking优秀优秀
适合场景现代项目、快速开发大型复杂项目、需要高度定制

选择策略:

/**
 * 选择 Vite 的场景:
 * - 新项目,没有历史包袱
 * - 追求开发体验和速度
 * - 使用现代框架(Vue3, React18+)
 * - 团队规模小,需要快速迭代
 * - 不需要兼容老旧浏览器
 * 
 * 选择 Webpack 的场景:
 * - 已有大型 Webpack 项目
 * - 需要深度定制构建流程
 * - 需要支持老旧浏览器(IE11)
 * - 需要成熟的插件和 loader
 * - 企业级复杂构建需求
 */

35. Vite 的优势

/**
 * Vite 的优势:
 * 
 * 1. 极速启动:1-3秒启动开发服务器
 * 2. 即时 HMR:模块级别热更新,保持状态
 * 3. 开箱即用:TypeScript、CSS 预处理器等内置支持
 * 4. 现代配置:ESM 配置文件,类型提示
 * 5. 多框架支持:Vue、React、Svelte、Preact
 * 6. 生产优化:Rollup 打包,Tree Shaking
 * 7. 简单配置:比 Webpack 配置更简洁
 * 8. 活跃社区:快速发展,文档完善
 */

36. Vite 的劣势

/**
 * Vite 的劣势:
 * 
 * 1. 生态不如 Webpack 成熟:部分 Webpack 插件没有 Vite 对应版本
 * 2. 生产构建使用 Rollup:需要理解两套工具
 * 3. 老旧浏览器兼容:开发环境依赖现代浏览器 ESM 支持
 * 4. 大型项目冷启动:首次依赖预构建可能较慢
 * 5. 定制能力:不如 Webpack 灵活
 */

37. 何时选择 Vite / Webpack

/**
 * 决策树:
 * 
 * 1. 是全新的项目吗?
 *    - 是 → 优先选择 Vite
 *    - 否 → 继续
 * 
 * 2. 现有项目使用 Webpack 吗?
 *    - 是 → 评估迁移成本,谨慎考虑
 *    - 否 → 继续
 * 
 * 3. 需要支持 IE11 吗?
 *    - 是 → 选择 Webpack
 *    - 否 → 继续
 * 
 * 4. 需要特殊的构建需求吗?
 *    - 是 → 评估 Vite 是否满足,否则选择 Webpack
 *    - 否 → 选择 Vite
 * 
 * 5. 团队对 Vite 有了解吗?
 *    - 是 → 选择 Vite
 *    - 否 → 学习成本可接受 → 选择 Vite
 */

九、配置文件与特性

38. Vite 配置文件(vite.config.js)

定义: Vite 使用 vite.config.js 作为配置文件,支持 ESM 格式和 TypeScript。

基础配置:

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

export default defineConfig(({ command, mode }) => {
  // command: 'serve'(开发)或 'build'(生产)
  // mode: 当前模式(development, production, 或自定义)
  
  return {
    // 项目根目录
    root: process.cwd(),
    
    // 基础路径
    base: '/',
    
    // 插件
    plugins: [vue()],
    
    // 开发服务器
    server: {
      port: 3000,
      open: true,
      proxy: {
        '/api': {
          target: 'http://localhost:8080',
          changeOrigin: true,
          rewrite: (path) => path.replace(/^\/api/, '')
        }
      }
    },
    
    // 构建配置
    build: {
      outDir: 'dist',
      sourcemap: false
    },
    
    // 路径别名
    resolve: {
      alias: {
        '@': resolve(__dirname, 'src')
      }
    },
    
    // CSS 配置
    css: {
      preprocessorOptions: {
        scss: {
          additionalData: '@import "@/styles/variables.scss";'
        }
      }
    },
    
    // 环境变量
    envDir: 'env',
    envPrefix: 'VITE_'
  }
})

TypeScript 配置文件:

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

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src')
    }
  }
})

39. Vite 环境变量

定义: Vite 支持通过 .env 文件管理环境变量,以 VITE_ 开头的环境变量会被暴露给客户端代码。

环境变量文件:

# .env                  # 所有模式加载
# .env.local            # 所有模式加载,被 git 忽略
# .env.development      # 开发模式加载
# .env.production       # 生产模式加载
# .env.staging          # staging 模式加载

# 示例 .env.development
VITE_API_URL=http://localhost:3000/api
VITE_APP_TITLE=My App (Dev)

# 示例 .env.production
VITE_API_URL=https://api.example.com
VITE_APP_TITLE=My App

使用环境变量:

// 在代码中使用
console.log(import.meta.env.VITE_API_URL)
console.log(import.meta.env.VITE_APP_TITLE)

// 内置环境变量
console.log(import.meta.env.MODE)           // 当前模式
console.log(import.meta.env.DEV)            // 是否开发环境
console.log(import.meta.env.PROD)           // 是否生产环境
console.log(import.meta.env.BASE_URL)       // 基础路径

// TypeScript 类型定义
// env.d.ts
interface ImportMetaEnv {
  readonly VITE_API_URL: string
  readonly VITE_APP_TITLE: string
  readonly MODE: string
  readonly DEV: boolean
  readonly PROD: boolean
  readonly BASE_URL: string
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

多环境配置示例:

// package.json
{
  "scripts": {
    "dev": "vite",
    "build:dev": "vite build --mode development",
    "build:staging": "vite build --mode staging",
    "build:prod": "vite build --mode production",
    "preview": "vite preview"
  }
}

// vite.config.js 中使用环境变量
export default defineConfig(({ mode }) => {
  console.log('Current mode:', mode)
  
  return {
    define: {
      // 可以在构建时注入额外的变量
      __APP_VERSION__: JSON.stringify(process.env.npm_package_version)
    }
  }
})

40. Vite 别名配置

// vite.config.js
import { resolve } from 'path'
import { defineConfig } from 'vite'

export default defineConfig({
  resolve: {
    alias: [
      // 基础别名
      {
        find: '@',
        replacement: resolve(__dirname, 'src')
      },
      {
        find: '~',
        replacement: resolve(__dirname, 'src/assets')
      },
      // 正则表达式匹配
      {
        find: /^~(.*)/,
        replacement: '$1'
      },
      // 函数方式
      {
        find: (id) => id.startsWith('virtual:'),
        replacement: '\0virtual:'
      }
    ]
  }
})

// 使用别名
import utils from '@/utils/index.js'
import logo from '~/images/logo.png'

41. Vite 代理配置

// vite.config.js
export default defineConfig({
  server: {
    proxy: {
      // 字符串方式
      '/api': 'http://localhost:3000',
      
      // 对象方式(更多选项)
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true, // 修改请求头中的 Host
        secure: false,      // 是否验证 SSL 证书
        rewrite: (path) => path.replace(/^\/api/, ''), // 重写路径
        
        // 配置代理超时
        timeout: 5000,
        
        // 代理 WebSocket
        ws: true,
        
        // 自定义代理逻辑
        configure: (proxy, options) => {
          proxy.on('error', (err, req, res) => {
            console.log('proxy error:', err)
          })
          proxy.on('proxyReq', (proxyReq, req, res) => {
            console.log('Sending Request to the Target:', req.method, req.url)
          })
          proxy.on('proxyRes', (proxyRes, req, res) => {
            console.log('Received Response from the Target:', proxyRes.statusCode)
          })
        }
      },
      
      // 正则匹配
      '^/fallback/.*': {
        target: 'http://jsonplaceholder.typicode.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/fallback/, '')
      }
    }
  }
})

42. Vite CSS 处理

定义: Vite 内置 CSS 处理支持,包括 CSS 导入、CSS Modules、CSS 预处理器等。

CSS 导入:

// main.js
// 导入全局 CSS
import './styles/global.css'

// 导入 CSS Modules
import styles from './Button.module.css'

// 导入 CSS Modules(特定类名)
const { primary, large } = styles

// 在 JSX/TSX 中使用
<button className={styles.primary}>Click</button>

// 导入 Sass/Less
import './styles/variables.scss'
import './styles/theme.less'

CSS Modules:

/* Button.module.css */
.primary {
  background-color: #42b883;
  color: white;
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
}

.large {
  font-size: 18px;
  padding: 12px 24px;
}
<!-- Vue 中使用 -->
<template>
  <button :class="$style.primary">Click</button>
</template>
// React 中使用
import styles from './Button.module.css'

function Button() {
  return <button className={styles.primary}>Click</button>
}

预处理器配置:

// vite.config.js
export default defineConfig({
  css: {
    // 预处理器选项
    preprocessorOptions: {
      scss: {
        // 注入全局 SCSS 变量
        additionalData: `
          @import "@/styles/variables.scss";
          @import "@/styles/mixins.scss";
        `,
        // Sass 1.70+ 使用 api: 'modern'
        api: 'modern-compiler'
      },
      less: {
        additionalData: '@import "@/styles/variables.less";',
        javascriptEnabled: true
      },
      stylus: {
        additionalData: `@import "@/styles/variables.styl"`
      }
    },
    
    // CSS Modules 配置
    modules: {
      generateScopedName: '[name]__[local]--[hash:base64:5]',
      localsConvention: 'camelCase' // 类名转换为驼峰
    },
    
    // PostCSS 配置
    postcss: {
      plugins: [
        // autoprefixer 等
      ]
    }
  }
})

43. Vite 静态资源

定义: Vite 支持多种静态资源处理方式,包括图片、字体、音频等。

资源处理方式:

// 1. 小资源内联(默认 < 4KB)
import logo from './logo.png'
// logo 会被转换为 base64 data URL

// 2. 大资源作为独立文件
import bigImage from './big-image.png'
// bigImage 是资源的公共 URL

// 3. 资源 URL 导入
import assetUrl from './asset.png?url'
// 强制作为 URL 处理,不内联

// 4. 资源原始导入
import assetRaw from './data.txt?raw'
// 导入资源内容为字符串

// 5. Worker 导入
import Worker from './worker.js?worker'
const worker = new Worker()

// 6. Web Worker 内联
import Worker from './worker.js?worker&inline'

静态资源配置:

// vite.config.js
export default defineConfig({
  build: {
    // 小于此值的资源会被内联为 base64(字节)
    assetsInlineLimit: 4096,
    
    // 静态资源输出目录
    assetsDir: 'assets',
    
    // 忽略资源
    rollupOptions: {
      output: {
        assetFileNames: (assetInfo) => {
          // 按类型分类输出
          if (/\.(png|jpe?g|gif|svg|webp|ico)$/.test(assetInfo.name)) {
            return 'images/[name]-[hash][extname]'
          }
          if (/\.css$/.test(assetInfo.name)) {
            return 'css/[name]-[hash][extname]'
          }
          if (/\.(woff2?|eot|ttf|otf)$/.test(assetInfo.name)) {
            return 'fonts/[name]-[hash][extname]'
          }
          return 'assets/[name]-[hash][extname]'
        }
      }
    }
  },
  
  // public 目录中的文件会被复制到输出目录
  publicDir: 'public'
})

44. Vite TypeScript 支持

定义: Vite 内置 TypeScript 支持,开发环境通过 esbuild 转换,生产环境通过 Rollup 处理。

TypeScript 配置:

// tsconfig.json
{
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "strict": true,
    "jsx": "preserve",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "lib": ["ESNext", "DOM"],
    "skipLibCheck": true,
    "noEmit": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
  "references": [{ "path": "./tsconfig.node.json" }]
}

Vue + TypeScript:

<!-- App.vue -->
<script lang="ts" setup>
import { ref } from 'vue'

const count = ref<number>(0)

function increment(): void {
  count.value++
}
</script>

<template>
  <button @click="increment">{{ count }}</button>
</template>

注意事项:

/**
 * Vite TypeScript 注意事项:
 * 
 * 1. Vite 开发环境只转换 TypeScript,不做类型检查
 *    - 类型检查需要单独运行 tsc --noEmit
 *    - 或使用 vue-tsc(Vue 项目)
 * 
 * 2. Vite 使用 esbuild 转换 TS,比 tsc 快 20-30 倍
 * 
 * 3. tsconfig.json 中的 paths 别名需要和 vite.config.js 中的 alias 同步
 * 
 * 4. 类型定义文件(.d.ts)需要正确配置
 */

45. Vite JSX 支持

// React JSX 配置
// vite.config.js
import react from '@vitejs/plugin-react'

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

// Vue JSX 配置
// vite.config.js
import vueJsx from '@vitejs/plugin-vue-jsx'

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

// JSX 代码示例
// App.jsx
import { defineComponent, ref } from 'vue'

export default defineComponent({
  setup() {
    const count = ref(0)
    
    return () => (
      <div>
        <button onClick={() => count.value++}>
          Count: {count.value}
        </button>
      </div>
    )
  }
})

46. Vite SSR(服务端渲染)

定义: Vite 提供 SSR 支持,可以在服务端渲染 Vue、React 等框架的应用。

SSR 基础配置:

// vite.config.js
export default defineConfig({
  build: {
    // SSR 构建配置
    ssr: true,
    rollupOptions: {
      input: 'src/entry-server.js'
    }
  }
})

SSR 入口文件:

// entry-server.js
import { createSSRApp } from 'vue'
import App from './App.vue'

export function render() {
  const app = createSSRApp(App)
  // 渲染为 HTML 字符串
  return { html: '<div id="app">...</div>' }
}

Node.js 服务器:

// server.js
import express from 'express'
import { createServer as createViteServer } from 'vite'

async function createServer() {
  const app = express()
  
  // 创建 Vite 服务器
  const vite = await createViteServer({
    server: { middlewareMode: true },
    appType: 'custom'
  })
  
  app.use(vite.middlewares)
  
  app.use('*', async (req, res) => {
    // 获取 SSR 入口
    const { render } = await vite.ssrLoadModule('/src/entry-server.js')
    const { html } = render()
    
    res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
  })
  
  app.listen(3000, () => {
    console.log('SSR Server running on http://localhost:3000')
  })
}

createServer()

47. Vite 中间件

// vite.config.js
export default defineConfig({
  server: {
    // 自定义中间件
    middlewareMode: false, // 是否仅作为中间件模式
    
    // 通过 configureServer 添加中间件
    proxy: {
      '/api': 'http://localhost:3000'
    }
  },
  
  plugins: [
    {
      name: 'custom-middleware',
      configureServer(server) {
        // 在 Vite 内置中间件之前
        server.middlewares.use((req, res, next) => {
          if (req.url === '/custom') {
            res.end('Custom response')
          } else {
            next()
          }
        })
      },
      
      // 或者在 configureServer 返回函数,在内置中间件之后执行
      configureServer(server) {
        return () => {
          server.middlewares.use((req, res, next) => {
            // 在所有内置中间件之后执行
            console.log('Request:', req.url)
            next()
          })
        }
      }
    }
  ]
})

48. Vite 开发服务器

// vite.config.js
export default defineConfig({
  server: {
    // 服务器主机
    host: '0.0.0.0', // 监听所有地址
    
    // 端口
    port: 3000,
    
    // 严格端口(端口被占用时报错)
    strictPort: false,
    
    // 是否使用 HTTPS
    https: false,
    
    // 启动后打开浏览器
    open: true,
    
    // CORS
    cors: true,
    
    // 响应头
    headers: {
      'Access-Control-Allow-Origin': '*'
    },
    
    // 文件监听
    watch: {
      // chokidar 选项
      usePolling: true, // 轮询模式(Docker 等环境需要)
      interval: 100
    },
    
    // 预构建配置
    warmup: {
      // 预热文件(提前转换,提升首次访问速度)
      clientFiles: [
        './src/components/*.vue',
        './src/layouts/*.vue'
      ]
    },
    
    // 文件系统访问限制
    fs: {
      strict: true, // 限制访问工作区外的文件
      allow: ['..'], // 允许访问的额外路径
      deny: ['/etc', '/root'] // 拒绝访问的路径
    }
  }
})

49. Vite 常见问题

Q1: HMR 不生效怎么办?

// 排查步骤:
// 1. 检查文件类型是否支持 HMR(.vue, .jsx, .css 等支持)
// 2. 检查是否有正确的 import.meta.hot.accept 处理
// 3. 检查 vite.config.js 中是否有正确的插件
// 4. 检查是否修改了不支持 HMR 的文件(如 vite.config.js)

// 手动添加 HMR 处理
// my-module.js
if (import.meta.hot) {
  import.meta.hot.accept((newModule) => {
    console.log('Module hot updated')
  })
}

Q2: 依赖预构建失败怎么办?

// 解决方案:
// 1. 清除缓存
// rm -rf node_modules/.vite

// 2. 强制重新预构建
// vite --force

// 3. 配置中指定依赖
export default defineConfig({
  optimizeDeps: {
    include: ['problematic-package'],
    exclude: ['another-package']
  }
})

Q3: 别名在 TypeScript 中不生效?

// 确保 tsconfig.json 中的 paths 和 vite.config.js 中的 alias 一致
// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}

// vite.config.js
export default defineConfig({
  resolve: {
    alias: {
      '@': '/src'
    }
  }
})

Q4: 生产环境静态资源 404?

// 检查 base 配置
export default defineConfig({
  // 如果部署在子路径下
  base: '/my-app/'
})

// 检查资源路径引用
// 正确方式
import logo from './logo.png'
// 使用
<img src={logo} />

// 错误方式(不会被处理)
<img src="./logo.png" />

Q5: 环境变量在客户端无法访问?

// 只有 VITE_ 开头的环境变量会暴露给客户端
// .env
VITE_API_URL=https://api.example.com  // ✅ 可访问
API_SECRET=secret123                   // ❌ 不可访问

// 代码中访问
console.log(import.meta.env.VITE_API_URL) // ✅
console.log(import.meta.env.API_SECRET)   // ❌ undefined