重写版的栈:TanStack Start、Vite、Cloudflare 与 Rust gpui
先把状态摆在最前面,免得读串了。
本文描述的是 OpenCut 重写版仓库当前的代码结构与官方 README 声明的路线图。该版本尚未发布,Editor API、插件体系、MCP server、无头模式等均为官方声明的计划,我们没有见到可用实现。核对日 2026-08-09。
OpenCut 现在有两个仓库,状态完全不同,混着谈就会得出荒唐结论:
| 重写版主仓 | classic(旧版) | |
|---|---|---|
| 仓库 | OpenCut-app/OpenCut | OpenCut-app/opencut-classic |
| star(2026-08-09) | 81917 | 213 |
| 归档状态 | 未归档 | 已归档,不再维护 |
| 最后推送 | 2026-08-05 | 2026-05-17 |
| 许可 | MIT | MIT |
主仓 README 的 Status 段第一句就是「OpenCut is being rewritten from the ground up」,并且明说旧版本仍在 opencut-app/opencut-classic,那才是今天该用的那个,opencut.app 线上跑的仍然是 classic 版本,重写版会先住在 new.opencut.app,直到它准备好接管。所以下面拆的这一整套栈,是一个还没发布的仓库里的配置文件。你现在打开 opencut.app 用到的东西,跟本文说的这套栈没有关系。
star 数只是这一天的元数据快照,不用它推导任何关于质量、稳定性或者适不适合你的结论。
第一层:proto 把工具链版本钉死在仓库里
重写版仓库根有一个 .prototools,全文只有三行有效内容加两行注释:
# proto pins tool versions workspace-wide.
# Every developer and CI machine gets the exact same versions automatically.
moon = "2.3.3"
bun = "1.3.11"
rust = "1.97.0"
这两行注释把设计意图讲得很直白:proto 在 workspace 范围内钉住工具版本,每个开发者和每台 CI 机器自动拿到完全相同的版本。
值得展开的是它解决的问题类别。前端项目里常见的一类事故不是代码写错,而是「你机器上能过、CI 上挂了」,追下去发现两边的包管理器大版本不一样;Rust 侧则是某个依赖要求的最低编译器版本比本地的新。.prototools 这种做法把这件事从「README 里写一句请安装 Node 20 以上」变成了仓库里的一份声明——版本不再是口头约定,而是能被检出、能被 diff、能在 code review 里被看见的东西。
安装步骤是 README 原文给的:
# Linux, macOS, WSL
bash <(curl -fsSL https://moonrepo.dev/install/proto.sh)
# Windows PowerShell
irm https://moonrepo.dev/install/proto.ps1 | iex
Windows 这边 README 还额外给了一条处置:shim 跑不起来时执行 Set-ExecutionPolicy -Scope CurrentUser RemoteSigned,为当前用户允许本地脚本。这条不是 OpenCut 特有的坑,凡是靠 shim 脚本转发命令的工具在 PowerShell 默认策略下都可能撞上,看到「无法加载脚本」类的报错先往这个方向想。改执行策略是改当前用户的脚本执行范围,动手前自己评估一下环境是否允许。
然后从仓库根跑 proto use,安装 .prototools 里钉住的工具。
这一层给你的判断依据:如果你的团队里有人用 Windows、有人用 macOS,或者你有独立的 CI 机器,把工具版本写进仓库这件事的收益是随人数线性放大的;如果就你一个人一台机器,收益基本只剩下「半年后回来还能构建」。代价是每个新人多装一个版本管理器,以及升级版本时要改仓库文件、走一次评审。这笔账自己算,别因为一个 8 万星的仓库这么做了就照抄。
第二层:moon 定义任务,inputs 决定缓存粒度
工具装好之后,命令是走 moon 的:
moon run web:dev # localhost:5173
moon run api:dev # localhost:8787
moon run desktop:dev # 见 apps/desktop/README.md
顺手记一下端口,重写版 web 是 5173,api 是 8787;classic 那边是 3000。三个数字别记混。
apps/web/moon.yml 的要点:
language: 'typescript'
layer: 'application'
tasks:
dev:
command: 'bun run dev'
options:
runInCI: false # dev server: skipped in CI, never cached
build:
command: 'bun run build'
inputs: ['src/**/*', 'public/**/*', 'vite.config.ts', 'tsconfig.json', 'package.json']
outputs: ['dist']
test:
command: 'bun run test'
inputs: ['src/**/*', 'tsconfig.json']
deploy:
command: 'bun run deploy'
deps: ['~:build']
这段配置里有三个设计点,是本文最值得你花时间看的部分。
第一,inputs 声明的是「什么变了才需要重跑」。 build 的 inputs 列了 src/**/*、public/**/*、vite.config.ts、tsconfig.json、package.json。反过来读这份清单更有意思:改 README、改 changelog/ 下的文档,都不在清单里,也就不会让 build 的缓存失效。缓存与增量的粒度就是被这一行决定的。写 monorepo 任务配置最容易犯的错是 inputs 写得太宽(比如直接写整个项目目录),结果每次改文档全量重编;写得太窄则更危险,改了配置文件却命中旧缓存,拿到一个不该存在的产物。
第二,outputs: ['dist'] 让产物本身可被缓存复用。 声明了输出目录,任务系统才知道命中缓存时该把什么恢复出来,而不是只跳过命令、留下一个空目录。
第三,deploy 用 deps: ['~:build'] 声明依赖,而不是靠人记得先 build。 这是把「部署前记得先构建」这条口头纪律写成了机器能执行的约束。同类的经验教训大家应该都有:文档里写着的步骤,总有人在赶时间的时候跳过。
dev 任务上的 runInCI: false 也顺着这个逻辑——开发服务器是长驻进程,进 CI 会一直挂着,所以直接排除,并且不做缓存。
仓库根 moon.yml 里还有一个 upload-logos 任务,形状不太一样:它读 .env.local(文件缺失就报错退出),要求 R2_BUCKET 变量,把 brand/marks/*.svg 用 wrangler r2 object put 传到 R2,选项是 cache: false 加 runInCI: false。这是一个典型的有副作用的任务,所以缓存必须关掉——缓存的前提是同样的输入产生同样的产物,而「往对象存储里推文件」这件事没有可复用的产物可言。你在自己项目里写这类任务时,cache: false 是要主动加的,别让它默认走进缓存逻辑。
第三层:web 端换成了 TanStack Start 加 Vite
apps/web/package.json 里的选型,和 classic 的 Next.js 栈完全不同,这是重写版最直观的变化:
| 方面 | 重写版选型 |
|---|---|
| 路由/框架 | TanStack Router + TanStack Start(@tanstack/react-router、@tanstack/react-start、@tanstack/react-router-ssr-query、@tanstack/router-plugin) |
| 构建 | Vite(vite dev --port 5173、@vitejs/plugin-react) |
| 部署 | Cloudflare(@cloudflare/vite-plugin,deploy 脚本是 bun run build && wrangler deploy) |
| React | 19.2 |
| 样式 | Tailwind CSS 4.1(@tailwindcss/vite)、tw-animate-css、tailwind-merge |
| UI | @base-ui/react、radix-ui、shadcn、cmdk、lucide-react、@hugeicons/*、vaul、sonner、input-otp、react-day-picker、react-resizable-panels、embla-carousel-react |
| 表单/校验 | react-hook-form、@hookform/resolvers、zod 4.4 |
| 图表 | recharts 3.8 |
| 测试 | Vitest 加 @testing-library/react、@testing-library/dom |
部署链路的变化同样是 package.json 上能直接看到的事实差异:classic 那边走的是 @opennextjs/cloudflare,重写版这边是 @cloudflare/vite-plugin 加 wrangler deploy。README 没有说明换栈的理由,所以这里只陈述两边 package.json 的差异,不去猜动机,也不评价哪一套更合适。
有一点得说清楚,免得你按 star 数去仓库里找编辑器代码却扑空:apps/web/src/components/ui/ 下是 40 多个 shadcn 风格的基础 UI 组件(accordion、button、dialog、command 之类),那是通用组件库,不是编辑器功能;整个 web app 目录连同 routes/、hooks/、lib/ 一共 98 个文件。
第四层:桌面端是 Rust 加 gpui
apps/desktop/ 的实际目录:
apps/desktop/
├── Cargo.toml
├── moon.yml
├── README.md
└── src/
├── main.rs
├── shell.rs
├── theme.rs
├── components/ (badge.rs, button.rs, context_menu.rs, label.rs,
│ mod.rs, resizable.rs, separator.rs)
└── panels/ (browser.rs, inspector.rs, preview.rs, timeline.rs, mod.rs)
重写主仓根目录的 Cargo.toml:
[workspace]
resolver = "3"
members = [
'apps/desktop',
# 'crates/*',
]
[workspace.package]
version = "0.1.0"
edition = "2024"
license = "MIT"
[workspace.dependencies]
gpui = "0.2.2"
四条事实值得单独拎出来。workspace 版本是 0.1.0,edition 是 2024。GUI 框架是 gpui 0.2.2,也就是 Zed 编辑器所用的那个 Rust GUI 框架——这里只陈述依赖事实,我们没有编译或运行过,任何关于它渲染表现的说法都不该从这篇文章里得到。
第四条最说明当前状态:crates/* 这一行在 members 里被注释掉了。官方 README 声明的计划里有一条是「桌面、移动、浏览器共用一套代码库(Rust 核心)」,那是尚未发布的路线图而不是已有能力;而在这个仓库里,这个 workspace 目前只有 apps/desktop 一个成员,还没有对应的 crate。这是事实陈述,不是进度评价,我们也不预测什么时候会有。
panels/ 下那四个文件名——browser、inspector、preview、timeline——勾勒出一个典型编辑器的四区布局。但要留意这是从文件名做的推断,不是我们见过的界面。
第五层:API 是一个 Cloudflare Worker
apps/api/ 只有四个文件:src/index.ts、wrangler.jsonc、moon.yml、package.json。moon run api:dev 起在 localhost:8787,也就是 wrangler dev 的默认端口。我们没有读 index.ts 的实现,所以这里不描述它提供什么接口。
关于仓库里那三份 changelog
重写主仓的 changelog/ 目录下有三份带 frontmatter 元数据的 markdown:
| 版本 | 日期 | 标题 | 条目数 | new / improved / fixed |
|---|---|---|---|---|
| 0.1.0 | 2026-02-23 | Editor foundation | 14 | 8 / 5 / 1 |
| 0.2.0 | 2026-03-01 | Motion & effects | 19 | 4 / 4 / 11 |
| 0.3.0 | 2026-04-15 | Masks, animation & more | 52 | 15 / 13 / 16 |
按各版 summary 原文:0.1.0 是属性面板大改、1000 多种字体、混合模式、新取色器、预览区直接操作;0.2.0 是关键帧动画、带逐片段模糊的新特效系统、ripple 编辑模式;0.3.0 是蒙版、曲线图形编辑器、音量与速度控制、预览缩放、画布背景、贴纸。
这里必须谨慎表述归属:这三份文件位于重写主仓的 changelog/ 目录,日期是 2026-02 到 2026-04。仓库 changelog 记录了这三个版本及其条目——仅此而已。README 没有明说它属于哪一个代码库的发布历史,我们不做推断。同样,changelog 里那些「播放性能大幅改善」之类的自述是官方 changelog 原文记载,不是我们的测评结论。
读完这篇你能拿它做什么
如果你的目的是用 OpenCut 剪视频,这篇文章对你没用,你要看的是 classic,那个仓库已归档、不再维护,而 opencut.app 线上跑的正是它。
如果你的目的是跟进重写版,那么这套配置文件就是当前能看到的全部确定信息:工具链钉在 moon 2.3.3、bun 1.3.11、rust 1.97.0,web 是 TanStack Start 加 Vite 部署到 Cloudflare,desktop 是 Rust 加 gpui 0.2.2,api 是一个 Worker,Rust 核心的 crate 还没进 workspace。README 在「即将到来」里列出的那几条——Editor API、一等公民的第三方插件、跨三端共用一套代码库、面向 AI agent 的 MCP server、用于自动化与批量渲染的无头模式、编辑器内置的脚本标签页——每一条都是官方 README 声明的计划、尚未发布,没有一条已经落地,我们也没有见到可用实现。
如果你想参与进去,先看清楚主仓 README 的这句原文:架构还在设计中,他们还没准备好接受外部贡献。想跟进的人可以加 Discord(discord.gg/zmR9N35cjK)或者开 issue。别兴冲冲提了 PR 才发现仓库不收。
如果你只是想借鉴这套工程组织方式,那么真正可迁移的其实是前两层:把工具版本写进仓库,以及用 inputs / outputs / deps 把任务之间的依赖和缓存边界显式声明出来。这两件事跟你做不做视频编辑器没关系。
另外,两个 README 都提到了赞助关系:classic README 感谢 Vercel 与 fal.ai 对开源软件的支持,主仓 README 的 Sponsors 段列出 fal.ai(描述为「生成式图像、视频、音频模型集于一处」),并留了 sponsor@opencut.app 招赞助。如实提一句,不作褒贬。
延伸阅读
- 从 0.1.0 到 0.3.0:官方 changelog 记了些什么
- Editor API、插件、MCP server、无头渲染:五条承诺目前都还是承诺
- inputs、outputs、deps:任务缓存的粒度是自己声明出来的
本文依据 OpenCut 官方仓库(github.com/OpenCut-app/OpenCut 与已归档的 github.com/OpenCut-app/opencut-classic)的 README、docs/ 架构文档、package.json、Cargo.toml 与 changelog/ 整理,核对日 2026-08-09。本文内容为仓库源码与文档口径,我们没有编译或运行过任何版本的 OpenCut。
本文描述的是 OpenCut 重写版仓库当前的代码结构与官方 README 声明的路线图。该版本尚未发布,Editor API、插件体系、MCP server、无头模式等均为官方声明的计划,我们没有见到可用实现。
许可条款请以官方 LICENSE 原文为准,本文不构成法律意见。