docusaurus-plugin-openapi-docs 4.6.0 → 4.7.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/README.md CHANGED
@@ -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 utils_2 = require("./markdown/utils");
23
24
  const openapi_1 = require("./openapi");
24
25
  const options_1 = require("./options");
25
26
  const sidebars_1 = __importDefault(require("./sidebars"));
@@ -238,8 +239,17 @@ custom_edit_url: null
238
239
  if (downloadUrl) {
239
240
  item.downloadUrl = downloadUrl;
240
241
  }
241
- const markdown = pageGeneratorByType[item.type](item);
242
- item.markdown = markdown;
242
+ // Generate markdown, with externalization for API and schema pages
243
+ let externalFiles = [];
244
+ if (options.externalJsonProps &&
245
+ (item.type === "api" || item.type === "schema")) {
246
+ const result = (0, utils_2.runWithExternalization)(item.id, () => pageGeneratorByType[item.type](item));
247
+ item.markdown = result.result;
248
+ externalFiles = result.files;
249
+ }
250
+ else {
251
+ item.markdown = pageGeneratorByType[item.type](item);
252
+ }
243
253
  if (isSchemasOnly && item.type !== "schema") {
244
254
  return;
245
255
  }
@@ -281,6 +291,14 @@ custom_edit_url: null
281
291
  if (item.id.length === 0) {
282
292
  throw Error("Operation must have summary or operationId defined");
283
293
  }
294
+ // Write externalized JSON files
295
+ for (const jsonFile of externalFiles) {
296
+ const jsonPath = `${outputDir}/${jsonFile.filename}`;
297
+ if (!fs_1.default.existsSync(jsonPath)) {
298
+ fs_1.default.writeFileSync(jsonPath, jsonFile.content, "utf8");
299
+ console.log(chalk_1.default.green(`Successfully created "${jsonPath}"`));
300
+ }
301
+ }
284
302
  fs_1.default.writeFileSync(`${outputDir}/${item.id}.api.mdx`, view, "utf8");
285
303
  console.log(chalk_1.default.green(`Successfully created "${outputDir}/${item.id}.api.mdx"`));
286
304
  }
@@ -331,6 +349,14 @@ custom_edit_url: null
331
349
  }
332
350
  // eslint-disable-next-line testing-library/render-result-naming-convention
333
351
  const schemaView = (0, mustache_1.render)(schemaMdTemplate, item);
352
+ // Write externalized JSON files in schemas directory
353
+ for (const jsonFile of externalFiles) {
354
+ const jsonPath = `${outputDir}/schemas/${jsonFile.filename}`;
355
+ if (!fs_1.default.existsSync(jsonPath)) {
356
+ fs_1.default.writeFileSync(jsonPath, jsonFile.content, "utf8");
357
+ console.log(chalk_1.default.green(`Successfully created "${jsonPath}"`));
358
+ }
359
+ }
334
360
  fs_1.default.writeFileSync(`${outputDir}/schemas/${item.id}.schema.mdx`, schemaView, "utf8");
335
361
  console.log(chalk_1.default.green(`Successfully created "${outputDir}/${item.id}.schema.mdx"`));
336
362
  }
@@ -361,6 +387,11 @@ custom_edit_url: null
361
387
  cwd: path_1.default.resolve(apiDir),
362
388
  deep: 1,
363
389
  });
390
+ // Clean up externalized JSON files
391
+ const jsonFiles = await (0, utils_1.Globby)(["*.json", "!versions.json"], {
392
+ cwd: path_1.default.resolve(apiDir),
393
+ deep: 1,
394
+ });
364
395
  apiMdxFiles.map((mdx) => fs_1.default.unlink(`${apiDir}/${mdx}`, (err) => {
365
396
  if (err) {
366
397
  console.error(chalk_1.default.red(`Cleanup failed for "${apiDir}/${mdx}"`), chalk_1.default.yellow(err));
@@ -377,6 +408,15 @@ custom_edit_url: null
377
408
  console.log(chalk_1.default.green(`Cleanup succeeded for "${apiDir}/${sidebar}"`));
378
409
  }
379
410
  }));
