跳到主要内容

开发者文档

本页介绍 wanim 的内部工作方式,以及如何从源码构建。如果你只想编写场景,请先看画廊。

这背后的想法很简单。Pyodide 是编译为 WebAssembly 的完整 CPython 运行时,对于需要随处嵌入的演示来说太过笨重。 因此 wanim 把 manim 代码当作一种小型的领域专用语言(DSL):先把它翻译成 JavaScript,再在兼容 manim 的库上运行。

完整流程​

一个场景在绘制之前,要经过四个阶段:

  1. 预处理。 py2js/src/preprocess.ts 改写 Python 源码:去掉 manim、numpy、random、math、 itertools 的导入语句(它们的名字改为作为全局变量注入),把 numpy 调用改写为 numpy-ts 调用, 并把编译器无法解析的少数语法(例如嵌套的多重 for 推导式)展开。其他导入会产生明确的错误。
  2. 编译。 预处理后的源码交给 rapydscript-ng 编译。 使用的是固定到某个提交的版本,并附加了 wanim 的补丁。补丁做两件事:一是让算术运算经过辅助函数, 使运算符重载(例如 UP + LEFT、矩阵乘法)正常工作;二是把 self.play(...) 和 self.wait(...) 改写为 await 调用,让 Python 代码的写法仍然是同步的。
  3. 运行。 编译得到的 JavaScript 在一个隐藏的 iframe 中执行,该 iframe 提供独立的全局作用域, 运行时的垫片(shim)已经就位。场景通过库来创建对象,库中的类通过包装器暴露给 rapydscript。
  4. 绘制。 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 源码以及你期望的结果。