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

Create / use react-docgen vite plugin to show docsPage info #299

Merged
merged 7 commits into from
Apr 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions examples/react/.storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,9 @@ module.exports = {
// customize the Vite config here
return config;
},
// Normally this wouldn't be necessary, but `react-docgen-typescript` is the default,
// and since typescript is installed in the monorepo, we can't rely on that check.
typescript: {
reactDocgen: 'react-docgen',
},
};
9 changes: 9 additions & 0 deletions examples/react/.storybook/preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
};
2 changes: 2 additions & 0 deletions packages/builder-vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@
"@storybook/csf-tools": "^6.3.3",
"@storybook/source-loader": "^6.3.12",
"@vitejs/plugin-react": "^1.0.8",
"ast-types": "^0.14.2",
"es-module-lexer": "^0.9.3",
"glob": "^7.2.0",
"glob-promise": "^4.2.0",
"magic-string": "^0.26.1",
"react-docgen": "^6.0.0-alpha.0",
"slash": "^3.0.0",
"vite-plugin-mdx": "^3.5.6"
},
Expand Down
46 changes: 46 additions & 0 deletions packages/builder-vite/plugins/docgen-handlers/actualNameHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* This is heavily based on the react-docgen `displayNameHandler`
* (https://github.com/reactjs/react-docgen/blob/26c90c0dd105bf83499a83826f2a6ff7a724620d/src/handlers/displayNameHandler.ts)
* but instead defines an `actualName` property on the generated docs that is taken first from the component's actual name.
* This addresses an issue where the name that the generated docs are stored under is incorrectly named with the `displayName`
* and not the component's actual name.
*
* This is inspired by `actualNameHandler` from https://github.com/storybookjs/babel-plugin-react-docgen, but is modified
* directly from displayNameHandler, using the same approach as babel-plugin-react-docgen.
*/

import { namedTypes as t } from 'ast-types';
import type { NodePath } from 'ast-types/lib/node-path';
import { getNameOrValue, isReactForwardRefCall } from 'react-docgen/dist/utils';
// import { getNameOrValue, isReactForwardRefCall } from 'react-docgen/lib/utils';
import type { Importer } from 'react-docgen/dist/parse';
// import type { Importer } from 'react-docgen/lib/parse';
import type Documentation from 'react-docgen/lib/Documentation';

export default function actualNameHandler(documentation: Documentation, path: NodePath, importer: Importer): void {
if (t.ClassDeclaration.check(path.node) || t.FunctionDeclaration.check(path.node)) {
documentation.set('actualName', getNameOrValue(path.get('id')));
} else if (
t.ArrowFunctionExpression.check(path.node) ||
t.FunctionExpression.check(path.node) ||
isReactForwardRefCall(path, importer)
) {
let currentPath = path;
while (currentPath.parent) {
if (t.VariableDeclarator.check(currentPath.parent.node)) {
documentation.set('actualName', getNameOrValue(currentPath.parent.get('id')));
return;
} else if (t.AssignmentExpression.check(currentPath.parent.node)) {
const leftPath = currentPath.parent.get('left');
if (t.Identifier.check(leftPath.node) || t.Literal.check(leftPath.node)) {
documentation.set('actualName', getNameOrValue(leftPath));
return;
}
}
currentPath = currentPath.parent;
}
// Could not find an actual name
documentation.set('actualName', '');
}
return;
}
53 changes: 53 additions & 0 deletions packages/builder-vite/plugins/react-docgen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {
parse,
handlers as docgenHandlers,
resolver as docgenResolver,
importers as docgenImporters,
} from 'react-docgen';
import type { DocumentationObject } from 'react-docgen/lib/Documentation';
import MagicString from 'magic-string';
import type { Plugin } from 'vite';
import actualNameHandler from './docgen-handlers/actualNameHandler';

type DocObj = DocumentationObject & { actualName: string };

// TODO: None of these are able to be overridden, so `default` is aspirational here.
const defaultHandlers = Object.values(docgenHandlers).map((handler) => handler);
const defaultResolver = docgenResolver.findAllExportedComponentDefinitions;
const defaultImporter = docgenImporters.makeFsImporter();
const handlers = [...defaultHandlers, actualNameHandler];

export function reactDocgen(): Plugin {
return {
name: 'react-docgen',
enforce: 'pre',
async transform(src: string, id: string) {
// JSX syntax is only allowed in .tsx and .jsx, but components can technically be created without JSX
if (/\.(mjs|tsx?|jsx?)$/.test(id)) {
try {
// Since we're using `findAllExportedComponentDefinitions`, this will always be an array.
const docgenResults = parse(src, defaultResolver, handlers, { importer: defaultImporter, filename: id }) as
| DocObj[];
const s = new MagicString(src);

docgenResults.forEach((info) => {
const { actualName, ...docgenInfo } = info;
if (actualName) {
const docNode = JSON.stringify(docgenInfo);
s.append(`;${actualName}.__docgenInfo=${docNode}`);
}
});

return {
code: s.toString(),
map: s.generateMap(),
};
} catch (e) {
// Usually this is just an error from react-docgen that it couldn't find a component
// Only uncomment for troubleshooting
// console.error(e);
}
}
},
};
}
22 changes: 22 additions & 0 deletions packages/builder-vite/types/react-docgen.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// TODO: delete this stub file once a new alpha of react-docgen is released (will include ts types).

declare module 'react-docgen' {
declare const parse;
declare const handlers;
declare const resolver;
declare const importers;
}

declare module 'react-docgen/lib/Documentation' {
export type DocumentationObject = Record<string, any>;
export default Documentation;
}

declare module 'react-docgen/dist/utils' {
declare const getNameOrValue;
declare const isReactForwardRefCall;
}

declare module 'react-docgen/dist/parse' {
declare type Importer = any;
}
4 changes: 4 additions & 0 deletions packages/builder-vite/vite-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ export async function pluginConfig(options: ExtendedOptions, _type: PluginConfig

if (reactDocgen === 'react-docgen-typescript' && typescriptPresent) {
plugins.push(require('@joshwooding/vite-plugin-react-docgen-typescript').default(reactDocgenTypescriptOptions));
} else if (reactDocgen) {
const { reactDocgen } = await import('./plugins/react-docgen');
// Needs to run before the react plugin, so add to the front
plugins.unshift(reactDocgen());
}
}

Expand Down
27 changes: 25 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3505,10 +3505,12 @@ __metadata:
"@storybook/source-loader": ^6.3.12
"@types/express": ^4.17.13
"@vitejs/plugin-react": ^1.0.8
ast-types: ^0.14.2
es-module-lexer: ^0.9.3
glob: ^7.2.0
glob-promise: ^4.2.0
magic-string: ^0.26.1
react-docgen: ^6.0.0-alpha.0
slash: ^3.0.0
vite-plugin-mdx: ^3.5.6
vue-docgen-api: ^4.40.0
Expand Down Expand Up @@ -15742,6 +15744,27 @@ fsevents@^1.2.7:
languageName: node
linkType: hard

"react-docgen@npm:^6.0.0-alpha.0":
version: 6.0.0-alpha.0
resolution: "react-docgen@npm:6.0.0-alpha.0"
dependencies:
"@babel/core": ^7.7.5
"@babel/generator": ^7.12.11
"@babel/runtime": ^7.7.6
ast-types: ^0.14.2
commander: ^2.19.0
doctrine: ^3.0.0
estree-to-babel: ^3.1.0
neo-async: ^2.6.1
node-dir: ^0.1.10
resolve: ^1.17.0
strip-indent: ^3.0.0
bin:
react-docgen: bin/react-docgen.js
checksum: b1e7ad594a6191ca9e83d1d94e103db6d7e643ef69e9a5d7ca593f0f94e124bcbd48f89aaae8d4952b465781025c74fc7ee2dd7d433136e07a6f3e9529411416
languageName: node
linkType: hard

"react-dom@npm:16.14.0, react-dom@npm:^16.4.14":
version: 16.14.0
resolution: "react-dom@npm:16.14.0"
Expand Down Expand Up @@ -16313,7 +16336,7 @@ fsevents@^1.2.7:
languageName: node
linkType: hard

"resolve@npm:^1.22.0":
"resolve@npm:^1.17.0, resolve@npm:^1.22.0":
version: 1.22.0
resolution: "resolve@npm:1.22.0"
dependencies:
Expand All @@ -16336,7 +16359,7 @@ fsevents@^1.2.7:
languageName: node
linkType: hard

"resolve@patch:resolve@^1.22.0#~builtin<compat/resolve>":
"resolve@patch:resolve@^1.17.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.22.0#~builtin<compat/resolve>":
version: 1.22.0
resolution: "resolve@patch:resolve@npm%3A1.22.0#~builtin<compat/resolve>::version=1.22.0&hash=07638b"
dependencies:
Expand Down