Skip to content

Commit 1c368e2

Browse files
authoredJan 17, 2025
Merge pull request #8 from stereobooster/new-d2
New d2 plugin based on WASM
2 parents 66509e1 + dda3501 commit 1c368e2

File tree

11 files changed

+45
-171
lines changed

11 files changed

+45
-171
lines changed
 

‎netlify.toml

-2
This file was deleted.

‎packages/docs/src/content/docs/diagrams/d2.mdx

+10-44
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Rehype plugin to generate [D2](https://d2lang.com/) diagrams in place of code fe
1010
<Tabs>
1111
<TabItem label="Diagram">
1212

13-
```d2 pad=10
13+
```d2
1414
direction: right
1515
x.y.z -> a.b.c: Label
1616
```
@@ -19,7 +19,7 @@ x.y.z -> a.b.c: Label
1919
<TabItem label="Markdown">
2020

2121
````md
22-
```d2 pad=10
22+
```d2
2323
direction: right
2424
x.y.z -> a.b.c: Label
2525
```
@@ -32,12 +32,6 @@ x.y.z -> a.b.c: Label
3232

3333
<PackageManagers pkg="@beoe/rehype-d2" />
3434

35-
You need to install D2 as well:
36-
37-
```sh
38-
curl -fsSL https://d2lang.com/install.sh | sh -s --
39-
```
40-
4135
## Usage
4236

4337
```js
@@ -82,66 +76,38 @@ export type D2Options = {
8276
* Set the diagram layout engine to the passed string. For a
8377
* list of available options, run layout.
8478
*/
85-
layout?: string;
86-
/**
87-
* @default 100
88-
* Pixels padded around the rendered diagram.
89-
*/
90-
pad?: number;
91-
/**
92-
* @default -1
93-
* Scale the output. E.g., 0.5 to halve the default size.
94-
* Default -1 means that SVG's will fit to screen and all others
95-
* will use their default render size. Setting to 1 turns off
96-
* SVG fitting to screen.
97-
*/
98-
scale?: number;
79+
layout?: "dagre" | "elk";
9980
/**
10081
* @default false
10182
* Renders the diagram to look like it was sketched by hand.
10283
*/
10384
sketch?: boolean;
104-
/**
105-
* @default true
106-
* Bundle all assets and layers into the output svg.
107-
*/
108-
bundle?: boolean;
109-
/**
110-
* Center the SVG in the containing viewbox, such as your
111-
* browser screen.
112-
*/
113-
center?: boolean;
11485
};
11586
```
11687

11788
You can set it globally:
11889

11990
```ts
12091
use(rehypeD2, {
121-
d2Options: { pad: 20 },
92+
d2Options: { layout: "dagre" },
12293
});
12394
```
12495

12596
Or locally:
12697

12798
````md
128-
```d2 pad=10
99+
```d2
129100
...
130101
```
131102
````
132103

133-
## Tips
134-
135-
### Note about Netlify
136-
137-
You can create small Netlify plugin to install D2. See example [here](https://github.com/stereobooster/beoe/tree/main/plugins/netlify-plugin-d2)
138-
139104
## Issues
140105

141-
- [ ] [Support links in connections](https://github.com/terrastruct/d2/pull/1955)
142-
- [ ] [JS package](https://github.com/terrastruct/d2/discussions/234#discussioncomment-11286029)
106+
- [x] [Support links in connections](https://github.com/terrastruct/d2/pull/1955)
107+
- [x] [JS package](https://github.com/terrastruct/d2/discussions/234#discussioncomment-11286029)
108+
- [ ] [Export JSON graph](https://github.com/terrastruct/d2/discussions/2224)
109+
- [ ] [Class-based dark mode](https://github.com/terrastruct/d2/pull/1803)
143110
- [ ] [Remove embeded fonts](https://github.com/terrastruct/d2/discussions/132)
144111
- [ ] [Smaller embeded icons](https://github.com/terrastruct/d2/discussions/2223)
145-
- [ ] [Export JSON graph](https://github.com/terrastruct/d2/discussions/2224)
146-
- [ ] [Class-based dark mode](https://github.com/terrastruct/d2/discussions/2225)
112+
- [ ] [d2js: full options args](https://github.com/terrastruct/d2/issues/2297), like `pad`
147113
- [ ] Link resolution callback

‎packages/rehype-d2/README.md

-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ which can look like this:
2222

2323
## Usage
2424

25-
You need to install [D2](https://d2lang.com/tour/install) in order to use this plugin. **But** they are working on [WASM version](https://github.com/terrastruct/d2/discussions/234#discussioncomment-11286029), so hopefully this will change soon.
26-
2725
```js
2826
import rehypeD2 from "@beoe/rehype-d2";
2927

‎packages/rehype-d2/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
"clean": "rm -rf dist"
3434
},
3535
"dependencies": {
36-
"@beoe/rehype-code-hook-img": "workspace:*"
36+
"@beoe/rehype-code-hook-img": "workspace:*",
37+
"@terrastruct/d2": "^0.1.21"
3738
},
3839
"devDependencies": {
3940
"@types/hast": "^3.0.4",

‎packages/rehype-d2/src/d2.ts

+12-112
Original file line numberDiff line numberDiff line change
@@ -1,118 +1,18 @@
1-
import { spawn } from "node:child_process";
2-
3-
// https://github.com/stereobooster/venn-nodejs/blob/main/index.js
4-
export function exec(
5-
command: string,
6-
args: string[],
7-
stdin?: string,
8-
cwd?: string
9-
) {
10-
return new Promise<string>((resolve, reject) => {
11-
const child = spawn(command, args, {
12-
cwd,
13-
stdio: [],
14-
windowsHide: true,
15-
});
16-
17-
const output: string[] = [];
18-
let errorMessage = `Unable to run command: '${command} ${args.join(" ")}'.`;
19-
20-
child.stdout.on("data", (data: Buffer) => {
21-
const lines = data
22-
.toString()
23-
.split("\n")
24-
.filter((line) => line.length > 0);
25-
26-
output.push(...lines);
27-
});
28-
29-
child.stderr.on("data", (data: Buffer) => {
30-
errorMessage += `\n${data.toString()}`;
31-
});
32-
33-
child.on("error", (error) => {
34-
reject(new Error(errorMessage, { cause: error }));
35-
});
36-
37-
child.on("close", (code) => {
38-
if (code !== 0) {
39-
reject(new Error(errorMessage));
40-
41-
return;
42-
}
43-
44-
resolve(output.join(""));
45-
});
46-
47-
if (stdin) {
48-
child.stdin.write(stdin);
49-
child.stdin.end();
50-
}
51-
});
52-
}
1+
// @ts-ignore https://github.com/terrastruct/d2/issues/2286
2+
import { D2 } from "@terrastruct/d2";
533

544
export type D2Options = {
55-
/**
56-
* @default 0
57-
* Set the diagram theme ID.
58-
*/
59-
theme?: string;
60-
/**
61-
* @default -1
62-
* The theme to use when the viewer's browser is in dark mode.
63-
* When left unset --theme is used for both light and dark mode.
64-
* Be aware that explicit styles set in D2 code will still be
65-
* applied and this may produce unexpected results. We plan on
66-
* resolving this by making style maps in D2 light/dark mode
67-
* specific. See https://github.com/terrastruct/d2/issues/831.
68-
*/
69-
darkTheme?: string;
70-
/**
71-
* Set the diagram layout engine to the passed string. For a
72-
* list of available options, run layout.
73-
*/
74-
layout?: string;
75-
/**
76-
* @default 100
77-
* Pixels padded around the rendered diagram.
78-
*/
79-
pad?: number;
80-
/**
81-
* @default -1
82-
* Scale the output. E.g., 0.5 to halve the default size.
83-
* Default -1 means that SVG's will fit to screen and all others
84-
* will use their default render size. Setting to 1 turns off
85-
* SVG fitting to screen.
86-
*/
87-
scale?: number;
88-
/**
89-
* @default false
90-
* Renders the diagram to look like it was sketched by hand.
91-
*/
5+
layout?: "dagre" | "elk";
926
sketch?: boolean;
93-
/**
94-
* @default true
95-
* Bundle all assets and layers into the output svg.
96-
*/
97-
bundle?: boolean;
98-
/**
99-
* Center the SVG in the containing viewbox, such as your
100-
* browser screen.
101-
*/
102-
center?: boolean;
7+
themeID?: number;
1038
};
1049

105-
export function d2(code: string, options?: D2Options) {
106-
const args = [];
107-
108-
if (options?.theme) args.push(`--theme=${options.theme}`);
109-
// if (options?.darkTheme) args.push(`--dark-theme=${options.darkTheme}`);
110-
if (options?.layout) args.push(`--layout=${options.layout}`);
111-
if (options?.pad !== undefined) args.push(`--pad=${options.pad}`);
112-
if (options?.scale) args.push(`--scale=${options.scale}`);
113-
if (options?.sketch) args.push(`--sketch`);
114-
if (options?.bundle) args.push(`--bundle`);
115-
if (options?.center) args.push(`--center`);
116-
117-
return exec("d2", [...args, "-"], code);
10+
export async function d2(
11+
code: string,
12+
options?: D2Options
13+
): Promise<string> {
14+
const d2Instance = new D2();
15+
const result = await d2Instance.compile(code, options);
16+
const svg = await d2Instance.render(result.diagram, options);
17+
return svg
11818
}

‎packages/rehype-d2/src/index.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,27 @@ import { rehypeCodeHookImg } from "@beoe/rehype-code-hook-img";
66
import { D2Options, d2 } from "./d2.js";
77

88
export type RehypeD2Config = {
9-
d2Options?: D2Options;
9+
d2Options?: Omit<D2Options, "themeID"> & {
10+
theme?: string;
11+
darkTheme?: string;
12+
};
1013
};
1114

1215
export const rehypeD2 = rehypeCodeHookImg<RehypeD2Config>({
1316
language: "d2",
1417
render: async (code: string, opts) => {
1518
const { darkMode, d2Options, ...rest } = opts;
1619
const newD2Options = { ...d2Options, ...rest };
20+
if (newD2Options.theme !== undefined) {
21+
// @ts-ignore
22+
newD2Options.themeID = parseFloat(newD2Options.theme);
23+
}
1724
const svg = await d2(code, newD2Options);
1825
const darkSvg = darkMode
1926
? await d2(code, {
20-
...d2Options,
21-
theme: newD2Options?.darkTheme ?? "200",
27+
...newD2Options,
28+
// @ts-ignore
29+
themeID: parseFloat(newD2Options?.darkTheme ?? "200"),
2230
})
2331
: undefined;
2432
return { svg, darkSvg };
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<figure class="beoe d2"><svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 256 434"><svg id="d2-svg" class="d2-1843626214" viewBox="-101 -101 256 434"><rect width="256" height="434" x="-101" y="-101" stroke-width="0" rx="0" style="fill:#fff"></rect><style>.d2-1843626214 .text-bold{font-family:"d2-1843626214-font-bold"}@font-face{font-family:d2-1843626214-font-bold;src:url(data:application/font-woff;base64,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)}.connection,.shape{stroke-linejoin:round}.shape{shape-rendering:geometricPrecision}.connection{stroke-linecap:round}.d2-1843626214 .fill-N1{fill:#0a0f25}.d2-1843626214 .fill-B6{fill:#f7f8fe}.d2-1843626214 .stroke-B1{stroke:#0d32b2}</style><g id="x"><g class="shape"><path fill="#F7F8FE" stroke="#0D32B2" d="M1 0h53v66H1z" class="stroke-B1 fill-B6" style="stroke-width:2"></path></g><text x="27.5" y="38.5" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">x</text></g><g id="y"><g class="shape"><path fill="#F7F8FE" stroke="#0D32B2" d="M0 166h54v66H0z" class="stroke-B1 fill-B6" style="stroke-width:2"></path></g><text x="27" y="204.5" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">y</text></g><g id="(x -> y)[0]"><marker id="mk-3488378134" markerHeight="12" markerUnits="userSpaceOnUse" markerWidth="10" orient="auto" refX="7" refY="6" viewBox="0 0 10 12"><path stroke-width="2" d="m0 0 10 6-10 6z" class="connection" style="fill:#0d32b2"></path></marker><path fill="none" stroke="#0D32B2" marker-end="url(#mk-3488378134)" d="M27 68v94" class="connection stroke-B1" mask="url(#d2-1843626214)" style="stroke-width:2"></path></g><mask id="d2-1843626214" width="256" height="434" x="-101" y="-101" maskUnits="userSpaceOnUse"><path fill="#fff" d="M-101-101h256v434h-256z"></path><path fill="rgba(0,0,0,0.75)" d="M23.5 22.5h8v21h-8zM22.5 188.5h9v21h-9z"></path></mask></svg></svg></figure>
1+
<figure class="beoe d2"><svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 256 434"><svg id="d2-svg" class="d2-1706184380" viewBox="-101 -101 256 434"><rect width="256" height="434" x="-101" y="-101" stroke-width="0" rx="0" style="fill:#fff"></rect><style>.d2-1706184380 .text-bold{font-family:"d2-1706184380-font-bold"}@font-face{font-family:d2-1706184380-font-bold;src:url(data:application/font-woff;base64,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)}.connection,.shape{stroke-linejoin:round}.shape{shape-rendering:geometricPrecision}.connection{stroke-linecap:round}.d2-1706184380 .fill-N1{fill:#0a0f25}.d2-1706184380 .fill-B6{fill:#f7f8fe}.d2-1706184380 .stroke-B1{stroke:#0d32b2}</style><g id="x"><g class="shape"><path fill="#F7F8FE" stroke="#0D32B2" d="M1 0h53v66H1z" class="stroke-B1 fill-B6" style="stroke-width:2"></path></g><text x="27.5" y="38.5" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">x</text></g><g id="y"><g class="shape"><path fill="#F7F8FE" stroke="#0D32B2" d="M0 166h54v66H0z" class="stroke-B1 fill-B6" style="stroke-width:2"></path></g><text x="27" y="204.5" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">y</text></g><g id="(x -> y)[0]"><marker id="mk-3488378134" markerHeight="12" markerUnits="userSpaceOnUse" markerWidth="10" orient="auto" refX="7" refY="6" viewBox="0 0 10 12"><path stroke-width="2" d="m0 0 10 6-10 6z" class="connection" style="fill:#0d32b2"></path></marker><path fill="none" stroke="#0D32B2" marker-end="url(#mk-3488378134)" d="M27 68v94" class="connection stroke-B1" mask="url(#d2-1706184380)" style="stroke-width:2"></path></g><mask id="d2-1706184380" width="256" height="434" x="-101" y="-101" maskUnits="userSpaceOnUse"><path fill="#fff" d="M-101-101h256v434h-256z"></path><path fill="rgba(0,0,0,0.75)" d="M23.5 22.5h8v21h-8zM22.5 188.5h9v21h-9z"></path></mask></svg></svg></figure>

0 commit comments

Comments
 (0)