@matfsw/docusaurus-plantuml-plugin 1.0.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,81 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.1.0]
11
+
12
+ ### Added
13
+
14
+ - **Graphviz/DOT diagrams.** `dot`, `graphviz` and `gv` fences are now laid out by Graphviz in
15
+ the reader's browser and replaced with SVG, exactly as `plantuml` fences already were. Zoom
16
+ and pan, lazy rendering, caching, sanitization, the accessible markup and the error panel are
17
+ all shared with the PlantUML path.
18
+
19
+ **This costs no additional bytes.** `@plantuml/core` already bundles `viz-global.js` — Viz.js
20
+ containing Graphviz — because PlantUML needs it for its own layout, and the plugin has been
21
+ emitting and loading it since `0.1.0`. Rendering DOT reuses that engine rather than adding a
22
+ dependency. See [ADR 0004](docs/adr/0004-graphviz-engine-reuse.md).
23
+
24
+ - A `graphviz` option group: `enabled`, `languages`, `engine`, `allowEngineOverride`,
25
+ `maxSourceBytes` and `transparentBackground`. Unknown keys inside it are rejected at build
26
+ time, like unknown keys at the top level.
27
+
28
+ - Per-fence layout engine selection with `engine=neato` (or `circo`, `fdp`, `twopi`, `sfdp`,
29
+ `osage`, `patchwork`, `nop`, `nop1`, `nop2`, `dot`) in the fence metastring. Disable with
30
+ `graphviz.allowEngineOverride: false`.
31
+
32
+ - Two `data-*` attributes on the diagram `<figure>`: `data-diagram-engine`
33
+ (`plantuml` | `graphviz`) and `data-diagram-layout` (the Graphviz layout engine, absent on
34
+ PlantUML diagrams). Both are part of the documented public surface.
35
+
36
+ - `GraphvizOptions`, `ResolvedGraphvizOptions`, `GraphvizEngine`, `GRAPHVIZ_ENGINES` and
37
+ `DEFAULT_GRAPHVIZ_OPTIONS` are exported from the package root.
38
+
39
+ ### Changed
40
+
41
+ - **`dot`, `graphviz` and `gv` fences now render as diagrams instead of as code blocks.** This
42
+ is the one behaviour change in this release. If you have such fences that are meant to stay
43
+ code, set `graphviz: {enabled: false}` or narrow `graphviz.languages`.
44
+
45
+ - **The two runtimes are now loaded independently.** `viz-global.js` (~1.4 MB) and
46
+ `plantuml.js` (~6.8 MB) previously shared one loader promise, so any diagram fetched both.
47
+ They are now separate singletons, and a page with only DOT diagrams never downloads the
48
+ PlantUML engine. Nothing changes for a PlantUML-only page: the same two files load in the
49
+ same order from the same single `<script>` tag.
50
+
51
+ - The build now fails with an actionable message if the installed `@plantuml/core` is missing
52
+ either runtime file, rather than letting a broken install reach a reader's browser as a
53
+ runtime load failure.
54
+
55
+ - Progress text, the default accessible label and the error heading name the engine that is
56
+ actually involved — "Loading Graphviz runtime…", "Graphviz diagram", "Error: Graphviz diagram
57
+ could not be rendered".
58
+
59
+ - The missing-plugin-data error panel now says "Diagram plugin data is missing" rather than
60
+ "PlantUML plugin data is missing", since it applies to both engines.
61
+
62
+ ### Notes
63
+
64
+ - **Invalid DOT is reported with Graphviz's own diagnostic, including the line number** — for
65
+ example `syntax error in line 3 near '}'`. PlantUML reports invalid diagrams by rendering an
66
+ error picture, which the plugin has to detect heuristically
67
+ ([ADR 0002](docs/adr/0002-render-to-string.md)); Graphviz returns structured errors, so the
68
+ reader is told exactly what is wrong and where.
69
+
70
+ - **Graphviz diagrams are not re-rendered on a colour-mode toggle.** Graphviz has no dark
71
+ theme, so rather than laying the graph out twice, the plugin renders on a transparent
72
+ background and retargets Graphviz's default black strokes, fills and text at the page's text
73
+ colour in CSS. Colours set in the DOT source always win, in both modes. As a result the
74
+ colour mode is absent from the Graphviz cache key and a theme toggle does not disturb a
75
+ reader's zoom.
76
+
77
+ - **`graphviz.maxSourceBytes` defaults to 100 000.** Graphviz lays out synchronously: a
78
+ 300-edge graph renders in ~26 ms, but a 3 000-edge graph takes ~2.25 s and visibly freezes the
79
+ page. Oversized sources are refused with an explanatory panel, before the engine is even
80
+ downloaded.
81
+
82
+ - Rendering remains 100% same-origin and client-side. No CDN, no rendering service, no
83
+ `unsafe-eval`, and no Graphviz installation on the build machine or the reader's.
84
+
10
85
  ## [1.0.3]
11
86
 
