Skip to content

Vite

Vite 是当前前端新项目的事实标准构建工具。Vue 官方强推 Vite 作为默认脚手架,React 社区在 create-react-app 停止维护后也大规模转向 Vite,Svelte、Solid 等新兴框架同样首选 Vite 作为开发环境。它的成功并非偶然——在 Webpack 统治了前端构建近十年之后,Vite 从根本上重新思考了"开发环境究竟需要什么"这个问题。

核心设计

Vite 的核心思想是:开发环境和生产环境的需求本质不同,不应该用同一套构建策略处理。

开发环境下,Vite 利用浏览器原生 ESM(ES Modules)按需加载源文件,不进行全量打包。浏览器请求哪个模块,Vite 就编译哪个模块,然后即时返回。这意味着冷启动不需要从入口文件开始递归扫描整个依赖图谱,启动时间基本不随项目规模增长。热更新时只需要重新编译单个被修改的模块,速度通常在百毫秒级别——在 Webpack 时代,大型项目的热更新可能需要数秒。

生产环境下,Vite 使用 Rollup 进行打包。Rollup 基于 ESM 的静态分析能力更强,Tree Shaking 效果更精确,产物的体积通常比 Webpack 更小。开发和生产使用不同的构建策略,是 Vite 相比 Webpack(开发和生产都用同一套 Bundle 机制)最本质的区别。

预构建

Vite 的开发服务器基于浏览器原生 ESM,但 node_modules 下的第三方依赖通常不提供 ESM 格式——它们大多是 CommonJS 规范,且一个库内部可能包含几百个小模块。如果让浏览器直接请求这些文件,会触发大量 HTTP 请求和格式转换开销,严重拖慢加载速度。

Vite 在启动开发服务器时使用 Esbuild 对 node_modules 中的依赖进行预构建(Pre-bundling),将 CommonJS 转换为 ESM,并将数百个小模块合并为少量 bundle 文件。预构建的结果缓存在 node_modules/.vite 目录中,只有在 package.json 的依赖列表变化或 Lock 文件变化时才会重新执行。整个预构建过程基于 Esbuild 的 Go 语言实现,速度极快,通常在几百毫秒内完成。

HMR 与依赖预构建

Vite 的热模块替换(HMR)同样基于原生 ESM。当文件被修改时,Vite 只重新编译该文件本身,然后通过 WebSocket 通知浏览器重新请求这个模块。浏览器收到通知后,利用 ESM 的动态导入能力在运行时替换模块的导出,无需刷新页面,且应用状态保持不变。

但这里有一个需要理解的技术细节:当被修改的文件是一个被预构建的依赖的内部模块时,Vite 并不会重新触发整个依赖的预构建,而是将变更的模块从预构建 bundle 中排除,以单独的 ESM 模块形式提供给浏览器。这种机制让第三方依赖的开发调试变得可行——你可以在 node_modules 中直接修改源码(如调试场景),Vite 会自动适配。

配置体系

Vite 的配置文件是 vite.config.ts(或 .js),基于 ESM 语法导出一个配置对象。配置项分为几类:开发服务器选项(server.portserver.proxy)、构建选项(build.rollupOptionsbuild.target)、依赖优化选项(optimizeDeps)、插件配置(plugins)。

环境变量通过 .env 文件管理,Vite 会将环境变量注入到 import.meta.env 对象上。只有以 VITE_ 前缀开头的变量才会暴露给客户端代码,其余变量仅在 SSR 场景下可用。这个设计是出于安全考虑——数据库密码、API 密钥等敏感信息不应暴露到浏览器端。开发模式下 import.meta.env.MODEdevelopment,生产构建时为 production,可通过 --mode 参数自定义。

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

export default defineConfig({
  plugins: [vue()],
  server: {
    port: 3000,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
      }
    }
  },
  resolve: {
    alias: {
      '@': '/src'
    }
  }
})

路径别名(resolve.alias)是 Vite 配置中使用频率最高的选项之一。将 @ 映射到项目 src 目录后,所有导入语句都可以使用 @/components/Button.vue 这种短路径,避免相对路径嵌套过深导致的可读性问题。但需要注意,别名配置需要同时声明给 TypeScript(通过 tsconfig.jsonpaths 字段),否则编辑器的类型推断和跳转功能无法正常工作。

生产构建

Vite 的生产构建使用 Rollup,这意味着 Rollup 的所有配置能力都可以通过 build.rollupOptions 使用。代码分割通过 output.manualChunks 配置,将框架、工具库、业务代码分离为独立的 chunk,配合内容哈希实现长期缓存。CSS 在生产构建中默认被提取为独立的 .css 文件,同样包含内容哈希。

Vite 的构建目标是现代浏览器,默认只转译语法到 ES2020(如可选链、空值合并等保留原样)。如果需要兼容旧版浏览器,需要通过 @vitejs/plugin-legacy 引入 Babel 进行降级转译和 Polyfill 注入,这会增加产物的体积和构建时间。工程上建议明确项目的浏览器支持范围,避免不必要的转译开销。

对于库开发(而非应用开发),Vite 提供了库模式(Library Mode)。通过配置 build.lib,Vite 可以将项目打包为 ES Module 和 UMD 格式,同时生成类型声明文件。这种模式通常用于开发 npm 组件库或工具库,产出可以直接发布到 npm registry 供其他项目使用。

与 Webpack 的本质区别

理解 Vite 的最好方式是把它和 Webpack 做对比。Webpack 在开发模式下基于 Bundle 范式——启动时从 Entry 递归构建完整的依赖图谱,打包成有限数量的 bundle 后才能启动开发服务器。项目越大,冷启动越慢,因为它必须处理完所有模块。Vite 在开发模式下基于原生 ESM 范式——不打包,浏览器按需请求模块,服务端按需编译。冷启动时间与项目规模基本无关。

这个差异在热更新时更加明显。Webpack 的热更新需要沿着依赖链找到所有受影响的模块并重新编译,链越长耗时越多。Vite 的热更新只编译被修改的文件本身,然后通过 ESM 动态导入替换模块导出,耗时几乎恒定。

但需要认识到,Vite 的原生 ESM 范式也有代价。在模块数量极多的巨型项目中,浏览器首次加载页面时会发起大量 HTTP 请求(每个 ESM 模块一个请求),在 HTTP/1.1 环境下可能触发瀑布流问题。Vite 通过预构建缓解了第三方依赖的请求数量,但业务代码本身的模块数量仍然会影响首屏加载速度。这也是为什么 Vite 在大型存量项目迁移中面临挑战——而 Rspack 保持了 Webpack 的 Bundle 范式,在巨型项目中反而有更稳定的性能表现。

从工程实践的角度来看,Vite 是当前新项目的最佳选择——开发体验好、配置简单、生态完善。但如果项目有大量 CommonJS 依赖、自定义 Webpack Loader/Plugin、或特殊的构建流程,迁移到 Vite 的成本需要认真评估。工具的选择永远应该服务于项目的实际需求,而非追逐技术趋势。