尧图精选

Vite生态新玩具:五大工具让前端工程化效率翻倍

🕒 发布时间:2026/9/9 18:28:22 📁 来源:尧图网络
前两天在 Vue 社区刷到尤雨溪转发的一条状态他给 Vite 生态里的几个新项目点了一串赞。有人开玩笑说这是“尤雨溪推荐的新玩具”我挨个试了一遍之后觉得这说法其实不太准确——这些库没有一个是为了“好玩”写的它们都在解决真实工程里让前端又慢又烦的问题写 import 写到手软、测试环境慢到想砸电脑、CSS 类名起名困难、路由配置和维护两套系统。这次要重点聊的五个“玩具”分别是 Vitest、UnoCSS、unplugin-auto-import、unplugin-vue-components、vite-plugin-pages。它们不是同一个层级的替代品而是围绕 Vite 构建管线长出来的生态工具有的管测试有的管样式有的管路由有的是把“自动导入”这件事做到极致。如果你正在用 Vue 3 Vite 做中后台项目或者准备从 Webpack 迁移过来这篇应该能帮你省掉不少折腾时间。1. 先说清楚Vite 生态为什么能长出这么多“玩具”很多人以为 Vite 生态爆发是因为尤雨溪个人号召力强其实更根本的原因是插件机制和工程习惯的转变。Vite 从设计第一天就把“开发者体验”放在最前面这让所有围绕它做工具的人都有一个共同目标把日常重复劳动尽可能压缩到零。1.1 插件化是土壤unplugin 是推土机Vite 原本就支持 Rollup 插件协议同时扩展了 dev server 侧独有的钩子比如configureServer、handleHotUpdate。这意味着任何第三方工具都能精准地介入模块解析、代码转换和静态资源处理不需要 Fork 整个 Vite 源码。只要写好一个插件就能在冷启动、热更新、生产构建这三条链路里同时生效。更关键的是unplugin这个项目它把同一个插件逻辑同时编译成 Vite、Webpack、Rollup 三种版本。很多生态工具选择了unplugin作为底层这就让“新玩具”不只在 Vite 圈子传播老 Webpack 项目也能顺手用上。于是你会发现类似自动导入、按需组件、图标插件这些东西一出现就形成了网络效应功能互相叠加使用成本越来越低。1.2 从“你要管所有事”到“框架替你管”另一个背景是前端团队的工程习惯在变化。五六年前我们倾向于显式写 import因为 IDE 自动补全不强隐式全局变量会让代码很难追踪。现在 Volar、ESLint、TS 类型系统都足够成熟隐式导入反而成了提升效率的正道。这五个工具本质上都在做同一件事把“约定”变成“配置”把“配置”变成“零配置”。文件路由约定src/pages下放文件省掉路由表组件自动导入约定src/components下的文件可以被直接使用Vitest 约定复用 Vite 配置UnoCSS 约定类名即样式。这种模式让新项目跑起来的速度远超老一代工具链也让老项目里有了一种“少写代码就是少写 Bug”的爽感。2. 新玩具之一Vitest把单元测试的成本打到接近零Vitest 是 Vite 团队官方语义下最成功的“副产物”之一。它起初是 Antoine 等人的个人项目后来因为性能实在比 Jest 快太多直接成了 Vite 生态里默认测试方案。尤雨溪在各场分享里也反复提到 Vitest 和 Vite 的关系不该让测试使用另一套配置和另一套编译体系。2.1 零配置接入靠的是“复用”而不是“新造”一个 Vite 工程要接入 Vitest通常只需要装一个依赖在vite.config.ts里加test字段/// reference typesvitest / import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], test: { environment: jsdom, globals: true, setupFiles: [./src/test/setup.ts], }, })这里最舒服的一点Vitest 会直接读取vite.config.ts里的resolve.alias、plugins、css.preprocessorOptions。你在项目里配置过指向src测试文件里不用再给一份moduleNameMapper也不会出现“业务代码能跑、测试代码报模块找不到”的老问题。我在一个后台项目里从 Jest 迁移到 Vitest改动量比想象中小大部分describe、it、expect写法完全兼容。最大的感受是跑动速度Jest全量跑一个 40 个测试文件的模块大约要 25 秒Vitest 快照大概 6 到 8 秒。区别来自 Vite 原生 esbuild 转译它不做 Jest 那种模块图逐层拷贝直接在内存里按需转换。2.2 组件测试和 Mock 的风格差异Vitest 对 Vue 组件的测试体验很直接。配合vue/test-utils可以这样写import { mount } from vue/test-utils import Counter from ../components/Counter.vue test(点击按钮数字加一, async () { const wrapper mount(Counter) await wrapper.find(button).trigger(click) expect(wrapper.text()).toContain(1) })不需要额外的babel-jest也不需要ts-jestComponent 里的script setup语法在 Vite 的 vue 插件下直接转译。跑测试时还能用vi.mock来模拟接口模块import { vi, beforeEach } from vitest vi.mock(/api/user, () ({ getUserList: vi.fn(() Promise.resolve([])), }))有一个小区别值得注意Jest 的__mocks__目录优先级非常高Vitest 更倾向于在测试文件里显式vi.mock。团队如果有很多历史用例依赖根目录__mocks__自动生效迁移时需要在vite.config.ts加deps相关配置或者让开发人员先规范化 mock 写法。2.3 用 Vitest 做接口层的冒烟测试除了组件测试我更喜欢用 Vitest 做“接口数据层”的轻量测试。项目里会有大量src/api模块每个模块导出一个请求函数。以前要手动起 dev server、打开页面验证现在我可以直接对 API 函数做冒烟测试mock 掉axios模块断言参数拼接、错误处理是否符合预期。import { request } from /utils/request import { getDetail } from /api/detail vi.mock(/utils/request) test(getDetail 拼接正确参数, async () { const mockGet vi.mocked(request.get).mockResolvedValue({ data: { id: 1 } }) await getDetail({ id: 1 }) expect(mockGet).toHaveBeenCalledWith(/detail, { params: { id: 1 } }) })这类测试不用启动浏览器十几毫秒就全跑完却能把最常见的参数拼错、路径写错这类问题挡在提交之前。接入 Vitest 后我在 CI 里加入vitest run任务整个流程没有额外成本。3. 新玩具之二UnoCSS原子化 CSS 的另一种打开方式UnoCSS 是 Antony Fu 主导的项目尤雨溪也在很多场合公开安利过。它常被拿来和 Tailwind CSS 对比但严格来说 UnoCSS 不是简单“另一个 Tailwind 替代品”而是一个“原子化 CSS 引擎”。你给它一堆工具类预设它按需生成类名对应的样式而不是预先把整包工具类打进 CSS。3.1 和 Tailwind JIT 的区别Tailwind 也有 JIT 模式但它默认是完整设计系统配置文件里铺了一整套颜色、间距、断点、动画体系。UnoCSS 默认的presetUno兼容 Tailwind 的类名习惯但你可以只保留自己需要的一小部分预设甚至写一个定制的规则来生成任意工具类。比如我想让.p-4这个类生成padding: 1rem不需要启动任何构建脚本只需要配置// uno.config.ts import { defineConfig } from unocss export default defineConfig({ rules: [ [p-4, { padding: 1rem }], ], })运行时你去页面里用了p-4UnoCSS 才会生成这个工具类。这个机制带来一个体验优势新项目里不需要整套设计系统可以先从一两个工具类起步再慢慢加。3.2 实际接入步骤安装依赖npm i -D unocss在vite.config.ts里注册import UnoCSS from unocss/vite import { presetUno, presetAttributify, presetIcons } from unocss export default defineConfig({ plugins: [ UnoCSS({ presets: [presetUno(), presetAttributify(), presetIcons({ scale: 1.2 })], }), ], })然后需要在入口main.ts引入生成后的样式import virtual:uno.css这一步容易漏。很多初学者注册完插件后页面没有任何样式变化就是忘了加这行virtual:uno.css导入。它的作用是让 UnoCSS 的虚拟模块接管样式生成运行时根据页面类名输出对应 CSS。3.3 扫描机制和动态类名问题UnoCSS 默认扫描项目源码中出现的合法工具类字符串然后生成对应 CSS。问题是如果你在业务代码里动态拼接类名比如div :classtext-${color} ${active ? shadow-lg : }text-${color}这种模板字符串在静态扫描时无法被确定UnoCSS 不认识它就不会生成text-red-500或text-blue-500。解决办法有两个把完整的类名写进安全列表safelist让 UnoCSS 无条件生成。不要动态拼完整类名而是直接传颜色变量让 CSS 变量机制处理。我个人推荐第二种。比如做成div :style{ color: var(--color-${color}) }或者预设一组固定类名用于 switch case不要拼字符串。否则上线后某些样式随机失效排查非常闹心。3.4 配合图标预设UnoCSS 的presetIcons比传统图标库方案轻太多。以前用 Font Awesome 要引入整份字体文件现在可基于unplugin-icons或在线图标集做按需加载。简单场景下我把presetIcons加入预设后直接在模板里写div classi-carbon:user /它会把 Carbon 图标集里的 user 图标转成内联 SVG按需输出。这套体验完整地体现了一个观点不是工具越重越好而是“用到什么才生成什么”最舒服。4. 新玩具之三和之四自动导入组合拳拆掉 import 的墙4.1 unplugin-auto-import让 API 自动出现在作用域里unplugin-auto-import解决的问题很简单你在每个组件里写import { ref, computed } from vue写烦了甚至经常漏写。它在编译阶段扫描代码发现ref或computed这些标识符时自动注入对应的 import 语句而你在源码里不需要写。配置示例import AutoImport from unplugin-auto-import/vite export default defineConfig({ plugins: [ AutoImport({ imports: [vue, vue-router], dts: src/auto-imports.d.ts, }), ], })启用之后组件里可以这样写script setup langts const count ref(0) const router useRouter() function goHome() { router.push(/) } /script没有显式 importref和useRouter在运行时自动可用。dts: src/auto-imports.d.ts这个选项千万别忽略它自动生成类型声明文件让 Volar 和 TypeScript 认识这些全局 API。如果没有 dts编辑器会一直飘红虽然编译能过但体验很分裂。4.2 unplugin-vue-components组件也可以自动导入unplugin-vue-components是另一个维度扫描src/components目录把模板中出现且未注册的组件自动导入。配置import Components from unplugin-vue-components/vite import { ElementPlusResolver } from unplugin-vue-components/resolvers export default defineConfig({ plugins: [ Components({ dirs: [src/components], resolvers: [ElementPlusResolver()], dts: src/components.d.ts, }), ], })这么配置以后你在模板里写el-button它会自动按需导入 Element Plus 的 Button 组件及对应样式不需要在 main.ts 里全局注册整个 Element Plus。自定义组件同理template BaseCard / /template只要src/components/BaseCard.vue存在组件会被自动识别并导入。团队协作时新成员不再需要关心“这个组件是不是要 import 进来”只要向components目录丢文件即可。4.3 自动导入的坑与建议这套组合拳虽爽但有两个坑必须提前知道。第一如果项目里有 ESLint默认的no-undef规则会疯狂报错因为ref、computed这些变量在运行时存在但 Lint 阶段看不到声明。解决办法是在 ESLint 配置里增加vue-global-api或引入生成的 dts。更简单的方法是直接用 Vue 官方推荐的最新 Vue ESLint 配置它已经默认兼容 script setup 的全局 API。第二自动导入不是越全越好。unplugin-auto-import支持引入任意模块但我不会把业务里的request、store等对象全部自动导入。一来会让依赖关系变得隐晦二来模块内命名冲突时你根本不记得是哪里来的。我的建议是只自动导入框架级 API比如vue、vue-router、pinia这类大家固定的东西业务模块一律保持手写 import。4.4 这套组合拳的真实效果我们团队有一个 Vue3 后台管理模板改造之前组件文件平均要写 6 到 8 行 import用上自动导入之后超过一半文件只剩业务代码。从代码 Review 角度diff 里少了大量 import 行注意力更集中。从编译角度未响应的 import 不再出现打包体积也因此变小。我甚至看过同事提交的组件里保留着一行import { ref } from vue现在完全可以删掉。5. 新玩具之五vite-plugin-pages文件路由让动态路由回归直觉vue3 vite 动态路由一直是个高频搜索词因为手写路由表在大型项目里维护成本确实高。vite-plugin-pages将文件系统路径映射为路由定义相当于把路由表变成项目目录结构配合动态路由语法很多后端管理系统可以直接省掉一整套菜单配置模块。5.1 基本接入方式npm i -D vite-plugin-pages在vite.config.tsimport Pages from vite-plugin-pages export default defineConfig({ plugins: [ Pages({ dirs: src/pages, extensions: [vue, tsx], }), ], })然后在src/router/index.ts里import { createRouter, createWebHistory } from vue-router import pages from ~pages const router createRouter({ history: createWebHistory(), routes: pages, }) export default router这里的~pages是插件注入的虚拟模块等于自动生成了完整的路由表。你往src/pages/下新增一个UserList.vue路由表和侧边栏菜单同步更新组件文件位置就是路径定义。5.2 动态路由的写法从方括号到嵌套vite-plugin-pages 的动态路由跟 Next.js 文件路由有点类似。[id].vue对应动态参数src/pages/user/[id].vue访问/user/10时组件内通过const route useRoute() console.log(route.params.id) // 10嵌套路由也很自然。你创建src/pages/user/index.vue和src/pages/user/detail.vue它们会生成同级路由如果想让user文件夹成为父级布局在src/pages/user.vue里写router-view /子模板就作为 children 渲染。5.3 用 definePage 给路由加 meta中后台项目绕不开菜单配置和权限码。vite-plugin-pages 支持在 SFC 里通过route块定义额外的路由元信息比如route { name: UserDetail, meta: { title: 用户详情, requiresAuth: true } } /route template div用户详情页面/div /template这样菜单生成时可以直接读取路由对象里的meta.title权限守卫也能检查meta.requiresAuth。我实际用下来发现这套方案比单独维护router.config.ts清晰很多改一个页面只需要改动文件夹和文件内的 route 块不会出现“路由表里有但页面不存在”的孤儿配置。5.4 动态路由的边界和刷新问题文件路由虽好用但有一个老问题页面路径里存在动态参数时404 和刷新行为要特别注意。开发模式下 Vite 的 dev server 会默认 fallback 到 index.html基本不会有事但生产环境如果部署在 Nginx需要配置try_files $uri $uri/ /index.html。否则访问/user/10刷新时服务器会直接返回 404。另一个问题是路由base。如果项目部署在子路径比如https://example.com/admin/需要同时设置 Vite 的base和 vue-router 的createWebHistory(/admin/)否则页面刷新后资源路径和路由路径都会错位。这个问题在“动态路由 文件路由”组合里特别容易踩因为文件路由自动生成的是相对路径路由你容易忽略整个应用其实挂在二级目录下。6. 装上这些玩具后我踩过的几个坑工具再漂亮落地时总有几个意外。下面这几个坑都来自真实发生过的报错网上检索热度也很高值得单独拿出来说说。6.1 npm install vite 时先确认 Node 版本安装 Vite 依赖时最常见的坑不是网络而是 Node 版本过低。Vite 5 要求 Node 18很多老项目还停在 Node 14npm install vite能装上但一跑vite dev就启动失败。报错通常是一大段堆栈最后落在UPSTREAM_INSTALL_NODE_MODULES之类的无关提示上非常容易误导人。我现在的习惯是装之前先node -v然后对照当前 Vite 版本的engines字段。团队里统一用.nvmrc或volta固定 Node 版本能避免一大批“我这能跑那不能跑”的扯皮问题。另外如果你是在一个已有项目中临时npm install vite而不是用官方脚手架初始化请务必检查有没有旧版webpack-dev-server或其它打包器残留。Vite 默认按项目根目录的index.html作为入口和 Webpack 的src/main.js入口逻辑完全不同手动安装容易把入口路径搞混。6.2 Windows 下 NODE_OPTIONS 报错“不是内部或外部命令”我经常看见有人在群里发这个命令$ node_options--max-old-space-size4096 vite然后在 Windows 的 CMD 或 PowerShell 里得到报错node_options 不是内部或外部命令原因很简单变量值 命令是 Linux/macOS 的 shell 语法Windows 原生命令行根本不认识它。很多人是把网上 Unix 教程里的命令直接复制过来踩坑不稀奇。Windows 上想设置 Node 内存上限分为两种情况CMD 里写set NODE_OPTIONS--max-old-space-size4096 vitePowerShell 里写$env:NODE_OPTIONS--max-old-space-size4096 vite更推荐用cross-env包在 package.json scripts 里这样写{ scripts: { dev: cross-env NODE_OPTIONS--max-old-space-size4096 vite } }这样团队跨平台执行结果一致不用每个人记一套 shell 语法。这个错误本质跟 Vite 无关但因为它会作用于vite启动命令所以网上搜“vite node_options 不是内部命令”的人特别多。6.3 自动导入插件把同名变量识别错unplugin-auto-import是根据标识符名注入 import 的如果你的业务模块里也存在同名函数比如自己也写了一个名为useRouter的函数编译器可能把这个调用注入成 vue-router 的useRouter导致业务逻辑走了错误分支。遇到这种情况优先检查自动导入依赖列表里是否覆盖了不该全局化的名称。我的建议是维护一份“内部命名空间”规范自动导入只覆盖vue、vue-router、pinia这些官方 API业务函数一律以use、fetch、handle这类前缀开头保持命名可追踪。6.4 dts 文件需要提交进仓库很多团队配置了unplugin-auto-import或unplugin-vue-components生成的auto-imports.d.ts、components.d.ts被加进了.gitignore结果 CI 上或同事拉代码后 IDE 类型检查直接报错。这两份 dts 是运行时代码在类型系统里的“替身”应当提交进版本库。一旦生成后续变化不大只会随新增 API 或组件偶尔更新。不提交版本库会导致团队里人人各自生成一份版本不同步难以排查。6.5 不要为了用新玩具而用新玩具最后说个和具体报错无关的坑。Vitest、UnoCSS、自动导入、文件路由这些工具很香但并不是每个项目都得上全套。我见过一个小型营销页项目总共三五个组件也硬塞了 UnoCSS 和自动导入结果配置复杂度比页面本身还高最后调试样式反而花了更多时间。如果你是老项目迁移建议先只引入 Vitest 做测试再逐步加入组件自动导入让团队适应一个阶段再上下一个。新项目可以一次性配好但前提是文档和脚手架模板要同步更新。工具是给业务服务的不是给简历镀金的。7. 一个可复用的 Vite 新项目推荐配置如果让我开一个全新的 Vue3 Vite 中后台项目我会把这些工具按优先级组合起来最终的vite.config.ts大概长这样import { defineConfig } from vite import vue from vitejs/plugin-vue import UnoCSS from unocss/vite import AutoImport from unplugin-auto-import/vite import Components from unplugin-vue-components/vite import Pages from vite-plugin-pages export default defineConfig({ plugins: [ vue(), Pages(), UnoCSS(), AutoImport({ imports: [vue, vue-router, pinia], dts: src/auto-imports.d.ts, }), Components({ dirs: [src/components], dts: src/components.d.ts, }), ], })对应的package.jsonscripts 可以这样配置{ scripts: { dev: vite, build: vue-tsc --noEmit vite build, test: vitest run, test:watch: vitest } }创建路由文件时不再需要手动维护路由表只需往src/pages里放文件写组件时不需要写 import 前缀加接口模块时可以直接用 Vitest 写冒烟测试样式类的常用布局用 UnoCSS 类名完成偶尔需要复杂布局再写普通style scoped。这套组合跑起来后日常开发里真正被重复劳动占用的时间会减少很多。但有一个值得反复强调的经验任何新工具先让团队里一个人完整读过文档并给出内部教程再全员铺开。我见过太多项目因为“尤雨溪推荐了”“社区说很好用”就全员盲上结果配置没吃透、坑也没踩全最后反而怀疑工具本身。工具链升级最稳的姿态永远是先在小型子项目里跑通完整闭环再复制到主工程。最后再分享一个小技巧如果你在多个项目间复制这套 Vite 配置建议把AutoImport、Components、UnoCSS这几段抽到一个独立的build/plugins.ts文件里导出主配置里只写plugins: [vue(), ...commonPlugins()]。项目升级时只改一处不用反复粘贴配置。Vite 生态的“玩具”更新速度很快把配置集中管理会让我们跟进新版本时从容很多。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →