storybookjs / storybook

Storybook is the industry standard workshop for building, documenting, and testing UI components in isolation
https://storybook.js.org
MIT License
83.95k stars 9.22k forks source link

[Bug]: Unexpected token 'default' - Storing sub-components as properties on Components #24665

Open CodeSmith32 opened 10 months ago

CodeSmith32 commented 10 months ago

Describe the bug

I'm building a component library using React / TypeScript / Vite. For a few components, I assign a sub-component as a property on another component, when their functionality is tied, e.g. in my case:

<Grid>
    <Grid.Cell>...</Grid.Cell>
    <Grid.Cell>...</Grid.Cell>
</Grid>

But furthermore, my export process looks something like:

export default function Grid() { ... }

export function Cell() { ... }

Grid.Cell = Cell;

Now, this pattern I've tested and confirmed that it works fine in a normal Vite project. But when running it specifically in the Storybook iframe, it fails with Unexpected token 'default'.

Because Grid is the default export, it appears the compiled code includes default in the exported code because it thinks that default.Cell is another component:

try {
    // @ts-ignore
    Grid.displayName = "Grid";
    // @ts-ignore
    Grid.__docgenInfo = { /* ... JSON details ... */ };
}
catch (__react_docgen_typescript_loader_error) { }
try {
    // @ts-ignore
    default.Cell.displayName = "default.Cell";
    // @ts-ignore
    default.Cell.__docgenInfo = { /* ... JSON details ... */ };
}
catch (__react_docgen_typescript_loader_error) { }
try {
    // @ts-ignore
    Cell.displayName = "Cell";
    // @ts-ignore
    Cell.__docgenInfo = { /* ... JSON details ... */ };
}
catch (__react_docgen_typescript_loader_error) { }

The default.Cell in the middle section is a syntax error, not even something try catch can handle. Thus the whole module throws the unhelpful error: Unexpected token 'default', even though VSCode, ESLint, etc. find no errors project-wide.

To Reproduce

It can be reproduced with the following component:

test.tsx

import React from "react"

export default function Test({children}:React.ComponentProps<"div">) {
    return (
        <div className="test">
            {children}
        </div>
    )
}

export function Sub({children}:React.ComponentProps<"div">) {
    return (
        <div className="sub">
            {children}
        </div>
    )
}

Test.Sub = Sub;

and the following story:

Test.stories.tsx

import React from "react";
import { Meta, StoryObj } from "@storybook/react";
import Test from "./test";

const meta = {
    title: "Test/Error Example",
    parameters: {
        layout: "centered",
    },
} satisfies Meta<typeof Test>

export default meta;

type Story = StoryObj<typeof meta>

export const Example:Story = {
    args: {},
    render: () => <>
        <Test>
            <Test.Sub>Hello World</Test.Sub>
        </Test>
    </>
}

But bear in mind, the storybook server must be started after the code is written.

That is,

At this point, the storybook works fine. Restart the storybook server again, and it'll crash. I presume this means the code is only compiling during storybook startup time (or maybe I have a poor config?).

System

Output of npx storybook@latest info

Storybook Environment Info:

  System:
    OS: Windows 10 10.0.19045
    CPU: (8) x64 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz
  Binaries:
    Node: 18.18.0 - D:\Apps\nodejs\node.EXE
    npm: 9.8.1 - D:\Apps\nodejs\npm.CMD <----- active
    pnpm: 8.8.0 - D:\Apps\nodejs\pnpm.CMD
  Browsers:
    Edge: Spartan (44.19041.3570.0), Chromium (118.0.2088.76)
  npmPackages:
    @storybook/addon-essentials: ^7.5.2 => 7.5.2
    @storybook/addon-interactions: ^7.5.2 => 7.5.2
    @storybook/addon-links: ^7.5.2 => 7.5.2
    @storybook/addon-onboarding: ^1.0.8 => 1.0.8
    @storybook/blocks: ^7.5.2 => 7.5.2
    @storybook/react: ^7.5.2 => 7.5.2
    @storybook/react-vite: ^7.5.2 => 7.5.2
    @storybook/testing-library: ^0.2.2 => 0.2.2
    eslint-plugin-storybook: ^0.6.15 => 0.6.15
    storybook: ^7.5.2 => 7.5.2

Additional context