12
87
  ### Security
@@ -126,7 +201,8 @@ Initial release.
126
201
  opt-out.
127
202
  - Plugin option validation that rejects unknown keys and out-of-range values at build time.
128
203
 
129
- [Unreleased]: https://github.com/matf/docusaurus-plantuml-plugin/compare/v1.0.3...HEAD
204
+ [Unreleased]: https://github.com/matf/docusaurus-plantuml-plugin/compare/v1.1.0...HEAD
205
+ [1.1.0]: https://github.com/matf/docusaurus-plantuml-plugin/compare/v1.0.3...v1.1.0
130
206
  [1.0.3]: https://github.com/matf/docusaurus-plantuml-plugin/compare/v1.0.2...v1.0.3
131
207
  [1.0.2]: https://github.com/matf/docusaurus-plantuml-plugin/compare/v1.0.1...v1.0.2
132
208
  [1.0.1]: https://github.com/matf/docusaurus-plantuml-plugin/compare/v1.0.0...v1.0.1
package/README.md CHANGED
@@ -1,11 +1,15 @@
1
1
  # @matfsw/docusaurus-plantuml-plugin
2
2
 
3
- Render PlantUML diagrams in Docusaurus 3 entirely in the browser — no Java, no PlantUML
4
- server, no Kroki, no CDN.
3
+ Render **PlantUML** and **Graphviz/DOT** diagrams in Docusaurus 3 entirely in the browser — no
4
+ Java, no PlantUML server, no Graphviz install, no Kroki, no CDN.
5
5
 
