OpenAPI: Reduce initial loaded bundle size

This commit is contained in:
fuma-nama
2024-08-08 22:43:19 +08:00
parent 85725447d6
commit 90af678294
10 changed files with 73 additions and 67 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-openapi': patch
---
Reduce initial loaded bundle size
+1 -1
View File
@@ -183,7 +183,7 @@ export async function generateTags(
function pageContent(
doc: OpenAPI.Document,
props: Omit<ApiPageProps, 'ctx'>,
props: Omit<ApiPageProps, 'document'>,
): string {
const slugger = new Slugger();
const toc: TableOfContents = [];
@@ -83,7 +83,6 @@ export function Operation({
name="body"
schema={noRef(body.content[type].schema ?? {})}
ctx={{
parseObject: true,
readOnly: method.method === 'GET',
writeOnly: method.method !== 'GET',
required: body.required ?? false,
+3 -3
View File
@@ -5,12 +5,12 @@ import type { APIPlaygroundProps } from '@/render/playground';
import { CodeBlock } from '@/render/codeblock';
import {
API,
APIInfo,
APIPlayground,
APIExample,
Root,
APIInfo,
APIExample,
Property,
ObjectCollapsible,
APIPlayground,
} from '@/ui';
export interface ResponsesProps {
+11 -6
View File
@@ -23,8 +23,12 @@ interface Context {
required: boolean;
/** Render the full object */
parseObject: boolean;
/**
* Render the full object
*
* @defaultValue true
* */
parseObject?: boolean;
/**
* Parse binary format string to be files
@@ -60,7 +64,8 @@ export function Schema({
(schema.writeOnly === true && !ctx.writeOnly)
)
return null;
ctx.allowFile ??= true;
const parseObject = ctx.parseObject ?? true;
const stack = ctx.stack ?? [];
const { renderer } = ctx.render;
@@ -75,7 +80,7 @@ export function Schema({
}
// object type
if (isObject(schema) && ctx.parseObject) {
if (isObject(schema) && parseObject) {
const { additionalProperties, properties } = schema;
if (additionalProperties === true) {
@@ -139,7 +144,7 @@ export function Schema({
);
}
if (isObject(schema) && !ctx.parseObject) {
if (isObject(schema) && !parseObject) {
child.push(
<renderer.ObjectCollapsible key="attributes" name="Attributes">
<Schema
@@ -286,7 +291,7 @@ function getSchemaType(schema: OpenAPI.SchemaObject, ctx: Context): string {
}
if (schema.type === 'string' && schema.format === 'binary' && ctx.allowFile)
return 'File';
return 'file';
if (schema.type) return schema.type;
if (isObject(schema)) return 'object';
+31 -9
View File
@@ -1,29 +1,34 @@
import type { ReactElement } from 'react';
import { type OpenAPIV3 as OpenAPI } from 'openapi-types';
import Slugger from 'github-slugger';
import { Operation } from '@/render/operation';
import type { RenderContext } from '@/types';
import { createMethod } from '@/schema/method';
import { defaultRenderer, type Renderer } from '@/render/renderer';
export interface ApiPageProps
extends Pick<
RenderContext,
'generateCodeSamples' | 'generateTypeScriptSchema'
> {
document: OpenAPI.Document;
export interface ApiPageProps {
/**
* An array of operation
*/
operations: { path: string; method: OpenAPI.HttpMethods }[];
hasHead: boolean;
ctx: RenderContext;
renderer?: Partial<Renderer>;
}
export function APIPage({
operations,
ctx,
hasHead = true,
}: ApiPageProps): ReactElement {
const schema = ctx.document;
export function APIPage(props: ApiPageProps): ReactElement {
const { operations, document, hasHead = true } = props;
const ctx = getContext(document, props);
return (
<ctx.renderer.Root baseUrl={ctx.baseUrl}>
{operations.map((item) => {
const operation = schema.paths[item.path]?.[item.method];
const operation = document.paths[item.path]?.[item.method];
if (!operation) return null;
const method = createMethod(item.method, operation);
@@ -40,3 +45,20 @@ export function APIPage({
</ctx.renderer.Root>
);
}
function getContext(
document: OpenAPI.Document,
options: ApiPageProps,
): RenderContext {
return {
document,
renderer: {
...defaultRenderer,
...options.renderer,
},
generateTypeScriptSchema: options.generateTypeScriptSchema,
generateCodeSamples: options.generateCodeSamples,
baseUrl: document.servers?.[0].url ?? 'https://example.com',
slugger: new Slugger(),
};
}
+6 -31
View File
@@ -1,23 +1,15 @@
import type { OpenAPIV3 as OpenAPI } from 'openapi-types';
import Parser from '@apidevtools/json-schema-ref-parser';
import type { FC } from 'react';
import Slugger from 'github-slugger';
import { type FC } from 'react';
import { APIPage, type ApiPageProps } from '@/server/api-page';
import type { RenderContext } from '@/types';
import { defaultRenderer, type Renderer } from '@/render/renderer';
export interface OpenAPIOptions
extends Pick<
RenderContext,
'generateCodeSamples' | 'generateTypeScriptSchema'
> {
extends Omit<Partial<ApiPageProps>, 'document'> {
documentOrPath: string | OpenAPI.Document;
renderer?: Partial<Renderer>;
}
export interface OpenAPIServer {
APIPage: FC<Omit<ApiPageProps, 'ctx'>>;
APIPage: FC<Omit<ApiPageProps, 'document'>>;
}
export function createOpenAPI(options: OpenAPIOptions): OpenAPIServer {
@@ -25,26 +17,9 @@ export function createOpenAPI(options: OpenAPIOptions): OpenAPIServer {
return {
APIPage: async (props) => {
const ctx = getContext((await document) as OpenAPI.Document, options);
return <APIPage ctx={ctx} {...props} />;
return (
<APIPage document={(await document) as OpenAPI.Document} {...props} />
);
},
};
}
function getContext(
document: OpenAPI.Document,
options: OpenAPIOptions,
): RenderContext {
return {
document,
renderer: {
...defaultRenderer,
...options.renderer,
},
generateTypeScriptSchema: options.generateTypeScriptSchema,
generateCodeSamples: options.generateCodeSamples,
baseUrl: document.servers?.[0].url ?? 'https://example.com',
slugger: new Slugger(),
};
}
+10 -10
View File
@@ -14,16 +14,16 @@ export const attachFile: BuildPageTreeOptions['attachFile'] = (node, file) => {
if ('method' in data && typeof data.method === 'string') {
const color = getBadgeColor(data.method);
node.name = [
node.name,
' ',
// eslint-disable-next-line react/jsx-key -- static
<span
className={badgeVariants({ className: 'ms-auto text-nowrap', color })}
>
{data.method}
</span>,
];
node.name = (
<>
{node.name}{' '}
<span
className={badgeVariants({ className: 'ms-auto text-nowrap', color })}
>
{data.method}
</span>
</>
);
}
return node;
+5
View File
@@ -2,9 +2,14 @@
import { type ButtonHTMLAttributes, type HTMLAttributes } from 'react';
import { Check, Copy } from 'lucide-react';
import { cn, useCopyButton, buttonVariants } from 'fumadocs-ui/components/api';
import dynamic from 'next/dynamic';
import { ApiProvider, useApiContext } from '@/ui/contexts/api';
import { type RootProps } from '@/render/renderer';
export const APIPlayground = dynamic(() =>
import('./playground').then((mod) => mod.APIPlayground),
);
export function Root({
children,
baseUrl,
+1 -6
View File
@@ -1,4 +1,3 @@
import dynamic from 'next/dynamic';
import { cn } from 'fumadocs-ui/components/api';
import { Fragment, type HTMLAttributes, type ReactNode } from 'react';
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
@@ -6,11 +5,7 @@ import { badgeVariants, getBadgeColor } from '@/ui/components/variants';
import type { APIInfoProps, PropertyProps } from '@/render/renderer';
import { CopyRouteButton } from '@/ui/client';
export { Root, useSchemaContext } from './client';
export const APIPlayground = dynamic(() =>
import('./playground').then((mod) => mod.APIPlayground),
);
export { Root, useSchemaContext, APIPlayground } from './client';
function Route({ route }: { route: string }): ReactNode {
const segments = route.split('/').filter((part) => part.length > 0);