Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto-generate routes for components #7

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,21 @@ module.exports = {
Any pattern supported by [`fast-glob`](https://github.com/mrmlnc/fast-glob) is allowed here
(including negations).

## Generating Routes

By enabling `createRoutes` flag in the options, the following routes will be created:

- `{baseRoute}`, listing all the components
- `{baseRoute}/{ComponentDisplayName}`, rendering the component information (name, description,
props)

You may also override the default interface by implementing the following components in your
`src/components` folder:

- `<ReactComponentList />`
- `<ReactComponent />`
- `<PropTable />`

## Reading Annotations

Using the default settings, annotations are stored inside of the `.docusaurus` directory. The
Expand All @@ -63,8 +78,8 @@ import * as React from 'react';
import { useDynamicImport } from 'docusaurus-plugin-react-docgen-typescript/pkg/dist-src/hooks/useDynamicImport';

export const PropTable = ({ name }) => {
const props = useDynamicImport(name);
const { props } = useDynamicImport(name);

if (!props) {
return null;
}
Expand Down Expand Up @@ -116,3 +131,5 @@ export const PropTable = ({ name }) => {
| `tsConfig` | `string` | No | Specify the path to your custom tsconfig file (note that in most cases the default config is sufficient) |
| `compilerOptions` | `CompilerOptions` | No | Pass custom ts compiler options in lieu of of a custom `tsConfig` |
| `parserOptions` | [`ParserOptions`](https://github.com/styleguidist/react-docgen-typescript#options) | No | Options passed to `react-docgen-typescript` |
| `createRoutes` | `boolean` | No | Whether to create a route for each component |
| `baseRoute` | `string` | No | When creating routes, defines the base route |
17,205 changes: 10,505 additions & 6,700 deletions package-lock.json

Large diffs are not rendered by default.

19 changes: 16 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
"lint": "eslint src --ext=ts,tsx,js,jsx,json",
"prebuild": "rm -rf pkg",
"build": "npm-run-all -p build:*",
"build:src": "esbuild --format=esm --sourcemap --outbase=src --platform=node src/index.ts src/hooks/useDynamicImport.ts --outdir=pkg/dist-src",
"build:cjs": "esbuild --format=cjs --sourcemap --outbase=src --platform=node src/index.ts src/hooks/useDynamicImport.ts --outdir=pkg/dist-node",
"build:src": "FORMAT=esm node ./scripts/build.js",
"build:cjs": "FORMAT=cjs node ./scripts/build.js",
"build:types": "tsc",
"prepare": "husky install",
"prepublishOnly": "pinst --disable",
Expand Down Expand Up @@ -48,10 +48,15 @@
"devDependencies": {
"@djthoms/eslint-config": "^4.3.0",
"@djthoms/prettier-config": "^3.2.0",
"@docusaurus/types": "^2.0.0-alpha.73",
"@docusaurus/core": "^2.0.0-beta.9",
"@docusaurus/module-type-aliases": "2.0.0-beta.9",
"@docusaurus/theme-classic": "^2.0.0-beta.9",
"@docusaurus/types": "2.0.0-beta.9",
"@tsconfig/docusaurus": "^1.0.4",
"@types/node": "^14.6.2",
"@types/react": "^17.0.3",
"esbuild": "^0.11.14",
"esbuild-css-modules-plugin": "^2.0.9",
"husky": "^6.0.0",
"lint-staged": "^10.5.4",
"npm-run-all": "^4.1.5",
Expand All @@ -75,6 +80,14 @@
"extends": [
"@djthoms/eslint-config",
"@djthoms/eslint-config/typescript"
],
"overrides": [
{
"files": "build.js",
"rules": {
"@typescript-eslint/no-var-requires": "off"
}
}
]
}
}
26 changes: 26 additions & 0 deletions scripts/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env node

const esbuild = require('esbuild');
const cssModules = require('esbuild-css-modules-plugin');

const format = process.env.FORMAT;

esbuild.build({
logLevel: 'info',
sourcemap: true,
bundle: false,
outbase: 'src',
format: format,
outdir: format === 'esm' ? 'pkg/dist-src' : 'pkg/dist-node',
platform: 'node',
plugins: [cssModules()],
entryPoints: [
'src/index.ts',
'src/hooks/useDynamicImport.ts',
'src/theme/ReactDocLayout/index.tsx',
'src/theme/ReactDocLayout/styles.module.css',
'src/theme/ReactComponentPage/index.tsx',
'src/theme/ReactComponentItem/index.tsx',
'src/theme/ReactPropTable/index.tsx',
],
});
2 changes: 1 addition & 1 deletion src/hooks/useDynamicImport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const useDynamicImport = (name: string): Props => {
let resolved = false;

import(`@docgen/${name}.json`)
.then(props => {
.then(({ props }) => {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it was easier to update the component's createData with the whole metadata, not only the props.

if (!resolved) {
resolved = true;
setProps(props.default);
Expand Down
97 changes: 85 additions & 12 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import path from 'path';
import globby from 'globby';
import docgen, { ParserOptions, ComponentDoc, FileParser } from 'react-docgen-typescript';
import { DEFAULT_PLUGIN_ID } from '@docusaurus/core/lib/constants';

import { Plugin, DocusaurusContext, RouteConfig } from '@docusaurus/types';
import { Plugin, LoadContext, RouteConfig } from '@docusaurus/types';
import { docuHash } from '@docusaurus/utils';
import { CompilerOptions } from 'typescript';
import { PropSidebarItem } from '@docusaurus/plugin-content-docs-types';

type Route = Pick<RouteConfig, 'exact' | 'component' | 'path' | 'priority'>;

Expand All @@ -18,10 +21,13 @@ type Union =
};

type Options = Union & {
id: string;
src: string | string[];
tsConfig?: string;
compilerOptions?: CompilerOptions;
parserOptions?: ParserOptions;
createRoutes?: boolean;
baseRoute?: string;
};

const getParser = (
Expand All @@ -39,23 +45,37 @@ const getParser = (
};

export default function plugin(
context: DocusaurusContext,
{ src, global = false, route, tsConfig, compilerOptions, parserOptions }: Options
context: LoadContext,
{
id,
src,
global = false,
route,
tsConfig,
compilerOptions,
parserOptions,
createRoutes,
baseRoute = '/docs/react',
}: Options
): Plugin<ComponentDoc[]> {
const { generatedFilesDir } = context;

const pluginId = id ?? DEFAULT_PLUGIN_ID;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inspired by docusaurus-plugin-openapi setup.

const pluginName = 'docusaurus-plugin-react-docgen-typescript';
const pluginDataDirRoot = path.join(generatedFilesDir, pluginName);

const dataDir = path.join(pluginDataDirRoot, pluginId);

return {
name: 'docusaurus-plugin-react-docgen-typescript',
name: pluginName,
async loadContent() {
return getParser(tsConfig, compilerOptions, parserOptions)(await globby(src));
},
configureWebpack(config) {
configureWebpack() {
return {
resolve: {
alias: {
'@docgen': path.join(
config.resolve.alias['@generated'],
'docusaurus-plugin-react-docgen-typescript',
'default'
),
'@docgen': dataDir,
},
},
};
Expand All @@ -77,10 +97,63 @@ export default function plugin(
},
});
} else {
content.map(component =>
createData(`${component.displayName}.json`, JSON.stringify(component.props))
const sidebarName = `react-docgen-typescript-sidebar-${pluginId}`;
const sidebar: PropSidebarItem[] = content.map(component => ({
type: 'link',
href: `${baseRoute}/${component.displayName}`,
label: component.displayName,
}));

const componentMetadataPath = await createData(
`${docuHash('component-metadata-prop')}.json`,
JSON.stringify(
{
apiSidebars: {
[sidebarName]: sidebar,
},
},
null,
2
)
);

const routes: RouteConfig[] = await Promise.all(
content.map(async component => {
const componentData = await createData(
`${component.displayName}.json`,
JSON.stringify(component)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the only breaking change: component instead of component.props

);

if (createRoutes) {
return {
path: `${baseRoute}/${component.displayName}`,
exact: true,
component: '@theme/ReactComponentItem',
sidebar: sidebarName,
modules: {
componentMetadata: componentMetadataPath,
data: componentData,
},
};
}
})
);

if (createRoutes) {
addRoute({
path: baseRoute,
component: '@theme/ReactComponentPage',
sidebar: sidebarName,
routes,
modules: {
componentMetadata: componentMetadataPath,
},
});
}
}
},
getThemePath(): string {
return path.resolve(__dirname, './theme');
},
};
}
3 changes: 3 additions & 0 deletions src/metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type ReactComponentMetadata = {
apiSidebars: import('@docusaurus/plugin-content-docs-types').PropSidebars;
};
22 changes: 22 additions & 0 deletions src/theme/ReactComponentItem/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';

import type { Props } from '@theme/DocPage';
import { ComponentDoc } from 'react-docgen-typescript';
import ReactPropTable from '@theme/ReactPropTable';

export type ReactComponentItemProps = Omit<Props, 'versionMetadata'> & {
data: ComponentDoc;
};

export default function ReactComponentItem(props: ReactComponentItemProps): JSX.Element {
const { data } = props;

return (
<>
<h2>{data.displayName}</h2>
<p>{data.description}</p>
<h3>Props</h3>
<ReactPropTable data={data.props} />
</>
);
}
28 changes: 28 additions & 0 deletions src/theme/ReactComponentPage/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import { ComponentDoc } from 'react-docgen-typescript';
import renderRoutes from '@docusaurus/renderRoutes';
import type { Props } from '@theme/DocPage';
import ReactDocLayout from '@theme/ReactDocLayout';
import { ReactComponentMetadata } from '../../metadata';
import { matchPath, useLocation } from '@docusaurus/router';

export type ReactComponentPageProps = Omit<Props, 'versionMetadata'> & {
componentMetadata: ReactComponentMetadata;
data: ComponentDoc[];
};

export default function ReactComponentPage({
componentMetadata,
route: { routes: componentRoutes, ...route },
}: ReactComponentPageProps): JSX.Element {
const location = useLocation();
const currentRoute =
componentRoutes.find(componentRoute => matchPath(location.pathname, componentRoute)) ||
route;

return (
<ReactDocLayout componentMetadata={componentMetadata} currentRoute={currentRoute}>
{renderRoutes(componentRoutes)}
</ReactDocLayout>
);
}