D2.js 演进全解析:从首个公开版本到 d2-config 的能力矩阵与源码实现
【免费下载链接】d2D2 is a modern diagram scripting language that turns text to diagrams.项目地址: https://gitcode.com/GitHub_Trending/d2/d2
本文以@d2lang/d2(D2.js)包的官方变更记录 d2js/js/CHANGELOG.md 为主线,梳理该 JavaScript/WASM 封装自首个公开版本以来的全部能力演进:包括d2-config带来的十余项渲染配置、自定义字体与相对导入支持、TypeScript 签名,以及D2.dispose()、并发调用修复等 Next 版本改动。读者读完本文将掌握 D2.js 的完整 API 面、各配置项的取值与语义,并能结合 index.d.ts、src/index.js 与 d2wasm/functions.go 理解其 Worker + WASM 底层运行机制。
一、版本脉络总览:一条从 "可用" 到 "完备" 的演进线
CHANGELOG 记录了 d2.js 包(注意:不包含主项目 d2 的变更)的三个阶段,对应三个版本区间:
| 版本 | 时间 | 定位 |
|---|---|---|
0.1.21 | 2025-01-12 | 首个公开版本(First public release) |
0.1.22 | 2025-03-20 | 引入d2-config、字体、相对导入与 TypeScript 签名 |
Next(未发布) | — | dispose()、并发修复、弃用兼容导出、体积缩减、支持 D2 0.7.1 |
当前包版本为0.1.33(见 d2js/js/package.json),即 0.1.22 之后的多个补丁级发布,CHANGELOG 中的 "Next" 条目指向的是这些后续累积改动。包名从旧命名空间@terrastruct/d2过渡为@d2lang/d2,旧包在过渡期继续同步发布以兼容存量用户,新装项目应直接使用@d2lang/d2。
二、0.1.21:首个公开版本的架构基石
首个版本确立了 D2.js 的核心架构——用 Web Worker 调用 WASM 文件("D2.js uses webworkers to call a WASM file")。这一设计从 src/index.js 中可以清晰看到:
new D2()构造函数创建nextRequestId计数器与pendingRequests请求映射表,并异步调用init()完成 worker 创建与 WASM 加载;sendMessage(type, data)是所有 API 的统一出口:为每个请求分配自增 ID,存入pendingRequests,再通过worker.postMessage({ id, type, data })发送;- worker 返回的消息中,
type === "result"或"error"时按data.id查找对应的 Promise 并 resolve/reject(见setupMessageHandler)。
平台的差异化由 src/platform.browser.js 与 src/platform.node.js 提供:
- 浏览器端:将
wasm_exec.js与 worker 脚本打包进 Blob,通过URL.createObjectURL创建 module 类型 Worker,WASM 二进制直接内联,无外部网络依赖; - Node 端:运行时按需动态
import("node:worker_threads")等模块,从包目录加载d2.wasm与worker.js。
浏览器与 Node 共享同一套D2API,这正是 README 宣称的 "Isomorphic"(同构)特性——同一份代码可无差别运行在两端,例如 d2js/js/examples/basic.html 展示的最小浏览器用例:
<script type="module"> import { D2 } from "../dist/browser/index.js"; const d2 = new D2(); const result = await d2.compile("x -> y"); const svg = await d2.render(result.diagram, result.renderOptions); document.getElementById("output").innerHTML = svg; </script>三、0.1.22:d2-config与渲染能力矩阵
0.1.22 是里程碑式的一次发布,核心是支持d2-config——即允许在 D2 脚本内以配置块声明渲染选项,同时让这些选项在 JavaScript 侧以结构化参数传入。
3.1 十余项新增选项及其语义
按 CHANGELOG 与 index.d.ts 中的RenderOptions定义,选项可划分为四组:
输出布局与几何
center:是否将 SVG 在所在 viewbox 中居中,默认false;pad:图形四周的内边距像素,默认100;scale:输出缩放倍数,例如0.5表示缩小一半。默认值会渲染出"适配屏幕"的 SVG;显式设为1则关闭适配;target:指定要渲染的 board。以layers.x.*形式渲染某一层及其全部子层;传''渲染所有 scenarios/steps/layers;默认只渲染根 board。多 board 输出目前仅支持动画 SVG,因此同时必须设置animateInterval > 0。
主题与外观
themeID:主题 ID,默认0(默认主题);darkThemeID:客户端处于深色模式时使用的主题 ID;forceAppendix:是否强制为 tooltip 与链接追加附录(appendix),默认false;sketch:手绘草图风格,默认false(0.1.21 已有,在 0.1.22 中得到完整传递支持)。
输出格式
animateInterval:单位为毫秒。设置后多个 board 会被打包进一个 SVG,按该间隔依次过渡(对应 Go 侧的d2animate.Wrap);salt:为输出 ID 追加的盐值字符串,用于在同一 HTML 文档中内嵌多个相同图表时避免重复 ID 导致 HTML 非法;noXMLTag:从输出 SVG 中省略<?xml ...?>声明,便于直接内嵌 HTML。
布局引擎(属于CompileOptions而非RenderOptions)
layout:取值'dagre'或'elk',默认'dagre'。
这些选项在 WASM 侧的实现位于 d2wasm/functions.go 的Compile函数:themeID、darkThemeID、center、pad、scale、sketch逐一被映射进d2svg.RenderOpts;forceAppendix、target、animateInterval、salt、noXMLTag则写入返回给 JS 侧的RenderOptions,供后续render()调用使用。layout通过LayoutResolver在"dagre"与"elk"两个引擎间路由,未知引擎会返回layout option 'x' not recognized错误(HTTP 风格错误码 400)。
3.2d2-config:脚本内的声明式配置
0.1.22 引入的d2-config意味着渲染选项可以在 D2 源文件内以配置块书写,编译后这些配置与 JS 侧传入的选项合并——compile()返回的CompileResponse.renderOptions正是"渲染选项与图表内配置合并后的结果"(见 index.d.ts 中CompileResponse的注释:Render options merged with configuration set in diagram)。实测中,脚本内配置的主题覆盖(themeOverrides)会体现在返回的renderOptions中,例如expect(resultOverridden.renderOptions.themeOverrides.b1).toBe("#000000")(见 d2js/js/test/unit/basic.test.js)。
3.3 相对导入支持与 ELK 错误处理增强
0.1.22 支持relative imports:编译请求以fs字段携带一份"D2 文件路径 → 内容"的映射,inputPath指定入口文件(默认index),从而支持 D2 语言的 imports 能力。在 src/index.js 中,compile()对字符串输入会包装为{ fs: { index: input }, options },对对象输入则透传并合并选项。底层由 d2wasm/functions.go 的Compile将fs构造成memfs.New(...)内存文件系统,再交给d2lib.Compile,相对路径引用因此在虚拟文件系统内得到解析。同时该版本改进了 ELK 布局的错误处理,把布局失败以明确的错误信息返回而非静默失败。
3.4 自定义字体:四字重 TTF 注入
0.1.22 新增fontRegular、fontItalic、fontBold、fontSemiBold四个CompileOptions,每个都接收一个包含.ttf文件字节的Uint8Array。若不提供,则分别回退到 Source Sans Pro 的 Regular/Italic/Bold/Semibold 内置字体(见 d2js/js/README.md)。
WASM 侧的实现逻辑(d2wasm/functions.goCompile):四个字体字节数组先被收集,只要任意一个非空,就调用d2fonts.AddFontFamily("custom", ...)注册名为custom的字族并设为compileOpts.FontFamily;注册失败(如非法字体数据)会返回错误码 400。这意味着开发者可以注入任意授权字体,让图表完全贴合产品视觉体系。
3.5 TypeScript 签名首次落地
0.1.22 首次提供index.d.ts类型签名。该文件不仅是 API 的文档,还刻画了编译产物的完整数据结构:
Diagram:编译后的图表对象,包含shapes、connections、root、legend,以及layers/scenarios/steps等多 board 结构;Graph:底层图结构(对应d2graph.Graph),含edges、objects与主题信息;Shape/Connection/Text等:完整的形状与连线类型,Arrowhead甚至枚举了从none、arrow到cf-one、cf-many-required的全部箭头形态。
四、Next 版本:围绕健壮性与 API 卫生的关键修复
CHANGELOG "Next" 区列出了未发布版本(即 0.1.23+ 各次补丁发布)的改动,每一项都能在源码或测试中找到对应实现。
4.1D2.dispose():主动释放 Worker 资源
新增的dispose()用于终止支撑当前实例的后台 worker。在 src/index.js 的实现中:
- 幂等:重复调用返回同一个
disposePromise; - 立即将
disposed置为true,并rejectPendingRequests(new Error("D2 instance has been disposed"))拒绝所有在途请求; - 等待
ready初始化完成后调用worker.terminate()。
这解决了此前困扰 Node 用户的进程无法退出问题——CHANGELOG 原文强调调用时机:"当实例不再需要时调用,以便 Node 进程可以退出、浏览器 worker 资源被释放"。所有单元测试(d2js/js/test/unit/basic.test.js)与 CJS/ESM 集成测试(d2js/js/test/integration/cjs.test.cjs、d2js/js/test/integration/esm.test.mjs)均在末尾调用await d2.dispose()。此外,sendMessage在disposed后调用会直接抛错,防止在已释放实例上误操作。
4.2 并发调用共享实例修复
Next 修复了"concurrent calls sharing a D2 instance"问题。从源码看,请求-响应的关联依赖pendingRequests映射表与自增id:每个sendMessage都会先await this.ready再登记请求。此前的竞态隐患在于初始化完成前发起多个调用可能因ready未就绪而丢失响应;当前实现通过"先等待 ready、再登记 ID、后 postMessage"的顺序保证了多个并发调用可以正确路由到各自的 Promise,是pendingRequests设计得以并发安全的前提。
4.3 弃用旧兼容导出:getELKGraph与getObjOrder
raw WASM 层的d2.getELKGraph与d2.getObjOrder兼容导出被标记弃用:它们仍可调用一个发布周期,且每个导出只输出一次迁移警告。弃用原因在 d2wasm/functions.go 的注释中写得很明确:
getELKGraph的替代方案是d2.compile配合options.layout: "elk"——ELK 布局已内置进 D2 本体,无需在 JS 侧预处理 ELK 图;getObjOrder的替代方案是 Go 集成中的d2oracle.GetObjOrder。
两者均通过sync.Once保证警告仅触发一次。这是典型的 API 卫生策略:给出明确的迁移路径,同时避免对存量调用方的破坏。
4.4 其余修复与支持
- Unicode 字符后的补全修复:LSP 补全(
GetCompletions)改用 UTF-16 定位(d2lsp.GetCompletionItemsUTF16),修正了中文等多字节字符后的光标偏移问题; - TypeScript 签名修复:基于用户反馈持续修正
index.d.ts中与运行时行为不符的声明; - theme-overrides 不生效修复:脚本内
themeOverrides此前未能正确传导至渲染,修复后通过RenderOptions携带(单元测试以b1: "#000000"断言验证); - ELK 布局中 grids 修复:网格(grid)图形在 ELK 引擎下的布局问题;
- 显著缩减 bundle 体积:减少内联资源与冗余代码,降低浏览器加载成本;
- 支持 D2 0.7.1:WASM 内核随主项目升级,
version()可返回对应版本号。
五、完整实战:一条数据从 D2 源码到 SVG 的调用链
综合 README(d2js/js/README.md)与源码,一次完整的 D2.js 调用可以分为五步:
import { D2 } from '@d2lang/d2'; // Node 与浏览器写法一致 const d2 = new D2(); // 1. 创建实例(异步初始化 worker + WASM) // 2. 编译:字符串输入走默认入口 "index" const result = await d2.compile('x -> y', { layout: 'dagre', sketch: true, themeID: 0, }); // 3. 渲染:compile 返回的 renderOptions 已合并脚本内 d2-config const svg = await d2.render(result.diagram, result.renderOptions); // 4. 释放资源(Next 版本引入) await d2.dispose();多文件导入场景:传入fs映射与inputPath,例如:
const fs = { "project.d2": "a: @import", "import.d2": "x: {shape: circle}", }; const result = await d2.compile({ fs, inputPath: "project.d2", options: { sketch: true }, }); const svg = await d2.render(result.diagram, result.renderOptions);这条链路在 Worker 内的对应处理见 src/worker.browser.js:compile消息把数据JSON.stringify后交给 WASM 导出,返回的 JSON 若含error字段则抛错,否则将response.data回传主线程;render消息额外做了一次 base64 解码(SVG 以字节流返回)。WASM 侧的编译入口则是 d2wasm/functions.go 的Compile,它依次完成:校验fs与inputPath→ 构造内存文件系统与文本测量器 → 注册自定义字体 → 解析layout→ 调用d2lib.Compile→ 格式化源码回写fs→ 组装CompileResponse(含diagram、graph、合并后的renderOptions)。
六、迁移与工程实践建议
- 从旧导出迁移:若你曾直接调用 raw WASM 的
getELKGraph/getObjOrder,请改走compile()的标准路径——layout: 'elk'已内置、对象顺序可通过返回的graph推导。弃用警告只会出现一次,迁移完成后即可在后续版本移除这些调用。 - 始终 dispose:在单页应用中,图表生命周期结束时调用
await d2.dispose(),避免 worker 泄漏与 Node 进程挂起;重复调用是安全的。 - 充分利用 d2-config:把
themeID、pad、scale、animateInterval、noXMLTag等写在 D2 脚本配置块中,JS 侧只负责业务输入,图表语义保持自包含。 - 多图共存用 salt:同一 HTML 中内嵌多个相同图表时,为每个实例传入不同的
salt,防止 SVG 中重复 ID 破坏 HTML 结构与样式定位。 - 多 board 动画的前提:
target指向多个 board 时务必同时设置animateInterval > 0,否则编译会以错误拒绝(源码中明确校验!noChildren && animateInterval <= 0时报错)。
七、结语
从 0.1.21 的 Worker + WASM 最小可用架构,到 0.1.22 的d2-config选项矩阵、自定义字体与相对导入,再到 Next 阶段的dispose()、并发安全与 API 卫生清理,D2.js 的演进史本身就是一份"如何做好一个 WASM 封装层"的范本。它的 API 设计始终遵循同一原则:Node 与浏览器同构、脚本与 JS 双入口配置、所有复杂细节收敛在 Worker 与 WASM 一侧。持续关注 d2js/js/CHANGELOG.md 即可跟踪其后续演进,而本文涉及的 index.d.ts、src/index.js 与 d2wasm/functions.go 则是深入理解其行为的三个最佳入口。
【免费下载链接】d2D2 is a modern diagram scripting language that turns text to diagrams.项目地址: https://gitcode.com/GitHub_Trending/d2/d2
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考