@matfsw/docusaurus-plantuml-plugin 0.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 +33 -0
- package/LICENSE +21 -0
- package/README.md +781 -0
- package/dist/assets.d.ts +15 -0
- package/dist/assets.js +32 -0
- package/dist/constants.d.ts +22 -0
- package/dist/constants.js +24 -0
- package/dist/index.cjs +288 -0
- package/dist/index.d.cts +97 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +74 -0
- package/dist/options.d.ts +46 -0
- package/dist/options.js +131 -0
- package/dist/runtime/assetLoader.d.ts +23 -0
- package/dist/runtime/assetLoader.js +125 -0
- package/dist/runtime/cache.d.ts +20 -0
- package/dist/runtime/cache.js +219 -0
- package/dist/runtime/errors.d.ts +31 -0
- package/dist/runtime/errors.js +108 -0
- package/dist/runtime/queue.d.ts +27 -0
- package/dist/runtime/queue.js +101 -0
- package/dist/runtime/renderer.d.ts +23 -0
- package/dist/runtime/renderer.js +70 -0
- package/dist/runtime/sanitize.d.ts +12 -0
- package/dist/runtime/sanitize.js +47 -0
- package/dist/runtime/types.d.ts +15 -0
- package/dist/runtime/types.js +1 -0
- package/dist/theme/MDXComponents/Code/index.d.ts +16 -0
- package/dist/theme/MDXComponents/Code/index.js +28 -0
- package/dist/theme/PlantUmlDiagram/index.d.ts +16 -0
- package/dist/theme/PlantUmlDiagram/index.js +134 -0
- package/dist/theme/PlantUmlDiagram/styles.module.css +107 -0
- package/dist/theme/codeBlockMeta.d.ts +27 -0
- package/dist/theme/codeBlockMeta.js +58 -0
- package/dist/theme/usePlantUmlConfig.d.ts +14 -0
- package/dist/theme/usePlantUmlConfig.js +29 -0
- package/package.json +112 -0
package/README.md
ADDED
|
@@ -0,0 +1,781 @@
|
|
|
1
|
+
# @matfsw/docusaurus-plantuml-plugin
|
|
2
|
+
|
|
3
|
+
Render PlantUML diagrams in Docusaurus 3 entirely in the browser — no Java, no PlantUML
|
|
4
|
+
server, no Kroki, no CDN.
|
|
5
|
+
|
|
6
|
+
Write a `plantuml` fence in any `.md` or `.mdx` file and the code block is replaced, in the
|
|
7
|
+
reader's browser, with an SVG rendered by the official [`@plantuml/core`][plantuml-core]
|
|
8
|
+
package:
|
|
9
|
+
|
|
10
|
+
````markdown
|
|
11
|
+
```plantuml title="Authentication sequence"
|
|
12
|
+
@startuml
|
|
13
|
+
actor User
|
|
14
|
+
participant Browser
|
|
15
|
+
participant API
|
|
16
|
+
|
|
17
|
+
User -> Browser: Sign in
|
|
18
|
+
Browser -> API: POST /sessions
|
|
19
|
+
API --> Browser: Access token
|
|
20
|
+
Browser --> User: Signed in
|
|
21
|
+
@enduml
|
|
22
|
+
```
|
|
23
|
+
````
|
|
24
|
+
|
|
25
|
+
**Rendering is 100% same-origin and client-side. Your diagram source never leaves the
|
|
26
|
+
browser.** It is not sent to plantuml.com, to a Kroki instance, or to any other service, and
|
|
27
|
+
the engine itself is served from your own site's `baseUrl`.
|
|
28
|
+
|
|
29
|
+
## Status and compatibility
|
|
30
|
+
|
|
31
|
+
Status: **beta**. The public surface (plugin options, `data-*` attributes, fence syntax) is
|
|
32
|
+
stable enough to build on, but this is a `0.x` release and breaking changes will happen in
|
|
33
|
+
minor versions until `1.0.0`.
|
|
34
|
+
|
|
35
|
+
| Requirement | Supported |
|
|
36
|
+
| --------------- | -------------------------------------------------------------------------------------- |
|
|
37
|
+
| Docusaurus | `3.5.0` and later `3.x` |
|
|
38
|
+
| Node.js (build) | `>= 20` |
|
|
39
|
+
| React | `18.x` or `19.x` (matching your Docusaurus install) |
|
|
40
|
+
| Bundler | webpack (default) and Rspack (`future.v4` / `@docusaurus/faster`) |
|
|
41
|
+
| PlantUML engine | `@plantuml/core` `1.2026.6` |
|
|
42
|
+
| Browsers | Modern evergreen browsers; no IE (see [Browser compatibility](#browser-compatibility)) |
|
|
43
|
+
|
|
44
|
+
The plugin requires a theme that already provides `MDXComponents/Code` — in practice
|
|
45
|
+
`@docusaurus/theme-classic`, which you almost certainly have through `preset-classic`.
|
|
46
|
+
|
|
47
|
+
CI runs the full browser suite against Docusaurus `3.5.2` (the oldest supported release), the
|
|
48
|
+
version the example pins, and the newest `3.x` resolved at run time.
|
|
49
|
+
|
|
50
|
+
### Docusaurus 3.5–3.10.0 need a `webpackbar` override
|
|
51
|
+
|
|
52
|
+
This is an upstream issue that has nothing to do with this plugin, but it will stop your build
|
|
53
|
+
before the plugin is ever reached, so it is worth knowing about.
|
|
54
|
+
|
|
55
|
+
`@docusaurus/bundler` depended on `webpackbar@^6.0.1` up to and including Docusaurus `3.10.0`,
|
|
56
|
+
and `webpackbar` 6 fails `ProgressPlugin`'s option validation against `webpack >= 5.109`. A
|
|
57
|
+
fresh install of any Docusaurus `3.5`–`3.10.0` site therefore fails to build with:
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
ValidationError: Invalid options object.
|
|
61
|
+
Progress Plugin has been initialized using an options object that does not match the API schema.
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
If you are on one of those versions and see this, add an override to your **site's**
|
|
65
|
+
`package.json` and reinstall:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"overrides": {
|
|
70
|
+
"webpackbar": "^7.0.0"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Docusaurus `3.10.2` and later depend on `webpackbar@^7` already and need no override. This is
|
|
76
|
+
exactly what the compatibility job in CI does to test the `3.5.2` leg.
|
|
77
|
+
|
|
78
|
+
### A note on the engine's licence
|
|
79
|
+
|
|
80
|
+
`@plantuml/core` is MIT-licensed **from version `1.2026.6` onwards**. Earlier versions were
|
|
81
|
+
published under GPL-3.0-or-later. This plugin depends on `^1.2026.6` for that reason; if you
|
|
82
|
+
pin or dedupe `@plantuml/core` to an older version yourself, you inherit the older licence.
|
|
83
|
+
|
|
84
|
+
## Installation
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npm install @matfsw/docusaurus-plantuml-plugin
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`@plantuml/core`, `copy-webpack-plugin` and `dompurify` are ordinary dependencies of this
|
|
91
|
+
package and are installed for you. `@docusaurus/core`, `react` and `react-dom` are peer
|
|
92
|
+
dependencies and come from your site.
|
|
93
|
+
|
|
94
|
+
## Minimal configuration
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
// docusaurus.config.ts
|
|
98
|
+
import type {Config} from '@docusaurus/types';
|
|
99
|
+
|
|
100
|
+
const config: Config = {
|
|
101
|
+
// ...
|
|
102
|
+
plugins: ['@matfsw/docusaurus-plantuml-plugin'],
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export default config;
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
That is the whole setup. Every option has a default; pass an options object only to change
|
|
109
|
+
one:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
// docusaurus.config.ts
|
|
113
|
+
import type {Config} from '@docusaurus/types';
|
|
114
|
+
import type {PlantUmlPluginOptions} from '@matfsw/docusaurus-plantuml-plugin';
|
|
115
|
+
|
|
116
|
+
const config: Config = {
|
|
117
|
+
// ...
|
|
118
|
+
plugins: [
|
|
119
|
+
[
|
|
120
|
+
'@matfsw/docusaurus-plantuml-plugin',
|
|
121
|
+
{
|
|
122
|
+
languages: ['plantuml', 'puml'],
|
|
123
|
+
theme: 'auto',
|
|
124
|
+
lazy: true,
|
|
125
|
+
cache: 'memory',
|
|
126
|
+
sanitizeSvg: true,
|
|
127
|
+
showSourceOnError: true,
|
|
128
|
+
renderTimeoutMs: 20_000,
|
|
129
|
+
cacheMaxEntries: 50,
|
|
130
|
+
} satisfies PlantUmlPluginOptions,
|
|
131
|
+
],
|
|
132
|
+
],
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export default config;
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
A plain JavaScript `docusaurus.config.js` works too — the package ships both an ESM and a
|
|
139
|
+
CommonJS build of the Node-side entry point.
|
|
140
|
+
|
|
141
|
+
## Authoring diagrams
|
|
142
|
+
|
|
143
|
+
### Markdown (`.md`)
|
|
144
|
+
|
|
145
|
+
````markdown
|
|
146
|
+
# Sequence diagram
|
|
147
|
+
|
|
148
|
+
```plantuml title="Authentication sequence"
|
|
149
|
+
@startuml
|
|
150
|
+
actor User
|
|
151
|
+
participant API
|
|
152
|
+
User -> API: POST /sessions
|
|
153
|
+
API --> User: Access token
|
|
154
|
+
@enduml
|
|
155
|
+
```
|
|
156
|
+
````
|
|
157
|
+
|
|
158
|
+
The `title="..."` in the fence metastring becomes both the `<figcaption>` and the accessible
|
|
159
|
+
label of the diagram. It is optional.
|
|
160
|
+
|
|
161
|
+
### MDX (`.mdx`)
|
|
162
|
+
|
|
163
|
+
The same fence syntax works in MDX, alongside JSX:
|
|
164
|
+
|
|
165
|
+
````mdx
|
|
166
|
+
import Admonition from '@theme/Admonition';
|
|
167
|
+
|
|
168
|
+
<Admonition type="info" title="MDX">
|
|
169
|
+
Fenced PlantUML works here too.
|
|
170
|
+
</Admonition>
|
|
171
|
+
|
|
172
|
+
```puml
|
|
173
|
+
@startuml
|
|
174
|
+
Alice -> Bob : puml alias in MDX
|
|
175
|
+
@enduml
|
|
176
|
+
```
|
|
177
|
+
````
|
|
178
|
+
|
|
179
|
+
### Supported fence aliases
|
|
180
|
+
|
|
181
|
+
By default the plugin claims two fence languages:
|
|
182
|
+
|
|
183
|
+
- `plantuml`
|
|
184
|
+
- `puml`
|
|
185
|
+
|
|
186
|
+
Matching is **case-insensitive**, so ` ```PlantUML `, ` ```PUML ` and ` ```plantuml ` are all
|
|
187
|
+
recognised. Change the set with the `languages` option; entries are normalised to lower case,
|
|
188
|
+
must be non-empty, and must not repeat.
|
|
189
|
+
|
|
190
|
+
Everything else — inline code, ordinary fenced blocks in any other language, and code blocks
|
|
191
|
+
authored as JSX children — is delegated to the original Docusaurus `Code` component with its
|
|
192
|
+
props untouched. Existing code-block behaviour (highlighting, line numbers, titles,
|
|
193
|
+
`showLineNumbers`, copy button) is unchanged.
|
|
194
|
+
|
|
195
|
+
## Options
|
|
196
|
+
|
|
197
|
+
| Option | Type | Default | Description |
|
|
198
|
+
| ------------------- | --------------------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
199
|
+
| `languages` | `string[]` | `['plantuml', 'puml']` | Fence languages treated as PlantUML. Matched case-insensitively; must contain at least one non-empty, non-duplicate entry. |
|
|
200
|
+
| `theme` | `'auto' \| 'light' \| 'dark'` | `'auto'` | Diagram colour scheme. `auto` follows the Docusaurus colour mode; `light`/`dark` pin it. |
|
|
201
|
+
| `lazy` | `boolean` | `true` | Render a diagram only once it scrolls near the viewport (300 px root margin). |
|
|
202
|
+
| `cache` | `'none' \| 'memory' \| 'session'` | `'memory'` | Where rendered SVG is cached. |
|
|
203
|
+
| `sanitizeSvg` | `boolean` | `true` | Run rendered SVG through DOMPurify before inserting it. See [Security model](#svg-sanitization-and-security-model). |
|
|
204
|
+
| `showSourceOnError` | `boolean` | `true` | Include the diagram source in a `<details>` block on the error panel. |
|
|
205
|
+
| `renderTimeoutMs` | `number` | `20000` | Abort a single render (and the runtime load) after this many milliseconds. Integer, `100`–`600000`. |
|
|
206
|
+
| `cacheMaxEntries` | `number` | `50` | Upper bound on cached SVG entries. Positive integer. |
|
|
207
|
+
| `id` | `string` | `'default'` | Docusaurus plugin instance id; only relevant if you register the plugin more than once. |
|
|
208
|
+
|
|
209
|
+
Options are validated during the Docusaurus configuration phase, before anything is built.
|
|
210
|
+
**Unknown keys are rejected** rather than ignored, because a typo in `docusaurus.config.ts`
|
|
211
|
+
would otherwise silently disable the option you meant to set:
|
|
212
|
+
|
|
213
|
+
```text
|
|
214
|
+
[docusaurus-plugin-plantuml-client] Unknown option 'sanitiseSvg'. Supported options:
|
|
215
|
+
'languages', 'theme', 'lazy', 'cache', 'sanitizeSvg', 'showSourceOnError',
|
|
216
|
+
'renderTimeoutMs', 'cacheMaxEntries'.
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
`PlantUmlPluginOptions`, `ResolvedPlantUmlOptions`, `CacheMode`, `DiagramTheme`,
|
|
220
|
+
`DEFAULT_OPTIONS`, `resolveOptions` and `PlantUmlOptionsError` are exported from the package
|
|
221
|
+
root. The renderer, queue and cache internals are deliberately not exported.
|
|
222
|
+
|
|
223
|
+
## Light and dark mode
|
|
224
|
+
|
|
225
|
+
With `theme: 'auto'` (the default), the diagram follows the site's colour mode through
|
|
226
|
+
Docusaurus' own `useColorMode()` hook. Toggling the site theme re-renders every visible
|
|
227
|
+
diagram with the engine's `{dark}` flag flipped, which changes PlantUML's own fill and stroke
|
|
228
|
+
palette rather than merely inverting the picture with CSS.
|
|
229
|
+
|
|
230
|
+
The colour mode is part of the cache key, so a toggle can never serve the other mode's SVG,
|
|
231
|
+
and the engine itself is loaded once per page regardless of how often you toggle.
|
|
232
|
+
|
|
233
|
+
Set `theme: 'light'` or `theme: 'dark'` to pin the diagram palette independently of the site
|
|
234
|
+
theme.
|
|
235
|
+
|
|
236
|
+
The current mode is exposed on the wrapper as `data-plantuml-theme="light|dark"`.
|
|
237
|
+
|
|
238
|
+
## Lazy loading
|
|
239
|
+
|
|
240
|
+
With `lazy: true` (the default), a diagram is not rendered until it scrolls within 300 px of
|
|
241
|
+
the viewport, observed with `IntersectionObserver`. Consequently:
|
|
242
|
+
|
|
243
|
+
- The ~8 MB PlantUML runtime is fetched only on pages that actually contain a diagram, and
|
|
244
|
+
only once such a diagram approaches the viewport.
|
|
245
|
+
- Diagrams far down a long page cost nothing until the reader gets there.
|
|
246
|
+
|
|
247
|
+
If `IntersectionObserver` is unavailable (older browsers, some test environments), the
|
|
248
|
+
component renders immediately rather than staying blank forever.
|
|
249
|
+
|
|
250
|
+
Set `lazy: false` to start rendering every diagram as soon as it mounts.
|
|
251
|
+
|
|
252
|
+
## Caching
|
|
253
|
+
|
|
254
|
+
Rendered SVG is cached under a deterministic key:
|
|
255
|
+
|
|
256
|
+
```text
|
|
257
|
+
<coreVersion>|<light|dark>|<san|raw>|<sourceLength>|<FNV-1a hash of source>
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Everything that can change the output is in the key: the `@plantuml/core` version (so an
|
|
261
|
+
engine upgrade invalidates every entry), the colour mode, whether the output was sanitized,
|
|
262
|
+
and the source itself. The source length is included alongside the 32-bit hash so that a hash
|
|
263
|
+
collision alone cannot serve the wrong diagram.
|
|
264
|
+
|
|
265
|
+
| Mode | Behaviour |
|
|
266
|
+
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
267
|
+
| `'none'` | Nothing is stored. Every mount re-renders. |
|
|
268
|
+
| `'memory'` | Default. Process-lifetime `Map`, LRU-evicted down to `cacheMaxEntries` (50). Cleared on full page load. |
|
|
269
|
+
| `'session'` | `sessionStorage` under `plantuml-client:` keys, FIFO-trimmed to `cacheMaxEntries`. Survives client-side navigation and reloads within the tab. |
|
|
270
|
+
|
|
271
|
+
The cache is shared by every diagram on the page, so the same source rendered twice is
|
|
272
|
+
rendered once. A cache hit skips the render queue entirely.
|
|
273
|
+
|
|
274
|
+
`sessionStorage` is treated as best-effort: entries that fail to parse or carry the wrong
|
|
275
|
+
entry version are discarded and re-rendered, and if `sessionStorage` throws at all (private
|
|
276
|
+
browsing modes, quota exhaustion, embedded browsers) the cache silently degrades to an
|
|
277
|
+
in-memory cache for the rest of the session. A storage failure never breaks rendering.
|
|
278
|
+
|
|
279
|
+
## Accessibility
|
|
280
|
+
|
|
281
|
+
Each diagram renders as:
|
|
282
|
+
|
|
283
|
+
```html
|
|
284
|
+
<figure
|
|
285
|
+
aria-busy="true"
|
|
286
|
+
data-plantuml-diagram="plantuml"
|
|
287
|
+
data-plantuml-status="rendering"
|
|
288
|
+
data-plantuml-theme="light"
|
|
289
|
+
>
|
|
290
|
+
<div role="img" aria-label="Authentication sequence"><svg>…</svg></div>
|
|
291
|
+
<figcaption>Authentication sequence</figcaption>
|
|
292
|
+
<noscript><pre>@startuml …</pre></noscript>
|
|
293
|
+
</figure>
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
- The fence `title="..."` becomes the `<figcaption>` and the `aria-label`. Without a title,
|
|
297
|
+
the accessible label falls back to **`PlantUML diagram`**.
|
|
298
|
+
- Progress is conveyed with `aria-busy` on the `<figure>`, deliberately **not** with an
|
|
299
|
+
`aria-live` region: a page with a dozen diagrams would otherwise announce a dozen state
|
|
300
|
+
changes and make the page unusable with a screen reader.
|
|
301
|
+
- Errors are announced once, through `role="alert"` on the error panel.
|
|
302
|
+
- Failure is never signalled by colour alone — the panel carries a literal `Error:` label and
|
|
303
|
+
a ⚠ glyph in addition to the danger colouring, so it survives colour-blindness and
|
|
304
|
+
forced-colours mode.
|
|
305
|
+
- Readers without JavaScript get the diagram source in a `<noscript>` block.
|
|
306
|
+
- The spinner animation is disabled under `prefers-reduced-motion: reduce`.
|
|
307
|
+
|
|
308
|
+
### `data-*` contract
|
|
309
|
+
|
|
310
|
+
These attributes are part of the public surface; write your own end-to-end tests against
|
|
311
|
+
them.
|
|
312
|
+
|
|
313
|
+
| Attribute | Values |
|
|
314
|
+
| ----------------------- | ---------------------------------------------------------- |
|
|
315
|
+
| `data-plantuml-diagram` | the fence language that matched, e.g. `plantuml` or `puml` |
|
|
316
|
+
| `data-plantuml-status` | `idle` \| `loading` \| `rendering` \| `ready` \| `error` |
|
|
317
|
+
| `data-plantuml-theme` | `light` \| `dark` |
|
|
318
|
+
|
|
319
|
+
## SVG sanitization and security model
|
|
320
|
+
|
|
321
|
+
PlantUML output is generated from diagram source, and diagram source can express markup —
|
|
322
|
+
labels, `<style>` blocks, hyperlinks. The rendered SVG is therefore treated as untrusted and,
|
|
323
|
+
by default, passed through [DOMPurify][dompurify] before it reaches the DOM:
|
|
324
|
+
|
|
325
|
+
- `USE_PROFILES: {svg: true, svgFilters: true}` — geometry, text, gradients, filters, markers
|
|
326
|
+
and links survive.
|
|
327
|
+
- Additionally forbidden: `foreignObject`, `script`, `iframe`, `object`, `embed`, `audio`,
|
|
328
|
+
`video`. `foreignObject` matters most: it is the one SVG element that can host arbitrary
|
|
329
|
+
HTML, which would reintroduce precisely the injection surface the SVG profile exists to
|
|
330
|
+
remove. PlantUML does not need it.
|
|
331
|
+
- `formaction`, `xlink:show` and `ping` attributes are dropped; `role` and the `aria-label` /
|
|
332
|
+
`aria-labelledby` / `aria-describedby` attributes are preserved so the diagram stays
|
|
333
|
+
accessible.
|
|
334
|
+
- Event-handler attributes (`onload`, `onclick`, …) and `javascript:` URLs are removed by
|
|
335
|
+
DOMPurify itself.
|
|
336
|
+
|
|
337
|
+
If sanitization removes so much that no `<svg>` root remains, the plugin raises an error
|
|
338
|
+
rather than showing a silently blank figure.
|
|
339
|
+
|
|
340
|
+
### Disabling sanitization
|
|
341
|
+
|
|
342
|
+
`sanitizeSvg: false` is a supported, documented opt-out — and a genuine risk. With it off,
|
|
343
|
+
**whatever HTML or JavaScript a diagram author can express in PlantUML's output is injected
|
|
344
|
+
into your page verbatim** and executes with your site's origin, cookies and storage. Turn it
|
|
345
|
+
off only if every diagram source in your site is fully trusted — that is, if nobody who could
|
|
346
|
+
not already commit arbitrary code to the site can influence a diagram. If you accept diagrams
|
|
347
|
+
from external contributors, from generated content, or from anything user-supplied, leave it
|
|
348
|
+
on.
|
|
349
|
+
|
|
350
|
+
### What leaves the browser
|
|
351
|
+
|
|
352
|
+
Nothing. The engine is copied into your own build output and served from your own origin; the
|
|
353
|
+
renderer calls it in-process and inserts the result into the DOM. There is no HTTP fallback
|
|
354
|
+
to plantuml.com or anywhere else, and none can be configured.
|
|
355
|
+
|
|
356
|
+
## Error behaviour
|
|
357
|
+
|
|
358
|
+
A failed diagram produces a contained error panel. It never crashes the page, never blocks
|
|
359
|
+
the render queue, and never silently disappears.
|
|
360
|
+
|
|
361
|
+
The panel shows a heading (`⚠ Error: PlantUML diagram could not be rendered`), the message,
|
|
362
|
+
and — unless `showSourceOnError: false` — the original source in a collapsed `<details>`
|
|
363
|
+
element. The wrapper carries `data-plantuml-status="error"`.
|
|
364
|
+
|
|
365
|
+
Failures are classified internally as one of:
|
|
366
|
+
|
|
367
|
+
| Kind | Cause |
|
|
368
|
+
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
369
|
+
| `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). |
|
|
370
|
+
| `engine` | The engine threw, or its output could not be used at all. |
|
|
371
|
+
| `diagram` | The source is not valid PlantUML. |
|
|
372
|
+
| `timeout` | One render exceeded `renderTimeoutMs`. |
|
|
373
|
+
| `config` | The plugin's global data is missing — usually the plugin is not registered. |
|
|
374
|
+
| `aborted` | The component unmounted or re-rendered; not surfaced to the reader. |
|
|
375
|
+
|
|
376
|
+
Note that **invalid PlantUML is not reported through the engine's error callback**. The
|
|
377
|
+
engine renders an "error picture" and reports success, so the rendered SVG is inspected for
|
|
378
|
+
error markers before it is trusted. See
|
|
379
|
+
[ADR 0002](docs/adr/0002-render-to-string.md) for the exact detection rules.
|
|
380
|
+
|
|
381
|
+
## `baseUrl` support
|
|
382
|
+
|
|
383
|
+
The runtime assets are emitted into
|
|
384
|
+
|
|
385
|
+
```text
|
|
386
|
+
<baseUrl>assets/plantuml-client-<coreVersion>/
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
and resolved in the browser through Docusaurus' own `useBaseUrl()`. A site deployed at
|
|
390
|
+
`baseUrl: '/plantuml-test/'` therefore fetches:
|
|
391
|
+
|
|
392
|
+
```text
|
|
393
|
+
/plantuml-test/assets/plantuml-client-1.2026.6/viz-global.js
|
|
394
|
+
/plantuml-test/assets/plantuml-client-1.2026.6/plantuml.js
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
No path is hard-coded to the site root, so project-pages deployments and reverse-proxied
|
|
398
|
+
subpaths work without configuration. The engine version is part of the directory name, so an
|
|
399
|
+
upgrade changes the URL and stale caches are defeated automatically. The bundled example site
|
|
400
|
+
deploys under a non-root `baseUrl` precisely so this path is exercised on every build.
|
|
401
|
+
|
|
402
|
+
## Browser compatibility
|
|
403
|
+
|
|
404
|
+
The runtime requires:
|
|
405
|
+
|
|
406
|
+
- ES modules and dynamic `import()`
|
|
407
|
+
- `AbortController`
|
|
408
|
+
- `MutationObserver`
|
|
409
|
+
- `IntersectionObserver` — optional; a graceful fallback renders immediately when it is absent
|
|
410
|
+
- `sessionStorage` — optional; only for `cache: 'session'`, which degrades to memory without it
|
|
411
|
+
|
|
412
|
+
That is modern evergreen Chrome, Edge, Firefox and Safari. Internet Explorer is not
|
|
413
|
+
supported and will not be.
|
|
414
|
+
|
|
415
|
+
Nothing in this plugin affects readers on pages without diagrams — for them the site behaves
|
|
416
|
+
exactly as it did before installation.
|
|
417
|
+
|
|
418
|
+
## Bundle-size implications
|
|
419
|
+
|
|
420
|
+
The PlantUML runtime is large:
|
|
421
|
+
|
|
422
|
+
| File | Size (uncompressed) |
|
|
423
|
+
| --------------- | ------------------- |
|
|
424
|
+
| `plantuml.js` | ≈ 6.8 MB |
|
|
425
|
+
| `viz-global.js` | ≈ 1.4 MB |
|
|
426
|
+
| **Total** | **≈ 8.2 MB** |
|
|
427
|
+
|
|
428
|
+
Crucially, **none of that is in your site's initial bundle**. The files are emitted as static
|
|
429
|
+
assets, not imported into the webpack graph (the dynamic import is marked `webpackIgnore`), so:
|
|
430
|
+
|
|
431
|
+
- Pages with no diagram never request them.
|
|
432
|
+
- Pages with diagrams request them once, on first render — and with `lazy: true`, only when a
|
|
433
|
+
diagram nears the viewport.
|
|
434
|
+
- The files are served from your origin with your own cache headers, and are re-fetched only
|
|
435
|
+
when the engine version changes.
|
|
436
|
+
|
|
437
|
+
The published npm package itself is small (the compiled plugin and theme components only);
|
|
438
|
+
the 8 MB comes from the `@plantuml/core` dependency and lands in your build output, not in
|
|
439
|
+
your JavaScript bundle. Budget for roughly 8 MB of extra static assets per deployed site.
|
|
440
|
+
|
|
441
|
+
Serve these assets with compression enabled — they are ordinary JavaScript and compress well.
|
|
442
|
+
|
|
443
|
+
## Content Security Policy
|
|
444
|
+
|
|
445
|
+
Everything is same-origin. You need no `connect-src` entry for any third party, no CDN
|
|
446
|
+
allowance, and this plugin's own code uses no `eval` or `new Function`.
|
|
447
|
+
|
|
448
|
+
What you do need:
|
|
449
|
+
|
|
450
|
+
```text
|
|
451
|
+
script-src 'self';
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
`viz-global.js` is injected as a classic `<script>` element pointing at your own origin, so
|
|
455
|
+
`'self'` must be allowed for scripts. `plantuml.js` is then loaded with a dynamic `import()`
|
|
456
|
+
of a same-origin URL.
|
|
457
|
+
|
|
458
|
+
You may additionally need:
|
|
459
|
+
|
|
460
|
+
```text
|
|
461
|
+
script-src 'self' 'wasm-unsafe-eval';
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
The engine is asm.js/TeaVM-compiled JavaScript plus Viz.js. Depending on the engine build and
|
|
465
|
+
how your CSP treats WebAssembly compilation, `'wasm-unsafe-eval'` may be required — treat
|
|
466
|
+
this as "add it if diagrams fail with a CSP error mentioning WebAssembly", not as a
|
|
467
|
+
certainty. If you use a nonce- or hash-based `script-src`, note that the injected
|
|
468
|
+
`<script>` element is created at runtime and will not carry your nonce; a `'self'` source
|
|
469
|
+
expression is the straightforward configuration.
|
|
470
|
+
|
|
471
|
+
No `unsafe-eval` from this plugin's own code is required.
|
|
472
|
+
|
|
473
|
+
## Development
|
|
474
|
+
|
|
475
|
+
Clone the repository and install with `npm ci`. Node 20 or newer is required (see `.nvmrc`).
|
|
476
|
+
|
|
477
|
+
| Command | What it does |
|
|
478
|
+
| -------------------------- | --------------------------------------------------------------------------------- |
|
|
479
|
+
| `npm run build` | Clean, `tsc` the ESM + theme output, `tsup` the CommonJS entry, finalize |
|
|
480
|
+
| `npm run clean` | Remove build output |
|
|
481
|
+
| `npm run typecheck` | `tsc --noEmit` over the whole project |
|
|
482
|
+
| `npm run lint` | ESLint |
|
|
483
|
+
| `npm run lint:fix` | ESLint with `--fix` |
|
|
484
|
+
| `npm run format` | Prettier write |
|
|
485
|
+
| `npm run format:check` | Prettier check |
|
|
486
|
+
| `npm test` | Unit and component tests (alias for `test:unit`) |
|
|
487
|
+
| `npm run test:unit` | `vitest run` |
|
|
488
|
+
| `npm run test:watch` | `vitest` in watch mode |
|
|
489
|
+
| `npm run test:coverage` | `vitest run --coverage` |
|
|
490
|
+
| `npm run test:integration` | Pack the tarball, install it into a clean fixture, build and serve it |
|
|
491
|
+
| `npm run test:e2e` | Playwright against the served fixture |
|
|
492
|
+
| `npm run test:all` | Format, lint, typecheck, unit, build, pack check, integration — the CI gate |
|
|
493
|
+
| `npm run pack:check` | Inspect `npm pack --json` output for missing or unexpected files and size drift |
|
|
494
|
+
| `npm run verify:tag` | Assert the pushed `v<version>` tag matches `package.json` |
|
|
495
|
+
| `npm run sync:meta` | Propagate `project.config.json` into every file that repeats the project identity |
|
|
496
|
+
| `npm run sync:check` | Fail if any derived file has drifted (run in CI) |
|
|
497
|
+
| `npm run example:install` | Install the example site's dependencies |
|
|
498
|
+
| `npm run example:start` | Docusaurus dev server for the example site |
|
|
499
|
+
| `npm run example:build` | Production build of the example site |
|
|
500
|
+
| `npm run example:serve` | Serve the example site's production build |
|
|
501
|
+
|
|
502
|
+
### Renaming the package or repository
|
|
503
|
+
|
|
504
|
+
Project identity lives in `project.config.json` — package name, GitHub repository, plugin id,
|
|
505
|
+
licence, description. Change the one line you need and run:
|
|
506
|
+
|
|
507
|
+
```bash
|
|
508
|
+
npm run sync:meta
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
This rewrites `package.json`, the example site's dependency and `docusaurus.config.ts`.
|
|
512
|
+
`npm run sync:check` fails the build if anything has drifted.
|
|
513
|
+
|
|
514
|
+
The internal Docusaurus plugin name — `docusaurus-plugin-plantuml-client`, the key under
|
|
515
|
+
which the plugin publishes its global data — is deliberately **independent** of the npm
|
|
516
|
+
package name, so renaming the package cannot break global-data lookups in already-built sites.
|
|
517
|
+
|
|
518
|
+
## Test strategy
|
|
519
|
+
|
|
520
|
+
Four layers, all deterministic and none of them calling an external PlantUML service:
|
|
521
|
+
|
|
522
|
+
1. **Unit and component tests** (Vitest + jsdom). Language detection and case handling,
|
|
523
|
+
delegation of non-PlantUML blocks, fence source and title extraction, option defaults and
|
|
524
|
+
validation, singleton asset loading and concurrent load deduplication, loader failure and
|
|
525
|
+
retry, FIFO queue ordering, queue continuation after failure, timeouts, unmount during
|
|
526
|
+
render, light/dark cache separation, memory cache eviction, `sessionStorage` failure and
|
|
527
|
+
corruption handling, SVG sanitization against synthetic malicious input, the loading/ready/
|
|
528
|
+
error UI, SSR without browser globals, and the `IntersectionObserver` fallback.
|
|
529
|
+
Docusaurus `@theme/*` and `@docusaurus/*` aliases only exist inside a real Docusaurus
|
|
530
|
+
webpack build, so the Vitest config points them at local stubs.
|
|
531
|
+
2. **Package verification** (`npm run pack:check`). Runs `npm pack --dry-run --json`, prints
|
|
532
|
+
the file list with sizes, and fails on missing compiled files or declarations, a missing
|
|
533
|
+
README or LICENSE, published tests, source maps, example build output, nested tarballs, CI
|
|
534
|
+
configuration, `exports` entries pointing at unpacked files, or size budget overruns.
|
|
535
|
+
3. **Packed-tarball integration** (`npm run test:integration`). Builds the package, runs
|
|
536
|
+
`npm pack`, installs the resulting `.tgz` — not a workspace link — into a clean Docusaurus
|
|
537
|
+
fixture in a temporary directory, runs a full `docusaurus build`, and serves it. This is
|
|
538
|
+
what proves the package works as consumers actually install it.
|
|
539
|
+
4. **Browser end-to-end** (`npm run test:e2e`, Playwright against the served production
|
|
540
|
+
build). Asserts that real `<svg>` elements appear inside the expected container with the
|
|
541
|
+
expected diagram labels, that multiple diagrams render on one page, that `.md` and `.mdx`
|
|
542
|
+
both work, that ordinary code blocks are untouched, that an invalid diagram produces the
|
|
543
|
+
documented error state, that toggling dark mode re-renders, that client-side navigation
|
|
544
|
+
away and back still works, that the runtime is not loaded on a diagram-free page and is
|
|
545
|
+
loaded at most once when diagrams are present, that every asset URL carries the configured
|
|
546
|
+
`baseUrl`, that no request goes to an external host, and that the build hydrates without
|
|
547
|
+
React mismatch warnings.
|
|
548
|
+
|
|
549
|
+
The example site under `examples/docusaurus/` is the fixture: it deploys under
|
|
550
|
+
`baseUrl: '/plantuml-test/'` and contains a sequence diagram, a Graphviz-dependent class
|
|
551
|
+
diagram, a component diagram, several diagrams on one page, `.md` and `.mdx` documents, a
|
|
552
|
+
`puml` alias, title metadata, an invalid diagram, an ordinary TypeScript block, and a page
|
|
553
|
+
with no diagram at all.
|
|
554
|
+
|
|
555
|
+
## Release process
|
|
556
|
+
|
|
557
|
+
Releases are triggered by pushing a version tag:
|
|
558
|
+
|
|
559
|
+
```bash
|
|
560
|
+
npm version 1.2.3 # updates package.json and creates the v1.2.3 tag
|
|
561
|
+
git push --follow-tags
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
Pushing `v<version>` runs `.github/workflows/publish.yml`, which:
|
|
565
|
+
|
|
566
|
+
1. Installs cleanly with `npm ci`.
|
|
567
|
+
2. Runs formatting, lint, type checking, unit tests, the build, the package verification and
|
|
568
|
+
the packed-package Docusaurus integration test. **No publish happens until all of them
|
|
569
|
+
pass.**
|
|
570
|
+
3. Runs `scripts/verify-tag-version.mjs`, which fails the release if the tag does not exactly
|
|
571
|
+
match the `version` in `package.json` (tag `v1.2.3` ⇒ version `1.2.3`).
|
|
572
|
+
4. Publishes with `npm publish --access public` — the package is scoped, so `--access public`
|
|
573
|
+
is required for the first and every subsequent publish.
|
|
574
|
+
|
|
575
|
+
Prereleases follow the same path: a version like `1.2.3-beta.1` is published under a
|
|
576
|
+
non-`latest` dist-tag, so `npm install @matfsw/docusaurus-plantuml-plugin` keeps resolving to
|
|
577
|
+
the last stable release.
|
|
578
|
+
|
|
579
|
+
Authentication is npm **trusted publishing** over GitHub Actions OIDC. There is no long-lived
|
|
580
|
+
npm token anywhere in this repository, in its workflows, or in its secrets. The workflow runs
|
|
581
|
+
in a GitHub environment named `npm`, so maintainers can require manual approval before a
|
|
582
|
+
release proceeds. Provenance is attested automatically by trusted publishing.
|
|
583
|
+
|
|
584
|
+
## npm trusted publisher: one-time setup
|
|
585
|
+
|
|
586
|
+
Do this once, as the npm package owner. It is not needed for day-to-day contribution.
|
|
587
|
+
|
|
588
|
+
1. **Create the package on npm.** Trusted publishing is configured per package, so the
|
|
589
|
+
package must exist first. Publish the initial version manually (`npm publish
|
|
590
|
+
--access public` from a local checkout with a personal login) or create it through
|
|
591
|
+
whatever bootstrap process you prefer.
|
|
592
|
+
2. **Open the package settings on npmjs.com** — `https://www.npmjs.com/package/@matfsw/docusaurus-plantuml-plugin`
|
|
593
|
+
→ _Settings_.
|
|
594
|
+
3. **Add a trusted publisher** and choose **GitHub Actions** as the provider.
|
|
595
|
+
4. **Enter the owner and repository**: owner `matf`, repository `docusaurus-plantuml-plugin`.
|
|
596
|
+
They must match the GitHub repository exactly.
|
|
597
|
+
5. **Enter the workflow filename**: `publish.yml`. This is the **filename only**, not
|
|
598
|
+
`.github/workflows/publish.yml`. npm matches on the filename, which is why the workflow
|
|
599
|
+
file must never be renamed.
|
|
600
|
+
6. **Set the environment name**: `npm`. It must match the `environment:` in the workflow.
|
|
601
|
+
7. **Allow `npm publish`** for this trusted publisher.
|
|
602
|
+
8. **Confirm `package.json` `repository.url` matches the GitHub repository exactly** —
|
|
603
|
+
`git+https://github.com/matf/docusaurus-plantuml-plugin.git`. Provenance verification
|
|
604
|
+
compares it. `npm run sync:meta` keeps it correct.
|
|
605
|
+
9. **Enable publishing-access restrictions** once you have verified a trusted-publish
|
|
606
|
+
actually works: in the package settings, require two-factor authentication or trusted
|
|
607
|
+
publishing for publication, so no token can publish behind your back.
|
|
608
|
+
10. **Remove obsolete automation tokens.** Any granular or classic token that existed only to
|
|
609
|
+
publish this package should be deleted from your npm account.
|
|
610
|
+
|
|
611
|
+
Trusted publishing authenticates **publication**. It does **not** provide credentials for
|
|
612
|
+
_installing_ private dependencies. This repository has no private dependencies, so **do not**
|
|
613
|
+
add a read token to CI. Add one only if the repository genuinely starts consuming a private
|
|
614
|
+
package.
|
|
615
|
+
|
|
616
|
+
## Limitations
|
|
617
|
+
|
|
618
|
+
- **A theme providing `MDXComponents/Code` must already be installed.** The plugin wraps that
|
|
619
|
+
component via `@theme-init/`; with no underlying implementation there is nothing to wrap.
|
|
620
|
+
`@docusaurus/theme-classic` (via `preset-classic`) satisfies this.
|
|
621
|
+
- **A site-level swizzle of `MDXComponents/Code` wins.** If your site swizzles that component
|
|
622
|
+
itself, the site's version is what Docusaurus uses and this plugin is bypassed.
|
|
623
|
+
- **Two plugins wrapping the same component do not compose.** If another plugin also wraps
|
|
624
|
+
`MDXComponents/Code`, only one wrapper survives. See
|
|
625
|
+
[ADR 0001](docs/adr/0001-theme-init-alias.md).
|
|
626
|
+
- **`docusaurus swizzle` is not supported for this plugin's components.**
|
|
627
|
+
`getTypeScriptThemePath` is intentionally not implemented, to keep the published package
|
|
628
|
+
lean. Fork the component if you need to change it.
|
|
629
|
+
- **Code blocks authored as JSX children are not intercepted.** When a fence's children
|
|
630
|
+
contain React elements rather than plain text, the source cannot be recovered reliably, so
|
|
631
|
+
the block is handed back to the original component untouched.
|
|
632
|
+
- **Diagrams are not rendered during static-site generation.** The HTML Docusaurus emits
|
|
633
|
+
contains the deferred placeholder and the `<noscript>` source. Search engines that do not
|
|
634
|
+
execute JavaScript will not see the diagram image.
|
|
635
|
+
- **One diagram renders at a time.** The engine has module-level shared state, so a page with
|
|
636
|
+
many large diagrams renders them sequentially. This is a correctness requirement, not a
|
|
637
|
+
tuning knob.
|
|
638
|
+
- **`renderTimeoutMs` covers both loading and rendering.** On a very slow connection, the
|
|
639
|
+
8 MB download is subject to the same budget as a render.
|
|
640
|
+
- **Consuming the plugin through a `file:` link needs a webpack tweak.** See the entry in
|
|
641
|
+
Troubleshooting below.
|
|
642
|
+
|
|
643
|
+
## Troubleshooting
|
|
644
|
+
|
|
645
|
+
### Diagrams stay on "Loading PlantUML runtime…" and the assets 404 under a subpath
|
|
646
|
+
|
|
647
|
+
Open the network tab and check the failing URL. It should be
|
|
648
|
+
`<baseUrl>assets/plantuml-client-<version>/viz-global.js`. If the request goes to
|
|
649
|
+
`/assets/...` when your site lives at `/docs/`, the site's `baseUrl` is wrong — not the
|
|
650
|
+
plugin's paths, which are always resolved through `useBaseUrl()`.
|
|
651
|
+
|
|
652
|
+
### Incorrect `baseUrl`
|
|
653
|
+
|
|
654
|
+
Docusaurus' `baseUrl` must be the deployment subpath with **leading and trailing slashes**:
|
|
655
|
+
`baseUrl: '/plantuml-test/'`, not `'plantuml-test'` or `'/plantuml-test'`. A wrong value
|
|
656
|
+
breaks the PlantUML assets the same way it breaks every other Docusaurus asset. Fix it and
|
|
657
|
+
rebuild.
|
|
658
|
+
|
|
659
|
+
### A reverse proxy rewrites asset paths
|
|
660
|
+
|
|
661
|
+
If nginx, Apache or a CDN strips or rewrites a path prefix, the browser will request a URL
|
|
662
|
+
your origin does not serve. Make the proxy pass `<baseUrl>assets/**` through unmodified, and
|
|
663
|
+
verify by requesting the exact `viz-global.js` URL from the network tab with `curl`.
|
|
664
|
+
|
|
665
|
+
### A reverse proxy serves JavaScript with the wrong MIME type
|
|
666
|
+
|
|
667
|
+
`plantuml.js` is loaded with a dynamic `import()`, and browsers refuse to execute an ES module
|
|
668
|
+
served as `text/plain`, `application/octet-stream` or `text/html`. The console shows a strict
|
|
669
|
+
MIME type checking error. Check with:
|
|
670
|
+
|
|
671
|
+
```bash
|
|
672
|
+
curl -I https://example.com/your-base-url/assets/plantuml-client-1.2026.6/plantuml.js
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
The `content-type` must be a JavaScript type (`text/javascript` or
|
|
676
|
+
`application/javascript`). If you get `text/html`, the proxy is serving your SPA fallback
|
|
677
|
+
instead of the file — the file is not reaching the origin at all.
|
|
678
|
+
|
|
679
|
+
### A restrictive Content Security Policy
|
|
680
|
+
|
|
681
|
+
Symptom: a console message naming a violated directive, and diagrams stuck loading. Ensure
|
|
682
|
+
`script-src` allows `'self'`; add `'wasm-unsafe-eval'` if the violation mentions WebAssembly.
|
|
683
|
+
See [Content Security Policy](#content-security-policy). No `connect-src` change for third
|
|
684
|
+
parties is ever needed, because nothing third-party is contacted.
|
|
685
|
+
|
|
686
|
+
### A corporate proxy breaks `npm install` or GitHub Actions
|
|
687
|
+
|
|
688
|
+
`@plantuml/core` is an ~8 MB dependency and slow or MITM-ing proxies time out on it or serve
|
|
689
|
+
a truncated tarball. Symptoms are an install failure, an integrity mismatch, or a build-time
|
|
690
|
+
`Could not resolve '@plantuml/core'`. Configure `npm config set proxy` / `https-proxy` and
|
|
691
|
+
your corporate CA (`npm config set cafile`), raise `npm config set fetch-timeout`, and delete
|
|
692
|
+
`node_modules` plus the npm cache before retrying. On self-hosted GitHub Actions runners,
|
|
693
|
+
give the runner the same proxy and CA configuration.
|
|
694
|
+
|
|
695
|
+
### A browser extension blocks worker or WASM execution
|
|
696
|
+
|
|
697
|
+
Script-blocking and privacy extensions sometimes block large inline-compiled JavaScript,
|
|
698
|
+
WebAssembly compilation or worker creation. If diagrams fail for one reader but work in a
|
|
699
|
+
private window with extensions disabled, that is the cause. Ask the reader to allow-list the
|
|
700
|
+
site.
|
|
701
|
+
|
|
702
|
+
### The build fails with "Progress Plugin ... does not match the API schema"
|
|
703
|
+
|
|
704
|
+
Not this plugin. `@docusaurus/bundler` pinned `webpackbar@^6.0.1` until Docusaurus `3.10.2`,
|
|
705
|
+
and that version of `webpackbar` is rejected by `webpack >= 5.109`. Any Docusaurus
|
|
706
|
+
`3.5`–`3.10.0` site fails this way from a fresh install, with or without this plugin
|
|
707
|
+
installed. Add `"overrides": {"webpackbar": "^7.0.0"}` to your site's `package.json` and
|
|
708
|
+
reinstall, or upgrade Docusaurus to `3.10.2` or later. See
|
|
709
|
+
[Status and compatibility](#docusaurus-3510-need-a-webpackbar-override).
|
|
710
|
+
|
|
711
|
+
### Duplicate or mismatched Docusaurus / React dependencies
|
|
712
|
+
|
|
713
|
+
Symptom: the page fails with something like _`useColorMode` is called outside the
|
|
714
|
+
`ColorModeProvider`_, or React complains about invalid hook calls. This means two copies of
|
|
715
|
+
React or of `@docusaurus/theme-common` are loaded.
|
|
716
|
+
|
|
717
|
+
This bites specifically when the plugin is consumed through a local `file:` link. webpack
|
|
718
|
+
resolves symlinks to their real path by default, so the plugin's theme components resolve
|
|
719
|
+
`@docusaurus/theme-common` and `react` from the _linked package's_ directory instead of from
|
|
720
|
+
the site — two module instances, two React contexts. The example site works around it with a
|
|
721
|
+
tiny inline plugin:
|
|
722
|
+
|
|
723
|
+
```ts
|
|
724
|
+
function resolveLinkedPluginFromSite() {
|
|
725
|
+
return {
|
|
726
|
+
name: 'example-resolve-linked-plugin',
|
|
727
|
+
configureWebpack() {
|
|
728
|
+
return {resolve: {symlinks: false}};
|
|
729
|
+
},
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
A normal `npm install` from the registry has no symlink and needs none of this — which is
|
|
735
|
+
exactly what the packed-tarball integration test proves. If you hit this on a real install
|
|
736
|
+
instead, run `npm ls react @docusaurus/core` and dedupe.
|
|
737
|
+
|
|
738
|
+
### Invalid PlantUML source
|
|
739
|
+
|
|
740
|
+
The error panel shows PlantUML's own message (with the engine's version-nag lines stripped),
|
|
741
|
+
and the source is available under _Show diagram source_. Common causes: a missing `@enduml`,
|
|
742
|
+
a typo that makes PlantUML guess the wrong diagram type, or a diagram kind this engine build
|
|
743
|
+
does not implement. Paste the source into any PlantUML tool to confirm, and note that an
|
|
744
|
+
unterminated `@startuml` surfaces as a Java exception rather than a syntax error.
|
|
745
|
+
|
|
746
|
+
### Stale browser or service-worker caches
|
|
747
|
+
|
|
748
|
+
The asset directory carries the engine version, so an engine upgrade cannot be served from a
|
|
749
|
+
stale cache. Your own site's JavaScript can be, though — particularly behind a service worker
|
|
750
|
+
registered by a PWA plugin. Hard-reload, or unregister the service worker under _Application →
|
|
751
|
+
Service Workers_ in DevTools, before concluding that a fix did not land.
|
|
752
|
+
|
|
753
|
+
### A stale build cache serves an old copy of the plugin
|
|
754
|
+
|
|
755
|
+
After upgrading the plugin, webpack's persistent cache in `node_modules/.cache` (and
|
|
756
|
+
`.docusaurus/`) can keep serving the previous version's theme components — you change the
|
|
757
|
+
plugin and nothing changes in the browser. This is a real failure mode we have hit. Clear it:
|
|
758
|
+
|
|
759
|
+
```bash
|
|
760
|
+
npx docusaurus clear
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
Then rebuild. If it persists, remove `node_modules/.cache` and `.docusaurus/` by hand.
|
|
764
|
+
|
|
765
|
+
## Further reading
|
|
766
|
+
|
|
767
|
+
- [`docs/architecture.md`](docs/architecture.md) — SSR boundary, asset loading, render queue,
|
|
768
|
+
caching, sanitization, dark-mode re-rendering.
|
|
769
|
+
- [`docs/adr/0001-theme-init-alias.md`](docs/adr/0001-theme-init-alias.md) — why the plugin
|
|
770
|
+
wraps `@theme-init/`, not `@theme-original/`.
|
|
771
|
+
- [`docs/adr/0002-render-to-string.md`](docs/adr/0002-render-to-string.md) — why the string
|
|
772
|
+
API is used instead of the DOM `render()` API, and how invalid diagrams are detected.
|
|
773
|
+
- [`CONTRIBUTING.md`](CONTRIBUTING.md)
|
|
774
|
+
- [`SECURITY.md`](SECURITY.md)
|
|
775
|
+
|
|
776
|
+
## Licence
|
|
777
|
+
|
|
778
|
+
MIT. See [LICENSE](LICENSE).
|
|
779
|
+
|
|
780
|
+
[plantuml-core]: https://www.npmjs.com/package/@plantuml/core
|
|
781
|
+
[dompurify]: https://github.com/cure53/DOMPurify
|