标签

Vite

Vite 是一种构建工具,旨在为现代网络项目提供更快,更精美的开发体验。

Vite
服务端5月31日 16:34
Vite 相比 Webpack 快在哪里,又有哪些迁移坑?Vite 是一个面向现代前端项目的构建工具,开发期基于浏览器原生 ESM 提供按需转换,生产期默认用 Rollup 输出优化后的静态资源。它相比 Webpack 最明显的优势是启动快、HMR 快、默认配置更贴近现代框架。但如果把 Vite 理解成“Webpack 的全面替代品”,迁移时很容易踩坑,因为两者的开发模型、插件生态和打包边界并不完全一样。 ## Vite 快在哪里 Webpack 开发服务器启动时,通常要从入口开始构建完整依赖图。项目越大,首次启动越慢。Vite 则把源码模块交给浏览器按需请求,开发服务器只在请求到某个文件时转换它。依赖部分由 esbuild 预构建,速度很快,也能把 CommonJS 依赖整理成浏览器更容易消费的 ESM。 HMR 也是同样思路。Webpack 的热更新仍然围绕打包图生成补丁;Vite 只需要定位变化模块,通过 WebSocket 通知浏览器重新 import 对应模块。对于组件很多的大型后台项目,这个差异会非常明显,尤其是只改一个页面组件时。 ## Vite 不只是快 Vite 的另一个价值是默认配置少。TypeScript、CSS Modules、PostCSS、静态资源导入、环境变量、框架插件等能力开箱就能用。新项目不需要先写一大段 loader 和 plugin 配置,团队成员更容易统一开发方式。 ```bash npm create vite@latest my-app -- --template react-ts cd my-app npm install npm run dev ``` 生产构建上,Vite 使用 Rollup,因此 tree-shaking、代码分割、动态导入和库模式都有成熟支持。对常规 SPA、组件库、文档站、管理后台来说,Vite 的默认体验已经够稳。 ## Webpack 仍然有优势的地方 Webpack 的优势是可塑性和历史生态。大量老项目依赖自定义 loader、复杂 alias、Module Federation、非标准资源处理和企业内部插件,这些不是换个命令就能迁到 Vite。Webpack 对各种“历史包袱”的兼容经验更多,遇到特殊构建链时反而更省心。 所以选择不是简单的“Vite 新,Webpack 旧”。如果是新项目、现代浏览器、React/Vue/Svelte 等主流框架,Vite 通常更合适。如果是多年老项目、强依赖 Webpack 插件、微前端运行时共享复杂,迁移前要先做依赖盘点。 ```ts // vite.config.ts import { defineConfig } from 'vite' import path from 'node:path' export default defineConfig({ resolve: { alias: { '@': path.resolve(__dirname, 'src') } }, define: { __APP_VERSION__: JSON.stringify(process.env.npm_package_version) } }) ``` 迁移时常见坑是把 Webpack 的 `process.env.X` 直接搬过来。Vite 客户端环境变量默认需要 `VITE_` 前缀,通过 `import.meta.env` 读取;如果硬要兼容旧写法,就需要显式 `define`。 ## 迁移时要先验证什么 第一看依赖格式,尤其是 CommonJS、动态 require 和只面向 Node 的包。第二看资源导入方式,例如 svg loader、less 全局变量、mdx 或自定义文件类型。第三看构建产物,包括 chunk 大小、路由懒加载是否正常、静态资源路径是否适配 CDN。 Vite 的迁移收益很高,但最好分阶段做。先让 dev server 跑起来,再处理测试、构建、预览和 CI。一次性重写全部构建链,问题会混在一起,很难判断是业务代码、依赖格式还是 Vite 配置导致的。 ## 追问 ### Vite 一定比 Webpack 快吗? 开发期大多数现代项目会更快,尤其是冷启动和局部 HMR。生产构建不一定总是数量级领先,因为 Vite 默认还是走 Rollup,复杂项目的插件和压缩也会花时间。速度还取决于依赖数量、插件质量和文件系统性能。判断时应该用自己项目的启动、保存更新、build 三组数据,而不是只看 benchmark。 ### 老 Webpack 项目适合直接迁 Vite 吗? 不建议直接大爆炸迁移。老项目通常藏着 loader、全局变量、资源规则和环境变量约定,一次性替换很容易让问题堆在一起。更稳的方式是先做一个最小页面或独立包验证,再逐步迁移入口。取舍点在于:如果构建链已经严重拖慢开发,迁移值得做;如果依赖大量私有插件,维护 Webpack 可能更便宜。 ### Vite 和 Webpack 的插件能通用吗? 不能直接通用。Vite 插件接口接近 Rollup,并额外提供开发服务器相关钩子;Webpack 插件和 loader 是另一套机制。部分 Rollup 插件能在 Vite 生产构建中使用,但开发期是否可用要看插件有没有处理 ESM 请求模型。迁移时不要只找同名插件,要验证 dev 和 build 两个阶段都正常。 ### 为什么 Vite 生产环境还会打包? 开发环境追求反馈速度,可以让浏览器按需加载很多模块。生产环境要控制请求数、缓存、压缩、兼容性和资源 hash,所以仍然需要打包优化。直接把源码 ESM 发布出去在小 demo 里可行,在真实业务里通常会带来首屏请求过多和缓存不可控。Vite 的取舍是开发期少打包,生产期认真打包。 ### 迁移后线上白屏一般怎么查? 先看构建产物的资源路径,尤其是部署在子路径时 `base` 是否正确。再看动态导入 chunk 是否 404,很多白屏其实是懒加载文件路径被 CDN 或网关改坏了。然后检查环境变量和浏览器兼容目标,开发环境能跑不代表旧浏览器能执行生产代码。最后用 `vite preview` 复现构建产物,而不是在 dev server 里反复刷新。
服务端5月31日 16:34
Vite HMR 是如何做到修改代码后几乎秒更新的?Vite HMR 的核心不是“重新打一个更小的包”,而是利用浏览器原生 ESM 和开发服务器的模块图,只更新真正变化的模块。文件改动后,Vite 服务器通过 WebSocket 通知浏览器,浏览器再按新的模块 URL 拉取更新内容。如果某个模块声明自己能接受更新,页面就不刷新;如果更新无法安全接住,Vite 才会退回整页刷新。 ## HMR 的基本链路 开发服务器启动后,Vite 会维护一张模块图,记录 URL、文件路径、导入关系和被哪些模块引用。浏览器加载页面时,每个源码模块都以 ESM 方式请求,比如 `/src/App.tsx`、`/src/main.tsx`。当你保存文件,文件监听器捕获变化,Vite 找到对应模块,再判断这次更新能不能沿依赖链被某个 HMR 边界接住。 浏览器和服务端之间有一条 WebSocket 连接。服务端发送的不是完整应用代码,而是类似“某个模块更新了,请重新 import 它”的消息。浏览器端 HMR runtime 收到消息后,会给模块 URL 加上时间戳,绕过浏览器缓存并重新加载。 ```js if (import.meta.hot) { import.meta.hot.accept((newModule) => { console.log('module updated', newModule) }) import.meta.hot.dispose(() => { console.log('clean side effects') }) } ``` `accept` 表示当前模块愿意处理自己的更新,`dispose` 用来清理定时器、订阅、WebSocket 连接等副作用。很多业务项目不用手写这段,是因为 React、Vue 插件已经替你处理组件级刷新。 ## 为什么 Vite 的 HMR 通常更快 Webpack 这类传统打包工具在开发期也能 HMR,但它的更新建立在打包产物和依赖图补丁之上。项目大了以后,重新编译和生成补丁的成本会变高。Vite 开发期不需要先把源码合成一个大 bundle,改哪个文件通常只转换哪个文件,速度不会随着项目模块数线性变慢。 这并不代表 Vite HMR 永远是几十毫秒。如果改动的是被大量模块共同依赖的基础文件,例如全局样式、路由入口、状态管理单例,更新仍然可能扩大影响范围。快的前提是模块边界清晰,框架插件能正确识别组件更新。 ## HMR 边界和整页刷新的取舍 HMR 最难的地方不是重新加载代码,而是保证状态不乱。比如 React 组件改了样式或 JSX,保留组件状态通常是好事;但如果改了 Hook 调用顺序、模块顶层副作用或全局单例,强行热替换可能比刷新更危险。Vite 的策略是尽量精确更新,但在无法确认安全时刷新页面。 开发时看到页面刷新,不一定是 Vite 慢,可能是当前模块没有形成可接受的 HMR 边界。常见原因包括:模块既导出组件又导出普通运行时变量、组件外部有不可清理的副作用、插件没有覆盖该文件类型。 ```ts // vite.config.ts export default { server: { hmr: { protocol: 'ws', host: 'localhost', port: 5173 } } } ``` 在容器、远程开发机或反向代理下,HMR 失败常常不是代码问题,而是 WebSocket 地址不通。页面能打开但保存后没反应时,先看浏览器 Network 里的 WS 连接是否成功。 ## 追问 ### HMR 和 Live Reload 有什么区别? Live Reload 通常是文件变化后刷新整个页面,简单可靠,但页面状态会丢。HMR 只替换变化模块,理论上可以保留表单输入、组件状态和当前路由。取舍在于 HMR 需要模块声明更新边界,也需要框架插件配合。遇到状态被错误保留导致调试迷惑时,手动刷新反而更可信。 ### 为什么有些修改会触发整页刷新? 因为 Vite 没找到安全的 HMR 接收方,或者插件判断这次更新不适合局部替换。比如修改入口文件、环境变量、模块顶层副作用,都会让局部更新风险变高。整页刷新是保守策略,不是失败。真正要排查的是为什么某个应该局部更新的组件没有形成边界。 ### React 或 Vue 项目为什么很少手写 import.meta.hot? 框架插件已经把常见组件更新逻辑封装好了。React Fast Refresh 会判断组件签名,Vue 插件会处理 SFC 的 template、script、style 更新。业务代码手写 HMR 主要出现在自定义状态容器、插件开发、WebSocket 客户端或非组件模块里。边界是:插件只能处理它理解的模式,写法太混合时仍可能退回刷新。 ### HMR 失效应该从哪里排查? 先看浏览器控制台和 Network,确认 WebSocket 是否连上。再看终端是否捕获到文件变化,容器挂载、网络盘和 WSL 场景都可能让文件监听不稳定。然后检查模块是否有语法错误或循环依赖,因为更新模块加载失败会中断本轮 HMR。不要一开始就改 Vite 配置,先确认链路上的“文件变化、消息发送、浏览器拉取”三步是否成立。 ### HMR 会影响生产环境吗? 不会,HMR runtime 只在开发环境注入,生产构建不会依赖它。需要注意的是,HMR 让开发反馈很快,但也可能掩盖初始化流程问题,因为页面没有从零加载。涉及登录态初始化、全局缓存、首屏请求顺序时,偶尔手动刷新页面是必要的。生产问题最终还是要用 `vite build` 和真实构建产物验证。
服务端5月31日 16:34
为什么 Vite 开发用 esbuild,生产构建却用 Rollup?Vite 在开发环境主要依赖原生 ESM、esbuild 和开发服务器,在生产环境默认使用 Rollup 打包。这个设计不是“开发一套、上线另一套”的随意拼接,而是把两个阶段最在意的目标分开处理:开发时要快启动、快更新、少等待;上线时要稳定产物、代码分割、tree-shaking、资源压缩和缓存友好。 ## 开发环境为什么不先打包 传统打包工具在启动 dev server 前,通常要先分析入口、递归构建依赖图,再把一大包代码交给浏览器。项目越大,启动越慢,改一个文件也可能牵动一片模块。Vite 的思路更直接:源码模块先按浏览器原生 ESM 方式请求,浏览器需要哪个文件,开发服务器就转换哪个文件。 这里 esbuild 主要做两件事。第一是依赖预构建,把 CommonJS 或多入口依赖转换成更适合浏览器加载的 ESM。第二是处理 TypeScript、JSX 等语法转换,因为 esbuild 用 Go 写,冷启动和转换速度通常明显快于纯 JavaScript 转译链。 ```ts // vite.config.ts import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], optimizeDeps: { include: ['react', 'react-dom'] }, server: { port: 5173, hmr: true } }) ``` 这段配置里的 `optimizeDeps` 就是开发期优化依赖的入口。踩坑点在于:如果某个依赖内部动态引用很复杂,Vite 自动扫描可能漏掉它,表现为开发时偶发加载失败或 HMR 异常,这时手动 include 往往比怀疑业务代码更有效。 ## 生产环境为什么还要 Rollup 生产环境关注的是最终资源质量。浏览器可以加载 ESM,但真实线上页面不能把几千个源码模块原样丢给用户,否则请求数量、缓存策略、兼容性和压缩效果都会变差。Rollup 更擅长静态分析,它能把模块合并成合理的 chunk,删除未使用导出,提取 CSS,并配合插件处理资源 hash。 Vite 使用 Rollup 不是因为 esbuild 不快,而是因为“快”和“产物可控”不是同一个目标。esbuild 也能打包,但 Rollup 的插件生态、chunk 控制和库模式构建更成熟。对业务应用来说,生产构建慢一点可以接受;如果产物拆包不合理、缓存失效频繁或公共依赖重复打入多个 chunk,线上代价会更高。 ```ts export default defineConfig({ build: { target: 'es2018', sourcemap: false, rollupOptions: { output: { manualChunks: { vendor: ['react', 'react-dom'] } } } } }) ``` `manualChunks` 不是越细越好。拆得太粗,首屏包大;拆得太细,请求和调度成本上升。常见取舍是把稳定的大依赖单独拆出来,让业务代码变更时不影响 vendor 缓存。 ## 开发和生产差异会带来什么边界 Vite 的双模式很快,但也意味着开发环境不等于生产环境。开发期按需转换,生产期整体打包,所以有些问题只会在 `vite build` 后暴露,比如动态导入路径无法静态分析、Node API 被错误带入浏览器、CSS 顺序在打包后变化。 因此团队里不能只跑 `vite` 就认为没问题。提交前至少跑一次 `vite build`,涉及路由懒加载、第三方库升级、浏览器兼容目标变化时,还应该用 `vite preview` 看构建产物。 ## 追问 ### Vite 开发环境完全不用打包吗? 严格说,源码部分通常不做传统意义上的全量打包,而是按浏览器请求即时转换。依赖部分会做预构建,因为很多 npm 包并不是天然适合浏览器直接按 ESM 加载。这个取舍让启动速度更快,但也带来依赖扫描边界,遇到动态 require 或条件导出时可能需要手动配置。不要把“不打包”理解成“不处理”,Vite 只是把处理时机拆开了。 ### esbuild 这么快,为什么生产不全用它? esbuild 的优势是速度,尤其适合开发期频繁转换。生产构建还要考虑 chunk 结构、插件兼容、资源命名、CSS 提取和库模式输出,这些是 Rollup 长期积累的强项。用 esbuild 全量生产打包不是不行,但复杂项目里可控性通常不如 Rollup。实际选择时,开发体验优先速度,线上产物优先稳定和可调。 ### 开发正常但 build 失败通常查哪里? 先看动态导入、环境变量和依赖格式。开发时某些代码路径可能没被浏览器请求到,生产构建会做全局静态分析,所以隐藏问题更容易暴露。常见坑是 `import(path)` 的 path 无法被 Rollup 推断,或把 `process.env`、`fs` 这类 Node 侧能力带进前端包。排查时不要只盯报错文件,顺着依赖链看是谁把不该进浏览器的代码引入了。 ### 如何判断要不要配置 manualChunks? 小项目通常先别配,Vite 默认策略已经够用。只有当构建产物里某个 chunk 过大、公共依赖重复、或者业务发布导致大依赖缓存频繁失效时,再考虑手动拆分。配置时要看真实 bundle 分析结果,而不是凭感觉把所有库都拆出来。拆包本质是在首屏请求数和缓存命中率之间做取舍。 ### vite preview 能代替线上验证吗? 不能完全代替,但它能帮你用本地静态服务器检查生产产物。它适合发现资源路径、懒加载、路由 fallback、hash 文件引用等问题。边界在于它不等同于 CDN、网关和真实浏览器矩阵,也不会模拟线上缓存策略。上线前如果改了 `base`、反向代理或静态资源域名,仍然需要在接近生产的环境验证。
服务端5月31日 16:17
Vite 依赖预构建有什么用?什么时候需要手动配置?Vite 启动快,并不是因为它什么都不做。业务源码交给浏览器按需请求,但 npm 依赖会先做一次依赖预构建。它把 CommonJS/UMD 转成 ESM,也把零散依赖合并成较少请求。 ## 为什么需要预构建 浏览器原生 ESM 不认识裸模块导入,也不能直接执行 CommonJS。Vite 会扫描入口源码,找到第三方依赖,再用 esbuild 打到缓存目录。 ```ts import React from 'react' import { debounce } from 'lodash-es' ``` 如果让浏览器逐个请求某些 ESM 包内部的几百个文件,开发首屏会很慢。预构建的取舍是冷启动多做一点工作,换来后续加载更稳定。 ## 缓存在哪里 预构建结果通常在 `node_modules/.vite`。Vite 会根据 lockfile、package.json、配置文件判断缓存能否复用。依赖没变时,第二次启动通常更快。 ```bash npx vite --force ``` `--force` 会强制刷新预构建缓存。升级依赖、切分支或调整别名后,如果开发环境行为很怪,先刷新缓存是有效排查手段。不要把 `.vite` 提交到仓库,它只是本机开发产物。 ## include 用来补扫描盲区 Vite 的扫描很快,但不是全知全能。动态导入、运行时拼接、插件生成入口,都可能让依赖第一次没被发现。页面访问到新依赖后,Vite 会重新优化并刷新页面。 ```ts import { defineConfig } from 'vite' export default defineConfig({ optimizeDeps: { include: ['lodash-es', 'dayjs/plugin/utc'] } }) ``` `include` 适合提前加入确定会用、但扫描器可能看不到的依赖。不要把所有依赖都塞进去,预构建越多,冷启动和缓存失效时等待越明显。 ## exclude 不是性能开关 `exclude` 用来排除不该预构建的依赖。比如某个包必须保持源码形态给插件处理,或者预构建后模块结构出错,才适合排除。 ```ts export default defineConfig({ optimizeDeps: { exclude: ['some-vite-plugin-runtime'] } }) ``` 常见误区是觉得大型依赖排除后一定更快。实际上它可能产生更多浏览器请求,或者导致 CommonJS 无法直接运行。是否排除要看错误信息和模块格式,不要只看包体积。 ## esbuildOptions 只做兼容修正 预构建由 esbuild 执行,因此可以通过 `optimizeDeps.esbuildOptions` 做少量定制,例如 define、loader 或 esbuild 插件。 ```ts export default defineConfig({ optimizeDeps: { esbuildOptions: { define: { global: 'globalThis' }, loader: { '.js': 'jsx' } } } }) ``` 边界是它只面对依赖预构建,不等同于整个项目的编译配置。修依赖兼容性可以,用它替代 Babel 或框架插件就不合适。 ## monorepo 更容易踩坑 在 monorepo 里,workspace 包可能既像源码,又像依赖。Vite 对链接依赖通常会按源码处理,方便调试,但深层依赖可能没有被预构建。 ```ts export default defineConfig({ optimizeDeps: { include: ['@acme/shared > lodash-es'] }, server: { fs: { allow: ['..'] } } }) ``` 本地包的取舍是调试便利和启动性能。复用包最好明确输出 ESM,并写清 `exports`、`module` 和 `types`。 ## 追问 ### 预构建和生产打包是一回事吗? 不是。预构建主要服务开发环境,让依赖能被浏览器快速加载;生产构建由 Rollup 负责,目标是输出可部署产物。踩坑点是以为 `optimizeDeps` 能控制生产分包,实际生产分包要看 `build.rollupOptions`。 ### 为什么启动后页面会自动刷新一次? 通常是首次扫描没发现某个依赖,页面访问到它时才触发重新预构建。预构建完成后模块地址变化,浏览器需要刷新。可以把这个依赖加入 `include`,用一点冷启动时间换更稳定的开发过程。 ### exclude 大依赖能减少时间吗? 有时能,但不是通用优化。排除后浏览器可能请求更多文件,Vite 也可能做更多运行时转换。只有预构建导致兼容问题,或依赖必须保持源码给插件处理时,才适合 exclude。 ### CommonJS 包为什么依赖预构建? 浏览器不能直接执行 `require` 和 `module.exports`。Vite 用 esbuild 把 CommonJS 转成 ESM,开发环境才能加载。边界是转换不能替代 Node 运行时能力,依赖如果需要 `fs`、`path`,前端仍要换库或做 polyfill。
服务端5月31日 16:17
如何开发一个 Vite 插件?常用钩子怎么选?Vite 插件就是一个带 `name` 和若干钩子的对象。它和 Rollup 插件很像,因为生产构建阶段复用了 Rollup;但 Vite 还有开发服务器、HMR、HTML 转换和依赖预构建,所以写插件不能只按构建阶段思考。 ## 最小插件怎么写 插件通常写成函数,方便接收选项。真正需要哪个钩子就写哪个,不要一开始就把所有钩子列满。 ```ts import type { Plugin } from 'vite' export function replaceVersion(version: string): Plugin { return { name: 'replace-version', transform(code, id) { if (!id.endsWith('.ts') && !id.endsWith('.tsx')) return null return code.replace(/__APP_VERSION__/g, JSON.stringify(version)) } } } ``` `transform` 很常用,也最容易拖慢项目。每个模块都可能经过它,所以一定要先过滤文件类型和范围。 ## config 和 configResolved 怎么选 `config` 适合补默认配置、插入别名、修改构建选项。`configResolved` 在最终配置确定后执行,适合读取配置、保存状态、输出调试信息,不建议再改配置。 ```ts export function inspectMode(): Plugin { let isBuild = false return { name: 'inspect-mode', configResolved(config) { isBuild = config.command === 'build' }, transform(code, id) { if (isBuild && id.endsWith('.js')) return code return null } } } ``` 边界是“改配置”和“读配置”要分开。都塞进 `configResolved`,后续会遇到插件顺序和覆盖问题。 ## resolveId 和 load 适合虚拟模块 如果希望用户写 `import data from 'virtual:xxx'`,就需要 `resolveId` 和 `load`。前者声明模块由插件接管,后者返回模块代码。 ```ts export function virtualBuildTime(): Plugin { const virtualId = 'virtual:build-time' const resolvedId = '\0' + virtualId return { name: 'virtual-build-time', resolveId(id) { if (id === virtualId) return resolvedId }, load(id) { if (id === resolvedId) { return `export const buildTime = ${JSON.stringify(new Date().toISOString())}` } } } } ``` 虚拟模块适合构建时间、自动路由、主题变量、Markdown 索引。内部 ID 加 `\0` 前缀,可以避免被当成真实文件继续解析。 ## configureServer 和 handleHotUpdate 只服务开发 `configureServer` 可以加中间件,适合 mock、调试端点或开发期资源服务。`handleHotUpdate` 用来控制文件变化后的 HMR 范围。 ```ts export function mockHealth(): Plugin { return { name: 'mock-health', apply: 'serve', configureServer(server) { server.middlewares.use('/__health', (_req, res) => res.end('ok')) } } } ``` 这些能力不会出现在生产构建里。要在文档里写清楚,否则使用者可能误以为插件提供了线上接口能力。 ## 插件顺序和阶段要克制 `enforce: 'pre' | 'post'` 控制相对顺序,`apply: 'serve' | 'build'` 限制运行阶段。只服务开发的插件写 `apply: 'serve'`,只改产物的插件写 `apply: 'build'`。 ```ts export function devOnlyPlugin(): Plugin { return { name: 'dev-only', apply: 'serve', enforce: 'pre' } } ``` 不要滥用 `pre`。太早处理文件,可能抢在 Vue、React、Svelte 插件之前,把还没转换的框架文件处理坏。 ## 追问 ### Vite 插件和 Rollup 插件能完全通用吗? 不能完全通用。生产构建阶段兼容度高,开发阶段则有 dev server、HMR、HTML 入口等 Vite 专属行为。取舍是纯构建类插件尽量按 Rollup 写,开发体验类插件要使用 Vite 钩子。 ### transform 为什么要先过滤 id? 因为开发时每个请求模块都可能经过 transform,不过滤会处理大量无关文件。小项目不明显,大项目 HMR 会变慢。边界是不要一刀切排除所有依赖,有些 workspace 源码包确实需要被插件处理。 ### 什么时候该用虚拟模块? 当数据没有真实源文件,或不想让业务直接读文件系统时,可以用虚拟模块。比如构建时间、路由清单、主题变量都合适。踩坑点是虚拟模块增加调试成本,所以命名和输出要稳定。 ### handleHotUpdate 要不要精细控制? 只有理解依赖关系时才精细控制。配置文件影响全局行为时,直接 full reload 往往比错误的局部 HMR 更可靠。取舍是刷新体验和正确性,插件开发里正确性优先。
服务端5月31日 16:17
Vite 配置文件怎么写?路径别名如何避免踩坑?Vite 配置文件不复杂,但项目变大后,真正需要管理的东西会变多:开发服务器、代理、路径别名、构建输出、CSS、环境变量、依赖预构建和插件顺序。好的配置不是把所有选项都写满,而是把开发体验和生产产物的边界分清楚。 ## 配置文件从哪里来 Vite 默认识别 `vite.config.js`、`vite.config.mjs`、`vite.config.ts`、`vite.config.cjs`。现代 TypeScript 项目通常用 `vite.config.ts`,配合 `defineConfig` 能拿到类型提示。 ```ts import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], server: { port: 5173, open: true }, build: { outDir: 'dist', sourcemap: false } }) ``` 如果配置要根据命令变化,可以导出函数。`command` 区分 `serve` 和 `build`,`mode` 对应 development、production 或自定义模式。 ```ts export default defineConfig(({ command, mode }) => ({ base: mode === 'production' ? '/app/' : '/', build: { sourcemap: command === 'serve' } })) ``` ## server 只影响开发环境 `server` 常用选项有 `host`、`port`、`open`、`proxy`、`cors`、`https`、`watch`。本地联调最常见的是代理,把 `/api` 转到后端服务,避免开发环境跨域。 ```ts export default defineConfig({ server: { host: '0.0.0.0', port: 5173, proxy: { '/api': { target: 'https://api.example.com', changeOrigin: true, rewrite: p => p.replace(/^\/api/, '') } } } }) ``` 边界要记住:`server.proxy` 不会进入生产环境。上线后的代理要交给 Nginx、网关、BFF 或后端 CORS。 ## build 决定产物形态 `build.outDir` 控制输出目录,`assetsDir` 控制资源目录,`target` 决定语法目标,`minify` 决定压缩方式。复杂分包和文件命名通常写在 `rollupOptions`。 ```ts export default defineConfig({ build: { target: 'es2020', chunkSizeWarningLimit: 800, rollupOptions: { output: { chunkFileNames: 'assets/[name]-[hash].js', assetFileNames: 'assets/[name]-[hash][extname]' } } } }) ``` 不要一看到 chunk 警告就手写大量 `manualChunks`。分包能改善缓存,也可能制造更多请求;先分析体积来源,再动配置更稳。 ## 路径别名要配两处 Vite 的别名写在 `resolve.alias`,TypeScript 的编辑器和类型解析写在 `tsconfig.json` 的 `paths`。只配一边,常见结果是开发能跑但编辑器报错,或编辑器不报错但构建失败。 ```ts import { fileURLToPath, URL } from 'node:url' import { defineConfig } from 'vite' export default defineConfig({ resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } } }) ``` ```json { "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"] } } } ```` ESM 配置里不要直接用 `__dirname`。用 `fileURLToPath(new URL(..., import.meta.url))` 更稳。 ## CSS 和 optimizeDeps 怎么放 CSS 配置常用于 CSS Modules、预处理器和 PostCSS;`optimizeDeps` 用来处理依赖预构建的扫描盲区或兼容性问题。 ```ts export default defineConfig({ css: { modules: { localsConvention: 'camelCaseOnly' }, preprocessorOptions: { scss: { additionalData: '@use "@/styles/vars.scss" as *;' } } }, optimizeDeps: { include: ['lodash-es'] } }) ``` `additionalData` 适合注入变量和 mixin,不适合注入会产出实际 CSS 的规则,否则每个文件都会重复生成。 ## 追问 ### 为什么配了 @ 别名,编辑器还是报错? 通常是只配了 `vite.config.ts`,没配 `tsconfig.json`。Vite 管运行和构建,TypeScript 管类型和编辑器提示,它们不是同一个解析器。取舍上两份配置有重复,但显式配置最可控。 ### server.proxy 能当线上代理吗? 不能。它只在 `vite dev` 的开发服务器里生效,构建产物只是静态文件。线上要用 Nginx、CDN 边缘函数、网关或后端 CORS;本地正常、线上 404,多半就是把开发代理当成了生产能力。 ### base 应该怎么配置? 部署在域名根路径就用 `/`,部署在 `/app/` 就写 `/app/`。同时还要同步前端路由的 basename 或 history base。只改 Vite 的 `base` 不改路由,刷新深层页面仍可能出问题。 ### optimizeDeps.include 什么时候需要? 当动态导入、插件生成入口或依赖扫描盲区导致开发时反复重新优化,可以把依赖加入 `include`。但它不是越多越好,包含太多会拖慢冷启动。先看控制台提示和缓存行为,再决定是否手动配置。
服务端5月31日 16:17
Vite 如何加载图片、CSS 和 public 静态资源?Vite 处理静态资源时,先分清两类文件:一类在 `src` 里被代码引用,会进入构建图;另一类放在 `public`,按原路径复制到输出目录。开发环境为了快,Vite 通常让浏览器直接请求资源;生产构建时,Rollup 会接管引用、加 hash、压缩或内联。 ## src 中的图片和字体怎么加载 放在 `src/assets` 的图片、字体、视频,只要被 JS、TS、Vue、React 组件或 CSS 引用,Vite 就能分析到它们。开发时导入结果通常是一个可访问 URL,构建后会变成带 hash 的文件名,方便长期缓存。 ```ts import logoUrl from './assets/logo.png' const img = new Image() img.src = logoUrl document.body.appendChild(img) ``` 这种方式最稳,因为 `base`、输出目录和文件 hash 都由 Vite 处理。不要在代码里拼 `/src/assets/${name}.png`,开发时可能能访问,构建后就容易失效。 ## ?url、?raw、?inline 有什么区别 默认导入图片、字体等资源时,Vite 返回 URL。`?raw` 会把文件内容当字符串返回,适合文本模板、shader;`?inline` 会强制内联成 base64;`?url` 则显式要求返回资源地址。 ```ts import fileUrl from './manual.pdf?url' import shader from './shader.glsl?raw' import icon from './icon.svg?inline' ``` 内联能少一次请求,但会让 JS 或 CSS 变大,也会让缓存粒度变粗。小 SVG 可以内联,大图片不要为了省请求硬塞进 bundle。 ## CSS 也是模块 Vite 允许直接导入 CSS,也支持 CSS Modules 和 Sass、Less 等预处理器。开发时样式会被注入并支持 HMR;生产时会提取、压缩,并根据 chunk 关系输出。 ```ts import './global.css' import styles from './button.module.css' button.className = styles.primary ``` CSS Modules 适合组件局部样式,全局 CSS 适合 reset、主题变量和少量基础规则。不要把所有页面样式都塞进入口文件,否则异步页面拆包后仍会被全局样式影响。 ## public 目录适合固定路径文件 `public` 下的文件会原样复制到构建输出根目录,不会被 hash、压缩或依赖分析。站点图标、robots.txt、第三方验证文件、必须固定 URL 的下载文件适合放这里。 ```html <link rel="icon" href="/favicon.ico" /> <img src="/brand/banner.png" /> ``` 如果资源需要版本化缓存,就不要放 public;如果外部系统必须按固定路径访问,public 更合适。部署在子路径时要特别注意 `base`,根路径写死会导致线上资源 404。 ## 常用配置怎么写 ```ts import { defineConfig } from 'vite' export default defineConfig({ base: '/app/', assetsInclude: ['**/*.glb'], build: { assetsInlineLimit: 4096, rollupOptions: { output: { assetFileNames: 'assets/[name]-[hash][extname]' } } } }) ``` `assetsInclude` 适合让 Vite 把自定义后缀当资源处理,比如 3D 模型或特殊二进制文件。如果需要读取内容并转换成代码,就应该写插件,而不是只加资源匹配。 ## 追问 ### src/assets 和 public 应该怎么选? 被组件或样式引用、希望加 hash 的资源放 `src/assets`。必须固定路径、给浏览器或第三方平台直接读取的文件放 `public`。取舍在于是否进入构建图:进入构建图更利于缓存和路径改写,不进入构建图更直接但不受 Vite 保护。 ### assetsInlineLimit 调大是不是一定更快? 不一定。调大会减少请求,但会增大 JS 或 CSS 体积,首屏解析和缓存失效成本也会增加。边界是只内联很小、稳定、首屏必用的资源,大图和复用图片通常单独输出更好。 ### 为什么生产环境图片路径变了? 生产构建会给资源加 hash,并把它们复制到输出目录,这是为了长期缓存。代码只应该使用 import 得到的 URL,不要依赖开发时看到的 `/src/...` 地址。常见坑是接口返回源码相对路径,构建后前端无法再访问。 ### CSS 为什么没有按源文件拆开? Vite 的 CSS 输出跟 JS chunk 关系有关,不是按目录机械拆分。同步入口样式可能合并,异步页面样式可能独立输出。取舍是请求数量和缓存粒度,排查时应看构建产物和引用链,而不是只看源文件名。
服务端5月31日 16:17
Vite 和 Webpack 该怎么选?Vite 和 Webpack 的区别,不是“新工具淘汰旧工具”这么简单。Vite 开发阶段用原生 ESM 按需加载,生产阶段交给 Rollup 打包;Webpack 从一开始就围绕完整依赖图和打包体系设计。一个默认轻快,一个承载复杂工程多年积累,选择时要看项目边界。 ## 开发阶段差异最大 Webpack dev server 通常要先分析入口、loader、plugin 和依赖图,项目越大,冷启动越慢。Vite 启动时先把服务器跑起来,浏览器请求哪个源码模块,再转换哪个模块;第三方依赖则通过 esbuild 预构建,减少 CommonJS 兼容和请求过多的问题。 这也是 Vite HMR 通常更快的原因。它更接近局部更新,而 Webpack 往往要重新编译受影响的模块集合。对页面多、源码多、定制不重的项目,这个差距非常明显。 ## 生产构建各有优势 Vite 不是全程 esbuild,生产构建默认使用 Rollup。Rollup 在 ESM tree-shaking、库模式和产物结构控制上很成熟。Webpack 的优势则是复杂 loader、Module Federation、历史插件生态和高度定制能力。 ```ts export default defineConfig({ build: { target: 'es2020', rollupOptions: { output: { manualChunks: { vendor: ['react', 'react-dom'] } } } } }) ``` 新项目、现代浏览器、Vue 3、React、Svelte、组件库文档和普通中后台,大多可以优先选 Vite。如果是多年老系统,依赖自定义 loader、旧浏览器兼容、私有构建平台或复杂微前端,Webpack 仍然可能是更稳的选择。 ## 迁移时别硬翻译配置 迁移 Vite 最常见的坑有三类:CommonJS 和动态 require 处理不同;客户端环境变量要从 `process.env` 改成 `import.meta.env`;Webpack loader 和 Vite/Rollup 插件不是一一对应。不要把旧配置逐条翻译,很多配置在 Vite 里本来不需要。 ```ts const api = import.meta.env.VITE_API_URL ``` 更稳的迁移方式是从新页面、文档站、组件预览或独立子应用开始。先验证依赖、环境变量、部署和监控链路,再决定是否扩大范围。 ## 追问 ### Vite 一定比 Webpack 快吗? 开发冷启动和 HMR 上,Vite 通常更快,尤其是现代前端项目。生产构建不一定,取决于压缩器、插件、依赖体积和分包策略。Webpack 如果用了持久化缓存和 SWC/esbuild loader,也可能表现不错。边界是不要拿空模板 benchmark 替真实项目做决定。 ### 老项目适合直接迁到 Vite 吗? 不一定。老项目常有隐藏构建逻辑,比如自定义 loader、全局变量、polyfill 和特殊别名。直接迁移可能主流程正常,边缘页面或灰度环境才出问题。更稳的取舍是先迁低风险模块,再扩大范围。 ### 生产包体积谁更小? 没有绝对答案。Vite 借助 Rollup 的 ESM tree-shaking,Webpack 也有 splitChunks、sideEffects 和成熟插件。包体积更多取决于依赖写法、是否按需加载、是否引入大而全的库。常见坑是换工具前不清依赖,让构建工具背锅。 ### 微前端场景选哪个? 如果重度依赖 Module Federation,Webpack 仍然更成熟。Vite 也有相关插件,但在共享依赖、版本协商和遗留系统接入上要更谨慎。取舍是成熟稳定还是开发速度。核心交易系统不要为了工具升级牺牲可预测性。 ### 团队熟悉 Webpack,还值得换吗? 如果启动和 HMR 已经明显拖慢效率,值得小范围试点。Vite 配置心智更轻,但团队仍要理解 ESM、Rollup 和环境变量差异。迁移成本还包括 CI、部署、监控和文档更新。不要只让一个人迁完,否则后续没人维护。
服务端5月31日 16:17
Vite 为什么不默认做 TypeScript 类型检查?Vite 支持 TypeScript,但默认只转译,不做完整类型检查。很多人迁移后会疑惑:页面能跑,`tsc --noEmit` 却报错。原因是 Vite 把开发反馈速度放在前面,类型检查需要理解整个项目的类型图,如果塞进每次模块转换,HMR 会明显变慢。 ## Vite 负责转译,不负责兜底 `.ts`、`.tsx` 文件会被 esbuild 快速转成 JavaScript,类型标注会被擦除,JSX 和新语法也会被处理。但 esbuild 不会像 TypeScript 编译器那样做完整类型分析,所以“Vite 能运行”不等于“类型安全”。 ```ts type User = { id: number } const user: User = { id: '1' } // 需要 tsc 才能稳定拦住 ``` ## tsconfig 怎么配 Vite 项目建议使用 `moduleResolution: "bundler"`,它更贴近现代打包器对 ESM、exports 和条件导出的解析方式。`isolatedModules` 建议打开,能提前暴露不适合单文件转译的写法。`noEmit` 让 TypeScript 只检查,不输出文件,避免和 Vite 构建产物混在一起。 ```json { "compilerOptions": { "target": "ES2020", "module": "ESNext", "moduleResolution": "bundler", "strict": true, "isolatedModules": true, "noEmit": true, "skipLibCheck": true }, "include": ["src", "vite.config.ts"] } ``` `skipLibCheck` 是现实取舍。业务项目开启它可以减少依赖声明文件带来的噪音和耗时;基础库、SDK 或类型要求极高的项目要谨慎,因为它可能掩盖第三方类型问题。 ## 类型检查放到哪里 本地开发可以依赖编辑器提示,也可以接入 `vite-plugin-checker`。团队协作时,CI 必须执行 `tsc --noEmit`,否则有人关闭编辑器提示后,类型错误仍可能进入主干。 ```ts import checker from 'vite-plugin-checker' export default defineConfig({ plugins: [checker({ typescript: true })] }) ``` 路径别名要同时配置 Vite 和 TypeScript。`resolve.alias` 影响运行和构建,`paths` 影响编辑器和类型检查;只配一边,就会出现能运行但编辑器报错,或编辑器正常但构建失败。 ## 追问 ### 为什么 Vite 不直接检查类型? 因为 Vite 的核心链路是按需转译模块,而完整类型检查是项目级静态分析。把它放进每次请求,会拖慢冷启动和 HMR。取舍是先获得运行反馈,再用编辑器、checker 或 CI 保证类型安全。踩坑点是把 Vite build 通过当成类型通过。 ### `vite-plugin-checker` 和 `tsc --noEmit` 怎么选? 本地想尽快看到错误,可以用 `vite-plugin-checker`。上线前要稳定兜底,应该跑 `tsc --noEmit`。插件更贴近开发体验,但大项目里也会占内存和 CPU。边界是合并门禁不要依赖某个开发者的编辑器。 ### `isolatedModules` 为什么会让一些写法报错? 它要求每个文件都能被独立安全转译,这和 esbuild 的工作方式一致。只导出类型却按值导入、部分 namespace 写法,都可能因此暴露问题。限制看起来烦,但能提前发现运行时会消失的类型代码。取舍是少一点 TS 写法自由,换来构建链路一致。 ### 路径别名为什么要配两份? Vite 和 TypeScript 是两个解析系统。只配 Vite,编辑器和 `tsc` 可能找不到模块;只配 TS,构建时可能解析失败。很多人以为是缓存问题,其实是配置源不一致。monorepo 里还要看 workspace 包的 exports 和声明文件。 ### 生产构建前还需要 ESLint 吗? 需要,ESLint 和 TypeScript 关注点不同。TS 主要检查类型关系,ESLint 更擅长发现 Hook 依赖、未处理 Promise、禁用 API 和潜在逻辑问题。取舍是规则越多 CI 越慢,所以要区分阻断规则和建议规则。不要把 lint 只当格式化工具。
服务端5月31日 16:17
Vite 构建慢该从哪些地方优化?Vite 性能优化先别急着堆配置,先判断慢在哪里:冷启动、页面首次加载、HMR,还是生产构建。Vite 开发环境用原生 ESM 按需转换,生产构建走 Rollup,两条链路的瓶颈不一样。把 Webpack 时代的“全量调参”直接搬过来,常见结果是配置变复杂,速度没明显提升。 ## 先定位瓶颈 开发阶段优先看依赖预构建是否反复执行、插件 transform 是否过重、浏览器请求是否太碎。生产阶段重点看 source map、压缩器、大依赖和 chunk 策略。可以先用调试日志和产物分析确认问题,而不是凭感觉改配置。 ```bash pnpm vite --debug pnpm build ``` ## 依赖预构建怎么配 `optimizeDeps` 主要影响 dev server。对导入链复杂、CommonJS 包或请求特别碎的依赖,可以显式 include;对本地源码包、需要保留模块结构的包,再考虑 exclude。盲目排除大依赖会让浏览器请求暴增,启动看似快了,页面反而更慢。 ```ts export default defineConfig({ optimizeDeps: { include: ['lodash-es', 'axios'], exclude: ['@my-org/source-lib'] } }) ``` ## 构建优化看分包和压缩 `manualChunks` 不适合一刀切。稳定的大依赖可以单独拆,编辑器、图表、富文本这类低频模块适合动态导入。压缩默认用 esbuild 通常够快;只有确实需要更细压缩规则时,再换 terser。 ```ts export default defineConfig({ build: { sourcemap: false, minify: 'esbuild', rollupOptions: { output: { manualChunks: { react: ['react', 'react-dom'] } } } } }) ``` 插件也要按环境启用。图片压缩、可视化分析、Markdown 转换这类插件如果每次开发都跑,很容易拖慢 HMR。静态资源方面,`assetsInlineLimit` 不是越大越好,大图内联会撑大 JS 或 CSS,影响缓存。 ## 追问 ### 为什么 Vite 开发快,构建仍然可能慢? 开发环境不做全量打包,只按浏览器请求转换模块,所以反馈很快。生产构建仍要完整分析依赖图、tree-shaking、分包和压缩,大项目自然会慢。这里的取舍是开发体验和产物质量不能用同一套机制解决。常见坑是误以为 dev 快就代表 build 一定快。 ### `manualChunks` 应该怎么拆? 优先按访问频率和缓存收益拆,而不是按库名机械拆。React、Vue 这类稳定核心依赖适合单独 chunk,低频页面依赖更适合懒加载。拆太细会增加请求调度成本,拆太粗会拖慢首屏。边界是首屏必须用的代码不要为了分包好看强行拆远。 ### 要不要把大依赖放 CDN? CDN 外部化能减小包体,也可能利用公共缓存。代价是运行时依赖外部服务,版本、可用性和内网访问都要兜底。官网或活动页可以考虑,后台系统和离线部署通常不适合。常见坑是本地构建正常,线上 CDN 被拦截导致白屏。 ### source map 关掉是不是一定更好? 关闭 source map 能缩短构建并减少产物体积,但线上排错会变难。更合理的是普通生产包关闭,灰度或错误监控单独上传 source map。取舍点是速度、源码安全和问题定位效率。不要把 source map 直接公开到静态目录。 ### HMR 慢时先查什么? 先查改动是否牵连全局入口、统一导出文件或巨大的状态模块。再查插件是否每次 transform 都做同步 IO 或全量扫描。Vite 本身 HMR 通常很快,慢多半来自项目结构和插件副作用。monorepo 里还要明确 watch 范围。
服务端5月31日 16:17
Vite 如何集成 Vue、React 和 Svelte?插件配置怎么选?Vite 和框架集成的核心,不是把脚手架命令背下来,而是理解插件负责什么。Vite 自己处理开发服务器、依赖预构建、静态资源和 Rollup 构建;Vue、React、Svelte 这些框架特有的语法转换、热更新和编译选项,则交给对应插件。这样拆开看,配置就不容易乱。 ## 新项目优先用官方模板 创建项目时最稳妥的方式是使用官方模板。模板会装好框架插件、入口文件和基础 TypeScript 配置,适合从零开始。 ```bash npm create vite@latest my-vue-app -- --template vue-ts npm create vite@latest my-react-app -- --template react-ts npm create vite@latest my-svelte-app -- --template svelte-ts ``` 如果是老项目迁移,不建议一次性照搬模板。更安全的做法是先把构建入口跑通,再逐步迁移别名、环境变量、CSS 预处理器和测试配置。取舍点在于速度和稳定性:新项目追求开箱即用,迁移项目更需要可回滚。 ## Vue 怎么接入 Vue 项目主要使用 `@vitejs/plugin-vue`,它负责处理单文件组件、模板编译和 HMR。如果项目使用 JSX 或 TSX,再额外加 `@vitejs/plugin-vue-jsx`。 ```ts import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import vueJsx from '@vitejs/plugin-vue-jsx' export default defineConfig({ plugins: [vue(), vueJsx()], resolve: { alias: { '@': '/src' } } }) ``` Vue 的边界是插件只负责编译,不替你决定状态管理、路由和组件库。比如 Element Plus、Pinia、Vue Router 都要按各自方式接入。踩坑点是路径别名只配 Vite 不够,TypeScript 还要在 `tsconfig.json` 里配 `paths`,否则编辑器会报找不到模块。 ## React 怎么接入 React 官方插件通常选 `@vitejs/plugin-react`。它支持 Fast Refresh,也会处理 JSX 转换和 Babel 能力。如果团队追求更快的编译速度,可以评估 `@vitejs/plugin-react-swc`,但 Babel 插件生态依赖重的项目要谨慎。 ```ts import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], server: { port: 5173 }, build: { sourcemap: true } }) ``` 这里的取舍很实际:Babel 版兼容性更稳,SWC 版速度更好。大多数业务项目先用 Babel 版足够,只有在冷启动或 HMR 明显慢时再切换。React 17 以前的项目还要确认 JSX runtime 设置,避免升级 Vite 后出现 JSX 编译行为不一致。 ## Svelte 和 Solid 怎么接入 Svelte 使用 `@sveltejs/vite-plugin-svelte`,Solid 使用 `vite-plugin-solid`。这类框架的编译器参与度更高,所以不要随便混用插件顺序。一般把框架插件放在 plugins 数组前面,再放检查、压缩、分析等辅助插件。 ```ts import { defineConfig } from 'vite' import { svelte } from '@sveltejs/vite-plugin-svelte' export default defineConfig({ plugins: [svelte()], css: { preprocessorOptions: { scss: { additionalData: '@use "src/styles/vars" as *;' } } } }) ``` ## 通用配置别忘了配两处 路径别名、环境变量、CSS 预处理器是所有框架都会遇到的配置。Vite 负责运行时和构建解析,TypeScript 负责编辑器和类型检查,两边经常都要配。 ```json { "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"] } } } ``` ## 多框架仓库怎么处理 如果一个 monorepo 里同时有 Vue、React 和组件库,建议每个应用保留自己的 `vite.config.ts`,公共配置抽成函数复用。不要为了“统一”写一个巨大配置文件,然后用一堆条件判断区分框架。那样短期看少了重复,长期会让插件顺序、别名和构建输出互相牵连。 公共层可以放端口约定、alias 生成、环境变量校验和构建分析插件,框架插件仍然留在各应用里。边界清楚以后,某个 React 应用切到 SWC,不会影响 Vue 应用的编译行为。 测试配置也要一起考虑。Vitest、Playwright 或 Jest 不一定自动读取 Vite 的 alias 和 env,迁移时经常出现应用能跑、测试失败的情况。稳妥做法是把路径解析和环境变量校验抽成小工具,应用、测试和构建共用同一份约定。 ## 追问 ### Vite 集成框架时,插件顺序重要吗? 重要,但不用过度紧张。框架插件通常应该靠前,因为它们要先处理 Vue SFC、JSX 或 Svelte 文件。后面的插件再做检查、分析、压缩或自定义转换。踩坑点是多个插件都改同一种文件时,顺序会影响最终代码,遇到奇怪编译问题先简化 plugins 数组。 ### Vue 和 React 项目的 Vite 配置差异大吗? 基础能力差不多,差异主要在框架插件。Vue 插件处理模板和 SFC,React 插件处理 JSX、Fast Refresh 和 Babel/SWC 转换。取舍不在 Vite,而在框架生态:Vue 更强调模板编译约定,React 更依赖 JSX 和函数组件习惯。通用配置如 alias、proxy、env、build 基本可以复用。 ### 老 Webpack 项目迁到 Vite 要注意什么? 不要一开始就追求配置完全等价。Webpack loader、DefinePlugin、动态 require、别名和静态资源规则,都可能需要重写成 Vite/Rollup 的方式。边界是能用浏览器原生 ESM 的代码迁移最顺,依赖 CommonJS 魔法写法的项目会更麻烦。建议先迁入口和开发服务器,再处理构建产物差异。 ### React 插件选 Babel 还是 SWC? Babel 版生态兼容性更好,适合依赖 Babel 插件、宏或老项目的团队。SWC 版速度更快,适合配置简单、项目较大的场景。取舍是性能换生态,不能只看 benchmark。切换前最好跑完整测试和构建,因为一些非标准语法转换在两边表现可能不同。 ### 为什么 alias 配了 Vite,编辑器还是报错? 因为 Vite 的 `resolve.alias` 只影响开发服务器和构建。TypeScript、ESLint、测试框架都有自己的解析逻辑,编辑器通常看的是 `tsconfig.json`。边界是运行能成功不代表类型系统能识别。解决方式是同步配置 `compilerOptions.paths`,测试工具也要按需补 alias 映射。
服务端5月31日 16:17
Vite 环境变量怎么用?为什么只有 VITE_ 前缀能进客户端?Vite 的环境变量分两类:一类给构建工具和服务端配置用,一类会被注入到浏览器代码里。很多线上事故都出在这里:以为 `.env` 里的变量只是本地配置,结果把密钥用 `VITE_` 开头写进了前端包。记住一句话,凡是能通过 `import.meta.env` 在客户端读到的值,都应该被当成公开信息。 ## .env 文件怎么加载 Vite 会按当前 mode 读取环境变量。常见文件有 `.env`、`.env.local`、`.env.development`、`.env.production`、`.env.[mode].local`。本地覆盖文件通常不要提交,因为它经常放个人端口、测试地址或临时开关。 ```bash # .env VITE_APP_TITLE=Admin Console VITE_API_BASE=/api # .env.staging VITE_API_BASE=https://staging-api.example.com # .env.local,不提交 LOCAL_PROXY_TARGET=http://127.0.0.1:7001 ``` 客户端代码只能访问以 `VITE_` 开头的变量,这是 Vite 的安全边界。这个边界不是为了加密,而是为了避免你无意把 `DB_PASSWORD`、`JWT_SECRET` 这类服务端变量打进 JS。变量会在构建时被静态替换,所以生产包里的值不会随着服务器环境自动变化。 ```ts const apiBase = import.meta.env.VITE_API_BASE const isDev = import.meta.env.DEV const mode = import.meta.env.MODE ``` ## 内置变量有哪些 `import.meta.env.MODE` 表示当前模式,默认开发是 `development`,构建是 `production`。`DEV` 和 `PROD` 是布尔值,适合控制调试面板、mock 逻辑和埋点开关。`BASE_URL` 来自 `base` 配置,部署到子目录时很有用。`SSR` 表示代码是否运行在服务端渲染环境,写同构逻辑时要特别注意。 ## vite.config 里怎么读取 在配置文件里不能直接依赖客户端的 `import.meta.env`。需要用 `loadEnv(mode, root, prefix)` 主动加载,第三个参数决定读取哪些前缀。这里有个取舍:传空字符串可以读到所有变量,方便配置代理;但也更容易误用敏感信息,所以只在配置层使用,不要再塞回客户端。 ```ts import { defineConfig, loadEnv } from 'vite' export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), '') return { server: { proxy: { '/api': { target: env.LOCAL_PROXY_TARGET, changeOrigin: true } } } } }) ``` TypeScript 项目还应该补类型,否则变量名写错只能到运行时才发现。 ```ts /// <reference types="vite/client" /> interface ImportMetaEnv { readonly VITE_APP_TITLE: string readonly VITE_API_BASE: string } interface ImportMeta { readonly env: ImportMetaEnv } ``` ## 常见配置边界 环境变量还经常和代理、部署路径、CDN 地址混在一起。接口代理通常只属于开发服务器配置,应该写在 `server.proxy`,不要让业务代码感知本地代理目标。部署子路径则应该优先配置 `base`,再通过 `import.meta.env.BASE_URL` 拼静态资源路径。这样区分以后,开发、预发、生产不会因为一个变量名承担太多含义而互相影响。 还有一个容易忽略的点:`.env` 文件变更后通常要重启 Vite dev server。因为配置和环境变量是在启动阶段加载的,不是每次模块热更新都重新读取。 如果确实需要运行时切换配置,可以让后端在 HTML 里注入 `window.__APP_CONFIG__`,或者让前端启动后先请求一个公开配置接口。这样会多一次维护成本,但能避免每个环境都重新打包。边界仍然一样,接口返回的内容也是公开的,不能放密钥。 ## 追问 ### 为什么生产环境改了服务器变量,前端页面没变化? 因为 Vite 的客户端环境变量是在构建时替换的,不是浏览器运行时再去服务器读。你改了容器或机器上的环境变量,但没有重新 build,打出来的 JS 还是旧值。边界是:构建期配置用 Vite env,运行期配置要走接口、HTML 注入或独立配置文件。多租户系统尤其要注意,不能把租户域名这类运行时信息硬编码进包里。 ### VITE_ 前缀是不是安全机制? 它更像防误伤机制,不是安全机制。加了 `VITE_` 的值会进入客户端包,任何人都能在源码或网络资源里看到。取舍是公开配置可以用它,比如页面标题、接口基础路径、Sentry DSN;真正的密钥必须放服务端。踩坑最多的是把第三方 secret 写成 `VITE_XXX_SECRET`,这等于主动泄露。 ### mode 和 NODE_ENV 是一回事吗? 不是。mode 决定 Vite 加载哪组 `.env.[mode]` 文件,`NODE_ENV` 更多影响依赖库的生产/开发分支。你可以用 `vite build --mode staging` 生成预发包,但它仍然是生产构建。边界是不要用 `MODE !== 'production'` 判断是否压缩或是否启用调试,应该优先看 `import.meta.env.DEV` 和 `PROD`。 ### 环境变量应该怎么做类型和默认值? 类型文件只能告诉 TypeScript “这个变量应该存在”,不能保证运行时真的有值。关键变量最好在启动或构建阶段做校验,缺了就抛错。取舍是简单项目可以直接读,企业项目建议封装一层 `env.ts`,集中处理默认值、布尔转换和错误提示。布尔值也要小心,`.env` 读出来都是字符串,`"false"` 在 JS 里仍然是真值。 ### 框架项目里环境变量放在哪里更合适? 纯前端 Vite 应用可以放在项目根目录的 `.env` 系列文件里。SSR 框架或 BFF 项目要区分客户端变量和服务端变量,不能为了省事全部加 `VITE_`。边界是:浏览器需要知道的才进 `VITE_`,服务器专用的只在服务端读取。CI/CD 里通常提交 `.env.example`,真实值由流水线或部署平台注入。