Vue Vine:在一个 .vine.ts 文件里写多个 Vue 组件
阅读时间: 大约 8 分钟
Vue Vine:在一个 .vine.ts 文件里写多个 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\…“ 模板字符串。官方示例截图展示了典型形态:

从截图可见: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 集成都有官方方案,不是只有一个编译器包。
五、优势与局限
优势:
- 单文件多组件:紧密耦合的小组件不必再拆成碎片文件,写作心流更连贯;
- 类型打通:借 ts-morph 解决了 SFC
defineProps复杂类型解析不到位的老问题; - 与 SFC 共存:不是非此即彼,老项目可以局部试用
.vine.ts; - 工具链完整:Vite/Rsbuild、VSCode、ESLint、Nuxt、脚手架一应俱全。
局限(需客观看待):
- 非官方路线:它是社区平行方案,不进 Vue 核心。团队若押注它,需自行承担”核心未来可能提供不同方案”的长期分歧风险;
- 多一层编译链:ts-morph 引入了更重的构建期依赖,冷启动与构建耗时理论上高于原生 SFC;
- 自定义扩展名:
.vine.ts是新约定,新成员需要学习,CI/IDE 也得配插件; - 项目仍年轻:仓库体量与第三方资料远少于 Vue 主生态,遇到冷门问题时社区答案有限。
六、谁该考虑用它
- 组件粒度细、喜欢在一个文件里聚拢相关 UI 的团队:Vue Vine 显著减少碎片文件;
- 被 SFC props 复杂类型折磨的大型 TS 项目:ts-morph 的类型打通有实际价值;
- 已有 Vite/Nuxt 项目想渐进尝试:可与 SFC 混用,迁移成本低;
- 追求纯官方标准、抗拒额外编译约定的团队:继续用 SFC 更稳妥。