6
6
  **→ [Live demo](https://matf.github.io/docusaurus-plantuml-demo)** — every diagram on that site
7
7
  is rendered by your own browser.
8
8
 
9
+ > **The package name says PlantUML, and it renders Graphviz too.** Graphviz support was added
10
+ > in 1.1.0; the name is kept for the sake of everyone who already depends on it. See
11
+ > [Graphviz diagrams](#graphviz-dot-diagrams).
12
+
9
13
  Write a `plantuml` fence in any `.md` or `.mdx` file and the code block is replaced, in the
10
14
  reader's browser, with an SVG rendered by the official [`@plantuml/core`][plantuml-core]
11
15
  package:
@@ -25,9 +29,20 @@ Browser --> User: Signed in
25
29
  ```
26
30
  ````
27
31
 
32
+ …and a `dot` fence is replaced with an SVG laid out by Graphviz:
33
+
34
+ ````markdown
35
+ ```dot title="Build pipeline"
36
+ digraph {
37
+ rankdir=LR;
38
+ src -> build -> test -> deploy;
39
+ }
40
+ ```
41
+ ````
42
+
28
43
  **Rendering is 100% same-origin and client-side. Your diagram source never leaves the
29
44
  browser.** It is not sent to plantuml.com, to a Kroki instance, or to any other service, and
30
- the engine itself is served from your own site's `baseUrl`.
45
+ the engines themselves are served from your own site's `baseUrl`.
31
46
 
32
47
  ## Status and compatibility
33
48
 
@@ -134,6 +149,15 @@ const config: Config = {
134
149
  showSourceOnError: true,
135
150
  renderTimeoutMs: 20_000,
136
151
  cacheMaxEntries: 50,
152
+ zoom: true,
153
+ graphviz: {
154
+ enabled: true,
155
+ languages: ['dot', 'graphviz', 'gv'],
156
+ engine: 'dot',
157
+ allowEngineOverride: true,
158
+ maxSourceBytes: 100_000,
159
+ transparentBackground: true,
160
+ },
137
161
  } satisfies PlantUmlPluginOptions,
138
162
  ],
139
163
  ],
@@ -185,14 +209,20 @@ Alice -> Bob : puml alias in MDX
185
209
 
186
210
  ### Supported fence aliases
187
211
 
188
- By default the plugin claims two fence languages:
212
+ By default the plugin claims five fence languages across two engines:
213
+
214
+ | Engine | Default languages | Option |
215
+ | -------- | ----------------------- | -------------------- |
216
+ | PlantUML | `plantuml`, `puml` | `languages` |
217
+ | Graphviz | `dot`, `graphviz`, `gv` | `graphviz.languages` |
189
218
 
190
- - `plantuml`
191
- - `puml`
219
+ Matching is **case-insensitive**, so ` ```PlantUML `, ` ```PUML `, ` ```DOT ` and ` ```dot `
220
+ are all recognised. Change either set with its option; entries are normalised to lower case,
221
+ must be non-empty, must not repeat, and the two sets must not overlap.
192
222
 
193
- Matching is **case-insensitive**, so ` ```PlantUML `, ` ```PUML ` and ` ```plantuml ` are all
194
- recognised. Change the set with the `languages` option; entries are normalised to lower case,
195
- must be non-empty, and must not repeat.
223
+ > **Upgrading from 1.0.x:** `dot`, `graphviz` and `gv` fences were previously rendered as
224
+ > ordinary code blocks and are now rendered as diagrams. If you have such fences that are meant
225
+ > to stay code, set `graphviz: {enabled: false}`, or narrow `graphviz.languages`.
196
226
 
197
227
  Everything else — inline code, ordinary fenced blocks in any other language, and code blocks
198
228
  authored as JSX children — is delegated to the original Docusaurus `Code` component with its
@@ -212,8 +242,14 @@ props untouched. Existing code-block behaviour (highlighting, line numbers, titl
212
242
  | `renderTimeoutMs` | `number` | `20000` | Abort a single render (and the runtime load) after this many milliseconds. Integer, `100`–`600000`. |
213
243
  | `cacheMaxEntries` | `number` | `50` | Upper bound on cached SVG entries. Positive integer. |
214
244
  | `zoom` | `boolean` | `true` | Let readers zoom and pan diagrams. Adds a control toolbar and a focusable viewport. Override per fence with `zoom=false`. |
245
+ | `graphviz` | `object` | see below | Graphviz/DOT support. See [Graphviz options](#graphviz-options). |
215
246
  | `id` | `string` | `'default'` | Docusaurus plugin instance id; only relevant if you register the plugin more than once. |
216
247
 
248
+ Every option above except `graphviz` applies to **both** engines: `lazy`, `cache`,
249
+ `sanitizeSvg`, `showSourceOnError`, `renderTimeoutMs`, `cacheMaxEntries` and `zoom` behave
250
+ identically for a `dot` fence and a `plantuml` fence. `theme` is the exception — see
251
+ [Light and dark mode](#light-and-dark-mode).
252
+
217
253
  Options are validated during the Docusaurus configuration phase, before anything is built.
218
254
  **Unknown keys are rejected** rather than ignored, because a typo in `docusaurus.config.ts`
219
255
  would otherwise silently disable the option you meant to set:
@@ -221,15 +257,133 @@ would otherwise silently disable the option you meant to set:
221
257
  ```text
222
258
  [docusaurus-plugin-plantuml-client] Unknown option 'sanitiseSvg'. Supported options:
223
259
  'languages', 'theme', 'lazy', 'cache', 'sanitizeSvg', 'showSourceOnError',
224
- 'renderTimeoutMs', 'cacheMaxEntries', 'zoom'.
260
+ 'renderTimeoutMs', 'cacheMaxEntries', 'zoom', 'graphviz'.
225
261
  ```
226
262
 
227
- `PlantUmlPluginOptions`, `ResolvedPlantUmlOptions`, `CacheMode`, `DiagramTheme`,
228
- `DEFAULT_OPTIONS`, `resolveOptions` and `PlantUmlOptionsError` are exported from the package
229
- root. The renderer, queue and cache internals are deliberately not exported.
263
+ The same is true one level deeper, so `graphviz: {enigne: 'neato'}` fails the build rather than
264
+ quietly doing nothing.
265
+
266
+ `PlantUmlPluginOptions`, `ResolvedPlantUmlOptions`, `GraphvizOptions`,
267
+ `ResolvedGraphvizOptions`, `GraphvizEngine`, `GRAPHVIZ_ENGINES`, `CacheMode`, `DiagramTheme`,
268
+ `DEFAULT_OPTIONS`, `DEFAULT_GRAPHVIZ_OPTIONS`, `resolveOptions` and `PlantUmlOptionsError` are
269
+ exported from the package root. The renderer, queue and cache internals are deliberately not
270
+ exported.
271
+
272
+ ## Graphviz (DOT) diagrams
273
+
274
+ `dot`, `graphviz` and `gv` fences are laid out by [Graphviz][graphviz] compiled to WebAssembly,
275
+ in the reader's browser, exactly like PlantUML fences:
276
+
277
+ ````markdown
278
+ ```dot title="Build pipeline"
279
+ digraph {
280
+ rankdir=LR;
281
+ node [shape=box, style=rounded];
282
+ src -> build -> test -> deploy;
283
+ }
284
+ ```
285
+ ````
286
+
287
+ ### It costs no extra bytes
288
+
289
+ This is the part worth knowing: **the plugin already shipped Graphviz before it could render
290
+ DOT.** `@plantuml/core` bundles `viz-global.js` — Viz.js containing Graphviz — because PlantUML
291
+ needs it for its own layout, and the plugin has been emitting and loading it since 0.1.0.
292
+ Rendering DOT reuses that engine, so a site that already renders PlantUML pays **zero
293
+ additional bytes** for Graphviz support. See
294
+ [ADR 0004](docs/adr/0004-graphviz-engine-reuse.md).
295
+
296
+ The reverse also holds: a page containing only DOT diagrams loads only `viz-global.js`
297
+ (~1.4 MB) and never fetches the ~6.8 MB PlantUML engine.
298
+
299
+ ### Choosing a layout engine
300
+
301
+ Graphviz ships eleven layout engines. The default is `dot`; any fence can pick another with
302
+ `engine=`:
303
+
304
+ ````markdown
305
+ ```dot title="Undirected mesh" engine=neato
306
+ graph { a -- b -- c -- a }
307
+ ```
308
+ ````
309
+
310
+ Available engines: `circo`, `dot`, `fdp`, `neato`, `nop`, `nop1`, `nop2`, `osage`,
311
+ `patchwork`, `sfdp`, `twopi`. An unknown name fails with the supported set spelled out. Set
312
+ `graphviz.allowEngineOverride: false` to ignore the fence flag and pin every diagram to the
313
+ configured engine.
314
+
315
+ ### Colours and dark mode
316
+
317
+ Graphviz has no dark theme — it draws black on white regardless of the page. Rather than
318
+ re-rendering the graph for each colour mode, the plugin retargets Graphviz's _defaults_ at the
319
+ page's text colour in CSS, and renders on a transparent background.
320
+
321
+ **Colours set in the DOT source always win.** The rules only match elements Graphviz coloured
322
+ with its own black default (`stroke="black"`, `fill="black"`, and `<text>` with no `fill`), so
323
+ `color=red`, `fillcolor=lightblue` and friends are emitted and displayed exactly as authored,
324
+ in both modes.
325
+
326
+ Because nothing is re-rendered, toggling the theme on a page of DOT diagrams costs no layout
327
+ work at all and does not disturb a reader's zoom.
328
+
329
+ ### Error reporting
330
+
331
+ Graphviz returns structured diagnostics, so an invalid diagram is reported with the engine's
332
+ own message including the offending line:
333
+
334
+ ```text
335
+ Error: Graphviz diagram could not be rendered
336
+ syntax error in line 3 near '}'
337
+ ```
338
+
339
+ ### Graphviz options
340
+
341
+ ```ts
342
+ graphviz: {
343
+ enabled: true, // intercept DOT fences at all
344
+ languages: ['dot', 'graphviz', 'gv'], // fence languages, matched case-insensitively
345
+ engine: 'dot', // default layout engine
346
+ allowEngineOverride: true, // permit `engine=neato` on a fence
347
+ maxSourceBytes: 100_000, // refuse larger sources; see below
348
+ transparentBackground: true, // omit Graphviz's opaque white background
349
+ }
350
+ ```
351
+
352
+ | Option | Type | Default | Description |
353
+ | ----------------------- | ---------- | --------------------------- | --------------------------------------------------------------------------------------- |
354
+ | `enabled` | `boolean` | `true` | Intercept DOT fences. `false` leaves them as ordinary, highlighted code blocks. |
355
+ | `languages` | `string[]` | `['dot', 'graphviz', 'gv']` | Fence languages treated as Graphviz. Must not overlap the top-level `languages`. |
356
+ | `engine` | `string` | `'dot'` | Default layout engine, one of the eleven listed above. |
357
+ | `allowEngineOverride` | `boolean` | `true` | Whether a fence may select its own engine with `engine=`. |
358
+ | `maxSourceBytes` | `number` | `100000` | Refuse sources larger than this many UTF-8 bytes, showing an explanatory panel instead. |
359
+ | `transparentBackground` | `boolean` | `true` | Render with `bgcolor=transparent` so the page background shows through. |
360
+
361
+ A language claimed by both engines fails the build, because a fence has exactly one language
362
+ and the result would otherwise depend on evaluation order:
363
+
364
+ ```text
365
+ [docusaurus-plugin-plantuml-client] options.graphviz.languages and options.languages both
366
+ claim 'dot'. A fence language can only belong to one engine.
367
+ ```
368
+
369
+ ### Why `maxSourceBytes` exists
370
+
371
+ Graphviz lays out **synchronously**, so an enormous graph blocks the main thread rather than
372
+ merely taking a while. Measured on the bundled engine: a 300-edge graph renders in ~26 ms
373
+ (imperceptible), while a 3 000-edge graph takes ~2.25 s — a visible freeze. The default limit
374
+ turns that freeze into a contained error panel. Raise it if you genuinely have diagrams that
375
+ large, and expect the page to stall while they render.
376
+
377
+ The check happens **before** the engine is downloaded, so refusing an oversized diagram is
378
+ free.
230
379
 
231
380
  ## Light and dark mode
232
381
 
382
+ The two engines handle this differently, because PlantUML has a real dark theme and Graphviz
383
+ does not.
384
+
385
+ ### PlantUML — re-rendered
386
+
233
387
  With `theme: 'auto'` (the default), the diagram follows the site's colour mode through
234
388
  Docusaurus' own `useColorMode()` hook. Toggling the site theme re-renders every visible
235
389
  diagram with the engine's `{dark}` flag flipped, which changes PlantUML's own fill and stroke
@@ -241,7 +395,20 @@ and the engine itself is loaded once per page regardless of how often you toggle
241
395
  Set `theme: 'light'` or `theme: 'dark'` to pin the diagram palette independently of the site
242
396
  theme.
243
397
 
244
- The current mode is exposed on the wrapper as `data-plantuml-theme="light|dark"`.
398
+ ### Graphviz — restyled, not re-rendered
399
+
400
+ Graphviz draws black on white and has no dark palette to ask for, so the plugin adapts its
401
+ _defaults_ with CSS instead of rendering the graph twice. A colour-mode toggle therefore costs
402
+ no layout work for DOT diagrams, does not reset the reader's zoom, and needs no second cache
403
+ entry. The `theme` option does not apply to them.
404
+
405
+ See [Colours and dark mode](#colours-and-dark-mode) for exactly which elements are adapted and
406
+ why authored colours are never touched.
407
+
408
+ ### Both
409
+
410
+ The current mode is exposed on the wrapper as `data-plantuml-theme="light|dark"` for every
411
+ diagram, whichever engine produced it.
245
412
 
246
413
  ## Zoom and pan
247
414
 
@@ -444,13 +611,20 @@ transform layer, and a control group is added beside it:
444
611
  These attributes are part of the public surface; write your own end-to-end tests against
445
612
  them.
446
613
 
447
- | Attribute | Values |
448
- | --------------------------- | ---------------------------------------------------------- |
449
- | `data-plantuml-diagram` | the fence language that matched, e.g. `plantuml` or `puml` |
450
- | `data-plantuml-status` | `idle` \| `loading` \| `rendering` \| `ready` \| `error` |
451
- | `data-plantuml-theme` | `light` \| `dark` |
452
- | `data-plantuml-interactive` | `true`, on the `<figure>`, only when zoom is enabled |
453
- | `data-plantuml-zoom` | current scale on the viewport, e.g. `1`, `2.5` |
614
+ | Attribute | Values |
615
+ | --------------------------- | --------------------------------------------------------------- |
616
+ | `data-plantuml-diagram` | the fence language that matched, e.g. `plantuml`, `puml`, `dot` |
617
+ | `data-diagram-engine` | `plantuml` \| `graphviz` |
618
+ | `data-diagram-layout` | Graphviz layout engine, e.g. `dot`; absent on PlantUML diagrams |
619
+ | `data-plantuml-status` | `idle` \| `loading` \| `rendering` \| `ready` \| `error` |
620
+ | `data-plantuml-theme` | `light` \| `dark` |
621
+ | `data-plantuml-interactive` | `true`, on the `<figure>`, only when zoom is enabled |
622
+ | `data-plantuml-zoom` | current scale on the viewport, e.g. `1`, `2.5` |
623
+
624
+ The `data-plantuml-*` prefix is historical: it predates Graphviz support and is applied to
625
+ every diagram this plugin renders, whatever the engine. It was kept rather than renamed so that
626
+ existing author CSS and end-to-end tests keep working. Select on `data-diagram-engine` when the
627
+ two engines need different styling.
454
628
 
455
629
  ## SVG sanitization and security model
456
630
 
@@ -494,25 +668,32 @@ to plantuml.com or anywhere else, and none can be configured.
494
668
  A failed diagram produces a contained error panel. It never crashes the page, never blocks
495
669
  the render queue, and never silently disappears.
496
670
 
497
- The panel shows a heading (`⚠ Error: PlantUML diagram could not be rendered`), the message,
498
- and — unless `showSourceOnError: false` — the original source in a collapsed `<details>`
499
- element. The wrapper carries `data-plantuml-status="error"`.
671
+ The panel shows a heading naming the engine (`⚠ Error: PlantUML diagram could not be rendered`,
672
+ or `⚠ Error: Graphviz diagram could not be rendered`), the message, and — unless
673
+ `showSourceOnError: false` — the original source in a collapsed `<details>` element. The
674
+ wrapper carries `data-plantuml-status="error"`.
500
675
 
501
676
  Failures are classified internally as one of:
502
677
 
503
- | Kind | Cause |
504
- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
505
- | `load` | `viz-global.js` or `plantuml.js` could not be fetched, timed out, or was not the expected module (usually a proxy or service worker returning the wrong file). |
506
- | `engine` | The engine threw, or its output could not be used at all. |
507
- | `diagram` | The source is not valid PlantUML. |
508
- | `timeout` | One render exceeded `renderTimeoutMs`. |
509
- | `config` | The plugin's global data is missing — usually the plugin is not registered. |
510
- | `aborted` | The component unmounted or re-rendered; not surfaced to the reader. |
511
-
512
- Note that **invalid PlantUML is not reported through the engine's error callback**. The
513
- engine renders an "error picture" and reports success, so the rendered SVG is inspected for
514
- error markers before it is trusted. See
515
- [ADR 0002](docs/adr/0002-render-to-string.md) for the exact detection rules.
678
+ | Kind | Cause |
679
+ | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
680
+ | `load` | `viz-global.js` or `plantuml.js` could not be fetched, timed out, or was not the expected module (usually a proxy or service worker returning the wrong file). |
681
+ | `engine` | The engine threw, or its output could not be used at all. |
682
+ | `diagram` | The source is not valid PlantUML. |
683
+ | `syntax` | The source is not valid DOT. Carries Graphviz's own diagnostic, including the line number. |
684
+ | `too-large` | A DOT source exceeded `graphviz.maxSourceBytes`. |
685
+ | `timeout` | One render exceeded `renderTimeoutMs`. |
686
+ | `config` | The plugin's global data is missing, or a fence named a layout engine this Graphviz build does not have. |
687
+ | `aborted` | The component unmounted or re-rendered; not surfaced to the reader. |
688
+
689
+ The two engines report invalid source very differently:
690
+
691
+ - **Invalid PlantUML is not reported through the engine's error callback.** The engine renders
692
+ an "error picture" and reports success, so the rendered SVG is inspected for error markers
693
+ before it is trusted. See [ADR 0002](docs/adr/0002-render-to-string.md) for the exact
694
+ detection rules.
695
+ - **Invalid DOT is reported as structured data**, so the panel shows Graphviz's own message
696
+ verbatim — `syntax error in line 3 near '}'` — with no heuristics involved.
516
697
 
517
698
  ## `baseUrl` support
518
699
 
@@ -556,13 +737,13 @@ exactly as it did before installation.
556
737
 
557
738
  ## Bundle-size implications
558
739
 
559
- The PlantUML runtime is large:
740
+ The runtimes are large:
560
741
 
561
- | File | Size (uncompressed) |
562
- | --------------- | ------------------- |
563
- | `plantuml.js` | ≈ 6.8 MB |
564
- | `viz-global.js` | ≈ 1.4 MB |
565
- | **Total** | **≈ 8.2 MB** |
742
+ | File | Size (uncompressed) | Needed for |
743
+ | --------------- | ------------------- | ------------------------------------- |
744
+ | `plantuml.js` | ≈ 6.8 MB | PlantUML fences only |
745
+ | `viz-global.js` | ≈ 1.4 MB | DOT fences, and PlantUML's own layout |
746
+ | **Total** | **≈ 8.2 MB** | |
566
747
 
567
748
  Crucially, **none of that is in your site's initial bundle**. The files are emitted as static
568
749
  assets, not imported into the webpack graph (the dynamic import is marked `webpackIgnore`), so:
@@ -570,9 +751,15 @@ assets, not imported into the webpack graph (the dynamic import is marked `webpa
570
751
  - Pages with no diagram never request them.
571
752
  - Pages with diagrams request them once, on first render — and with `lazy: true`, only when a
572
753
  diagram nears the viewport.
754
+ - **Each runtime is requested only if a diagram actually needs it.** A page with only DOT
755
+ diagrams fetches `viz-global.js` and never the 6.8 MB PlantUML engine; a page with PlantUML
756
+ diagrams fetches both, because PlantUML needs Graphviz for its own layout.
573
757
  - The files are served from your origin with your own cache headers, and are re-fetched only
574
758
  when the engine version changes.
575
759
 
760
+ **Adding Graphviz support to a site that already renders PlantUML costs nothing**, because
761
+ `viz-global.js` was already being emitted and loaded.
762
+
576
763
  The published npm package itself is small (the compiled plugin and theme components only);
577
764
  the 8 MB comes from the `@plantuml/core` dependency and lands in your build output, not in
578
765
  your JavaScript bundle. Budget for roughly 8 MB of extra static assets per deployed site.
@@ -668,6 +855,15 @@ Four layers, all deterministic and none of them calling an external PlantUML ser
668
855
  layer: focal-point and clamping geometry as pure functions, the wheel policy (plain wheel
669
856
  ignored, Ctrl honoured, Cmd ignored), keyboard handling, both reset triggers, and listener
670
857
  teardown.
858
+ For Graphviz specifically: the nested option group and its validation including the
859
+ both-engines-claim-one-language check, `engine=` metastring parsing, the separate cache-key
860
+ namespace and its deliberate omission of the colour mode, structured error formatting and
861
+ line-number extraction, the `window.Viz` API assertion, the loader split (a DOT-only page
862
+ never importing the PlantUML module), the source-size guard measuring UTF-8 bytes before
863
+ loading anything, engine reuse across renders and across syntax errors, that a colour-mode
864
+ toggle re-renders a PlantUML diagram but not a Graphviz one, and that DOMPurify strips
865
+ `javascript:` from DOT's `URL=` links while preserving the colour attributes the dark-mode
866
+ CSS selects on.
671
867
  Docusaurus `@theme/*` and `@docusaurus/*` aliases only exist inside a real Docusaurus
672
868
  webpack build, so the Vitest config points them at local stubs.
673
869
  2. **Package verification** (`npm run pack:check`). Runs `npm pack --dry-run --json`, prints
@@ -691,12 +887,20 @@ Four layers, all deterministic and none of them calling an external PlantUML ser
691
887
  loaded at most once when diagrams are present, that every asset URL carries the configured
692
888
  `baseUrl`, that no request goes to an external host, and that the build hydrates without
693
889
  React mismatch warnings.
890
+ The Graphviz suite adds: DOT fences rendering to real SVG with the expected node labels, a
891
+ DOT-only page fetching `viz-global.js` once and `plantuml.js` never, both engines rendering
892
+ on one page with one download each, `engine=neato` and `engine=circo` producing distinct
893
+ layouts, the Graphviz diagnostic and its line number appearing in the error panel while the
894
+ rest of the page still renders, default colours changing with the colour mode while authored
895
+ colours do not, a `javascript:` URL never surviving into a link, zoom working the same as it
896
+ does for PlantUML, and a client-side navigation not re-downloading the engine.
694
897
 
695
898
  The example site under `examples/docusaurus/` is the fixture: it deploys under
696
899
  `baseUrl: '/plantuml-test/'` and contains a sequence diagram, a Graphviz-dependent class
697
900
  diagram, a component diagram, several diagrams on one page, `.md` and `.mdx` documents, a
698
- `puml` alias, title metadata, an invalid diagram, an ordinary TypeScript block, and a page
699
- with no diagram at all.
901
+ `puml` alias, title metadata, an invalid diagram, an ordinary TypeScript block, a page with no
902
+ diagram at all, a Graphviz page exercising several layout engines and authored colours, and a
903
+ DOT-only page that proves the PlantUML engine is never fetched for it.
700
904
 
701
905
  ## Release process
702
906
 
@@ -778,9 +982,15 @@ package.
778
982
  - **Diagrams are not rendered during static-site generation.** The HTML Docusaurus emits
779
983
  contains the deferred placeholder and the `<noscript>` source. Search engines that do not
780
984
  execute JavaScript will not see the diagram image.
781
- - **One diagram renders at a time.** The engine has module-level shared state, so a page with
782
- many large diagrams renders them sequentially. This is a correctness requirement, not a
783
- tuning knob.
985
+ - **One PlantUML diagram renders at a time.** The PlantUML engine has module-level shared
986
+ state, so a page with many large diagrams renders them sequentially. This is a correctness
987
+ requirement, not a tuning knob. Graphviz has no such constraint and is not queued.
988
+ - **Graphviz rendering blocks the main thread.** Viz.js lays out synchronously. A 300-edge
989
+ graph takes ~26 ms, but a 3 000-edge graph takes ~2.25 s and visibly freezes the page;
990
+ `graphviz.maxSourceBytes` is the guard. Rendering in a Web Worker would remove the ceiling
991
+ and is a possible future enhancement.
992
+ - **`theme` does not apply to Graphviz diagrams.** Graphviz has no dark palette; its colours
993
+ are adapted with CSS instead. See [Light and dark mode](#light-and-dark-mode).
784
994
  - **`renderTimeoutMs` covers both loading and rendering.** On a very slow connection, the
785
995
  8 MB download is subject to the same budget as a render.
786
996
  - **Consuming the plugin through a `file:` link needs a webpack tweak.** See the entry in
@@ -917,11 +1127,15 @@ Then rebuild. If it persists, remove `node_modules/.cache` and `.docusaurus/` by
917
1127
  ## Further reading
918
1128
 
919
1129
  - [`docs/architecture.md`](docs/architecture.md) — SSR boundary, asset loading, render queue,
920
- caching, sanitization, dark-mode re-rendering.
1130
+ caching, sanitization, and how each engine handles dark mode.
921
1131
  - [`docs/adr/0001-theme-init-alias.md`](docs/adr/0001-theme-init-alias.md) — why the plugin
922
1132
  wraps `@theme-init/`, not `@theme-original/`.
923
1133
  - [`docs/adr/0002-render-to-string.md`](docs/adr/0002-render-to-string.md) — why the string
924
1134
  API is used instead of the DOM `render()` API, and how invalid diagrams are detected.
1135
+ - [`docs/adr/0003-zoom-container-transform.md`](docs/adr/0003-zoom-container-transform.md) —
1136
+ why zoom transforms a wrapper rather than the SVG.
1137
+ - [`docs/adr/0004-graphviz-engine-reuse.md`](docs/adr/0004-graphviz-engine-reuse.md) — why DOT
1138
+ is rendered with the Graphviz already inside `@plantuml/core`, and what that couples us to.
925
1139
  - [`CONTRIBUTING.md`](CONTRIBUTING.md)
926
1140
  - [`SECURITY.md`](SECURITY.md)
927
1141
 
@@ -930,4 +1144,5 @@ Then rebuild. If it persists, remove `node_modules/.cache` and `.docusaurus/` by
930
1144
  MIT. See [LICENSE](LICENSE).
931
1145
 
932
1146
  [plantuml-core]: https://www.npmjs.com/package/@plantuml/core
1147
+ [graphviz]: https://graphviz.org/
933
1148
  [dompurify]: https://github.com/cure53/DOMPurify
package/dist/assets.js CHANGED
@@ -21,12 +21,29 @@ export function locatePlantUmlCore() {
21
21
  const packageDir = path.dirname(packageJsonPath);
22
22
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
23
23
  const version = typeof packageJson.version === 'string' ? packageJson.version : '0.0.0';
24
- return {
25
- packageDir,
26
- version,
27
- files: [
28
- path.join(packageDir, VIZ_SCRIPT_FILENAME),
29
- path.join(packageDir, PLANTUML_MODULE_FILENAME),
30
- ],
31
- };
24
+ const files = [
25
+ path.join(packageDir, VIZ_SCRIPT_FILENAME),
26
+ path.join(packageDir, PLANTUML_MODULE_FILENAME),
27
+ ];
28
+ files.forEach((file) => assertRuntimeFile(file, version));
29
+ return { packageDir, version, files };
30
+ }
31
+ /**
32
+ * Fails the build if a runtime asset is missing from the installed `@plantuml/core`.
33
+ *
34
+ * `viz-global.js` matters twice over: PlantUML needs it for layout, and it *is* the Graphviz
35
+ * engine this plugin renders DOT fences with. It is a declared `exports` entry of
36
+ * `@plantuml/core`, not a private file — but it is still shipped at PlantUML's discretion, so a
37
+ * routine dependency bump that dropped or renamed it would otherwise reach a reader's browser
38
+ * as a runtime load failure. Checking here turns that into a failed build with a name attached.
39
+ *
40
+ * See `docs/adr/0004-graphviz-engine-reuse.md`.
41
+ */
42
+ function assertRuntimeFile(file, version) {
43
+ if (fs.existsSync(file))
44
+ return;
45
+ throw new Error(`[${PLUGIN_NAME}] '@plantuml/core@${version}' does not contain '${path.basename(file)}', ` +
46
+ 'which this plugin serves as a runtime asset. This means the installed version of ' +
47
+ '@plantuml/core is not one this plugin supports. Pin a supported version, or open an ' +
48
+ 'issue if the package has genuinely changed its layout.');
32
49
  }
@@ -14,9 +14,20 @@ export declare const PLUGIN_NAME = "docusaurus-plugin-plantuml-client";
14
14
  export declare function assetsDirForVersion(coreVersion: string): string;
15
15
  export declare const VIZ_SCRIPT_FILENAME = "viz-global.js";
16
16
  export declare const PLANTUML_MODULE_FILENAME = "plantuml.js";
17
- /** `data-*` attribute names, kept in one place because tests and docs depend on them. */
17
+ /**
18
+ * `data-*` attribute names, kept in one place because tests and docs depend on them.
19
+ *
20
+ * The `data-plantuml-*` prefix is historical: it predates Graphviz support and is applied to
21
+ * every diagram this plugin renders, whatever the engine. Renaming it would break author CSS
22
+ * for no functional gain, so {@link DATA_ATTR.engine} was added alongside it instead —
23
+ * that is the attribute to select on when the two engines need different styling.
24
+ */
18
25
  export declare const DATA_ATTR: {
19
26
  readonly diagram: "data-plantuml-diagram";
27
+ /** `"plantuml"` or `"graphviz"` — which engine produced this figure. */
28
+ readonly engine: "data-diagram-engine";
29
+ /** Graphviz layout engine (`dot`, `neato`, …). Absent on PlantUML figures. */
30
+ readonly layout: "data-diagram-layout";
20
31
  readonly status: "data-plantuml-status";
21
32
  readonly theme: "data-plantuml-theme";
22
33
  /** `"true"` on the figure when the diagram is zoomable. */
package/dist/constants.js CHANGED
@@ -16,9 +16,20 @@ export function assetsDirForVersion(coreVersion) {
16
16
  }
17
17
  export const VIZ_SCRIPT_FILENAME = 'viz-global.js';
18
18
  export const PLANTUML_MODULE_FILENAME = 'plantuml.js';
19
- /** `data-*` attribute names, kept in one place because tests and docs depend on them. */
19
+ /**
20
+ * `data-*` attribute names, kept in one place because tests and docs depend on them.
21
+ *
22
+ * The `data-plantuml-*` prefix is historical: it predates Graphviz support and is applied to
23
+ * every diagram this plugin renders, whatever the engine. Renaming it would break author CSS
24
+ * for no functional gain, so {@link DATA_ATTR.engine} was added alongside it instead —
25
+ * that is the attribute to select on when the two engines need different styling.
26
+ */
20
27
  export const DATA_ATTR = {
21
28
  diagram: 'data-plantuml-diagram',
29
+ /** `"plantuml"` or `"graphviz"` — which engine produced this figure. */
30
+ engine: 'data-diagram-engine',
31
+ /** Graphviz layout engine (`dot`, `neato`, …). Absent on PlantUML figures. */
32
+ layout: 'data-diagram-layout',
22
33
  status: 'data-plantuml-status',
23
34
  theme: 'data-plantuml-theme',
24
35
  /** `"true"` on the figure when the diagram is zoomable. */