This seems like it may actually be a problem with @storybook/builder-vite or another package. As I couldn't really determine which, I figured I'd start by posting it here. But I will see if I can make any further discoveries on its source.

CodeSmith32 commented 10 months ago

So, I searched for __react_docgen_typescript_loader_error, and the issue is found in the following module: @joshwooding/vite-plugin-react-docgen-typescript

It appears inside the dist/chunks/generate.[cjs|mjs] files which appear to be an AST tree filter, which makes sense...

Doing a little digging and in the dist/index file, within the reactDocgenTypescript function, I logged the componentDocs variable to find that it includes default.Sub in the displayName:

[
  {
    tags: {},
    filePath: 'D:/Work/CiviLink-Software/civilink-vendor/civilink/stories/test.tsx',
    description: '',
    displayName: 'test',
    methods: [],
    props: {}
  },
  {
    tags: {},
    filePath: 'D:/Work/CiviLink-Software/civilink-vendor/civilink/stories/test.tsx',
    description: '',
    displayName: 'default.Sub',  // -----------------------------
    methods: [],
    props: {}
  },
  {
    tags: {},
    filePath: 'D:/Work/CiviLink-Software/civilink-vendor/civilink/stories/test.tsx',
    description: '',
    displayName: 'Sub',
    methods: [],
    props: {}
  }
]
CodeSmith32 commented 10 months ago

I did track this issue down to what seems to be a problem in one of the following modules:

On the 'vite-plugin' module, I've posted an issue with a possible solution. I presume this issue can be closed once a patch is applied and the package is updated in Storybook.

CodeSmith32 commented 10 months ago

Update: This issue is due to the fact that @joshwooding/vite-plugin-react-docgen-typescript depends on an outdated version of react-docgen-typescript, but the newest version (v2.2.3) hasn't been pushed to npm.

I've manually inserted the corrrective line in my copy of react-docgen-typescript so my project is at least no longer hindered. Hopefully someone gets this package updated soon!

shilman commented 10 months ago

If you configure SB to use react-docgen does that solve the problem? main.js:

export default {
  // ...
  typescript: { docgen: 'react-docgen' }
}

We're planning on making react-docgen the default in 8.0 and it is much more performant than react-docgen-typescript.

CodeSmith32 commented 10 months ago

@shilman, Yeah.. I guess it doesn't crash, but I also get no documentation.. :-?

I'm assuming the issue is because my documentation is on my Props types, not on the arguments. And the reason for that is because, from the props argument, I pull standard properties that I don't necessarily need or plan to document (like className, children, ...props, etc.).

Any recommendations? Can I 'hide' a property from docgen? (I think that would actually work in my situation.)

shilman commented 10 months ago

@CodeSmith32 Do you have a reproduction repo I can look at?

CodeSmith32 commented 10 months ago

Thanks @shilman,

Unfortunately, I don't. This is private to our company.

But I have identified the exact issue (again, see my other issue report or read the trace below). Even still, I also listed a test component above to demonstrate it, and this is just a fairly fresh Vite-React-Typescript-Storybook project. So, if you really want to reproduce it, it probably wouldn't take much to set up:

npm create vite@latest sample-project -- --template react-ts
cd sample-project 
npx storybook@latest init
# storybook will start; ctrl-C to stop it; docgen only runs when SB starts up

Then create the test.tsx and test.stories.tsx files I pasted above. Then just boot up SB and visit the component

npm run storybook

And you'll get the big red

Unexpected token 'default'

Again:

...this has become a problem.

I'm really just hoping @joshwooding pushes v2.2.3 to npm soon so SB can update the dependency version (or even I can at least npm upgrade react-docgen-typescript).

htutwaiphyoe commented 7 months ago

Oh, This has happened to me too. It has occurred in the Compount component in my project.

htutwaiphyoe commented 7 months ago

Updated: I solved this by separating function declarations from the 'export default' keyword across the codebase. Reference: https://github.com/storybookjs/storybook/discussions/13935#discussioncomment-3175691 Before export default function Something() { ... }

After function Something() { ... }

export default Something;

ekilah commented 1 month ago

I can confirm that @htutwaiphyoe 's workaround did seem to get me past the issue that I was having here. I only had to move the export default to a separate line in 2 react (class) components that referenced each other (one has some static rendering helpers that the other references, not sure how relevant that is)

And I've asked here about the dependency update: https://github.com/styleguidist/react-docgen-typescript/issues/505