Skip to content

Commit

Permalink
Merge pull request storybookjs#26254 from storybookjs/norbert/fix-rea…
Browse files Browse the repository at this point in the history
…ct-docgen-vite-plugin

ReactVite: Docgen ignore un-parsable files
  • Loading branch information
ndelangen authored Mar 1, 2024
2 parents c9d7eb1 + 70ee50f commit 075f911
Show file tree
Hide file tree
Showing 9 changed files with 197 additions and 11 deletions.
3 changes: 2 additions & 1 deletion code/frameworks/react-vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
"@storybook/builder-vite": "workspace:*",
"@storybook/react": "workspace:*",
"magic-string": "^0.30.0",
"react-docgen": "^7.0.0"
"react-docgen": "^7.0.0",
"resolve": "^1.22.8"
},
"devDependencies": {
"@types/node": "^18.0.0",
Expand Down
74 changes: 74 additions & 0 deletions code/frameworks/react-vite/src/plugins/docgen-resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { extname } from 'path';
import resolve from 'resolve';

export class ReactDocgenResolveError extends Error {
// the magic string that react-docgen uses to check if a module is ignored
readonly code = 'MODULE_NOT_FOUND';

constructor(filename: string) {
super(`'${filename}' was ignored by react-docgen.`);
}
}

/* The below code was copied from:
* https://github.com/reactjs/react-docgen/blob/df2daa8b6f0af693ecc3c4dc49f2246f60552bcb/packages/react-docgen/src/importer/makeFsImporter.ts#L14-L63
* because it wasn't exported from the react-docgen package.
* watch out: when updating this code, also update the code in code/presets/react-webpack/src/loaders/docgen-resolver.ts
*/

// These extensions are sorted by priority
// resolve() will check for files in the order these extensions are sorted
export const RESOLVE_EXTENSIONS = [
'.js',
'.cts', // These were originally not in the code, I added them
'.mts', // These were originally not in the code, I added them
'.ctsx', // These were originally not in the code, I added them
'.mtsx', // These were originally not in the code, I added them
'.ts',
'.tsx',
'.mjs',
'.cjs',
'.mts',
'.cts',
'.jsx',
];

export function defaultLookupModule(filename: string, basedir: string): string {
const resolveOptions = {
basedir,
extensions: RESOLVE_EXTENSIONS,
// we do not need to check core modules as we cannot import them anyway
includeCoreModules: false,
};

try {
return resolve.sync(filename, resolveOptions);
} catch (error) {
const ext = extname(filename);
let newFilename: string;

// if we try to import a JavaScript file it might be that we are actually pointing to
// a TypeScript file. This can happen in ES modules as TypeScript requires to import other
// TypeScript files with .js extensions
// https://www.typescriptlang.org/docs/handbook/esm-node.html#type-in-packagejson-and-new-extensions
switch (ext) {
case '.js':
case '.mjs':
case '.cjs':
newFilename = `${filename.slice(0, -2)}ts`;
break;

case '.jsx':
newFilename = `${filename.slice(0, -3)}tsx`;
break;
default:
throw error;
}

return resolve.sync(newFilename, {
...resolveOptions,
// we already know that there is an extension at this point, so no need to check other extensions
extensions: [],
});
}
}
23 changes: 18 additions & 5 deletions code/frameworks/react-vite/src/plugins/react-docgen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,22 @@ import {
parse,
builtinHandlers as docgenHandlers,
builtinResolvers as docgenResolver,
builtinImporters as docgenImporters,
makeFsImporter,
} from 'react-docgen';
import MagicString from 'magic-string';
import type { PluginOption } from 'vite';
import actualNameHandler from './docgen-handlers/actualNameHandler';
import {
RESOLVE_EXTENSIONS,
ReactDocgenResolveError,
defaultLookupModule,
} from './docgen-resolver';

type DocObj = Documentation & { 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 = new docgenResolver.FindExportedDefinitionsResolver();
const defaultImporter = docgenImporters.fsImporter;
const handlers = [...defaultHandlers, actualNameHandler];

type Options = {
Expand All @@ -36,14 +40,23 @@ export function reactDocgen({
name: 'storybook:react-docgen-plugin',
enforce: 'pre',
async transform(src: string, id: string) {
const relPath = path.relative(cwd, id);
if (!filter(relPath)) return;
if (!filter(path.relative(cwd, id))) {
return;
}

try {
const docgenResults = parse(src, {
resolver: defaultResolver,
handlers,
importer: defaultImporter,
importer: makeFsImporter((filename, basedir) => {
const result = defaultLookupModule(filename, basedir);

if (RESOLVE_EXTENSIONS.find((ext) => result.endsWith(ext))) {
return result;
}

throw new ReactDocgenResolveError(filename);
}),
filename: id,
}) as DocObj[];
const s = new MagicString(src);
Expand Down
1 change: 1 addition & 0 deletions code/presets/react-webpack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"fs-extra": "^11.1.0",
"magic-string": "^0.30.5",
"react-docgen": "^7.0.0",
"resolve": "^1.22.8",
"semver": "^7.3.7",
"webpack": "5"
},
Expand Down
74 changes: 74 additions & 0 deletions code/presets/react-webpack/src/loaders/docgen-resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { extname } from 'path';
import resolve from 'resolve';

export class ReactDocgenResolveError extends Error {
// the magic string that react-docgen uses to check if a module is ignored
readonly code = 'MODULE_NOT_FOUND';

constructor(filename: string) {
super(`'${filename}' was ignored by react-docgen.`);
}
}

/* The below code was copied from:
* https://github.com/reactjs/react-docgen/blob/df2daa8b6f0af693ecc3c4dc49f2246f60552bcb/packages/react-docgen/src/importer/makeFsImporter.ts#L14-L63
* because it wasn't exported from the react-docgen package.
* watch out: when updating this code, also update the code in code/frameworks/react-vite/src/plugins/docgen-resolver.ts
*/

// These extensions are sorted by priority
// resolve() will check for files in the order these extensions are sorted
export const RESOLVE_EXTENSIONS = [
'.js',
'.cts', // These were originally not in the code, I added them
'.mts', // These were originally not in the code, I added them
'.ctsx', // These were originally not in the code, I added them
'.mtsx', // These were originally not in the code, I added them
'.ts',
'.tsx',
'.mjs',
'.cjs',
'.mts',
'.cts',
'.jsx',
];

export function defaultLookupModule(filename: string, basedir: string): string {
const resolveOptions = {
basedir,
extensions: RESOLVE_EXTENSIONS,
// we do not need to check core modules as we cannot import them anyway
includeCoreModules: false,
};

try {
return resolve.sync(filename, resolveOptions);
} catch (error) {
const ext = extname(filename);
let newFilename: string;

// if we try to import a JavaScript file it might be that we are actually pointing to
// a TypeScript file. This can happen in ES modules as TypeScript requires to import other
// TypeScript files with .js extensions
// https://www.typescriptlang.org/docs/handbook/esm-node.html#type-in-packagejson-and-new-extensions
switch (ext) {
case '.js':
case '.mjs':
case '.cjs':
newFilename = `${filename.slice(0, -2)}ts`;
break;

case '.jsx':
newFilename = `${filename.slice(0, -3)}tsx`;
break;
default:
throw error;
}

return resolve.sync(newFilename, {
...resolveOptions,
// we already know that there is an extension at this point, so no need to check other extensions
extensions: [],
});
}
}
19 changes: 16 additions & 3 deletions code/presets/react-webpack/src/loaders/react-docgen-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
parse,
builtinResolvers as docgenResolver,
builtinHandlers as docgenHandlers,
builtinImporters as docgenImporters,
makeFsImporter,
ERROR_CODES,
utils,
} from 'react-docgen';
Expand All @@ -11,6 +11,12 @@ import type { LoaderContext } from 'webpack';
import type { Handler, NodePath, babelTypes as t, Documentation } from 'react-docgen';
import { logger } from '@storybook/node-logger';

import {
RESOLVE_EXTENSIONS,
ReactDocgenResolveError,
defaultLookupModule,
} from './docgen-resolver';

const { getNameOrValue, isReactForwardRefCall } = utils;

const actualNameHandler: Handler = function actualNameHandler(documentation, componentDefinition) {
Expand Down Expand Up @@ -54,7 +60,6 @@ type DocObj = Documentation & { actualName: string };

const defaultHandlers = Object.values(docgenHandlers).map((handler) => handler);
const defaultResolver = new docgenResolver.FindExportedDefinitionsResolver();
const defaultImporter = docgenImporters.fsImporter;
const handlers = [...defaultHandlers, actualNameHandler];

export default async function reactDocgenLoader(
Expand All @@ -71,7 +76,15 @@ export default async function reactDocgenLoader(
filename: this.resourcePath,
resolver: defaultResolver,
handlers,
importer: defaultImporter,
importer: makeFsImporter((filename, basedir) => {
const result = defaultLookupModule(filename, basedir);

if (RESOLVE_EXTENSIONS.find((ext) => result.endsWith(ext))) {
return result;
}

throw new ReactDocgenResolveError(filename);
}),
babelOptions: {
babelrc: false,
configFile: false,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.foo {
color: red;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import React from 'react';

import { imported } from '../imported';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore (css import not supported in TS)
import styles from '../imported.module.css';

const local = 'local-value';

Expand All @@ -26,6 +29,7 @@ interface PropsWriterProps {
importedReference?: string;
globalReference?: any;
stringGlobalName?: string;
myClass: typeof styles.foo;
}

/**
Expand All @@ -47,6 +51,7 @@ PropsWriter.defaultProps = {
importedReference: imported,
globalReference: Date,
stringGlobalName: 'top',
myClass: styles.foo,
};

export const component = PropsWriter;
6 changes: 4 additions & 2 deletions code/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6343,6 +6343,7 @@ __metadata:
fs-extra: "npm:^11.1.0"
magic-string: "npm:^0.30.5"
react-docgen: "npm:^7.0.0"
resolve: "npm:^1.22.8"
semver: "npm:^7.3.7"
typescript: "npm:^5.3.2"
webpack: "npm:5"
Expand Down Expand Up @@ -6491,6 +6492,7 @@ __metadata:
"@types/node": "npm:^18.0.0"
magic-string: "npm:^0.30.0"
react-docgen: "npm:^7.0.0"
resolve: "npm:^1.22.8"
typescript: "npm:^5.3.2"
vite: "npm:^4.0.0"
peerDependencies:
Expand Down Expand Up @@ -25657,7 +25659,7 @@ __metadata:
languageName: node
linkType: hard

"resolve@npm:1.22.8, resolve@npm:^1.10.0, resolve@npm:^1.13.1, resolve@npm:^1.14.2, resolve@npm:^1.15.1, resolve@npm:^1.17.0, resolve@npm:^1.19.0, resolve@npm:^1.22.1, resolve@npm:^1.22.4, resolve@npm:^1.4.0":
"resolve@npm:1.22.8, resolve@npm:^1.10.0, resolve@npm:^1.13.1, resolve@npm:^1.14.2, resolve@npm:^1.15.1, resolve@npm:^1.17.0, resolve@npm:^1.19.0, resolve@npm:^1.22.1, resolve@npm:^1.22.4, resolve@npm:^1.22.8, resolve@npm:^1.4.0":
version: 1.22.8
resolution: "resolve@npm:1.22.8"
dependencies:
Expand All @@ -25683,7 +25685,7 @@ __metadata:
languageName: node
linkType: hard

"resolve@patch:resolve@npm%3A1.22.8#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.10.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.13.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.15.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.17.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.4.0#optional!builtin<compat/resolve>":
"resolve@patch:resolve@npm%3A1.22.8#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.10.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.13.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.15.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.17.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.4.0#optional!builtin<compat/resolve>":
version: 1.22.8
resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin<compat/resolve>::version=1.22.8&hash=c3c19d"
dependencies:
Expand Down

0 comments on commit 075f911

Please sign in to comment.