开发者文档
本页介绍 wanim 的内部工作方式,以及如何从源码构建。如果你只想编写场景,请先看画廊。
这背后的想法很简单。Pyodide 是编译为 WebAssembly 的完整 CPython 运行时,对于需要随处嵌入的演示来说太过笨重。 因此 wanim 把 manim 代码当作一种小型的领域专用语言(DSL):先把它翻译成 JavaScript,再在兼容 manim 的库上运行。
完整流程
一个场景在绘制之前,要经过四个阶段:
- 预处理。
py2js/src/preprocess.ts改写 Python 源码:去掉manim、numpy、random、math、itertools的导入语句(它们的名字改为作为全局变量注入),把numpy调用改写为 numpy-ts 调用, 并把编译器无法解析的少数语法(例如嵌套的多重for推导式)展开。其他导入会产生明确的错误。 - 编译。 预处理后的源码交给 rapydscript-ng 编译。
使用的是固定到某个提交的版本,并附加了 wanim 的补丁。补丁做两件事:一是让算术运算经过辅助函数,
使运算符重载(例如
UP + LEFT、矩阵乘法)正常工作;二是把self.play(...)和self.wait(...)改写为await调用,让 Python 代码的写法仍然是同步的。 - 运行。 编译得到的 JavaScript 在一个隐藏的 iframe 中执行,该 iframe 提供独立的全局作用域, 运行时的垫片(shim)已经就位。场景通过库来创建对象,库中的类通过包装器暴露给 rapydscript。
- 绘制。
src/实时驱动场景。每一帧,场景提取几何信息,Canvas 2D 渲染器负责绘制。帧与帧之间, 运行时会让出控制权给浏览器,这就是self.wait()能真正暂停的原因。
runPythonOnCanvas(source, canvas, options) 完成上述全部四个阶段。<wanim-scene> 元素只是它的一个薄包装。
库
src/ 是 manim Community Edition 的 TypeScript 移植版,并针对网页做了扩展。它涵盖场景、图形对象、
VMobject、动画、相机,以及 ThreeDScene 的相机。交互控件和 InteractiveScene 是 wanim 在同一模型上新增的内容。
排版与文字的处理方式如下:
MathTex与Tex使用 MathJax,并打包为按需加载的 chunk。- 文字通过 opentype.js 渲染,字体回退链可以配置。默认的链指向免费的 CDN 版本 Tinos 与 Noto Serif SC。
numpy由 numpy-ts 提供支持,它持有一块 WebAssembly 堆。wanim 在启动时设定堆的大小, 也可以在首次使用前调用configureWasm修改。
仓库结构
wanim/
src/ 兼容 manim 的 TypeScript 库与 Canvas 2D 渲染器
src/element.ts <wanim-scene> 自定义元素
src/embed.ts URL 片段中的代码编码与 iframe 辅助函数
py2js/ 预处理、编译器桥接、运行时垫片、rapydscript 补丁
demo/ 编辑器页面与 iframe 播放器
scripts/ 打包、构建脚本与演示服务器
packaging/wanim/ npm 包的 package.json、README 与 LICENSE
tests/ 基于 Vitest 的库测试
构建与运行
需要 Node.js 20 或更高版本,以及 pnpm。
git clone https://github.com/Acfboy/Wanim.git
cd Wanim
pnpm install
pnpm build:demo # 打包运行时、编译打过补丁的编译器、构建元素
pnpm demo # 编辑器位于 http://localhost:5174/demo/
pnpm build:demo 完成以下工作:
- 对库进行类型检查并打包;
- 构建
py2js桥接与<wanim-scene>元素; - 按固定提交克隆 rapydscript-ng,应用
py2js/patches/rapydscript-ng.patch,并生成打过补丁的rapydscript.js。
第一次运行需要网络来完成克隆,检出的副本缓存在 .rapydscript-ng/ 中。
线上部署时,只需提供 dist/ 中的文件。加载方式见使用 wanim。
检查
pnpm typecheck # TypeScript 类型检查,不输出文件
pnpm test # 库测试(Vitest)
pnpm test:py2js # 预处理、错误格式化与运行时桥接测试
打包
npm 包由 packaging/wanim/ 与构建产物组装而成:
pnpm build:packages # 重新构建 dist/,并在 packages-out/ 中组装包
pnpm pack:packages # 基于当前的 dist/ 组装,并执行 npm pack
包名为 @wanimjs/wanim。它包含带类型声明的 Node 构建,以及一个 browser/ 目录,里面是预先构建好的浏览器 bundle。
子路径导出的列表见使用 wanim。
numpy-ts、MathJax 和 opentype.js 都是常规依赖。在浏览器 bundle 中,numpy-ts 保留为裸导入(bare import), 需要使用者通过 import map 或打包工具来映射。bundle 本身不包含它。
项目状态
wanim 目前是早期预览版。打包方式和嵌入 API 在正式版本发布前仍可能变化。在此之前,仓库是唯一的权威来源。 编译失败的场景和错误报告尤其有价值。请在 GitHub Issues 中提交,附上 Python 源码以及你期望的结果。