news 2026/9/12 4:16:05

D2.js 演进全解析:从首个公开版本到 d2-config 的能力矩阵与源码实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
D2.js 演进全解析:从首个公开版本到 d2-config 的能力矩阵与源码实现

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.212025-01-12首个公开版本(First public release)
0.1.222025-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.wasmworker.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函数:themeIDdarkThemeIDcenterpadscalesketch逐一被映射进d2svg.RenderOptsforceAppendixtargetanimateIntervalsaltnoXMLTag则写入返回给 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 的Compilefs构造成memfs.New(...)内存文件系统,再交给d2lib.Compile,相对路径引用因此在虚拟文件系统内得到解析。同时该版本改进了 ELK 布局的错误处理,把布局失败以明确的错误信息返回而非静默失败。

3.4 自定义字体:四字重 TTF 注入

0.1.22 新增fontRegularfontItalicfontBoldfontSemiBold四个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:编译后的图表对象,包含shapesconnectionsrootlegend,以及layers/scenarios/steps等多 board 结构;
  • Graph:底层图结构(对应d2graph.Graph),含edgesobjects与主题信息;
  • Shape/Connection/Text等:完整的形状与连线类型,Arrowhead甚至枚举了从nonearrowcf-onecf-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()。此外,sendMessagedisposed后调用会直接抛错,防止在已释放实例上误操作。

4.2 并发调用共享实例修复

Next 修复了"concurrent calls sharing a D2 instance"问题。从源码看,请求-响应的关联依赖pendingRequests映射表与自增id:每个sendMessage都会先await this.ready再登记请求。此前的竞态隐患在于初始化完成前发起多个调用可能因ready未就绪而丢失响应;当前实现通过"先等待 ready、再登记 ID、后 postMessage"的顺序保证了多个并发调用可以正确路由到各自的 Promise,是pendingRequests设计得以并发安全的前提。

4.3 弃用旧兼容导出:getELKGraphgetObjOrder

raw WASM 层的d2.getELKGraphd2.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,它依次完成:校验fsinputPath→ 构造内存文件系统与文本测量器 → 注册自定义字体 → 解析layout→ 调用d2lib.Compile→ 格式化源码回写fs→ 组装CompileResponse(含diagramgraph、合并后的renderOptions)。

六、迁移与工程实践建议

  1. 从旧导出迁移:若你曾直接调用 raw WASM 的getELKGraph/getObjOrder,请改走compile()的标准路径——layout: 'elk'已内置、对象顺序可通过返回的graph推导。弃用警告只会出现一次,迁移完成后即可在后续版本移除这些调用。
  2. 始终 dispose:在单页应用中,图表生命周期结束时调用await d2.dispose(),避免 worker 泄漏与 Node 进程挂起;重复调用是安全的。
  3. 充分利用 d2-config:把themeIDpadscaleanimateIntervalnoXMLTag等写在 D2 脚本配置块中,JS 侧只负责业务输入,图表语义保持自包含。
  4. 多图共存用 salt:同一 HTML 中内嵌多个相同图表时,为每个实例传入不同的salt,防止 SVG 中重复 ID 破坏 HTML 结构与样式定位。
  5. 多 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 4:14:57

牙科就诊管理系统:SpringBoot+Vue3+MyBatis技术架构解析

1. 项目概述&#xff1a;牙科就诊管理系统的技术架构与核心价值这个牙科就诊管理系统采用了当前企业级开发中最主流的"前后端分离"架构方案。前端基于Vue3的Composition API实现响应式界面&#xff0c;后端采用SpringBoot快速构建RESTful API&#xff0c;数据持久层使…

作者头像 李华
网站建设 2026/9/12 4:14:51

电钢琴选购指南:从键盘到音源的全面解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 4:14:43

MFA安全新挑战:IDN同形攻击与零宽字符钓鱼防御

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 4:12:18

论文的方法论怎么选对?一篇讲透从问题到方法

论文方法论总选错&#xff0c;多半不是方法名不好听&#xff0c;而是它跟你的问句、你的资料、你后续要用的分析没接上。这里把三条对齐线与四类错配形态摊开&#xff0c;帮你判断该动哪一边。从问题怎么一步步推到方法、几类方法怎么选、两类方法怎么结合&#xff0c;这些各有…

作者头像 李华
网站建设 2026/9/12 4:12:17

深度解析Gitee研发一体化:选型要点、流程实践与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 4:12:04

100 天 Python 学习路线:从第一行代码到项目交付的实战指南

100 天 Python 学习路线&#xff1a;从第一行代码到项目交付的实战指南 【免费下载链接】Python-100-Days Python - 100天从新手到大师 项目地址: https://gitcode.com/GitHub_Trending/py/Python-100-Days 做 Python 学习这件事&#xff0c;最常见的困境是资料零散、顺…

作者头像 李华