重写版的栈:TanStack Start、Vite、Cloudflare 与 Rust gpui

2026-08-09

先把状态摆在最前面,免得读串了。

本文描述的是 OpenCut 重写版仓库当前的代码结构与官方 README 声明的路线图。该版本尚未发布,Editor API、插件体系、MCP server、无头模式等均为官方声明的计划,我们没有见到可用实现。核对日 2026-08-09。

OpenCut 现在有两个仓库,状态完全不同,混着谈就会得出荒唐结论:

重写版主仓classic(旧版)
仓库OpenCut-app/OpenCutOpenCut-app/opencut-classic
star(2026-08-09)81917213
归档状态未归档已归档,不再维护
最后推送2026-08-052026-05-17
许可MITMIT

主仓 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.tstsconfig.jsonpackage.json。反过来读这份清单更有意思:改 README、改 changelog/ 下的文档,都不在清单里,也就不会让 build 的缓存失效。缓存与增量的粒度就是被这一行决定的。写 monorepo 任务配置最容易犯的错是 inputs 写得太宽(比如直接写整个项目目录),结果每次改文档全量重编;写得太窄则更危险,改了配置文件却命中旧缓存,拿到一个不该存在的产物。

第二,outputs: ['dist'] 让产物本身可被缓存复用。 声明了输出目录,任务系统才知道命中缓存时该把什么恢复出来,而不是只跳过命令、留下一个空目录。

第三,deploydeps: ['~:build'] 声明依赖,而不是靠人记得先 build。 这是把「部署前记得先构建」这条口头纪律写成了机器能执行的约束。同类的经验教训大家应该都有:文档里写着的步骤,总有人在赶时间的时候跳过。

dev 任务上的 runInCI: false 也顺着这个逻辑——开发服务器是长驻进程,进 CI 会一直挂着,所以直接排除,并且不做缓存。

仓库根 moon.yml 里还有一个 upload-logos 任务,形状不太一样:它读 .env.local(文件缺失就报错退出),要求 R2_BUCKET 变量,把 brand/marks/*.svgwrangler r2 object put 传到 R2,选项是 cache: falserunInCI: 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-plugindeploy 脚本是 bun run build && wrangler deploy
React19.2
样式Tailwind CSS 4.1(@tailwindcss/vite)、tw-animate-csstailwind-merge
UI@base-ui/reactradix-uishadcncmdklucide-react@hugeicons/*vaulsonnerinput-otpreact-day-pickerreact-resizable-panelsembla-carousel-react
表单/校验react-hook-form@hookform/resolverszod 4.4
图表recharts 3.8
测试Vitest 加 @testing-library/react@testing-library/dom

部署链路的变化同样是 package.json 上能直接看到的事实差异:classic 那边走的是 @opennextjs/cloudflare,重写版这边是 @cloudflare/vite-pluginwrangler 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/ 下那四个文件名——browserinspectorpreviewtimeline——勾勒出一个典型编辑器的四区布局。但要留意这是从文件名做的推断,不是我们见过的界面。

第五层:API 是一个 Cloudflare Worker

apps/api/ 只有四个文件:src/index.tswrangler.jsoncmoon.ymlpackage.jsonmoon run api:dev 起在 localhost:8787,也就是 wrangler dev 的默认端口。我们没有读 index.ts 的实现,所以这里不描述它提供什么接口。

关于仓库里那三份 changelog

重写主仓的 changelog/ 目录下有三份带 frontmatter 元数据的 markdown:

版本日期标题条目数new / improved / fixed
0.1.02026-02-23Editor foundation148 / 5 / 1
0.2.02026-03-01Motion & effects194 / 4 / 11
0.3.02026-04-15Masks, animation & more5215 / 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 招赞助。如实提一句,不作褒贬。

延伸阅读


本文依据 OpenCut 官方仓库(github.com/OpenCut-app/OpenCut 与已归档的 github.com/OpenCut-app/opencut-classic)的 README、docs/ 架构文档、package.jsonCargo.tomlchangelog/ 整理,核对日 2026-08-09。本文内容为仓库源码与文档口径,我们没有编译或运行过任何版本的 OpenCut。

本文描述的是 OpenCut 重写版仓库当前的代码结构与官方 README 声明的路线图。该版本尚未发布,Editor API、插件体系、MCP server、无头模式等均为官方声明的计划,我们没有见到可用实现。

许可条款请以官方 LICENSE 原文为准,本文不构成法律意见。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。