@matfsw/docusaurus-plantuml-plugin 1.0.3 → 1.1.1
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 +94 -1
- package/README.md +264 -49
- package/dist/assets.js +25 -8
- package/dist/constants.d.ts +12 -1
- package/dist/constants.js +12 -1
- package/dist/index.cjs +116 -17
- package/dist/index.d.cts +41 -1
- package/dist/options.d.ts +42 -1
- package/dist/options.js +105 -8
- package/dist/runtime/assetLoader.d.ts +13 -3
- package/dist/runtime/assetLoader.js +68 -8
- package/dist/runtime/cache.d.ts +21 -0
- package/dist/runtime/cache.js +12 -0
- package/dist/runtime/errors.d.ts +16 -1
- package/dist/runtime/errors.js +32 -0
- package/dist/runtime/graphvizRenderer.d.ts +24 -0
- package/dist/runtime/graphvizRenderer.js +95 -0
- package/dist/runtime/types.d.ts +41 -0
- package/dist/theme/MDXComponents/Code/index.d.ts +8 -3
- package/dist/theme/MDXComponents/Code/index.js +31 -8
- package/dist/theme/PlantUmlDiagram/index.d.ts +16 -4
- package/dist/theme/PlantUmlDiagram/index.js +49 -12
- package/dist/theme/PlantUmlDiagram/styles.module.css +31 -0
- package/dist/theme/codeBlockMeta.d.ts +9 -0
- package/dist/theme/codeBlockMeta.js +21 -0
- package/dist/theme/usePlantUmlConfig.d.ts +1 -1
- package/dist/theme/usePlantUmlConfig.js +20 -1
- package/package.json +6 -1
package/dist/index.cjs
CHANGED
|
@@ -80,17 +80,42 @@ function locatePlantUmlCore() {
|
|
|
80
80
|
const packageDir = import_node_path.default.dirname(packageJsonPath);
|
|
81
81
|
const packageJson = JSON.parse(import_node_fs.default.readFileSync(packageJsonPath, "utf8"));
|
|
82
82
|
const version = typeof packageJson.version === "string" ? packageJson.version : "0.0.0";
|
|
83
|
-
|
|
84
|
-
packageDir,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
const files = [
|
|
84
|
+
import_node_path.default.join(packageDir, VIZ_SCRIPT_FILENAME),
|
|
85
|
+
import_node_path.default.join(packageDir, PLANTUML_MODULE_FILENAME)
|
|
86
|
+
];
|
|
87
|
+
files.forEach((file) => assertRuntimeFile(file, version));
|
|
88
|
+
return { packageDir, version, files };
|
|
89
|
+
}
|
|
90
|
+
function assertRuntimeFile(file, version) {
|
|
91
|
+
if (import_node_fs.default.existsSync(file)) return;
|
|
92
|
+
throw new Error(
|
|
93
|
+
`[${PLUGIN_NAME}] '@plantuml/core@${version}' does not contain '${import_node_path.default.basename(file)}', which this plugin serves as a runtime asset. This means the installed version of @plantuml/core is not one this plugin supports. Pin a supported version, or open an issue if the package has genuinely changed its layout.`
|
|
94
|
+
);
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
// src/options.ts
|
|
98
|
+
var GRAPHVIZ_ENGINES = [
|
|
99
|
+
"circo",
|
|
100
|
+
"dot",
|
|
101
|
+
"fdp",
|
|
102
|
+
"neato",
|
|
103
|
+
"nop",
|
|
104
|
+
"nop1",
|
|
105
|
+
"nop2",
|
|
106
|
+
"osage",
|
|
107
|
+
"patchwork",
|
|
108
|
+
"sfdp",
|
|
109
|
+
"twopi"
|
|
110
|
+
];
|
|
111
|
+
var DEFAULT_GRAPHVIZ_OPTIONS = {
|
|
112
|
+
enabled: true,
|
|
113
|
+
languages: ["dot", "graphviz", "gv"],
|
|
114
|
+
engine: "dot",
|
|
115
|
+
allowEngineOverride: true,
|
|
116
|
+
maxSourceBytes: 1e5,
|
|
117
|
+
transparentBackground: true
|
|
118
|
+
};
|
|
94
119
|
var DEFAULT_OPTIONS = {
|
|
95
120
|
languages: ["plantuml", "puml"],
|
|
96
121
|
theme: "auto",
|
|
@@ -100,7 +125,8 @@ var DEFAULT_OPTIONS = {
|
|
|
100
125
|
showSourceOnError: true,
|
|
101
126
|
renderTimeoutMs: 2e4,
|
|
102
127
|
cacheMaxEntries: 50,
|
|
103
|
-
zoom: true
|
|
128
|
+
zoom: true,
|
|
129
|
+
graphviz: DEFAULT_GRAPHVIZ_OPTIONS
|
|
104
130
|
};
|
|
105
131
|
var CACHE_MODES = ["none", "memory", "session"];
|
|
106
132
|
var THEMES = ["auto", "light", "dark"];
|
|
@@ -128,28 +154,85 @@ function validateBoolean(value, key, fallback) {
|
|
|
128
154
|
}
|
|
129
155
|
return value;
|
|
130
156
|
}
|
|
131
|
-
function validateLanguages(value) {
|
|
132
|
-
if (value === void 0) return [...
|
|
157
|
+
function validateLanguages(value, key, fallback) {
|
|
158
|
+
if (value === void 0) return [...fallback];
|
|
133
159
|
if (!Array.isArray(value)) {
|
|
134
|
-
fail(`options
|
|
160
|
+
fail(`options.${key} must be an array of strings, received ${JSON.stringify(value)}.`);
|
|
135
161
|
}
|
|
136
162
|
if (value.length === 0) {
|
|
137
|
-
fail(
|
|
163
|
+
fail(`options.${key} must contain at least one language.`);
|
|
138
164
|
}
|
|
139
165
|
const normalized = value.map((entry, index) => {
|
|
140
166
|
if (typeof entry !== "string" || entry.trim() === "") {
|
|
141
167
|
fail(
|
|
142
|
-
`options
|
|
168
|
+
`options.${key}[${index}] must be a non-empty string, received ${JSON.stringify(entry)}.`
|
|
143
169
|
);
|
|
144
170
|
}
|
|
145
171
|
return entry.trim().toLowerCase();
|
|
146
172
|
});
|
|
147
173
|
const duplicates = normalized.filter((entry, index) => normalized.indexOf(entry) !== index);
|
|
148
174
|
if (duplicates.length > 0) {
|
|
149
|
-
fail(`options
|
|
175
|
+
fail(`options.${key} contains duplicate entries: ${quoteList([...new Set(duplicates)])}.`);
|
|
150
176
|
}
|
|
151
177
|
return normalized;
|
|
152
178
|
}
|
|
179
|
+
function validateMaxSourceBytes(value) {
|
|
180
|
+
if (value === void 0) return DEFAULT_GRAPHVIZ_OPTIONS.maxSourceBytes;
|
|
181
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
|
|
182
|
+
fail(
|
|
183
|
+
`options.graphviz.maxSourceBytes must be a positive integer, received ${JSON.stringify(value)}.`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
return value;
|
|
187
|
+
}
|
|
188
|
+
function validateGraphviz(value) {
|
|
189
|
+
if (value === void 0) {
|
|
190
|
+
return { ...DEFAULT_GRAPHVIZ_OPTIONS, languages: [...DEFAULT_GRAPHVIZ_OPTIONS.languages] };
|
|
191
|
+
}
|
|
192
|
+
if (!isPlainObject(value)) {
|
|
193
|
+
fail(`options.graphviz must be an object, received ${JSON.stringify(value)}.`);
|
|
194
|
+
}
|
|
195
|
+
const allowedKeys = [
|
|
196
|
+
"enabled",
|
|
197
|
+
"languages",
|
|
198
|
+
"engine",
|
|
199
|
+
"allowEngineOverride",
|
|
200
|
+
"maxSourceBytes",
|
|
201
|
+
"transparentBackground"
|
|
202
|
+
];
|
|
203
|
+
const unknownKeys = Object.keys(value).filter((key) => !allowedKeys.includes(key));
|
|
204
|
+
if (unknownKeys.length > 0) {
|
|
205
|
+
fail(
|
|
206
|
+
`Unknown option${unknownKeys.length > 1 ? "s" : ""} ${quoteList(unknownKeys.map((key) => `graphviz.${key}`))}. Supported options: ${quoteList(allowedKeys.map((key) => `graphviz.${key}`))}.`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
const { engine } = value;
|
|
210
|
+
if (engine !== void 0 && !GRAPHVIZ_ENGINES.includes(engine)) {
|
|
211
|
+
fail(
|
|
212
|
+
`options.graphviz.engine must be one of ${quoteList(GRAPHVIZ_ENGINES)}, received ${JSON.stringify(engine)}.`
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
enabled: validateBoolean(value.enabled, "graphviz.enabled", DEFAULT_GRAPHVIZ_OPTIONS.enabled),
|
|
217
|
+
languages: validateLanguages(
|
|
218
|
+
value.languages,
|
|
219
|
+
"graphviz.languages",
|
|
220
|
+
DEFAULT_GRAPHVIZ_OPTIONS.languages
|
|
221
|
+
),
|
|
222
|
+
engine: engine ?? DEFAULT_GRAPHVIZ_OPTIONS.engine,
|
|
223
|
+
allowEngineOverride: validateBoolean(
|
|
224
|
+
value.allowEngineOverride,
|
|
225
|
+
"graphviz.allowEngineOverride",
|
|
226
|
+
DEFAULT_GRAPHVIZ_OPTIONS.allowEngineOverride
|
|
227
|
+
),
|
|
228
|
+
maxSourceBytes: validateMaxSourceBytes(value.maxSourceBytes),
|
|
229
|
+
transparentBackground: validateBoolean(
|
|
230
|
+
value.transparentBackground,
|
|
231
|
+
"graphviz.transparentBackground",
|
|
232
|
+
DEFAULT_GRAPHVIZ_OPTIONS.transparentBackground
|
|
233
|
+
)
|
|
234
|
+
};
|
|
235
|
+
}
|
|
153
236
|
function validateRenderTimeout(value) {
|
|
154
237
|
if (value === void 0) return DEFAULT_OPTIONS.renderTimeoutMs;
|
|
155
238
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
@@ -174,7 +257,11 @@ function validateCacheMaxEntries(value) {
|
|
|
174
257
|
}
|
|
175
258
|
function resolveOptions(rawOptions) {
|
|
176
259
|
if (rawOptions === void 0 || rawOptions === null) {
|
|
177
|
-
return {
|
|
260
|
+
return {
|
|
261
|
+
...DEFAULT_OPTIONS,
|
|
262
|
+
languages: [...DEFAULT_OPTIONS.languages],
|
|
263
|
+
graphviz: { ...DEFAULT_GRAPHVIZ_OPTIONS, languages: [...DEFAULT_GRAPHVIZ_OPTIONS.languages] }
|
|
264
|
+
};
|
|
178
265
|
}
|
|
179
266
|
if (!isPlainObject(rawOptions)) {
|
|
180
267
|
fail(`options must be an object, received ${JSON.stringify(rawOptions)}.`);
|
|
@@ -189,6 +276,7 @@ function resolveOptions(rawOptions) {
|
|
|
189
276
|
"renderTimeoutMs",
|
|
190
277
|
"cacheMaxEntries",
|
|
191
278
|
"zoom",
|
|
279
|
+
"graphviz",
|
|
192
280
|
// Docusaurus injects `id` for multi-instance plugins.
|
|
193
281
|
"id"
|
|
194
282
|
]);
|
|
@@ -207,8 +295,19 @@ function resolveOptions(rawOptions) {
|
|
|
207
295
|
`options.cache must be one of ${quoteList(CACHE_MODES)}, received ${JSON.stringify(cache)}.`
|
|
208
296
|
);
|
|
209
297
|
}
|
|
298
|
+
const languages = validateLanguages(rawOptions.languages, "languages", DEFAULT_OPTIONS.languages);
|
|
299
|
+
const graphviz = validateGraphviz(rawOptions.graphviz);
|
|
300
|
+
if (graphviz.enabled) {
|
|
301
|
+
const claimedTwice = graphviz.languages.filter((entry) => languages.includes(entry));
|
|
302
|
+
if (claimedTwice.length > 0) {
|
|
303
|
+
fail(
|
|
304
|
+
`options.graphviz.languages and options.languages both claim ${quoteList(claimedTwice)}. A fence language can only belong to one engine.`
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
210
308
|
return {
|
|
211
|
-
languages
|
|
309
|
+
languages,
|
|
310
|
+
graphviz,
|
|
212
311
|
theme: theme ?? DEFAULT_OPTIONS.theme,
|
|
213
312
|
lazy: validateBoolean(rawOptions.lazy, "lazy", DEFAULT_OPTIONS.lazy),
|
|
214
313
|
cache: cache ?? DEFAULT_OPTIONS.cache,
|
package/dist/index.d.cts
CHANGED
|
@@ -5,6 +5,43 @@ import { WebpackPluginInstance } from 'webpack';
|
|
|
5
5
|
type CacheMode = 'none' | 'memory' | 'session';
|
|
6
6
|
/** Which PlantUML colour scheme to render. `auto` follows the Docusaurus colour mode. */
|
|
7
7
|
type DiagramTheme = 'auto' | 'light' | 'dark';
|
|
8
|
+
/**
|
|
9
|
+
* Graphviz layout engines available in the bundled build.
|
|
10
|
+
*
|
|
11
|
+
* Taken from `Viz.engines` of the `viz-global.js` that `@plantuml/core` ships. The loader
|
|
12
|
+
* re-checks the requested engine against the live list at render time, so this array only has
|
|
13
|
+
* to be good enough to reject typos in `docusaurus.config.*` at build time.
|
|
14
|
+
*/
|
|
15
|
+
declare const GRAPHVIZ_ENGINES: readonly ["circo", "dot", "fdp", "neato", "nop", "nop1", "nop2", "osage", "patchwork", "sfdp", "twopi"];
|
|
16
|
+
type GraphvizEngine = (typeof GRAPHVIZ_ENGINES)[number];
|
|
17
|
+
/** Graphviz/DOT options, nested so the two engines cannot collide in the option namespace. */
|
|
18
|
+
interface GraphvizOptions {
|
|
19
|
+
/** Intercept DOT fences at all. Set to `false` to leave `dot` code blocks as code blocks. */
|
|
20
|
+
enabled?: boolean;
|
|
21
|
+
/** Fenced-code languages treated as Graphviz DOT. Matched case-insensitively. */
|
|
22
|
+
languages?: string[];
|
|
23
|
+
/** Default layout engine used when a fence does not name one. */
|
|
24
|
+
engine?: GraphvizEngine;
|
|
25
|
+
/** Allow a fence to select a different layout engine with `engine=neato`. */
|
|
26
|
+
allowEngineOverride?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Refuse to render sources larger than this many UTF-8 bytes.
|
|
29
|
+
*
|
|
30
|
+
* Graphviz renders synchronously, so an enormous graph blocks the main thread; the guard
|
|
31
|
+
* turns a frozen tab into an explanatory panel. See `docs/adr/0004-graphviz-engine-reuse.md`.
|
|
32
|
+
*/
|
|
33
|
+
maxSourceBytes?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Render on a transparent background instead of Graphviz's opaque white, so diagrams sit on
|
|
36
|
+
* the page background in both colour modes.
|
|
37
|
+
*/
|
|
38
|
+
transparentBackground?: boolean;
|
|
39
|
+
}
|
|
40
|
+
/** Graphviz options after validation, with every default applied. */
|
|
41
|
+
type ResolvedGraphvizOptions = Required<Omit<GraphvizOptions, 'languages'>> & {
|
|
42
|
+
/** Normalized to lower case; matching is case-insensitive. */
|
|
43
|
+
languages: string[];
|
|
44
|
+
};
|
|
8
45
|
/**
|
|
9
46
|
* Options accepted by the plugin in `docusaurus.config.*`.
|
|
10
47
|
*
|
|
@@ -32,13 +69,16 @@ interface PlantUmlPluginOptions {
|
|
|
32
69
|
* Override for a single fence with `zoom` or `zoom=false` in its metastring.
|
|
33
70
|
*/
|
|
34
71
|
zoom?: boolean;
|
|
72
|
+
/** Graphviz/DOT diagram support. See {@link GraphvizOptions}. */
|
|
73
|
+
graphviz?: GraphvizOptions;
|
|
35
74
|
/** Docusaurus plugin instance id, used when the plugin is registered more than once. */
|
|
36
75
|
id?: string;
|
|
37
76
|
}
|
|
38
77
|
/** Plugin options after validation, with every default applied. */
|
|
39
|
-
type ResolvedPlantUmlOptions = Required<Omit<PlantUmlPluginOptions, 'id' | 'languages'>> & {
|
|
78
|
+
type ResolvedPlantUmlOptions = Required<Omit<PlantUmlPluginOptions, 'id' | 'languages' | 'graphviz'>> & {
|
|
40
79
|
/** Normalized to lower case; matching is case-insensitive. */
|
|
41
80
|
languages: string[];
|
|
81
|
+
graphviz: ResolvedGraphvizOptions;
|
|
42
82
|
};
|
|
43
83
|
declare const DEFAULT_OPTIONS: ResolvedPlantUmlOptions;
|
|
44
84
|
/** Thrown for invalid plugin options so Docusaurus fails the build with a useful message. */
|
package/dist/options.d.ts
CHANGED
|
@@ -2,6 +2,43 @@
|
|
|
2
2
|
export type CacheMode = 'none' | 'memory' | 'session';
|
|
3
3
|
/** Which PlantUML colour scheme to render. `auto` follows the Docusaurus colour mode. */
|
|
4
4
|
export type DiagramTheme = 'auto' | 'light' | 'dark';
|
|
5
|
+
/**
|
|
6
|
+
* Graphviz layout engines available in the bundled build.
|
|
7
|
+
*
|
|
8
|
+
* Taken from `Viz.engines` of the `viz-global.js` that `@plantuml/core` ships. The loader
|
|
9
|
+
* re-checks the requested engine against the live list at render time, so this array only has
|
|
10
|
+
* to be good enough to reject typos in `docusaurus.config.*` at build time.
|
|
11
|
+
*/
|
|
12
|
+
export declare const GRAPHVIZ_ENGINES: readonly ["circo", "dot", "fdp", "neato", "nop", "nop1", "nop2", "osage", "patchwork", "sfdp", "twopi"];
|
|
13
|
+
export type GraphvizEngine = (typeof GRAPHVIZ_ENGINES)[number];
|
|
14
|
+
/** Graphviz/DOT options, nested so the two engines cannot collide in the option namespace. */
|
|
15
|
+
export interface GraphvizOptions {
|
|
16
|
+
/** Intercept DOT fences at all. Set to `false` to leave `dot` code blocks as code blocks. */
|
|
17
|
+
enabled?: boolean;
|
|
18
|
+
/** Fenced-code languages treated as Graphviz DOT. Matched case-insensitively. */
|
|
19
|
+
languages?: string[];
|
|
20
|
+
/** Default layout engine used when a fence does not name one. */
|
|
21
|
+
engine?: GraphvizEngine;
|
|
22
|
+
/** Allow a fence to select a different layout engine with `engine=neato`. */
|
|
23
|
+
allowEngineOverride?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Refuse to render sources larger than this many UTF-8 bytes.
|
|
26
|
+
*
|
|
27
|
+
* Graphviz renders synchronously, so an enormous graph blocks the main thread; the guard
|
|
28
|
+
* turns a frozen tab into an explanatory panel. See `docs/adr/0004-graphviz-engine-reuse.md`.
|
|
29
|
+
*/
|
|
30
|
+
maxSourceBytes?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Render on a transparent background instead of Graphviz's opaque white, so diagrams sit on
|
|
33
|
+
* the page background in both colour modes.
|
|
34
|
+
*/
|
|
35
|
+
transparentBackground?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** Graphviz options after validation, with every default applied. */
|
|
38
|
+
export type ResolvedGraphvizOptions = Required<Omit<GraphvizOptions, 'languages'>> & {
|
|
39
|
+
/** Normalized to lower case; matching is case-insensitive. */
|
|
40
|
+
languages: string[];
|
|
41
|
+
};
|
|
5
42
|
/**
|
|
6
43
|
* Options accepted by the plugin in `docusaurus.config.*`.
|
|
7
44
|
*
|
|
@@ -29,14 +66,18 @@ export interface PlantUmlPluginOptions {
|
|
|
29
66
|
* Override for a single fence with `zoom` or `zoom=false` in its metastring.
|
|
30
67
|
*/
|
|
31
68
|
zoom?: boolean;
|
|
69
|
+
/** Graphviz/DOT diagram support. See {@link GraphvizOptions}. */
|
|
70
|
+
graphviz?: GraphvizOptions;
|
|
32
71
|
/** Docusaurus plugin instance id, used when the plugin is registered more than once. */
|
|
33
72
|
id?: string;
|
|
34
73
|
}
|
|
35
74
|
/** Plugin options after validation, with every default applied. */
|
|
36
|
-
export type ResolvedPlantUmlOptions = Required<Omit<PlantUmlPluginOptions, 'id' | 'languages'>> & {
|
|
75
|
+
export type ResolvedPlantUmlOptions = Required<Omit<PlantUmlPluginOptions, 'id' | 'languages' | 'graphviz'>> & {
|
|
37
76
|
/** Normalized to lower case; matching is case-insensitive. */
|
|
38
77
|
languages: string[];
|
|
78
|
+
graphviz: ResolvedGraphvizOptions;
|
|
39
79
|
};
|
|
80
|
+
export declare const DEFAULT_GRAPHVIZ_OPTIONS: ResolvedGraphvizOptions;
|
|
40
81
|
export declare const DEFAULT_OPTIONS: ResolvedPlantUmlOptions;
|
|
41
82
|
/** Thrown for invalid plugin options so Docusaurus fails the build with a useful message. */
|
|
42
83
|
export declare class PlantUmlOptionsError extends Error {
|
package/dist/options.js
CHANGED
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
import { PLUGIN_NAME } from './constants.js';
|
|
2
|
+
/**
|
|
3
|
+
* Graphviz layout engines available in the bundled build.
|
|
4
|
+
*
|
|
5
|
+
* Taken from `Viz.engines` of the `viz-global.js` that `@plantuml/core` ships. The loader
|
|
6
|
+
* re-checks the requested engine against the live list at render time, so this array only has
|
|
7
|
+
* to be good enough to reject typos in `docusaurus.config.*` at build time.
|
|
8
|
+
*/
|
|
9
|
+
export const GRAPHVIZ_ENGINES = [
|
|
10
|
+
'circo',
|
|
11
|
+
'dot',
|
|
12
|
+
'fdp',
|
|
13
|
+
'neato',
|
|
14
|
+
'nop',
|
|
15
|
+
'nop1',
|
|
16
|
+
'nop2',
|
|
17
|
+
'osage',
|
|
18
|
+
'patchwork',
|
|
19
|
+
'sfdp',
|
|
20
|
+
'twopi',
|
|
21
|
+
];
|
|
22
|
+
export const DEFAULT_GRAPHVIZ_OPTIONS = {
|
|
23
|
+
enabled: true,
|
|
24
|
+
languages: ['dot', 'graphviz', 'gv'],
|
|
25
|
+
engine: 'dot',
|
|
26
|
+
allowEngineOverride: true,
|
|
27
|
+
maxSourceBytes: 100_000,
|
|
28
|
+
transparentBackground: true,
|
|
29
|
+
};
|
|
2
30
|
export const DEFAULT_OPTIONS = {
|
|
3
31
|
languages: ['plantuml', 'puml'],
|
|
4
32
|
theme: 'auto',
|
|
@@ -9,6 +37,7 @@ export const DEFAULT_OPTIONS = {
|
|
|
9
37
|
renderTimeoutMs: 20_000,
|
|
10
38
|
cacheMaxEntries: 50,
|
|
11
39
|
zoom: true,
|
|
40
|
+
graphviz: DEFAULT_GRAPHVIZ_OPTIONS,
|
|
12
41
|
};
|
|
13
42
|
const CACHE_MODES = ['none', 'memory', 'session'];
|
|
14
43
|
const THEMES = ['auto', 'light', 'dark'];
|
|
@@ -38,27 +67,78 @@ function validateBoolean(value, key, fallback) {
|
|
|
38
67
|
}
|
|
39
68
|
return value;
|
|
40
69
|
}
|
|
41
|
-
|
|
70
|
+
/** Shared by `options.languages` and `options.graphviz.languages`; `key` names the one in play. */
|
|
71
|
+
function validateLanguages(value, key, fallback) {
|
|
42
72
|
if (value === undefined)
|
|
43
|
-
return [...
|
|
73
|
+
return [...fallback];
|
|
44
74
|
if (!Array.isArray(value)) {
|
|
45
|
-
fail(`options
|
|
75
|
+
fail(`options.${key} must be an array of strings, received ${JSON.stringify(value)}.`);
|
|
46
76
|
}
|
|
47
77
|
if (value.length === 0) {
|
|
48
|
-
fail(
|
|
78
|
+
fail(`options.${key} must contain at least one language.`);
|
|
49
79
|
}
|
|
50
80
|
const normalized = value.map((entry, index) => {
|
|
51
81
|
if (typeof entry !== 'string' || entry.trim() === '') {
|
|
52
|
-
fail(`options
|
|
82
|
+
fail(`options.${key}[${index}] must be a non-empty string, received ${JSON.stringify(entry)}.`);
|
|
53
83
|
}
|
|
54
84
|
return entry.trim().toLowerCase();
|
|
55
85
|
});
|
|
56
86
|
const duplicates = normalized.filter((entry, index) => normalized.indexOf(entry) !== index);
|
|
57
87
|
if (duplicates.length > 0) {
|
|
58
|
-
fail(`options
|
|
88
|
+
fail(`options.${key} contains duplicate entries: ${quoteList([...new Set(duplicates)])}.`);
|
|
59
89
|
}
|
|
60
90
|
return normalized;
|
|
61
91
|
}
|
|
92
|
+
function validateMaxSourceBytes(value) {
|
|
93
|
+
if (value === undefined)
|
|
94
|
+
return DEFAULT_GRAPHVIZ_OPTIONS.maxSourceBytes;
|
|
95
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value < 1) {
|
|
96
|
+
fail('options.graphviz.maxSourceBytes must be a positive integer, ' +
|
|
97
|
+
`received ${JSON.stringify(value)}.`);
|
|
98
|
+
}
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Validates the nested `graphviz` group.
|
|
103
|
+
*
|
|
104
|
+
* Unknown keys are rejected here too: a typo nested one level deep is exactly as silent — and
|
|
105
|
+
* exactly as confusing — as a typo at the top level.
|
|
106
|
+
*/
|
|
107
|
+
function validateGraphviz(value) {
|
|
108
|
+
if (value === undefined) {
|
|
109
|
+
return { ...DEFAULT_GRAPHVIZ_OPTIONS, languages: [...DEFAULT_GRAPHVIZ_OPTIONS.languages] };
|
|
110
|
+
}
|
|
111
|
+
if (!isPlainObject(value)) {
|
|
112
|
+
fail(`options.graphviz must be an object, received ${JSON.stringify(value)}.`);
|
|
113
|
+
}
|
|
114
|
+
const allowedKeys = [
|
|
115
|
+
'enabled',
|
|
116
|
+
'languages',
|
|
117
|
+
'engine',
|
|
118
|
+
'allowEngineOverride',
|
|
119
|
+
'maxSourceBytes',
|
|
120
|
+
'transparentBackground',
|
|
121
|
+
];
|
|
122
|
+
const unknownKeys = Object.keys(value).filter((key) => !allowedKeys.includes(key));
|
|
123
|
+
if (unknownKeys.length > 0) {
|
|
124
|
+
fail(`Unknown option${unknownKeys.length > 1 ? 's' : ''} ` +
|
|
125
|
+
`${quoteList(unknownKeys.map((key) => `graphviz.${key}`))}. ` +
|
|
126
|
+
`Supported options: ${quoteList(allowedKeys.map((key) => `graphviz.${key}`))}.`);
|
|
127
|
+
}
|
|
128
|
+
const { engine } = value;
|
|
129
|
+
if (engine !== undefined && !GRAPHVIZ_ENGINES.includes(engine)) {
|
|
130
|
+
fail(`options.graphviz.engine must be one of ${quoteList(GRAPHVIZ_ENGINES)}, ` +
|
|
131
|
+
`received ${JSON.stringify(engine)}.`);
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
enabled: validateBoolean(value.enabled, 'graphviz.enabled', DEFAULT_GRAPHVIZ_OPTIONS.enabled),
|
|
135
|
+
languages: validateLanguages(value.languages, 'graphviz.languages', DEFAULT_GRAPHVIZ_OPTIONS.languages),
|
|
136
|
+
engine: engine ?? DEFAULT_GRAPHVIZ_OPTIONS.engine,
|
|
137
|
+
allowEngineOverride: validateBoolean(value.allowEngineOverride, 'graphviz.allowEngineOverride', DEFAULT_GRAPHVIZ_OPTIONS.allowEngineOverride),
|
|
138
|
+
maxSourceBytes: validateMaxSourceBytes(value.maxSourceBytes),
|
|
139
|
+
transparentBackground: validateBoolean(value.transparentBackground, 'graphviz.transparentBackground', DEFAULT_GRAPHVIZ_OPTIONS.transparentBackground),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
62
142
|
function validateRenderTimeout(value) {
|
|
63
143
|
if (value === undefined)
|
|
64
144
|
return DEFAULT_OPTIONS.renderTimeoutMs;
|
|
@@ -90,7 +170,11 @@ function validateCacheMaxEntries(value) {
|
|
|
90
170
|
*/
|
|
91
171
|
export function resolveOptions(rawOptions) {
|
|
92
172
|
if (rawOptions === undefined || rawOptions === null) {
|
|
93
|
-
return {
|
|
173
|
+
return {
|
|
174
|
+
...DEFAULT_OPTIONS,
|
|
175
|
+
languages: [...DEFAULT_OPTIONS.languages],
|
|
176
|
+
graphviz: { ...DEFAULT_GRAPHVIZ_OPTIONS, languages: [...DEFAULT_GRAPHVIZ_OPTIONS.languages] },
|
|
177
|
+
};
|
|
94
178
|
}
|
|
95
179
|
if (!isPlainObject(rawOptions)) {
|
|
96
180
|
fail(`options must be an object, received ${JSON.stringify(rawOptions)}.`);
|
|
@@ -105,6 +189,7 @@ export function resolveOptions(rawOptions) {
|
|
|
105
189
|
'renderTimeoutMs',
|
|
106
190
|
'cacheMaxEntries',
|
|
107
191
|
'zoom',
|
|
192
|
+
'graphviz',
|
|
108
193
|
// Docusaurus injects `id` for multi-instance plugins.
|
|
109
194
|
'id',
|
|
110
195
|
]);
|
|
@@ -120,8 +205,20 @@ export function resolveOptions(rawOptions) {
|
|
|
120
205
|
if (cache !== undefined && !CACHE_MODES.includes(cache)) {
|
|
121
206
|
fail(`options.cache must be one of ${quoteList(CACHE_MODES)}, received ${JSON.stringify(cache)}.`);
|
|
122
207
|
}
|
|
208
|
+
const languages = validateLanguages(rawOptions.languages, 'languages', DEFAULT_OPTIONS.languages);
|
|
209
|
+
const graphviz = validateGraphviz(rawOptions.graphviz);
|
|
210
|
+
// A fence has exactly one language, so a language claimed by both engines would make the
|
|
211
|
+
// rendered output depend on the order the wrapper happens to check them in. Reject it.
|
|
212
|
+
if (graphviz.enabled) {
|
|
213
|
+
const claimedTwice = graphviz.languages.filter((entry) => languages.includes(entry));
|
|
214
|
+
if (claimedTwice.length > 0) {
|
|
215
|
+
fail(`options.graphviz.languages and options.languages both claim ` +
|
|
216
|
+
`${quoteList(claimedTwice)}. A fence language can only belong to one engine.`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
123
219
|
return {
|
|
124
|
-
languages
|
|
220
|
+
languages,
|
|
221
|
+
graphviz,
|
|
125
222
|
theme: theme ?? DEFAULT_OPTIONS.theme,
|
|
126
223
|
lazy: validateBoolean(rawOptions.lazy, 'lazy', DEFAULT_OPTIONS.lazy),
|
|
127
224
|
cache: cache ?? DEFAULT_OPTIONS.cache,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PlantUmlCoreModule } from './types.js';
|
|
1
|
+
import type { PlantUmlCoreModule, VizInstance } from './types.js';
|
|
2
2
|
export interface LoadRuntimeOptions {
|
|
3
3
|
/** Absolute, `baseUrl`-prefixed directory URL containing the two runtime files. */
|
|
4
4
|
assetsBaseUrl: string;
|
|
@@ -10,6 +10,14 @@ export interface LoadRuntimeOptions {
|
|
|
10
10
|
*/
|
|
11
11
|
importModule?: (url: string) => Promise<unknown>;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Resolves a ready-to-use Graphviz instance, loading `viz-global.js` on first use.
|
|
15
|
+
*
|
|
16
|
+
* One instance serves every DOT diagram on the page: Viz.js documents that an instance renders
|
|
17
|
+
* multiple graphs, and a 200-render soak with syntax errors interleaved left it fully healthy,
|
|
18
|
+
* so there is no reason to pay the instantiation cost per diagram.
|
|
19
|
+
*/
|
|
20
|
+
export declare function loadVizRuntime(options: LoadRuntimeOptions): Promise<VizInstance>;
|
|
13
21
|
/**
|
|
14
22
|
* Resolves the PlantUML engine, loading it on first use.
|
|
15
23
|
*
|
|
@@ -17,7 +25,9 @@ export interface LoadRuntimeOptions {
|
|
|
17
25
|
* diagram can retry rather than inheriting a permanent failure.
|
|
18
26
|
*/
|
|
19
27
|
export declare function loadPlantUmlRuntime(options: LoadRuntimeOptions): Promise<PlantUmlCoreModule>;
|
|
20
|
-
/** Whether the runtime has been requested on this page.
|
|
28
|
+
/** Whether the PlantUML runtime has been requested on this page. For tests and diagnostics. */
|
|
21
29
|
export declare function isRuntimeRequested(): boolean;
|
|
22
|
-
/**
|
|
30
|
+
/** Whether the Graphviz runtime has been requested on this page. For tests and diagnostics. */
|
|
31
|
+
export declare function isVizRuntimeRequested(): boolean;
|
|
32
|
+
/** Test-only: forgets both cached runtimes and removes the injected script tag. */
|
|
23
33
|
export declare function resetRuntimeLoader(): void;
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { PLANTUML_MODULE_FILENAME, VIZ_SCRIPT_FILENAME } from '../constants.js';
|
|
2
2
|
import { PlantUmlError } from './errors.js';
|
|
3
3
|
/**
|
|
4
|
-
* Loads the
|
|
4
|
+
* Loads the browser runtimes, each at most once per page session.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* There are two, and they are loaded independently:
|
|
7
|
+
*
|
|
8
|
+
* - `viz-global.js` (~1.4 MB) is a classic script that installs Graphviz on `window.Viz`. It
|
|
9
|
+
* is what renders DOT fences, and PlantUML also needs it for its own layout.
|
|
10
|
+
* - `plantuml.js` (~6.8 MB) is an ES module that renders PlantUML fences. It requires
|
|
11
|
+
* `window.Viz` to already exist, so it builds on the first.
|
|
12
|
+
*
|
|
13
|
+
* The split matters: a page with only DOT diagrams must never pay for the 6.8 MB PlantUML
|
|
14
|
+
* engine. Both files are served from the Docusaurus origin under `baseUrl`, never from a CDN.
|
|
9
15
|
*/
|
|
10
16
|
const SCRIPT_MARKER = 'data-plantuml-runtime';
|
|
11
17
|
let cachedRuntime = null;
|
|
18
|
+
let cachedViz = null;
|
|
12
19
|
function isBrowser() {
|
|
13
20
|
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
14
21
|
}
|
|
@@ -32,7 +39,7 @@ function loadVizGlobal(url, timeoutMs) {
|
|
|
32
39
|
if (existing.dataset.plantumlRuntimeState === 'loaded')
|
|
33
40
|
return Promise.resolve();
|
|
34
41
|
if (existing.dataset.plantumlRuntimeState === 'error') {
|
|
35
|
-
return Promise.reject(new PlantUmlError('load', `Failed to load the
|
|
42
|
+
return Promise.reject(new PlantUmlError('load', `Failed to load the Graphviz runtime from ${url}.`));
|
|
36
43
|
}
|
|
37
44
|
return waitForScript(existing, url, timeoutMs);
|
|
38
45
|
}
|
|
@@ -63,7 +70,7 @@ function waitForScript(script, url, timeoutMs) {
|
|
|
63
70
|
};
|
|
64
71
|
const onError = () => {
|
|
65
72
|
script.dataset.plantumlRuntimeState = 'error';
|
|
66
|
-
finish(() => reject(new PlantUmlError('load', `Failed to load the
|
|
73
|
+
finish(() => reject(new PlantUmlError('load', `Failed to load the Graphviz runtime from ${url}. ` +
|
|
67
74
|
'Check that the asset is served under your Docusaurus baseUrl with a JavaScript MIME type.')));
|
|
68
75
|
};
|
|
69
76
|
const timer = setTimeout(() => {
|
|
@@ -83,6 +90,54 @@ function assertCoreModule(value, url) {
|
|
|
83
90
|
}
|
|
84
91
|
return candidate;
|
|
85
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Checks that the loaded script really installed the Viz.js API this plugin renders DOT with.
|
|
95
|
+
*
|
|
96
|
+
* `viz-global.js` reaches the browser via `@plantuml/core`, which ships it for PlantUML's own
|
|
97
|
+
* layout rather than for this plugin's benefit. Asserting the shape here means a future
|
|
98
|
+
* `@plantuml/core` that changed engines fails with a message naming the cause, instead of a
|
|
99
|
+
* `TypeError` on `Viz.instance` several frames away. See `docs/adr/0004-graphviz-engine-reuse.md`.
|
|
100
|
+
*/
|
|
101
|
+
function assertVizGlobal(url) {
|
|
102
|
+
const candidate = globalThis.Viz;
|
|
103
|
+
if (!candidate ||
|
|
104
|
+
typeof candidate.instance !== 'function' ||
|
|
105
|
+
!Array.isArray(candidate.engines) ||
|
|
106
|
+
typeof candidate.graphvizVersion !== 'string') {
|
|
107
|
+
throw new PlantUmlError('load', `The script at ${url} did not install the expected Graphviz (Viz.js) API on window.Viz. ` +
|
|
108
|
+
'This usually means a proxy or service worker returned the wrong file, or that the ' +
|
|
109
|
+
'installed @plantuml/core no longer bundles Viz.js.');
|
|
110
|
+
}
|
|
111
|
+
return candidate;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Resolves a ready-to-use Graphviz instance, loading `viz-global.js` on first use.
|
|
115
|
+
*
|
|
116
|
+
* One instance serves every DOT diagram on the page: Viz.js documents that an instance renders
|
|
117
|
+
* multiple graphs, and a 200-render soak with syntax errors interleaved left it fully healthy,
|
|
118
|
+
* so there is no reason to pay the instantiation cost per diagram.
|
|
119
|
+
*/
|
|
120
|
+
export function loadVizRuntime(options) {
|
|
121
|
+
if (!isBrowser()) {
|
|
122
|
+
return Promise.reject(new PlantUmlError('load', 'The Graphviz runtime cannot be loaded during server-side rendering.'));
|
|
123
|
+
}
|
|
124
|
+
if (cachedViz)
|
|
125
|
+
return cachedViz;
|
|
126
|
+
const vizUrl = joinUrl(options.assetsBaseUrl, VIZ_SCRIPT_FILENAME);
|
|
127
|
+
const viz = loadVizGlobal(vizUrl, options.timeoutMs)
|
|
128
|
+
.then(() => assertVizGlobal(vizUrl).instance())
|
|
129
|
+
.catch((error) => {
|
|
130
|
+
// Clearing the cache lets a later diagram retry rather than inherit a permanent failure.
|
|
131
|
+
cachedViz = null;
|
|
132
|
+
throw error instanceof PlantUmlError
|
|
133
|
+
? error
|
|
134
|
+
: new PlantUmlError('load', `Failed to initialize the Graphviz engine from ${vizUrl}.`, {
|
|
135
|
+
cause: error,
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
cachedViz = viz;
|
|
139
|
+
return viz;
|
|
140
|
+
}
|
|
86
141
|
/**
|
|
87
142
|
* Resolves the PlantUML engine, loading it on first use.
|
|
88
143
|
*
|
|
@@ -112,13 +167,18 @@ export function loadPlantUmlRuntime(options) {
|
|
|
112
167
|
cachedRuntime = runtime;
|
|
113
168
|
return runtime;
|
|
114
169
|
}
|
|
115
|
-
/** Whether the runtime has been requested on this page.
|
|
170
|
+
/** Whether the PlantUML runtime has been requested on this page. For tests and diagnostics. */
|
|
116
171
|
export function isRuntimeRequested() {
|
|
117
172
|
return cachedRuntime !== null;
|
|
118
173
|
}
|
|
119
|
-
/**
|
|
174
|
+
/** Whether the Graphviz runtime has been requested on this page. For tests and diagnostics. */
|
|
175
|
+
export function isVizRuntimeRequested() {
|
|
176
|
+
return cachedViz !== null;
|
|
177
|
+
}
|
|
178
|
+
/** Test-only: forgets both cached runtimes and removes the injected script tag. */
|
|
120
179
|
export function resetRuntimeLoader() {
|
|
121
180
|
cachedRuntime = null;
|
|
181
|
+
cachedViz = null;
|
|
122
182
|
if (typeof document !== 'undefined') {
|
|
123
183
|
document.querySelectorAll(`script[${SCRIPT_MARKER}]`).forEach((node) => node.remove());
|
|
124
184
|
}
|
package/dist/runtime/cache.d.ts
CHANGED
|
@@ -14,6 +14,27 @@ export interface DiagramCache {
|
|
|
14
14
|
size(): number;
|
|
15
15
|
}
|
|
16
16
|
export declare function computeCacheKey({ source, dark, sanitized, coreVersion }: CacheKeyInput): string;
|
|
17
|
+
export interface GraphvizCacheKeyInput {
|
|
18
|
+
source: string;
|
|
19
|
+
/** Graphviz layout engine; `dot` and `neato` draw different pictures from one source. */
|
|
20
|
+
layout: string;
|
|
21
|
+
sanitized: boolean;
|
|
22
|
+
transparentBackground: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* `@plantuml/core` version. The Graphviz build is bundled inside that package, so its
|
|
25
|
+
* version is what changes when the engine changes — and unlike `Viz.graphvizVersion` it is
|
|
26
|
+
* known before the engine has loaded, which is what lets a cache hit skip loading entirely.
|
|
27
|
+
*/
|
|
28
|
+
coreVersion: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Cache key for a Graphviz diagram.
|
|
32
|
+
*
|
|
33
|
+
* The colour mode is deliberately **absent**: Graphviz output is colour-mode independent and
|
|
34
|
+
* adapts through CSS rather than through a re-render, so one entry serves both modes. The
|
|
35
|
+
* `graphviz|` prefix keeps these entries in a separate namespace from PlantUML's.
|
|
36
|
+
*/
|
|
37
|
+
export declare function computeGraphvizCacheKey({ source, layout, sanitized, transparentBackground, coreVersion, }: GraphvizCacheKeyInput): string;
|
|
17
38
|
export declare function createDiagramCache(mode: CacheMode, maxEntries: number): DiagramCache;
|
|
18
39
|
export declare function getSharedCache(mode: CacheMode, maxEntries: number): DiagramCache;
|
|
19
40
|
/** Test-only: drops every shared cache instance. */
|