返回首页
掘金本周最热

踩坑分享:Vite Plus 最佳实践

7.8Score
AI 深度提炼
  • rattail 提供面向 Vite+ 的开箱即用工程化预设,统一 lint、fmt 等配置
  • 内置 140+ 类型安全且可 Tree-shake 的工具函数及渐进式请求库
  • 通过 Agent Skills 增强 AI 编程助手对项目上下文的理解,减少代码幻觉
#Vite#前端工程化#TypeScript#AI编程#工具链
打开原文

写在前面

掘金的同学们大家好呀,作者是 [Varlet UI](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fvarletjs%2Fvarlet "https://github.com/varletjs/varlet") 的作者。掘金文章已经一年没更新了,去年跳槽到了一家创业公司负责前端架构工作,写文章这件事就一直搁置了。最近稍微缓过来了一点点(其实还是压力很大...),但不妨碍今天来给大家分享一下我们最新的开源项目 [rattail](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fvarletjs%2Frattail "https://github.com/varletjs/rattail")。

先聊聊 Vite Plus

上个月 `VoidZero` 正式以 `MIT` 协议开源了 [Vite+](https://link.juejin.cn/?target=https%3A%2F%2Fviteplus.dev "https://viteplus.dev"),它把 `Vite`、`Vitest`、`Oxlint`、`Oxfmt`、`Rolldown`、`tsdown` 统一收拢到了一个 `vp` 命令下面,一套工具链覆盖 `dev`、`build`、`test`、`lint`、`fmt`、`pack` 等所有工程化环节。作者第一时间就把 `varlet` 周边的项目迁移到了 `Vite+` 上面试试水,迁移下来发现效果特别好。以前那些散落在各处的 `eslint` 配置、`prettier` 配置、`lint-staged` 配置、`commitlint` 配置可以统一收拢到一个 `vite.config.ts` 里面,项目根目录一下子干净了不少(以前打开根目录看到十几个 `.xxxrc` 文件的日子终于结束了)。而且因为工具链统一了,AI Agent 在理解项目配置的时候幻觉也少了很多。

公司项目迁移

既然体验这么好,作者就决定把公司内部的前端项目也都迁移到 `Vite+` 上。迁移的过程中也让作者重新审视了一下 `rattail`,我们在 `varlet` 生态里积累了大量的工具函数、请求库、校验规则工厂、CLI 工具链,之前一直是分散在各个包里的,正好借这次机会做一次大整合,于是就有了 `rattail 2.0`——一个面向 `Vite+`、对 `AI Agent` 非常友好的前端工具链。140+ 工具函数、渐进式请求库、链式校验规则工厂、CLI 工具链、类型安全枚举,`pnpm add rattail` 一条命令全部拉齐。

目前作者也在公司项目中全面使用了 `Vite+` + `rattail` 这套技术栈,体验下来非常舒服。另外值得一提的是,这次 `rattail 2.0` 的迁移和开发过程中,作者大量使用了 `AI` 辅助编程,包括工具函数的编写、单元测试的补全、文档的生成等等,效率提升非常明显。配合 `rattail` 提供的 `Agent Skills`,AI Agent 能够很好的理解项目上下文并正确使用 `rattail` 的 API,整个工作流跑下来还是相当丝滑的。后续在业务开发中也明显感觉到,因为 `rattail` 把工具函数、请求库、校验规则这些东西 all in one 了,AI 在生成代码的时候幻觉变得特别少,而且很会按照规范做事。

相关链接

  • 官方文档: [rattail.varletjs.org](https://link.juejin.cn/?target=https%3A%2F%2Frattail.varletjs.org "https://rattail.varletjs.org")
  • 仓库地址: [github.com/varletjs/ra…](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fvarletjs%2Frattail "https://github.com/varletjs/rattail")

特性一览

  • ⚙️ 面向 [Vite+](https://link.juejin.cn/?target=https%3A%2F%2Fviteplus.dev "https://viteplus.dev") 的开箱即用配置预设
  • 🔧 CLI 工具链,支持发布、日志、Git Hooks、Commit Lint、API 生成
  • 🧰 140+ 工具函数,覆盖通用、字符串、数字、数组、对象、数学等场景
  • 🚀 基于 axios 的渐进式请求工具,支持 Vue 组合式 API
  • 📏 链式校验规则工厂,适配任意 UI 框架
  • 🏷️ 类型安全的枚举工具
  • 🤖 提供 Agent Skills,帮助 AI 编程助手理解和使用 Rattail
  • 🌲 可 Tree-shake,轻量,TypeScript 完整类型支持
  • 💪 90%+ 单元测试覆盖率

下面作者挑几个有意思的能力展开聊聊。

Vite+ 配置预设

做过前端工程化的同学应该都有体会,每次新项目光配 `eslint` 和 `prettier` 这些东西就够喝一壶的了,配好了还得处理各种冲突。`rattail` 内置了面向 `Vite+` 的开箱即用预设,一个 `vite.config.ts` 搞定 `lint`、`format`、`staged`、`git hooks` 等所有工程化配置。

import { lint, fmt, staged, clean, hook, defineConfig } from 'rattail/vite-plus'

export default defineConfig({
  lint: lint(),

  fmt: fmt(),

  staged: staged(),

  rattail: {
    clean: clean(),

    hook: hook(),
    
    api: {},

    release: {},

    changelog: {}
  },
})

之前作者为了配这些东西写了好几个配置文件,现在一个文件就够了(少写代码是第一生产力)。

CLI 工具链

安装 `rattail` 后会注册一个 `rt` 命令,覆盖了作者日常开发中最常用的几个场景。

# 清理产物
rt clean

# 安装 git hooks
rt hook

# 发布
rt release

# 生成 changelog
rt changelog

# 从 OpenAPI 生成 API 模块
rt api

这些命令都支持通过 `vite.config.ts` 中的 `rattail` 字段进行配置,也就是说项目根目录不需要再多出一堆 `.xxxrc` 文件了。这一点作者是比较在意的,毕竟谁也不想打开项目根目录看到十几个配置文件吧(有些项目根目录比 `node_modules` 还热闹)。

140+ 工具函数

`lodash` 大家都耳熟能详了,`rattail` 里的工具函数覆盖的场景和 `lodash` 类似,包括`类型判断`、`数组`、`对象`、`字符串`、`数学`、`函数`、`集合`、`文件`等分类,用法就不逐个列举了。和 `lodash` 不同的是,这些函数从第一天就是用 `TypeScript` 写的,类型推导是第一优先级,全部可 `Tree-shake`。除了 `lodash` 风格的工具函数以外,`rattail` 还内置了一些前端项目中常用的实用工具,比如 `sumHash` 计算哈希、`uuid` 生成唯一 ID、`mitt` 事件总线、`duration` 时间格式化、`storage` / `cookieStorage` 存储封装、`copyText` 复制文本、`download` 文件下载等等,省得同学们每次都要单独装一堆小包。更多的可以去[文档](https://link.juejin.cn/?target=https%3A%2F%2Frattail.varletjs.org "https://rattail.varletjs.org")里查看完整的 API 列表。

类型安全的枚举工具

这个是作者个人比较喜欢的一个工具。前端项目里到处都是枚举值,比如订单状态、用户角色之类的。一般我们用 `enum` 或者常量对象来管理它们,但是 `label`、`description` 这些配套信息就只能另外维护了。`enumOf` 把值和它的元信息放在一起管理,并且类型推导是完备的。

import { enumOf } from 'rattail'

const Status = enumOf({
  Pending: { value: 0, label: '待处理' },
  Active: { value: 1, label: '进行中' },
  Done: { value: 2, label: '已完成' },
})

Status.Pending        // 0
Status.Active         // 1
Status.values()       // [0, 1, 2]
Status.labels()       // ['待处理', '进行中', '已完成']
Status.label(Status.Pending) // '待处理'
Status.options()      // [{ value: 0, label: '待处理' }, ...]

// 直接丢给 select 组件的 options,再也不用手动维护了

前端项目里到处都需要枚举值和它对应的文案,以前每次都要写个 `map` 或者 `switch`,现在一个 `enumOf` 就够了。另外 `enumOf` 的 `label` 和 `description` 支持传入一个 getter 函数,配合 `vue-i18n` 之类的国际化方案可以很方便的实现多语言:

const Status = enumOf({
  Pending: { value: 0, label: () => t('status.pending') },
  Active: { value: 1, label: () => t('status.active') },
  Done: { value: 2, label: () => t('status.done') },
})

基于 axios 的渐进式请求工具

这个能力来自于作者之前开源的 `@varlet/axle`,现在通过 `rattail/axle` 直接引入。熟悉作者的同学可能看过之前介绍 `axle` 的文章,它在兼容 `axios` 的同时,天然支持 `Vue3 Composition API`。

import { createAxle } from 'rattail/axle'
import { createUseAxle } from 'rattail/axle/use'

const axle = createAxle({ baseURL: '/api' })
const useAxle = createUseAxle({ axle })

const [users, getUsers, { loading, error }] = useAxle({
  method: 'get',
  url: '/user',
  params: { current: 1, pageSize: 10 },
})

作者一直觉得前端请求库和 `Vue` 的响应式系统应该有更好的结合方式,`axle` 就是在这个方向上的一个尝试。如果你不喜欢 `axle` 也完全没问题,`rattail` 的其他能力和请求库是解耦的,换成你喜欢的方案就好。

OpenAPI 生成 API 模块

`rt api` 可以直接解析后端提供的 `OpenAPI` / `Swagger` schema 文件,自动生成类型安全的 API 调用代码,这个在实际项目里把工作流做通之后体验可太好了。

在 `vite.config.ts` 里配置好 schema 路径和输出目录:

import { defineConfig } from 'rattail/vite-plus'

export default defineConfig({
  rattail: {
    api: {
      input: './openapi.json'
    },
  },
})

执行 `rt api` 后会自动生成这样的代码:

import { api } from '@/request'
import { type paths } from './_types'

export type ApiGetUsers = paths['/users']['get']
export type ApiCreateUser = paths['/users']['post']
export type ApiGetUser = paths['/users/{uuid}']['get']
export type ApiUpdateUser = paths['/users/{uuid}']['put']
export type ApiDeleteUser = paths['/users/{uuid}']['delete']

export type ApiGetUsersQuery = ApiGetUsers['parameters']['query']
export type ApiGetUsersRequestBody = undefined
export type ApiGetUsersResponseBody = ApiGetUsers['responses']['200']['content']['application/json']
// ... 其他类型同理

export const apiGetUsers = api<
  ApiGetUsersResponseBody, ApiGetUsersQuery, ApiGetUsersRequestBody>('/users', 'get')
export const apiCreateUser = api<
  ApiCreateUserResponseBody, ApiCreateUserQuery, ApiCreateUserRequestBody>('/users', 'post')
export const apiGetUser = api<
  ApiGetUserResponseBody, ApiGetUserQuery, ApiGetUserRequestBody>('/users/:uuid', 'get')
export const apiUpdateUser = api<
  ApiUpdateUserResponseBody, ApiUpdateUserQuery, ApiUpdateUserRequestBody>('/users/:uuid', 'put')
export const apiDeleteUser = api<
  ApiDeleteUserResponseBody, ApiDeleteUserQuery, ApiDeleteUserRequestBody>('/users/:uuid', 'delete')

请求类型、响应类型全部从 `schema` 里提取,不需要手写。后端接口变了,重新跑一遍 `rt api` 就行,前后端的类型始终保持同步。这个工作流对 AI Agent 也特别友好,AI 可以直接基于生成的类型去写业务代码,不会出现参数类型对不上的问题。甚至 AI Agent 可以通过 api 定义的变化,推测出你接下来要写什么业务。默认使用 `axle`,也支持 `axios` 的预设,同时支持 `自定义输出`。

链式校验规则工厂

做表单的同学应该都写过类似 `required`、`min`、`max` 这些校验规则。不同的 UI 框架校验规则的格式还不一样,每个项目都要适配一遍。`rattail` 提供了一个链式校验规则工厂,写起来很流畅,并且可以适配任意 UI 框架。这种内联的声明式写法和 `TailwindCSS` 的思路类似,可读性和可迁移性都非常好,对 AI 也特别友好,AI 可以直接从模板里读懂校验意图,生成和修改规则的准确度很高。

以 `Naive UI` 和 `Element Plus` 为例:

<!-- Naive UI -->
<script setup lang="ts">
import type { FormItemRule } from 'naive-ui'
import { rulerFactory } from 'rattail/ruler'

const r = rulerFactory<FormItemRule>((validator, params = {}) => ({
  trigger: ['blur', 'change', 'input'],
  validator: (_, value) => validator(value),
  ...params,
}))
</script>

<template>
  <n-form :model>
    <n-form-item 
      path="name" 
      label="姓名"
      :rule="r().required('必填').min(2, '长度不正确').done()"
    >
      <n-input v-model:value="model.name" />
    </n-form-item>
  </n-form>
</template>
<!-- Element Plus -->
<script setup lang="ts">
import type { FormItemRule } from 'element-plus'
import { rulerFactory } from 'rattail/ruler'

const r = rulerFactory<FormItemRule>((validator, params) => ({
  validator(_, value, callback) {
    const e = validator(value)
    e ? callback(e) : callback()
  },
  trigger: ['blur', 'change', 'input'],
  ...params,
}))
</script>

<template>
  <el-form :model>
    <el-form-item 
      prop="email" 
      label="邮箱"
      :rules="r().email('必须是邮箱格式').done()"
    >
      <el-input v-model="model.email" />
    </el-form-item>
  </el-form>
</template>

AI Agent Skills

`rattail` 提供了一套 [Agent Skills](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fvarletjs%2Frattail%2Ftree%2Fmain%2Fskills "https://github.com/varletjs/rattail/tree/main/skills"),说白了就是给 AI 写了一份"说明书",让 AI Agent 知道 `rattail` 有哪些能力、怎么用,不用你每次都手动告诉 AI。作者觉得未来的开源库都应该考虑对 AI Agent 的友好度。

写在最后

`rattail` 的工具函数和能力大多来自前端社区的通用实践。感谢同学们能看到这里,但是希望 `rattail` 能够帮助到大家。项目基于 `MIT` 协议。如果在使用的过程中遇到任何问题,欢迎在 [issue](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fvarletjs%2Frattail%2Fissues "https://github.com/varletjs/rattail/issues") 里反馈给我们,同时也十分欢迎对项目有兴趣的同学给我们发 [pull request](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fvarletjs%2Frattail%2Fpulls "https://github.com/varletjs/rattail/pulls")。

支持我们的话留下一个 [star](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fvarletjs%2Frattail%2Fstargazers "https://github.com/varletjs/rattail/stargazers") 就好~

  • 官方文档: [rattail.varletjs.org](https://link.juejin.cn/?target=https%3A%2F%2Frattail.varletjs.org "https://rattail.varletjs.org")
  • 仓库地址: [github.com/varletjs/ra…](https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fvarletjs%2Frattail "https://github.com/varletjs/rattail")