411
+ // Clean up externalized JSON files
412
+ jsonFiles.map((jsonFile) => fs_1.default.unlink(`${apiDir}/${jsonFile}`, (err) => {
413
+ if (err) {
414
+ console.error(chalk_1.default.red(`Cleanup failed for "${apiDir}/${jsonFile}"`), chalk_1.default.yellow(err));
415
+ }
416
+ else {
417
+ console.log(chalk_1.default.green(`Cleanup succeeded for "${apiDir}/${jsonFile}"`));
418
+ }
419
+ }));
380
420
  }
381
421
  try {
382
422
  fs_1.default.rmSync(`${apiDir}/schemas`, { recursive: true });
@@ -10,6 +10,11 @@ exports.getSchemaName = getSchemaName;
10
10
  exports.getQualifierMessage = getQualifierMessage;
11
11
  function prettyName(schema, circular) {
12
12
  var _a, _b, _c, _d, _e;
13
+ // Handle enum-only schemas (valid in JSON Schema)
14
+ // When enum is present without explicit type, treat as string
15
+ if (schema.enum && !schema.type) {
16
+ return "string";
17
+ }
13
18
  if (schema.format) {
14
19
  if (schema.type) {
15
20
  return `${schema.type}<${schema.format}>`;
@@ -1,6 +1,38 @@
1
+ /**
2
+ * Represents an external JSON file to be written alongside the MDX.
3
+ */
4
+ export interface ExternalFile {
5
+ /** The filename for the JSON file (relative to outputDir) */
6
+ filename: string;
7
+ /** The JSON content to write */
8
+ content: string;
9
+ }
10
+ /**
11
+ * Result of running MDX generation with externalization.
12
+ */
13
+ export interface ExternalizationResult<T> {
14
+ /** The result of the generation function */
15
+ result: T;
16
+ /** External JSON files to write */
17
+ files: ExternalFile[];
18
+ }
19
+ /**
20
+ * Runs a function with externalization enabled.
21
+ * Any calls to create() within the function will externalize eligible component props.
22
+ *
23
+ * @param baseFilename - Base filename for the MDX file (without extension)
24
+ * @param fn - Function to run with externalization enabled
25
+ * @returns The function result and any external files that were collected
26
+ *
27
+ * @example
28
+ * const { result: mdx, files } = runWithExternalization("add-pet", () => {
29
+ * return createApiPageMD(item);
30
+ * });
31
+ */
32
+ export declare function runWithExternalization<T>(baseFilename: string, fn: () => T): ExternalizationResult<T>;
1
33
  /**
2
34
  * Children in the plugin does not accept DOM elements, when compared with Children in the theme.
3
- * It is designed for rendering HTML a strings.
35
+ * It is designed for rendering HTML as strings.
4
36
  */
5
37
  export type Children = string | undefined | (string | string[] | undefined)[];
6
38
  export type Props = Record<string, any> & {
@@ -9,6 +41,11 @@ export type Props = Record<string, any> & {
9
41
  export type Options = {
10
42
  inline?: boolean;
11
43
  };
44
+ /**
45
+ * Creates a JSX component string with the given tag, props, and options.
46
+ * When called within runWithExternalization(), props for eligible
47
+ * components are externalized to a single JSON file and spread.
48
+ */
12
49
  export declare function create(tag: string, props: Props, options?: Options): string;
13
50
  export declare function guard<T>(value: T | undefined, cb: (value: T) => Children): string;
14
51
  export declare function render(children: Children): string;
@@ -7,15 +7,82 @@
7
7
  * ========================================================================== */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.codeBlock = exports.curlyBrackets = exports.codeFence = exports.greaterThan = exports.lessThan = void 0;
10
+ exports.runWithExternalization = runWithExternalization;
10
11
  exports.create = create;
11
12
  exports.guard = guard;
12
13
  exports.render = render;
13
14
  exports.clean = clean;
15
+ /**
16
+ * Module-level externalization context.
17
+ * Note: AsyncLocalStorage would be cleaner but isn't available in browser bundles.
18
+ */
19
+ let externalizationContext = null;
20
+ /**
21
+ * Components whose props should be externalized to separate JSON files.
22
+ * These are the components that typically receive large JSON objects.
23
+ */
24
+ const EXTERNALIZABLE_COMPONENTS = new Set([
25
+ "StatusCodes",
26
+ "ParamsDetails",
27
+ "RequestSchema",
28
+ "Schema",
29
+ "SchemaItem",
30
+ ]);
31
+ /**
32
+ * Runs a function with externalization enabled.
33
+ * Any calls to create() within the function will externalize eligible component props.
34
+ *
35
+ * @param baseFilename - Base filename for the MDX file (without extension)
36
+ * @param fn - Function to run with externalization enabled
37
+ * @returns The function result and any external files that were collected
38
+ *
39
+ * @example
40
+ * const { result: mdx, files } = runWithExternalization("add-pet", () => {
41
+ * return createApiPageMD(item);
42
+ * });
43
+ */
44
+ function runWithExternalization(baseFilename, fn) {
45
+ // Set up context
46
+ externalizationContext = {
47
+ baseFilename,
48
+ componentCounters: {},
49
+ files: [],
50
+ };
51
+ try {
52
+ const result = fn();
53
+ const files = externalizationContext.files;
54
+ return { result, files };
55
+ }
56
+ finally {
57
+ // Always clear context
58
+ externalizationContext = null;
59
+ }
60
+ }
61
+ /**
62
+ * Creates a JSX component string with the given tag, props, and options.
63
+ * When called within runWithExternalization(), props for eligible
64
+ * components are externalized to a single JSON file and spread.
65
+ */
14
66
  function create(tag, props, options = {}) {
15
67
  const { children, ...rest } = props;
16
68
  let propString = "";
17
- for (const [key, value] of Object.entries(rest)) {
18
- propString += `\n ${key}={${JSON.stringify(value)}}`;
69
+ // Check if this component's props should be externalized
70
+ if (shouldExternalizeComponent(tag, rest)) {
71
+ const filename = generateExternalFilename(tag);
72
+ const content = JSON.stringify(rest);
73
+ // Add to external files
74
+ externalizationContext.files.push({
75
+ filename,
76
+ content,
77
+ });
78
+ // Use spread syntax with require
79
+ propString = `\n {...require("./${filename}")}`;
80
+ }
81
+ else {
82
+ // Inline props as usual
83
+ for (const [key, value] of Object.entries(rest)) {
84
+ propString += `\n ${key}={${JSON.stringify(value)}}`;
85
+ }
19
86
  }
20
87
  let indentedChildren = render(children).replace(/^/gm, " ");
21
88
  if (options.inline) {
@@ -26,6 +93,37 @@ function create(tag, props, options = {}) {
26
93
  indentedChildren += indentedChildren ? "\n" : "";
27
94
  return `<${tag}${propString}>\n${indentedChildren}</${tag}>`;
28
95
  }
96
+ /**
97
+ * Determines if a component's props should be externalized.
98
+ */
99
+ function shouldExternalizeComponent(tag, props) {
100
+ // No context means externalization is not enabled
101
+ if (!externalizationContext) {
102
+ return false;
103
+ }
104
+ if (!EXTERNALIZABLE_COMPONENTS.has(tag)) {
105
+ return false;
106
+ }
107
+ // Don't externalize if props are empty or only contain undefined/null
108
+ const hasContent = Object.values(props).some((v) => v !== undefined && v !== null);
109
+ if (!hasContent) {
110
+ return false;
111
+ }
112
+ return true;
113
+ }
114
+ /**
115
+ * Generates a unique filename for an externalized component's props.
116
+ */
117
+ function generateExternalFilename(componentName) {
118
+ var _a;
119
+ if (!externalizationContext) {
120
+ throw new Error("Externalization context not set");
121
+ }
122
+ const count = ((_a = externalizationContext.componentCounters[componentName]) !== null && _a !== void 0 ? _a : 0) + 1;
123
+ externalizationContext.componentCounters[componentName] = count;
124
+ const suffix = count > 1 ? `.${count}` : "";
125
+ return `${externalizationContext.baseFilename}.${componentName}${suffix}.json`;
126
+ }
29
127
  function guard(value, cb) {
30
128
  if (!!value || value === 0) {
31
129
  const children = cb(value);
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/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": "4.6.0",
4
+ "version": "4.7.0",
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": "c7c5ca9934031d166faa0740746ef1a39e72982d"
68
+ "gitHead": "f5829b8e478b0ee76344ba31edd67efdfea18990"
69
69
  }
package/src/index.ts CHANGED
@@ -21,6 +21,7 @@ import {
21
21
  createSchemaPageMD,
22
22
  createTagPageMD,
23
23
  } from "./markdown";
24
+ import { ExternalFile, runWithExternalization } from "./markdown/utils";
24
25
  import { processOpenapiFiles, readOpenapiFiles } from "./openapi";
25
26
  import { OptionsSchema } from "./options";
26
27
  import generateSidebarSlice from "./sidebars";
@@ -333,8 +334,21 @@ custom_edit_url: null
333
334
  if (downloadUrl) {
334
335
  item.downloadUrl = downloadUrl;
335
336
  }
336
- const markdown = pageGeneratorByType[item.type](item as any);
337
- item.markdown = markdown;
337
+
338
+ // Generate markdown, with externalization for API and schema pages
339
+ let externalFiles: ExternalFile[] = [];
340
+ if (
341
+ options.externalJsonProps &&
342
+ (item.type === "api" || item.type === "schema")
343
+ ) {
344
+ const result = runWithExternalization(item.id, () =>
345
+ pageGeneratorByType[item.type](item as any)
346
+ );
347
+ item.markdown = result.result;
348
+ externalFiles = result.files;
349
+ } else {
350
+ item.markdown = pageGeneratorByType[item.type](item as any);
351
+ }
338
352
  if (isSchemasOnly && item.type !== "schema") {
339
353
  return;
340
354
  }
@@ -383,6 +397,18 @@ custom_edit_url: null
383
397
  "Operation must have summary or operationId defined"
384
398
  );
385
399
  }
400
+
401
+ // Write externalized JSON files
402
+ for (const jsonFile of externalFiles) {
403
+ const jsonPath = `${outputDir}/${jsonFile.filename}`;
404
+ if (!fs.existsSync(jsonPath)) {
405
+ fs.writeFileSync(jsonPath, jsonFile.content, "utf8");
406
+ console.log(
407
+ chalk.green(`Successfully created "${jsonPath}"`)
408
+ );
409
+ }
410
+ }
411
+
386
412
  fs.writeFileSync(`${outputDir}/${item.id}.api.mdx`, view, "utf8");
387
413
  console.log(
388
414
  chalk.green(
@@ -470,6 +496,18 @@ custom_edit_url: null
470
496
  }
471
497
  // eslint-disable-next-line testing-library/render-result-naming-convention
472
498
  const schemaView = render(schemaMdTemplate, item);
499
+
500
+ // Write externalized JSON files in schemas directory
501
+ for (const jsonFile of externalFiles) {
502
+ const jsonPath = `${outputDir}/schemas/${jsonFile.filename}`;
503
+ if (!fs.existsSync(jsonPath)) {
504
+ fs.writeFileSync(jsonPath, jsonFile.content, "utf8");
505
+ console.log(
506
+ chalk.green(`Successfully created "${jsonPath}"`)
507
+ );
508
+ }
509
+ }
510
+
473
511
  fs.writeFileSync(
474
512
  `${outputDir}/schemas/${item.id}.schema.mdx`,
475
513
  schemaView,
@@ -517,6 +555,11 @@ custom_edit_url: null
517
555
  cwd: path.resolve(apiDir),
518
556
  deep: 1,
519
557
  });
558
+ // Clean up externalized JSON files
559
+ const jsonFiles = await Globby(["*.json", "!versions.json"], {
560
+ cwd: path.resolve(apiDir),
561
+ deep: 1,
562
+ });
520
563
  apiMdxFiles.map((mdx) =>
521
564
  fs.unlink(`${apiDir}/${mdx}`, (err) => {
522
565
  if (err) {
@@ -546,6 +589,22 @@ custom_edit_url: null
546
589
  }
547
590
  })
548
591
  );
592
+
593
+ // Clean up externalized JSON files
594
+ jsonFiles.map((jsonFile) =>
595
+ fs.unlink(`${apiDir}/${jsonFile}`, (err) => {
596
+ if (err) {
597
+ console.error(
598
+ chalk.red(`Cleanup failed for "${apiDir}/${jsonFile}"`),
599
+ chalk.yellow(err)
600
+ );
601
+ } else {
602
+ console.log(
603
+ chalk.green(`Cleanup succeeded for "${apiDir}/${jsonFile}"`)
604
+ );
605
+ }
606
+ })
607
+ );
549
608
  }
550
609
 
551
610
  try {
@@ -8,6 +8,12 @@
8
8
  import { SchemaObject } from "../openapi/types";
9
9
 
10
10
  function prettyName(schema: SchemaObject, circular?: boolean) {
11
+ // Handle enum-only schemas (valid in JSON Schema)
12
+ // When enum is present without explicit type, treat as string
13
+ if (schema.enum && !schema.type) {
14
+ return "string";
15
+ }
16
+
11
17
  if (schema.format) {
12
18
  if (schema.type) {
13
19
  return `${schema.type}<${schema.format}>`;
@@ -5,9 +5,93 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  * ========================================================================== */
7
7
 
8
+ /**
9
+ * Represents an external JSON file to be written alongside the MDX.
10
+ */
11
+ export interface ExternalFile {
12
+ /** The filename for the JSON file (relative to outputDir) */
13
+ filename: string;
14
+ /** The JSON content to write */
15
+ content: string;
16
+ }
17
+
18
+ /**
19
+ * Result of running MDX generation with externalization.
20
+ */
21
+ export interface ExternalizationResult<T> {
22
+ /** The result of the generation function */
23
+ result: T;
24
+ /** External JSON files to write */
25
+ files: ExternalFile[];
26
+ }
27
+
28
+ /**
29
+ * Context for externalization during MDX generation.
30
+ */
31
+ interface ExternalizationContext {
32
+ /** Base filename for external files (e.g., "add-pet" for "add-pet.api.mdx") */
33
+ baseFilename: string;
34
+ /** Counter for generating unique filenames per component type */
35
+ componentCounters: Record<string, number>;
36
+ /** Collected external files during generation */
37
+ files: ExternalFile[];
38
+ }
39
+
40
+ /**
41
+ * Module-level externalization context.
42
+ * Note: AsyncLocalStorage would be cleaner but isn't available in browser bundles.
43
+ */
44
+ let externalizationContext: ExternalizationContext | null = null;
45
+
46
+ /**
47
+ * Components whose props should be externalized to separate JSON files.
48
+ * These are the components that typically receive large JSON objects.
49
+ */
50
+ const EXTERNALIZABLE_COMPONENTS = new Set([
51
+ "StatusCodes",
52
+ "ParamsDetails",
53
+ "RequestSchema",
54
+ "Schema",
55
+ "SchemaItem",
56
+ ]);
57
+
58
+ /**
59
+ * Runs a function with externalization enabled.
60
+ * Any calls to create() within the function will externalize eligible component props.
61
+ *
62
+ * @param baseFilename - Base filename for the MDX file (without extension)
63
+ * @param fn - Function to run with externalization enabled
64
+ * @returns The function result and any external files that were collected
65
+ *
66
+ * @example
67
+ * const { result: mdx, files } = runWithExternalization("add-pet", () => {
68
+ * return createApiPageMD(item);
69
+ * });
70
+ */
71
+ export function runWithExternalization<T>(
72
+ baseFilename: string,
73
+ fn: () => T
74
+ ): ExternalizationResult<T> {
75
+ // Set up context
76
+ externalizationContext = {
77
+ baseFilename,
78
+ componentCounters: {},
79
+ files: [],
80
+ };
81
+
82
+ try {
83
+ const result = fn();
84
+ const files = externalizationContext.files;
85
+ return { result, files };
86
+ } finally {
87
+ // Always clear context
88
+ externalizationContext = null;
89
+ }
90
+ }
91
+
8
92
  /**
9
93
  * Children in the plugin does not accept DOM elements, when compared with Children in the theme.
10
- * It is designed for rendering HTML a strings.
94
+ * It is designed for rendering HTML as strings.
11
95
  */
12
96
  export type Children = string | undefined | (string | string[] | undefined)[];
13
97
 
@@ -15,6 +99,11 @@ export type Props = Record<string, any> & { children?: Children };
15
99
 
16
100
  export type Options = { inline?: boolean };
17
101
 
102
+ /**
103
+ * Creates a JSX component string with the given tag, props, and options.
104
+ * When called within runWithExternalization(), props for eligible
105
+ * components are externalized to a single JSON file and spread.
106
+ */
18
107
  export function create(
19
108
  tag: string,
20
109
  props: Props,
@@ -23,9 +112,27 @@ export function create(
23
112
  const { children, ...rest } = props;
24
113
 
25
114
  let propString = "";
26
- for (const [key, value] of Object.entries(rest)) {
27
- propString += `\n ${key}={${JSON.stringify(value)}}`;
115
+
116
+ // Check if this component's props should be externalized
117
+ if (shouldExternalizeComponent(tag, rest)) {
118
+ const filename = generateExternalFilename(tag);
119
+ const content = JSON.stringify(rest);
120
+
121
+ // Add to external files
122
+ externalizationContext!.files.push({
123
+ filename,
124
+ content,
125
+ });
126
+
127
+ // Use spread syntax with require
128
+ propString = `\n {...require("./${filename}")}`;
129
+ } else {
130
+ // Inline props as usual
131
+ for (const [key, value] of Object.entries(rest)) {
132
+ propString += `\n ${key}={${JSON.stringify(value)}}`;
133
+ }
28
134
  }
135
+
29
136
  let indentedChildren = render(children).replace(/^/gm, " ");
30
137
 
31
138
  if (options.inline) {
@@ -38,6 +145,49 @@ export function create(
38
145
  return `<${tag}${propString}>\n${indentedChildren}</${tag}>`;
39
146
  }
40
147
 
148
+ /**
149
+ * Determines if a component's props should be externalized.
150
+ */
151
+ function shouldExternalizeComponent(
152
+ tag: string,
153
+ props: Record<string, any>
154
+ ): boolean {
155
+ // No context means externalization is not enabled
156
+ if (!externalizationContext) {
157
+ return false;
158
+ }
159
+
160
+ if (!EXTERNALIZABLE_COMPONENTS.has(tag)) {
161
+ return false;
162
+ }
163
+
164
+ // Don't externalize if props are empty or only contain undefined/null
165
+ const hasContent = Object.values(props).some(
166
+ (v) => v !== undefined && v !== null
167
+ );
168
+ if (!hasContent) {
169
+ return false;
170
+ }
171
+
172
+ return true;
173
+ }
174
+
175
+ /**
176
+ * Generates a unique filename for an externalized component's props.
177
+ */
178
+ function generateExternalFilename(componentName: string): string {
179
+ if (!externalizationContext) {
180
+ throw new Error("Externalization context not set");
181
+ }
182
+
183
+ const count =
184
+ (externalizationContext.componentCounters[componentName] ?? 0) + 1;
185
+ externalizationContext.componentCounters[componentName] = count;
186
+
187
+ const suffix = count > 1 ? `.${count}` : "";
188
+ return `${externalizationContext.baseFilename}.${componentName}${suffix}.json`;
189
+ }
190
+
41
191
  export function guard<T>(
42
192
  value: T | undefined,
43
193
  cb: (value: T) => Children
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(),
@@ -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 {
package/lib/types.d.ts DELETED
@@ -1,138 +0,0 @@
1
- import type { SidebarItemDoc } from "@docusaurus/plugin-content-docs/lib/sidebars/types";
2
- import { InfoObject, OperationObject, SchemaObject, SecuritySchemeObject, TagObject } from "./openapi/types";
3
- export type { PropSidebarItemCategory, SidebarItemLink, PropSidebar, PropSidebarItem, } from "@docusaurus/plugin-content-docs/lib/sidebars/types";
4
- export interface PluginOptions {
5
- id?: string;
6
- docsPlugin?: string;
7
- docsPluginId: string;
8
- config: {
9
- [key: string]: APIOptions;
10
- };
11
- }
12
- export interface APIOptions {
13
- specPath: string;
14
- outputDir: string;
15
- template?: string;
16
- infoTemplate?: string;
17
- tagTemplate?: string;
18
- schemaTemplate?: string;
19
- downloadUrl?: string;
20
- hideSendButton?: boolean;
21
- showExtensions?: boolean;
22
- sidebarOptions?: SidebarOptions;
23
- version?: string;
24
- label?: string;
25
- baseUrl?: string;
26
- versions?: {
27
- [key: string]: APIVersionOptions;
28
- };
29
- proxy?: string;
30
- markdownGenerators?: MarkdownGenerator;
31
- showSchemas?: boolean;
32
- showInfoPage?: boolean;
33
- schemasOnly?: boolean;
34
- disableCompression?: boolean;
35
- maskCredentials?: boolean;
36
- }
37
- export interface MarkdownGenerator {
38
- createApiPageMD?: (pageData: ApiPageMetadata) => string;
39
- createInfoPageMD?: (pageData: InfoPageMetadata) => string;
40
- createTagPageMD?: (pageData: TagPageMetadata) => string;
41
- createSchemaPageMD?: (pageData: SchemaPageMetadata) => string;
42
- }
43
- export type ApiDocItemGenerator = (item: ApiPageMetadata | SchemaPageMetadata, context: {
44
- sidebarOptions: SidebarOptions;
45
- basePath: string;
46
- }) => SidebarItemDoc;
47
- export interface SidebarGenerators {
48
- createDocItem?: ApiDocItemGenerator;
49
- }
50
- export interface SidebarOptions {
51
- groupPathsBy?: string;
52
- categoryLinkSource?: "info" | "tag" | "auto";
53
- customProps?: {
54
- [key: string]: unknown;
55
- };
56
- sidebarCollapsible?: boolean;
57
- sidebarCollapsed?: boolean;
58
- sidebarGenerators?: SidebarGenerators;
59
- }
60
- export interface APIVersionOptions {
61
- specPath: string;
62
- outputDir: string;
63
- label: string;
64
- baseUrl: string;
65
- downloadUrl?: string;
66
- }
67
- export interface LoadedContent {
68
- loadedApi: ApiMetadata[];
69
- }
70
- export type ApiMetadata = ApiPageMetadata | InfoPageMetadata | TagPageMetadata | SchemaPageMetadata;
71
- export interface ApiMetadataBase {
72
- sidebar?: string;
73
- previous?: ApiNavLink;
74
- next?: ApiNavLink;
75
- id: string;
76
- unversionedId: string;
77
- infoId?: string;
78
- infoPath?: string;
79
- downloadUrl?: string;
80
- title: string;
81
- description: string;
82
- source: string;
83
- sourceDirName: string;
84
- slug?: string;
85
- permalink: string;
86
- sidebarPosition?: number;
87
- frontMatter: Record<string, unknown>;
88
- method?: string;
89
- path?: string;
90
- position?: number;
91
- }
92
- export interface ApiPageMetadata extends ApiMetadataBase {
93
- json?: string;
94
- type: "api";
95
- api: ApiItem;
96
- markdown?: string;
97
- }
98
- export interface ApiItem extends OperationObject {
99
- method: string;
100
- path: string;
101
- jsonRequestBodyExample: string;
102
- securitySchemes?: {
103
- [key: string]: SecuritySchemeObject;
104
- };
105
- postman?: Request;
106
- proxy?: string;
107
- info: InfoObject;
108
- extensions?: object;
109
- }
110
- export interface InfoPageMetadata extends ApiMetadataBase {
111
- type: "info";
112
- info: ApiInfo;
113
- markdown?: string;
114
- securitySchemes?: {
115
- [key: string]: SecuritySchemeObject;
116
- };
117
- }
118
- export interface TagPageMetadata extends ApiMetadataBase {
119
- type: "tag";
120
- tag: TagObject;
121
- markdown?: string;
122
- }
123
- export interface SchemaPageMetadata extends ApiMetadataBase {
124
- type: "schema";
125
- schema: SchemaObject;
126
- markdown?: string;
127
- }
128
- export interface TagGroupPageMetadata extends ApiMetadataBase {
129
- type: "tagGroup";
130
- name: string;
131
- tags: TagObject[];
132
- markdown?: string;
133
- }
134
- export type ApiInfo = InfoObject;
135
- export interface ApiNavLink {
136
- title: string;
137
- permalink: string;
138
- }
package/lib/types.js DELETED
@@ -1,8 +0,0 @@
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 });