docusaurus-plugin-openapi-docs 0.0.0-1089 → 0.0.0-1092
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/README.md +47 -24
- package/lib/index.js +48 -2
- package/lib/markdown/externalizeJsonProps.d.ts +31 -0
- package/lib/markdown/externalizeJsonProps.js +149 -0
- package/lib/options.js +1 -0
- package/lib/types.d.ts +7 -0
- package/package.json +2 -2
- package/src/index.ts +73 -2
- package/src/markdown/externalizeJsonProps.ts +201 -0
- package/src/options.ts +1 -0
- package/src/types.ts +7 -0
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ Key Features:
|
|
|
41
41
|
|
|
42
42
|
| Docusaurus OpenAPI Docs | Docusaurus |
|
|
43
43
|
| ----------------------- | --------------- |
|
|
44
|
-
| 4.
|
|
44
|
+
| 4.x.x (current) | `3.5.0 - 3.9.2` |
|
|
45
45
|
| 3.0.x (end-of-support) | `3.0.1 - 3.4.0` |
|
|
46
46
|
| 2.2.3 (legacy) | `2.4.1 - 2.4.3` |
|
|
47
47
|
| 1.7.3 (end-of-support) | `2.0.1 - 2.2.0` |
|
|
@@ -51,7 +51,7 @@ Key Features:
|
|
|
51
51
|
Run the following to bootstrap a Docusaurus v3 site (classic theme) with `docusaurus-openapi-docs`:
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
-
npx create-docusaurus@3.
|
|
54
|
+
npx create-docusaurus@3.9.2 my-website --package-manager yarn
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
> When prompted to select a template choose `Git repository`.
|
|
@@ -156,28 +156,29 @@ The `docusaurus-plugin-openapi-docs` plugin can be configured with the following
|
|
|
156
156
|
|
|
157
157
|
`config` can be configured with the following options:
|
|
158
158
|
|
|
159
|
-
| Name | Type | Default | Description
|
|
160
|
-
| -------------------- | --------- | ------- |
|
|
161
|
-
| `specPath` | `string` | `null` | Designated URL or path to the source of an OpenAPI specification file or directory of multiple OpenAPI specification files.
|
|
162
|
-
| `outputDir` | `string` | `null` | Desired output path for generated MDX and sidebar files.
|
|
163
|
-
| `proxy` | `string` | `null` | _Optional:_ Proxy URL to prepend to base URL when performing API requests from browser. Overrides site-wide `themeConfig.api.proxy` if set.
|
|
164
|
-
| `template` | `string` | `null` | _Optional:_ Customize MDX content with a desired template.
|
|
165
|
-
| `infoTemplate` | `string` | `null` | _Optional:_ Customize MDX content for **info** pages only.
|
|
166
|
-
| `tagTemplate` | `string` | `null` | _Optional:_ Customize MDX content for **tag** pages only.
|
|
167
|
-
| `schemaTemplate` | `string` | `null` | _Optional:_ Customize MDX content for **schema** pages only.
|
|
168
|
-
| `downloadUrl` | `string` | `null` | _Optional:_ Designated URL for downloading OpenAPI specification. (requires `info` section/doc)
|
|
169
|
-
| `hideSendButton` | `boolean` | `null` | _Optional:_ If set to `true`, hides the “Send API Request” button in the API demo panel.
|
|
170
|
-
| `showExtensions` | `boolean` | `null` | _Optional:_ If set to `true`, renders operation‑level vendor‑extensions in descriptions.
|
|
171
|
-
| `maskCredentials` | `boolean` | `true` | _Optional:_ If set to `false`, disables credential masking in generated code snippets. By default, credentials are masked for security.
|
|
172
|
-
| `sidebarOptions` | `object` | `null` | _Optional:_ Set of options for sidebar configuration. See below for a list of supported options.
|
|
173
|
-
| `version` | `string` | `null` | _Optional:_ Version assigned to a single or micro‑spec API specified in `specPath`.
|
|
174
|
-
| `label` | `string` | `null` | _Optional:_ Version label used when generating the version‑selector dropdown menu.
|
|
175
|
-
| `baseUrl` | `string` | `null` | _Optional:_ Base URL for versioned docs in the version‑selector dropdown.
|
|
176
|
-
| `versions` | `object` | `null` | _Optional:_ Options for versioning configuration. See below for a list of supported options.
|
|
177
|
-
| `markdownGenerators` | `object` | `null` | _Optional:_ Customize MDX content via generator functions. See below for a list of supported options.
|
|
178
|
-
| `showSchemas` | `boolean` | `null` | _Optional:_ If set to `true`, generates standalone schema pages and adds them to the sidebar.
|
|
179
|
-
| `showInfoPage` | `boolean` | `true` | _Optional:_ If set to `false`, disables generation of the info page (overview page with API title and description).
|
|
180
|
-
| `schemasOnly` | `boolean` | `false` | _Optional:_ If set to `true`, generates only schema pages (no API endpoint pages). Also available as `--schemas-only` CLI flag.
|
|
159
|
+
| Name | Type | Default | Description |
|
|
160
|
+
| -------------------- | --------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
161
|
+
| `specPath` | `string` | `null` | Designated URL or path to the source of an OpenAPI specification file or directory of multiple OpenAPI specification files. |
|
|
162
|
+
| `outputDir` | `string` | `null` | Desired output path for generated MDX and sidebar files. |
|
|
163
|
+
| `proxy` | `string` | `null` | _Optional:_ Proxy URL to prepend to base URL when performing API requests from browser. Overrides site-wide `themeConfig.api.proxy` if set. |
|
|
164
|
+
| `template` | `string` | `null` | _Optional:_ Customize MDX content with a desired template. |
|
|
165
|
+
| `infoTemplate` | `string` | `null` | _Optional:_ Customize MDX content for **info** pages only. |
|
|
166
|
+
| `tagTemplate` | `string` | `null` | _Optional:_ Customize MDX content for **tag** pages only. |
|
|
167
|
+
| `schemaTemplate` | `string` | `null` | _Optional:_ Customize MDX content for **schema** pages only. |
|
|
168
|
+
| `downloadUrl` | `string` | `null` | _Optional:_ Designated URL for downloading OpenAPI specification. (requires `info` section/doc) |
|
|
169
|
+
| `hideSendButton` | `boolean` | `null` | _Optional:_ If set to `true`, hides the “Send API Request” button in the API demo panel. |
|
|
170
|
+
| `showExtensions` | `boolean` | `null` | _Optional:_ If set to `true`, renders operation‑level vendor‑extensions in descriptions. |
|
|
171
|
+
| `maskCredentials` | `boolean` | `true` | _Optional:_ If set to `false`, disables credential masking in generated code snippets. By default, credentials are masked for security. |
|
|
172
|
+
| `sidebarOptions` | `object` | `null` | _Optional:_ Set of options for sidebar configuration. See below for a list of supported options. |
|
|
173
|
+
| `version` | `string` | `null` | _Optional:_ Version assigned to a single or micro‑spec API specified in `specPath`. |
|
|
174
|
+
| `label` | `string` | `null` | _Optional:_ Version label used when generating the version‑selector dropdown menu. |
|
|
175
|
+
| `baseUrl` | `string` | `null` | _Optional:_ Base URL for versioned docs in the version‑selector dropdown. |
|
|
176
|
+
| `versions` | `object` | `null` | _Optional:_ Options for versioning configuration. See below for a list of supported options. |
|
|
177
|
+
| `markdownGenerators` | `object` | `null` | _Optional:_ Customize MDX content via generator functions. See below for a list of supported options. |
|
|
178
|
+
| `showSchemas` | `boolean` | `null` | _Optional:_ If set to `true`, generates standalone schema pages and adds them to the sidebar. |
|
|
179
|
+
| `showInfoPage` | `boolean` | `true` | _Optional:_ If set to `false`, disables generation of the info page (overview page with API title and description). |
|
|
180
|
+
| `schemasOnly` | `boolean` | `false` | _Optional:_ If set to `true`, generates only schema pages (no API endpoint pages). Also available as `--schemas-only` CLI flag. |
|
|
181
|
+
| `externalJsonProps` | `boolean` | `true` | _Optional:_ If set to `false`, disables externalization of large JSON props. By default, large JSON is written to external files for better build performance. |
|
|
181
182
|
|
|
182
183
|
### sidebarOptions
|
|
183
184
|
|
|
@@ -226,6 +227,28 @@ The `docusaurus-plugin-openapi-docs` plugin can be configured with the following
|
|
|
226
227
|
| --------------- | ---------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
227
228
|
| `createDocItem` | `function` | `null` | Optional: Returns a `SidebarItemDoc` object containing metadata for a sidebar item.<br/><br/>**Function type:** `(item: ApiPageMetadata \| SchemaPageMetadata, context: { sidebarOptions: SidebarOptions; basePath: string }) => SidebarItemDoc` |
|
|
228
229
|
|
|
230
|
+
### Performance Optimization
|
|
231
|
+
|
|
232
|
+
By default, `externalJsonProps` is enabled to optimize Docusaurus build times. This externalizes large JSON objects to separate files, significantly improving build performance for large OpenAPI specs.
|
|
233
|
+
|
|
234
|
+
**How it works:**
|
|
235
|
+
|
|
236
|
+
- With `externalJsonProps: true` (default): Large JSON props are written to separate `.json` files and loaded via `require()`. This bypasses MDX AST processing entirely, allowing the bundler to handle JSON more efficiently.
|
|
237
|
+
|
|
238
|
+
- With `externalJsonProps: false`: Large JSON objects (responses, request bodies, parameters) are embedded directly in the MDX. The MDX compiler must parse these into an AST and serialize them back, which can be slow for deeply nested schemas.
|
|
239
|
+
|
|
240
|
+
**When to disable:**
|
|
241
|
+
|
|
242
|
+
You may set `externalJsonProps: false` if you have custom tooling that depends on parsing the MDX files directly:
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
petstore: {
|
|
246
|
+
specPath: "examples/petstore.yaml",
|
|
247
|
+
outputDir: "docs/petstore",
|
|
248
|
+
externalJsonProps: false, // Disable if needed for custom tooling
|
|
249
|
+
} satisfies OpenApiPlugin.Options,
|
|
250
|
+
```
|
|
251
|
+
|
|
229
252
|
## Theme Configuration Options
|
|
230
253
|
|
|
231
254
|
The `docusaurus-theme-openapi-docs` theme can be configured with the following options in `themeConfig.api`:
|
package/lib/index.js
CHANGED
|
@@ -20,6 +20,7 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
20
20
|
const json5_1 = __importDefault(require("json5"));
|
|
21
21
|
const mustache_1 = require("mustache");
|
|
22
22
|
const markdown_1 = require("./markdown");
|
|
23
|
+
const externalizeJsonProps_1 = require("./markdown/externalizeJsonProps");
|
|
23
24
|
const openapi_1 = require("./openapi");
|
|
24
25
|
const options_1 = require("./options");
|
|
25
26
|
const sidebars_1 = __importDefault(require("./sidebars"));
|
|
@@ -281,7 +282,23 @@ custom_edit_url: null
|
|
|
281
282
|
if (item.id.length === 0) {
|
|
282
283
|
throw Error("Operation must have summary or operationId defined");
|
|
283
284
|
}
|
|
284
|
-
|
|
285
|
+
let finalView = view;
|
|
286
|
+
let jsonFilesToWrite = [];
|
|
287
|
+
// Externalize large JSON props if enabled
|
|
288
|
+
if (options.externalJsonProps) {
|
|
289
|
+
const result = (0, externalizeJsonProps_1.externalizeJsonPropsSimple)(view, item.id);
|
|
290
|
+
finalView = result.mdx;
|
|
291
|
+
jsonFilesToWrite = result.jsonFiles;
|
|
292
|
+
// Write JSON files
|
|
293
|
+
for (const jsonFile of jsonFilesToWrite) {
|
|
294
|
+
const jsonPath = `${outputDir}/${jsonFile.filename}`;
|
|
295
|
+
if (!fs_1.default.existsSync(jsonPath)) {
|
|
296
|
+
fs_1.default.writeFileSync(jsonPath, jsonFile.content, "utf8");
|
|
297
|
+
console.log(chalk_1.default.green(`Successfully created "${jsonPath}"`));
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
fs_1.default.writeFileSync(`${outputDir}/${item.id}.api.mdx`, finalView, "utf8");
|
|
285
302
|
console.log(chalk_1.default.green(`Successfully created "${outputDir}/${item.id}.api.mdx"`));
|
|
286
303
|
}
|
|
287
304
|
catch (err) {
|
|
@@ -330,7 +347,22 @@ custom_edit_url: null
|
|
|
330
347
|
throw Error("Schema must have title defined");
|
|
331
348
|
}
|
|
332
349
|
// eslint-disable-next-line testing-library/render-result-naming-convention
|
|
333
|
-
|
|
350
|
+
let schemaView = (0, mustache_1.render)(schemaMdTemplate, item);
|
|
351
|
+
let jsonFilesToWrite = [];
|
|
352
|
+
// Externalize large JSON props if enabled
|
|
353
|
+
if (options.externalJsonProps) {
|
|
354
|
+
const result = (0, externalizeJsonProps_1.externalizeJsonPropsSimple)(schemaView, item.id);
|
|
355
|
+
schemaView = result.mdx;
|
|
356
|
+
jsonFilesToWrite = result.jsonFiles;
|
|
357
|
+
// Write JSON files in schemas directory
|
|
358
|
+
for (const jsonFile of jsonFilesToWrite) {
|
|
359
|
+
const jsonPath = `${outputDir}/schemas/${jsonFile.filename}`;
|
|
360
|
+
if (!fs_1.default.existsSync(jsonPath)) {
|
|
361
|
+
fs_1.default.writeFileSync(jsonPath, jsonFile.content, "utf8");
|
|
362
|
+
console.log(chalk_1.default.green(`Successfully created "${jsonPath}"`));
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
334
366
|
fs_1.default.writeFileSync(`${outputDir}/schemas/${item.id}.schema.mdx`, schemaView, "utf8");
|
|
335
367
|
console.log(chalk_1.default.green(`Successfully created "${outputDir}/${item.id}.schema.mdx"`));
|
|
336
368
|
}
|
|
@@ -361,6 +393,11 @@ custom_edit_url: null
|
|
|
361
393
|
cwd: path_1.default.resolve(apiDir),
|
|
362
394
|
deep: 1,
|
|
363
395
|
});
|
|
396
|
+
// Clean up externalized JSON files
|
|
397
|
+
const jsonFiles = await (0, utils_1.Globby)(["*.json", "!versions.json"], {
|
|
398
|
+
cwd: path_1.default.resolve(apiDir),
|
|
399
|
+
deep: 1,
|
|
400
|
+
});
|
|
364
401
|
apiMdxFiles.map((mdx) => fs_1.default.unlink(`${apiDir}/${mdx}`, (err) => {
|
|
365
402
|
if (err) {
|
|
366
403
|
console.error(chalk_1.default.red(`Cleanup failed for "${apiDir}/${mdx}"`), chalk_1.default.yellow(err));
|
|
@@ -377,6 +414,15 @@ custom_edit_url: null
|
|
|
377
414
|
console.log(chalk_1.default.green(`Cleanup succeeded for "${apiDir}/${sidebar}"`));
|
|
378
415
|
}
|
|
379
416
|
}));
|
|
417
|
+
// Clean up externalized JSON files
|
|
418
|
+
jsonFiles.map((jsonFile) => fs_1.default.unlink(`${apiDir}/${jsonFile}`, (err) => {
|
|
419
|
+
if (err) {
|
|
420
|
+
console.error(chalk_1.default.red(`Cleanup failed for "${apiDir}/${jsonFile}"`), chalk_1.default.yellow(err));
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
console.log(chalk_1.default.green(`Cleanup succeeded for "${apiDir}/${jsonFile}"`));
|
|
424
|
+
}
|
|
425
|
+
}));
|
|
380
426
|
}
|
|
381
427
|
try {
|
|
382
428
|
fs_1.default.rmSync(`${apiDir}/schemas`, { recursive: true });
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This module provides utilities for externalizing large JSON props in MDX files.
|
|
3
|
+
*
|
|
4
|
+
* The motivation is to improve MDX compilation performance. When large JSON objects
|
|
5
|
+
* are embedded directly as JSX props, MDX needs to parse them into AST and then
|
|
6
|
+
* serialize them back. By externalizing to JSON files and using require(), the
|
|
7
|
+
* MDX compiler can skip this expensive processing.
|
|
8
|
+
*
|
|
9
|
+
* @see https://github.com/facebook/docusaurus/discussions/11664
|
|
10
|
+
*/
|
|
11
|
+
export interface ExternalizedJsonFile {
|
|
12
|
+
/** The filename for the JSON file (without path) */
|
|
13
|
+
filename: string;
|
|
14
|
+
/** The JSON content to write */
|
|
15
|
+
content: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ExternalizeResult {
|
|
18
|
+
/** The transformed MDX content with require() statements */
|
|
19
|
+
mdx: string;
|
|
20
|
+
/** List of JSON files that need to be written */
|
|
21
|
+
jsonFiles: ExternalizedJsonFile[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Externalizes large JSON props from MDX content.
|
|
25
|
+
* Uses brace-counting to correctly handle deeply nested JSON.
|
|
26
|
+
*
|
|
27
|
+
* @param mdx - The original MDX content
|
|
28
|
+
* @param baseFilename - The base filename (without extension) for the MDX file
|
|
29
|
+
* @returns The transformed MDX and list of JSON files to write
|
|
30
|
+
*/
|
|
31
|
+
export declare function externalizeJsonPropsSimple(mdx: string, baseFilename: string): ExternalizeResult;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* ============================================================================
|
|
3
|
+
* Copyright (c) Palo Alto Networks
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
* ========================================================================== */
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.externalizeJsonPropsSimple = externalizeJsonPropsSimple;
|
|
10
|
+
/**
|
|
11
|
+
* Components and their props that should be externalized.
|
|
12
|
+
* These are the components that typically receive large JSON objects.
|
|
13
|
+
*/
|
|
14
|
+
const COMPONENTS_TO_EXTERNALIZE = [
|
|
15
|
+
{ component: "StatusCodes", prop: "responses" },
|
|
16
|
+
{ component: "ParamsDetails", prop: "parameters" },
|
|
17
|
+
{ component: "RequestSchema", prop: "body" },
|
|
18
|
+
{ component: "Schema", prop: "schema" },
|
|
19
|
+
{ component: "SchemaItem", prop: "schema" },
|
|
20
|
+
];
|
|
21
|
+
/**
|
|
22
|
+
* Minimum size (in characters) for a JSON prop to be externalized.
|
|
23
|
+
* Props smaller than this threshold will remain inline.
|
|
24
|
+
*/
|
|
25
|
+
const MIN_SIZE_THRESHOLD = 500;
|
|
26
|
+
/**
|
|
27
|
+
* Extracts the content between balanced braces starting at a given position.
|
|
28
|
+
* Returns the content (without outer braces) and the end position.
|
|
29
|
+
*/
|
|
30
|
+
function extractBalancedBraces(str, startIndex) {
|
|
31
|
+
if (str[startIndex] !== "{") {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
let depth = 0;
|
|
35
|
+
let inString = false;
|
|
36
|
+
let stringChar = "";
|
|
37
|
+
let escaped = false;
|
|
38
|
+
for (let i = startIndex; i < str.length; i++) {
|
|
39
|
+
const char = str[i];
|
|
40
|
+
if (escaped) {
|
|
41
|
+
escaped = false;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (char === "\\") {
|
|
45
|
+
escaped = true;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (!inString) {
|
|
49
|
+
if (char === '"' || char === "'") {
|
|
50
|
+
inString = true;
|
|
51
|
+
stringChar = char;
|
|
52
|
+
}
|
|
53
|
+
else if (char === "{") {
|
|
54
|
+
depth++;
|
|
55
|
+
}
|
|
56
|
+
else if (char === "}") {
|
|
57
|
+
depth--;
|
|
58
|
+
if (depth === 0) {
|
|
59
|
+
// Found the matching closing brace
|
|
60
|
+
return {
|
|
61
|
+
content: str.substring(startIndex + 1, i),
|
|
62
|
+
endIndex: i,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
if (char === stringChar) {
|
|
69
|
+
inString = false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return null; // Unbalanced braces
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Externalizes large JSON props from MDX content.
|
|
77
|
+
* Uses brace-counting to correctly handle deeply nested JSON.
|
|
78
|
+
*
|
|
79
|
+
* @param mdx - The original MDX content
|
|
80
|
+
* @param baseFilename - The base filename (without extension) for the MDX file
|
|
81
|
+
* @returns The transformed MDX and list of JSON files to write
|
|
82
|
+
*/
|
|
83
|
+
function externalizeJsonPropsSimple(mdx, baseFilename) {
|
|
84
|
+
const jsonFiles = [];
|
|
85
|
+
let transformedMdx = mdx;
|
|
86
|
+
for (const { component, prop } of COMPONENTS_TO_EXTERNALIZE) {
|
|
87
|
+
// Find pattern: prop={
|
|
88
|
+
const propPattern = new RegExp(`${prop}=\\{`, "g");
|
|
89
|
+
let match;
|
|
90
|
+
// Keep searching until no more matches (need to restart after each replacement)
|
|
91
|
+
let searchStart = 0;
|
|
92
|
+
while (true) {
|
|
93
|
+
propPattern.lastIndex = searchStart;
|
|
94
|
+
match = propPattern.exec(transformedMdx);
|
|
95
|
+
if (!match)
|
|
96
|
+
break;
|
|
97
|
+
const propStart = match.index;
|
|
98
|
+
const braceStart = propStart + prop.length + 1; // Position of '{'
|
|
99
|
+
// Extract the balanced content
|
|
100
|
+
const extracted = extractBalancedBraces(transformedMdx, braceStart);
|
|
101
|
+
if (!extracted) {
|
|
102
|
+
searchStart = braceStart + 1;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const jsonContent = extracted.content;
|
|
106
|
+
const propEnd = extracted.endIndex + 1; // Position after '}'
|
|
107
|
+
// Skip small or non-JSON content
|
|
108
|
+
const trimmed = jsonContent.trim();
|
|
109
|
+
if (jsonContent.length < MIN_SIZE_THRESHOLD ||
|
|
110
|
+
trimmed === "undefined" ||
|
|
111
|
+
trimmed === "null" ||
|
|
112
|
+
trimmed === "true" ||
|
|
113
|
+
trimmed === "false" ||
|
|
114
|
+
trimmed.startsWith("require(")) {
|
|
115
|
+
searchStart = propEnd;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
// Check if this is within the target component
|
|
119
|
+
// Look backwards for the component tag
|
|
120
|
+
const beforeProp = transformedMdx.substring(0, propStart);
|
|
121
|
+
const componentTagPattern = new RegExp(`<${component}[\\s\\S]*$`);
|
|
122
|
+
if (!componentTagPattern.test(beforeProp)) {
|
|
123
|
+
searchStart = propEnd;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
// Generate filename
|
|
127
|
+
const existingCount = jsonFiles.filter((f) => f.filename.includes(`.${prop}`)).length;
|
|
128
|
+
const suffix = existingCount > 0 ? `.${existingCount + 1}` : "";
|
|
129
|
+
const jsonFilename = `${baseFilename}.${prop}${suffix}.json`;
|
|
130
|
+
// Store the JSON file
|
|
131
|
+
jsonFiles.push({
|
|
132
|
+
filename: jsonFilename,
|
|
133
|
+
content: jsonContent.trim(),
|
|
134
|
+
});
|
|
135
|
+
// Replace the prop value with require()
|
|
136
|
+
const newProp = `${prop}={require("./${jsonFilename}")}`;
|
|
137
|
+
transformedMdx =
|
|
138
|
+
transformedMdx.substring(0, propStart) +
|
|
139
|
+
newProp +
|
|
140
|
+
transformedMdx.substring(propEnd);
|
|
141
|
+
// Continue searching after the replacement
|
|
142
|
+
searchStart = propStart + newProp.length;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
mdx: transformedMdx,
|
|
147
|
+
jsonFiles,
|
|
148
|
+
};
|
|
149
|
+
}
|
package/lib/options.js
CHANGED
|
@@ -48,6 +48,7 @@ exports.OptionsSchema = utils_validation_1.Joi.object({
|
|
|
48
48
|
schemasOnly: utils_validation_1.Joi.boolean(),
|
|
49
49
|
disableCompression: utils_validation_1.Joi.boolean(),
|
|
50
50
|
maskCredentials: utils_validation_1.Joi.boolean(),
|
|
51
|
+
externalJsonProps: utils_validation_1.Joi.boolean().default(true),
|
|
51
52
|
version: utils_validation_1.Joi.string().when("versions", {
|
|
52
53
|
is: utils_validation_1.Joi.exist(),
|
|
53
54
|
then: utils_validation_1.Joi.required(),
|
package/lib/types.d.ts
CHANGED
|
@@ -33,6 +33,13 @@ export interface APIOptions {
|
|
|
33
33
|
schemasOnly?: boolean;
|
|
34
34
|
disableCompression?: boolean;
|
|
35
35
|
maskCredentials?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* When enabled, large JSON props in generated MDX are written to external
|
|
38
|
+
* files and loaded via require(). This can significantly improve MDX
|
|
39
|
+
* compilation performance for large OpenAPI specs.
|
|
40
|
+
* @see https://github.com/facebook/docusaurus/discussions/11664
|
|
41
|
+
*/
|
|
42
|
+
externalJsonProps?: boolean;
|
|
36
43
|
}
|
|
37
44
|
export interface MarkdownGenerator {
|
|
38
45
|
createApiPageMD?: (pageData: ApiPageMetadata) => string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "docusaurus-plugin-openapi-docs",
|
|
3
3
|
"description": "OpenAPI plugin for Docusaurus.",
|
|
4
|
-
"version": "0.0.0-
|
|
4
|
+
"version": "0.0.0-1092",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"openapi",
|
|
@@ -65,5 +65,5 @@
|
|
|
65
65
|
"engines": {
|
|
66
66
|
"node": ">=14"
|
|
67
67
|
},
|
|
68
|
-
"gitHead": "
|
|
68
|
+
"gitHead": "52285283e2fc0ee02b7f5e42a9678a3bb89c095b"
|
|
69
69
|
}
|
package/src/index.ts
CHANGED
|
@@ -21,6 +21,10 @@ import {
|
|
|
21
21
|
createSchemaPageMD,
|
|
22
22
|
createTagPageMD,
|
|
23
23
|
} from "./markdown";
|
|
24
|
+
import {
|
|
25
|
+
externalizeJsonPropsSimple,
|
|
26
|
+
ExternalizedJsonFile,
|
|
27
|
+
} from "./markdown/externalizeJsonProps";
|
|
24
28
|
import { processOpenapiFiles, readOpenapiFiles } from "./openapi";
|
|
25
29
|
import { OptionsSchema } from "./options";
|
|
26
30
|
import generateSidebarSlice from "./sidebars";
|
|
@@ -383,7 +387,33 @@ custom_edit_url: null
|
|
|
383
387
|
"Operation must have summary or operationId defined"
|
|
384
388
|
);
|
|
385
389
|
}
|
|
386
|
-
|
|
390
|
+
|
|
391
|
+
let finalView = view;
|
|
392
|
+
let jsonFilesToWrite: ExternalizedJsonFile[] = [];
|
|
393
|
+
|
|
394
|
+
// Externalize large JSON props if enabled
|
|
395
|
+
if (options.externalJsonProps) {
|
|
396
|
+
const result = externalizeJsonPropsSimple(view, item.id);
|
|
397
|
+
finalView = result.mdx;
|
|
398
|
+
jsonFilesToWrite = result.jsonFiles;
|
|
399
|
+
|
|
400
|
+
// Write JSON files
|
|
401
|
+
for (const jsonFile of jsonFilesToWrite) {
|
|
402
|
+
const jsonPath = `${outputDir}/${jsonFile.filename}`;
|
|
403
|
+
if (!fs.existsSync(jsonPath)) {
|
|
404
|
+
fs.writeFileSync(jsonPath, jsonFile.content, "utf8");
|
|
405
|
+
console.log(
|
|
406
|
+
chalk.green(`Successfully created "${jsonPath}"`)
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
fs.writeFileSync(
|
|
413
|
+
`${outputDir}/${item.id}.api.mdx`,
|
|
414
|
+
finalView,
|
|
415
|
+
"utf8"
|
|
416
|
+
);
|
|
387
417
|
console.log(
|
|
388
418
|
chalk.green(
|
|
389
419
|
`Successfully created "${outputDir}/${item.id}.api.mdx"`
|
|
@@ -469,7 +499,27 @@ custom_edit_url: null
|
|
|
469
499
|
throw Error("Schema must have title defined");
|
|
470
500
|
}
|
|
471
501
|
// eslint-disable-next-line testing-library/render-result-naming-convention
|
|
472
|
-
|
|
502
|
+
let schemaView = render(schemaMdTemplate, item);
|
|
503
|
+
let jsonFilesToWrite: ExternalizedJsonFile[] = [];
|
|
504
|
+
|
|
505
|
+
// Externalize large JSON props if enabled
|
|
506
|
+
if (options.externalJsonProps) {
|
|
507
|
+
const result = externalizeJsonPropsSimple(schemaView, item.id);
|
|
508
|
+
schemaView = result.mdx;
|
|
509
|
+
jsonFilesToWrite = result.jsonFiles;
|
|
510
|
+
|
|
511
|
+
// Write JSON files in schemas directory
|
|
512
|
+
for (const jsonFile of jsonFilesToWrite) {
|
|
513
|
+
const jsonPath = `${outputDir}/schemas/${jsonFile.filename}`;
|
|
514
|
+
if (!fs.existsSync(jsonPath)) {
|
|
515
|
+
fs.writeFileSync(jsonPath, jsonFile.content, "utf8");
|
|
516
|
+
console.log(
|
|
517
|
+
chalk.green(`Successfully created "${jsonPath}"`)
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
473
523
|
fs.writeFileSync(
|
|
474
524
|
`${outputDir}/schemas/${item.id}.schema.mdx`,
|
|
475
525
|
schemaView,
|
|
@@ -517,6 +567,11 @@ custom_edit_url: null
|
|
|
517
567
|
cwd: path.resolve(apiDir),
|
|
518
568
|
deep: 1,
|
|
519
569
|
});
|
|
570
|
+
// Clean up externalized JSON files
|
|
571
|
+
const jsonFiles = await Globby(["*.json", "!versions.json"], {
|
|
572
|
+
cwd: path.resolve(apiDir),
|
|
573
|
+
deep: 1,
|
|
574
|
+
});
|
|
520
575
|
apiMdxFiles.map((mdx) =>
|
|
521
576
|
fs.unlink(`${apiDir}/${mdx}`, (err) => {
|
|
522
577
|
if (err) {
|
|
@@ -546,6 +601,22 @@ custom_edit_url: null
|
|
|
546
601
|
}
|
|
547
602
|
})
|
|
548
603
|
);
|
|
604
|
+
|
|
605
|
+
// Clean up externalized JSON files
|
|
606
|
+
jsonFiles.map((jsonFile) =>
|
|
607
|
+
fs.unlink(`${apiDir}/${jsonFile}`, (err) => {
|
|
608
|
+
if (err) {
|
|
609
|
+
console.error(
|
|
610
|
+
chalk.red(`Cleanup failed for "${apiDir}/${jsonFile}"`),
|
|
611
|
+
chalk.yellow(err)
|
|
612
|
+
);
|
|
613
|
+
} else {
|
|
614
|
+
console.log(
|
|
615
|
+
chalk.green(`Cleanup succeeded for "${apiDir}/${jsonFile}"`)
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
})
|
|
619
|
+
);
|
|
549
620
|
}
|
|
550
621
|
|
|
551
622
|
try {
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
* Copyright (c) Palo Alto Networks
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
* ========================================================================== */
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* This module provides utilities for externalizing large JSON props in MDX files.
|
|
10
|
+
*
|
|
11
|
+
* The motivation is to improve MDX compilation performance. When large JSON objects
|
|
12
|
+
* are embedded directly as JSX props, MDX needs to parse them into AST and then
|
|
13
|
+
* serialize them back. By externalizing to JSON files and using require(), the
|
|
14
|
+
* MDX compiler can skip this expensive processing.
|
|
15
|
+
*
|
|
16
|
+
* @see https://github.com/facebook/docusaurus/discussions/11664
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export interface ExternalizedJsonFile {
|
|
20
|
+
/** The filename for the JSON file (without path) */
|
|
21
|
+
filename: string;
|
|
22
|
+
/** The JSON content to write */
|
|
23
|
+
content: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ExternalizeResult {
|
|
27
|
+
/** The transformed MDX content with require() statements */
|
|
28
|
+
mdx: string;
|
|
29
|
+
/** List of JSON files that need to be written */
|
|
30
|
+
jsonFiles: ExternalizedJsonFile[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Components and their props that should be externalized.
|
|
35
|
+
* These are the components that typically receive large JSON objects.
|
|
36
|
+
*/
|
|
37
|
+
const COMPONENTS_TO_EXTERNALIZE = [
|
|
38
|
+
{ component: "StatusCodes", prop: "responses" },
|
|
39
|
+
{ component: "ParamsDetails", prop: "parameters" },
|
|
40
|
+
{ component: "RequestSchema", prop: "body" },
|
|
41
|
+
{ component: "Schema", prop: "schema" },
|
|
42
|
+
{ component: "SchemaItem", prop: "schema" },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Minimum size (in characters) for a JSON prop to be externalized.
|
|
47
|
+
* Props smaller than this threshold will remain inline.
|
|
48
|
+
*/
|
|
49
|
+
const MIN_SIZE_THRESHOLD = 500;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Extracts the content between balanced braces starting at a given position.
|
|
53
|
+
* Returns the content (without outer braces) and the end position.
|
|
54
|
+
*/
|
|
55
|
+
function extractBalancedBraces(
|
|
56
|
+
str: string,
|
|
57
|
+
startIndex: number
|
|
58
|
+
): { content: string; endIndex: number } | null {
|
|
59
|
+
if (str[startIndex] !== "{") {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let depth = 0;
|
|
64
|
+
let inString = false;
|
|
65
|
+
let stringChar = "";
|
|
66
|
+
let escaped = false;
|
|
67
|
+
|
|
68
|
+
for (let i = startIndex; i < str.length; i++) {
|
|
69
|
+
const char = str[i];
|
|
70
|
+
|
|
71
|
+
if (escaped) {
|
|
72
|
+
escaped = false;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (char === "\\") {
|
|
77
|
+
escaped = true;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!inString) {
|
|
82
|
+
if (char === '"' || char === "'") {
|
|
83
|
+
inString = true;
|
|
84
|
+
stringChar = char;
|
|
85
|
+
} else if (char === "{") {
|
|
86
|
+
depth++;
|
|
87
|
+
} else if (char === "}") {
|
|
88
|
+
depth--;
|
|
89
|
+
if (depth === 0) {
|
|
90
|
+
// Found the matching closing brace
|
|
91
|
+
return {
|
|
92
|
+
content: str.substring(startIndex + 1, i),
|
|
93
|
+
endIndex: i,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
if (char === stringChar) {
|
|
99
|
+
inString = false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return null; // Unbalanced braces
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Externalizes large JSON props from MDX content.
|
|
109
|
+
* Uses brace-counting to correctly handle deeply nested JSON.
|
|
110
|
+
*
|
|
111
|
+
* @param mdx - The original MDX content
|
|
112
|
+
* @param baseFilename - The base filename (without extension) for the MDX file
|
|
113
|
+
* @returns The transformed MDX and list of JSON files to write
|
|
114
|
+
*/
|
|
115
|
+
export function externalizeJsonPropsSimple(
|
|
116
|
+
mdx: string,
|
|
117
|
+
baseFilename: string
|
|
118
|
+
): ExternalizeResult {
|
|
119
|
+
const jsonFiles: ExternalizedJsonFile[] = [];
|
|
120
|
+
let transformedMdx = mdx;
|
|
121
|
+
|
|
122
|
+
for (const { component, prop } of COMPONENTS_TO_EXTERNALIZE) {
|
|
123
|
+
// Find pattern: prop={
|
|
124
|
+
const propPattern = new RegExp(`${prop}=\\{`, "g");
|
|
125
|
+
let match;
|
|
126
|
+
|
|
127
|
+
// Keep searching until no more matches (need to restart after each replacement)
|
|
128
|
+
let searchStart = 0;
|
|
129
|
+
while (true) {
|
|
130
|
+
propPattern.lastIndex = searchStart;
|
|
131
|
+
match = propPattern.exec(transformedMdx);
|
|
132
|
+
|
|
133
|
+
if (!match) break;
|
|
134
|
+
|
|
135
|
+
const propStart = match.index;
|
|
136
|
+
const braceStart = propStart + prop.length + 1; // Position of '{'
|
|
137
|
+
|
|
138
|
+
// Extract the balanced content
|
|
139
|
+
const extracted = extractBalancedBraces(transformedMdx, braceStart);
|
|
140
|
+
if (!extracted) {
|
|
141
|
+
searchStart = braceStart + 1;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const jsonContent = extracted.content;
|
|
146
|
+
const propEnd = extracted.endIndex + 1; // Position after '}'
|
|
147
|
+
|
|
148
|
+
// Skip small or non-JSON content
|
|
149
|
+
const trimmed = jsonContent.trim();
|
|
150
|
+
if (
|
|
151
|
+
jsonContent.length < MIN_SIZE_THRESHOLD ||
|
|
152
|
+
trimmed === "undefined" ||
|
|
153
|
+
trimmed === "null" ||
|
|
154
|
+
trimmed === "true" ||
|
|
155
|
+
trimmed === "false" ||
|
|
156
|
+
trimmed.startsWith("require(")
|
|
157
|
+
) {
|
|
158
|
+
searchStart = propEnd;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Check if this is within the target component
|
|
163
|
+
// Look backwards for the component tag
|
|
164
|
+
const beforeProp = transformedMdx.substring(0, propStart);
|
|
165
|
+
const componentTagPattern = new RegExp(`<${component}[\\s\\S]*$`);
|
|
166
|
+
if (!componentTagPattern.test(beforeProp)) {
|
|
167
|
+
searchStart = propEnd;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Generate filename
|
|
172
|
+
const existingCount = jsonFiles.filter((f) =>
|
|
173
|
+
f.filename.includes(`.${prop}`)
|
|
174
|
+
).length;
|
|
175
|
+
const suffix = existingCount > 0 ? `.${existingCount + 1}` : "";
|
|
176
|
+
const jsonFilename = `${baseFilename}.${prop}${suffix}.json`;
|
|
177
|
+
|
|
178
|
+
// Store the JSON file
|
|
179
|
+
jsonFiles.push({
|
|
180
|
+
filename: jsonFilename,
|
|
181
|
+
content: jsonContent.trim(),
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Replace the prop value with require()
|
|
185
|
+
const newProp = `${prop}={require("./${jsonFilename}")}`;
|
|
186
|
+
|
|
187
|
+
transformedMdx =
|
|
188
|
+
transformedMdx.substring(0, propStart) +
|
|
189
|
+
newProp +
|
|
190
|
+
transformedMdx.substring(propEnd);
|
|
191
|
+
|
|
192
|
+
// Continue searching after the replacement
|
|
193
|
+
searchStart = propStart + newProp.length;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
mdx: transformedMdx,
|
|
199
|
+
jsonFiles,
|
|
200
|
+
};
|
|
201
|
+
}
|
package/src/options.ts
CHANGED
|
@@ -52,6 +52,7 @@ export const OptionsSchema = Joi.object({
|
|
|
52
52
|
schemasOnly: Joi.boolean(),
|
|
53
53
|
disableCompression: Joi.boolean(),
|
|
54
54
|
maskCredentials: Joi.boolean(),
|
|
55
|
+
externalJsonProps: Joi.boolean().default(true),
|
|
55
56
|
version: Joi.string().when("versions", {
|
|
56
57
|
is: Joi.exist(),
|
|
57
58
|
then: Joi.required(),
|
package/src/types.ts
CHANGED
|
@@ -55,6 +55,13 @@ export interface APIOptions {
|
|
|
55
55
|
schemasOnly?: boolean;
|
|
56
56
|
disableCompression?: boolean;
|
|
57
57
|
maskCredentials?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* When enabled, large JSON props in generated MDX are written to external
|
|
60
|
+
* files and loaded via require(). This can significantly improve MDX
|
|
61
|
+
* compilation performance for large OpenAPI specs.
|
|
62
|
+
* @see https://github.com/facebook/docusaurus/discussions/11664
|
|
63
|
+
*/
|
|
64
|
+
externalJsonProps?: boolean;
|
|
58
65
|
}
|
|
59
66
|
|
|
60
67
|
export interface MarkdownGenerator {
|