Vue Vine:在一个 .vine.ts 文件里写多个 Vue 组件

开源
前端
Vue
TypeScript
工具链
2026/9/30
·

阅读时间: 大约 8 分钟

Vue Vine:在一个 .vine.ts 文件里写多个 Vue 组件

Vue Vine 官方标识:另一种编写 Vue 组件的方式

Vue.js 当年用单文件组件(SFC,.vue)把模板、脚本、样式收敛到一处,深刻影响了整个前端框架生态。但 SFC 有一条长期被社区抱怨的硬约束:一个文件只能放一个组件,写两个紧密耦合的小组件也要来回切文件、破坏心流。Vue Vine 就是为解决这个痛点而生的第三方编译方案,它不是要取代 SFC,而是提供一种”平行写法”。本文基于其 GitHub 仓库与官方文档梳理机制与取舍。

一、背景:SFC 的便利与它的代价

SFC 的”单文件单组件”是一种强约定:它让文件即组件、组件即文件,工程上非常清晰。但在真实业务里,一个按钮、一个列表项、一个容器往往和父组件高度内聚——把它们拆成 XxxButton.vue、XxxItem.vue 再互相 import,会产生大量只有几行的碎片文件。社区多年来一直有人希望”在一个文件里写多个组件”,但 Vue 核心团队出于 SFC 架构稳定性考虑,没有把它做成官方特性(这会是一次难以兼容的 breaking change)。

Vue Vine 的思路是:不动 Vue 核心,在编译期另起一套平行语法,让你既能在一个 .vine.ts 文件里写多个组件,又能继续和现有 SFC 混用。

二、是什么:TypeScript 里的组件宏

Vue Vine 的文件扩展名是 .vine.ts。它不使用 <template> 块,而是把组件写成普通 TypeScript 函数,再通过编译宏返回一段 vine\…“ 模板字符串。官方示例截图展示了典型形态:

Vue Vine 官方示例:app.vine.ts 中函数式组件、scoped 样式与 vine 模板

从截图可见:function StateContainer(props: { id: string }) 直接用 TypeScript 类型标注 Props;vineStyle.scoped(scss\…`)写作用域样式;组件内部照常使用ref、watch、computed等 Vue API;最后return vine`

…
`返回模板,模板里依然能用v-if、v-else、{{ }}这些你熟悉的 Vue 指令。宏函数(如defineProps` 对应的能力)与 SFC 基本对齐,老用户几乎零学习成本。

三、技术机制:为什么需要 ts-morph

这是 Vue Vine 最关键的技术决策。Vue 3 的 defineProps 类型写法有一个长期局限:由于 SFC 模板编译器只在 AST(抽象语法树)级别处理 props 类型,它无法像普通 TypeScript 那样跨文件导入、解析复杂类型别名——很多类型在模板里会”丢类型”。

Vue Vine 引入了 ts-morph(TypeScript Compiler API 的封装)来做更深入的类型解析,从而把 props 的类型信息真正打通。代价是它比原生 SFC 多了一层编译依赖,但换来的是在 .vine.ts 里可以像写普通 TS 一样自由组织类型与多个组件。

接入方式很轻:npm i vue-vine 后在 vite.config.ts 里加 VineVitePlugin()(官方还提供 Rsbuild/Rspack 插件),在 tsconfig.json 的 types 里加上 vue-vine/macros 即可获得宏的类型提示。

一个 .vine.ts 文件里可以并列定义多个组件函数,每个函数各自 return vine\…`模板、各自带vineStyle.scoped作用域样式,最后像导出普通函数一样export出去给别处用。这正是它和 SFC 最直观的差异:SFC 以文件为组件边界,Vue Vine 以函数为组件边界,文件只是装这些函数的容器。对你而言,原先要拆成List.vue+ListItem.vue+ListHeader.vue` 的三件套,现在可以落在同一个文件里,import 路径也随之减少。

有人会问:直接在一个 SFC 里用渲染函数或内联模板塞多个组件不行吗?区别在于那样做会丢失 SFC 的模板编译优化与 scoped 样式隔离,写法也别扭;Vue Vine 是用一套独立编译宏把”多组件 + 真模板 + 作用域样式 + 完整类型”四件事都保住,而不是退回到写 h 函数。

四、生态:不是一个孤立的编译器

组成内容
构建集成Vite 插件、Rsbuild/Rspack 插件
编辑器VSCode 扩展、Open VSX 扩展
规范官方 ESLint 配置 @vue-vine/eslint-config
元框架Nuxt module
类型检查独立 tsc CLI 工具
脚手架create-vue-vine 交互式初始化
在线体验官方 Playground
协议MIT

这个生态覆盖面是它区别于”个人玩具”的关键:从写代码、 lint、类型检查到 Nuxt 集成都有官方方案,不是只有一个编译器包。

五、优势与局限

优势:

  1. 单文件多组件:紧密耦合的小组件不必再拆成碎片文件,写作心流更连贯;
  2. 类型打通:借 ts-morph 解决了 SFC defineProps 复杂类型解析不到位的老问题;
  3. 与 SFC 共存:不是非此即彼,老项目可以局部试用 .vine.ts;
  4. 工具链完整:Vite/Rsbuild、VSCode、ESLint、Nuxt、脚手架一应俱全。

局限(需客观看待):

  1. 非官方路线:它是社区平行方案,不进 Vue 核心。团队若押注它,需自行承担”核心未来可能提供不同方案”的长期分歧风险;
  2. 多一层编译链:ts-morph 引入了更重的构建期依赖,冷启动与构建耗时理论上高于原生 SFC;
  3. 自定义扩展名:.vine.ts 是新约定,新成员需要学习,CI/IDE 也得配插件;
  4. 项目仍年轻:仓库体量与第三方资料远少于 Vue 主生态,遇到冷门问题时社区答案有限。

六、谁该考虑用它

  • 组件粒度细、喜欢在一个文件里聚拢相关 UI 的团队:Vue Vine 显著减少碎片文件;
  • 被 SFC props 复杂类型折磨的大型 TS 项目:ts-morph 的类型打通有实际价值;
  • 已有 Vite/Nuxt 项目想渐进尝试:可与 SFC 混用,迁移成本低;
  • 追求纯官方标准、抗拒额外编译约定的团队:继续用 SFC 更稳妥。

参考来源