SheetDo:在浏览器里给 Excel 公式打分 — Astro + Univer 的技术实践

SheetDo 是一个完全在浏览器里运行的 Excel & Google Sheets 练习平台:内置交互式练习题、公式语法指南、快捷键速查表和实用微工具,全部免费、无需注册,进度保存在浏览器本地。
它最有意思的一点是判题完全在前端完成——不依赖任何后端沙箱。本文介绍这套「浏览器内公式判题器」的技术实现,原文(英文版)发布在 SheetDo 官方博客。
为什么不用后端沙箱?
大多数代码判题系统(LeetCode、OJ)的做法是把用户代码扔进后端沙箱执行。但对 Excel 练习来说,这个模型并不合适:
- 你希望用户获得真实的表格操作体验(选区、公式栏、单元格引用)
- 希望反馈即时(而不是提交后等几百毫秒)
- 希望每次提交的计算成本为零
SheetDo 的答案:把整个电子表格引擎搬到浏览器里。
技术栈:Astro + Univer
三个核心目标决定了选型:
- SEO + 快速首屏——练习页需要可抓取的静态 HTML 和结构化数据
- 真实的 Excel 交互——Canvas 网格、公式栏、单元格引用
- 零后端计算——所有重算都在客户端完成
团队也刻意跳过了「自己写公式解析器」:真实 Excel 涉及范围引用、绝对引用、动态数组、混乱的类型隐式转换,而 Univer 的 @univerjs/engine-formula 已经把这些都解决了。整体架构如下:
Astro 静态外壳(SEO、文案、JSON-LD、骨架屏)
│ client:only="react"
▼
ExerciseRunner(React Island)
│
├── SheetEditor(Univer 网格 + 公式引擎)
└── 多用例判题器(注入数据 → 重算 → 对比)
坑 1:Univer 和 SSR 天生不合
Univer 依赖 window、document 和 Canvas,Astro 的 SSR 会直接崩溃。解决方案是用 client:only="react" 挂载判题组件,同时提供一个 Astro 骨架屏作为 slot="fallback",避免 Island 启动时布局跳动。
Vite 也需要额外调教:Univer 的依赖图很深,开发环境下陈旧的 optimizeDeps 缓存曾导致 Island 白屏。最终的方案是在 astro.config.ts 中固定完整导入集,并把 @univerjs/* 标记为 ssr.noExternal。
坑 2:锁定表格,但要留给判题器“后门”
学习者看到的是一张完整的电子表格。如果不做锁定,用户可以直接改源数据来“作弊通过”。
做法是用 Univer 事件(BeforeSheetEditStart、BeforeCommandExecute)取消答案单元格之外的一切编辑,然后为判题器开一个短暂的程序化写入通道:
async function withEditableSheet(mutate) {
allowProgrammaticMutateRef.current = true;
try {
return await mutate(sheet);
} finally {
allowProgrammaticMutateRef.current = false;
}
}
用户碰不了锁定的单元格,判题器却可以自由改写测试用例和应用公式。
判题器:多用例 + 防硬编码
只检查一个期望值是不够的——求和题里写死 =1500 也能“通过”。点击提交时,SheetDo 会:
- 要求公式必须以
=开头 - 可选要求包含指定函数关键字(
XLOOKUP、SUM……) - 对每个测试用例:注入单元格数据 → 应用用户的公式 → 等待计算完成 → 对比结果
- 恢复可见的用例,逐条展示通过/失败
for (const tc of testCases) {
await editor.updateCells({ ...baseCells, ...tc.cells });
const value = await editor.applyFormula(target, formula);
evaluations.push({ actualValue: value, expectedValue: tc.expectedValue });
}
一个容易被忽略的细节:Univer 的计算是异步的。setFormula 之后必须等待 onCalculationResultApplied 事件再读单元格,否则判题读到的是旧值。此外,结果对比容忍浮点误差、货币符号和千分位逗号(valuesEqual 使用 1e-9 精度)。
练习题库的 Headless CI
每道 JSON 练习题都附有官方答案。CI 里用 Vitest 启动一个无 UI 的 Univer(只加载 core + sheets + formula 插件),把每道题的官方答案跑过所有测试用例,答案一旦“漂移”就挂掉 CI。不需要 Playwright、不需要浏览器,整个题库几秒钟跑完。
效果对比
| 后端沙箱 | 浏览器判题(Astro + Univer) | |
|---|---|---|
| 延迟 | 数百毫秒起 | 几十毫秒 |
| 计算成本 | 随提交量增长 | 仅 CDN 费用 |
| 离线 | 弱 | 首次加载后可用 |
| SEO | SPA 折腾 | 静态 HTML |
几点 takeaway:
- 重量级电子表格 UI 别进 SSR,用
client:only+ 骨架屏 - 判题靠“注入用例对比结果”,而不是字符串匹配
- 读结果前永远先等公式引擎算完
可以去 sheetdo.com 亲自体验。