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

Error: CSF: missing default export #16598

Closed
seanmcintyre opened this issue Nov 4, 2021 · 27 comments
Closed

Error: CSF: missing default export #16598

seanmcintyre opened this issue Nov 4, 2021 · 27 comments

Comments

@seanmcintyre
Copy link

Using 6.4.0-beta.27.

This may not be a bug? Writing stories in separate files and re-exporting them from a single file yields the error:

Error: CSF: missing default export


To Reproduce

Re-export stories from a single file. Example:

import { Panel } from '../Panel';

export default {
  title: 'components/Panel/props',
  component: Panel,
};

export { Active } from './active.story';
export { Attach } from './attach.story';
export { Resizable } from './resizable.story';

System

  System:
    OS: macOS 11.6
    CPU: (16) x64 Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
  Binaries:
    Node: 14.17.3 - ~/.nvm/versions/node/v14.17.3/bin/node
    Yarn: 1.22.4 - ~/.yarn/bin/yarn
    npm: 6.14.13 - ~/.nvm/versions/node/v14.17.3/bin/npm
  Browsers:
    Chrome: 95.0.4638.69
    Firefox: 93.0
    Safari: 15.0
  npmPackages:
    @storybook/addon-a11y: 6.4.0-beta.27 => 6.4.0-beta.27 
    @storybook/addon-essentials: 6.4.0-beta.27 => 6.4.0-beta.27 
    @storybook/addon-postcss: ^2.0.0 => 2.0.0 
    @storybook/addon-storysource: 6.4.0-beta.27 => 6.4.0-beta.27 
    @storybook/builder-webpack5: 6.4.0-beta.27 => 6.4.0-beta.27 
    @storybook/components: 6.4.0-beta.27 => 6.4.0-beta.27 
    @storybook/manager-webpack5: 6.4.0-beta.27 => 6.4.0-beta.27 
    @storybook/react: 6.4.0-beta.27 => 6.4.0-beta.27 
    @storybook/theming: 6.4.0-beta.27 => 6.4.0-beta.27 
@shilman
Copy link
Member

shilman commented Nov 5, 2021

hi @seanmcintyre! Thanks for filing this. Can you share your main.js config as well (specifically the features field)?

Starting in 6.4 we're statically analyzing the structure of CSF and this structure is not currently supported by the analysis. I need to think about whether/how to support it--it's technically possible, but may have some limitations. And if we choose not to support it, we should have a lot clearer error message.

@shilman
Copy link
Member

shilman commented Nov 5, 2021

Son of a gun!! I just released https://github.com/storybookjs/storybook/releases/tag/v6.4.0-beta.29 containing PR #16607 that references this issue. Upgrade today to the @next NPM tag to try it out!

npx sb upgrade --prerelease

Closing this issue. Please re-open if you think there's still more to do.

@shilman shilman closed this as completed Nov 5, 2021
@seanmcintyre
Copy link
Author

https://github.com/vimeo/iris/blob/refactor/.storybook/main.js

module.exports = {
  core: {
    builder: 'webpack5',
  },
  typescript: {
    check: true,
    checkOptions: {},
  },
  features: {
    postcss: false,
    previewCsfV3: false,
    buildStoriesJson: true,
    storyStoreV7: false,
  },
  reactOptions: {
    fastRefresh: true,
    strictMode: true,
  },
  stories: ['../src/**/*.story.tsx'],
  addons: [
    '@storybook/addon-essentials',
    '@storybook/addon-a11y',
    '@storybook/addon-storysource',
    './addons/themes/register',
  ],
  webpackFinal: (config) => {
    config.module.rules.push(storySource);
    return config;
  },
};

const storySource = {
  test: /\.(stories|story)\.[tj]sx?$/,
  loader: require.resolve('@storybook/source-loader'),
  exclude: [/node_modules/],
  enforce: 'pre',
};

@shilman
Copy link
Member

shilman commented Nov 9, 2021

@seanmcintyre does beta.29 or higher fix the problem for you?

@nagromLobo
Copy link

@shilman are you suggesting that CSF is not based on ES modules exporting objects with a particular API, but instead some static object structure? I must have misunderstood the docs if that's the case

@shilman
Copy link
Member

shilman commented Apr 20, 2022

@nagromLobo CSF is based on ES modules exporting objects with a particular API

That said, there are limitations. Due to implementation details, there are certain things we don't support. For example, the following is valid ESM:

const bar = 'bar';
export default { title: `foo/${bar}` }

However, we can't statically analyze that. So instead, you'd need to write it as:

export default { title: "foo/bar" }

We've tried to encode some of these constraints into linting rules: https://github.com/storybookjs/eslint-plugin-storybook

@nagromLobo
Copy link

nagromLobo commented Apr 20, 2022

To get storybook to play nice with typescript and our components we wrote some utils to generate the default export / individual stories. This suggests to me that that wouldn't be recommended. Practically I'm not seeing any problems (v6.4.21), though this makes me think that our storybook could run into issues in the future because we implemented this under the assumption that storybook was consuming runtime ES modules.

@shilman
Copy link
Member

shilman commented Apr 20, 2022

@nagromLobo yes I'd recommend against it. try setting features.breakingChangesV7: true in main.js to see why.

@nagromLobo
Copy link

nagromLobo commented Apr 20, 2022

@shilman Is there a recommended way to get types tied to meta/stories so that we could be (relatively) confident that if a component's props changed we wouldn't be breaking any meta/stories that reference it? (the types in the storybook docs use type-casting so they loose some type information)

@nagromLobo
Copy link

For more context we are currently doing

export const defineComponentMeta = <
  C extends keyof JSX.IntrinsicElements | JSXElementConstructor<any> = never
>(
  meta: ComponentMeta<C> & { args: ComponentProps<C> } // force required args to be set to avoid storybook passing `undefined` by default
) => {
  return meta;
};

consumed like:

type ITextInput = typeof TextInput;

export default defineComponentMeta<ITextInput>({
  component: TextInput,
  args: {
    label: 'Label',
    value: '',
    onChange: action('onChange'),
  },
  argTypes: {
    value: { control: false },
  },
});

Which helps us ensure that any required props are defined in args defaults, and no extra props are provided (in the case that a developer removes a prop from a component).

@shilman
Copy link
Member

shilman commented Apr 20, 2022

WIP here #13747

@nagromLobo
Copy link

Thanks for the quick responses and for pointing me to the right place!

@NiklasPor
Copy link

NiklasPor commented Apr 29, 2022

To get storybook to play nice with typescript and our components we wrote some utils to generate the default export / individual stories. This suggests to me that that wouldn't be recommended. Practically I'm not seeing any problems (v6.4.21), though this makes me think that our storybook could run into issues in the future because we implemented this under the assumption that storybook was consuming runtime ES modules.

We did exactly the same as you did @nagromLobo, we have custom helper functions that wrap the CSF syntax and have additional requirements (zeplinUrl, ... which need to be provided.

I guess the implementations based on runtime interpretation will break in V7? (Also we're using Angular Storybook, so we added some more quality of life features like a NgModule which can be passed directly)

@shilman
Copy link
Member

shilman commented Apr 29, 2022

@NiklasPor We'll have a legacy mode so you can still use old code in V7. However, we're seeing dramatic performance improvements coming from the on-demand store + lazy compilation in webpack5, so in order to benefit from those you'll need to update your code.

@NiklasPor
Copy link

@shilman Ok, that sounds great 🚀

@GideonMax
Copy link

I had the same problem, took me a while to realize I had to set storyStoreV7: false
I think the storybook docs should elaborate on this, as the current description for the storyStoreV7 flag is
"Configures Storybook to load stories on demand, rather than during boot up."
which doesn't really mention this problem.
Also, reading further into the article, it very vaguely mentions limitations in csf.

@KevinMind
Copy link

@shilman Is it not possible to evaluate the default export while statically analyzing the CSF module? We get dramatic DX imporvements by using this tiny helper for defining type safe meta/stories.

import {ComponentProps, JSXElementConstructor} from 'react';
import {Meta, StoryObj} from '@storybook/react';

export function storyFactory<
  C extends keyof JSX.IntrinsicElements | JSXElementConstructor<any> = never,
>(Component: C, metaPrams: Omit<Meta<C>, 'component'>) {
  return {
    meta: {
      ...(metaPrams ?? {}),
      component: Component,
    },
    story: (args: StoryObj<ComponentProps<typeof Component>>) => args,
  };
}

example usage:

import {storyFactory} from 'storybook-utils';

import {MyComponent} from './MyComponent';

const {meta, story} = storyFactory(MyComponent, {
  // define component level meta properties
  decorators: [],
  args: {}
});

// type safe stories
export const Default = story({
  args: {
    allConnectionsAssigned: false,
  },
});

export default meta;

Is there a reason why this should not be supported? As long as a function returns an object with valid meta properties, who cares how it is defined?

@AndrewBogdanovTSS
Copy link

The issue is still present in version v7.0.0-rc.7 though I'm actually using default export in the component

export default {
  title: 'Components/Tabs/Static',
  component: VfTabs,
  args: {
    tabs: ['tab 1', 'tab 2', 'tab 3']
  },
  argTypes: {
    variant: {
      control: false
    },
    default: {
      control: false,
      description: 'Default slot for sections of tabs'
    }
  }
} as unknown as Meta

@jeshio
Copy link

jeshio commented Apr 26, 2023

It's work only like this:

export default { ...dynamicMeta, title: 'Buttons/TextButton' }

@koranke
Copy link

koranke commented Mar 10, 2024

I'm new to Storybook and was following the tutorial for Angular (https://storybook.js.org/tutorials/intro-to-storybook/angular/en/simple-component/). At the step after updating config and restarting StoryBook, I get a similar error.

Unable to index ./src/app/components/task.stories.ts:
WARN NoMetaError: CSF: missing default export /home/gcaccia/taskbox/src/app/components/task.stories.ts (line 1, col 0)

Is the tutorial invalid with the latest version of StoryBook or does the error mean I've done something wrong?

Storybook 7.6.6 for angular started

@axedre
Copy link

axedre commented May 8, 2024

We're in a very similar situation as the one described by @KevinMind in his comment.
Is there still no update or timeline on when (or whether) such a syntax will ever be supported? For the record, our desired setup is quite similar:

definition:

export const storyFactory = (
  Component: React.ComponentType
): {
  meta: Meta<React.ComponentType>;
  story: StoryObj<React.ComponentType>;
} => ({
  meta: {
    component: Component,
    decorators: [/*...*/],
    parameters: {/*...*/}
  },
  story: {
    render: () => {
      // stuff

      return (
        <>
          {/* stuff */}
          <Component />
        </>
      );
    }
  }
});

usage:

const { meta, story } = storyFactory(() => (
  <MyComponent {...props} />
));

export { story };
export default meta;

which sadly yields:

Unable to index files: CSF: missing default export

@shilman
Copy link
Member

shilman commented May 8, 2024

@axedre that's a pretty lousy error message for your situation, but it is an error.

We're looking into adding an optional factory wrapper to CSF for better typescript support and that could land late this year. When it does, you might be able to hack it in limited ways.

But supporting arbitrary user defined functions is not on the roadmap and I don't see it ever being added to Storybook

@axedre
Copy link

axedre commented May 8, 2024

Hello @shilman , thanks for the reply.

But supporting arbitrary user defined functions is not on the roadmap and I don't see it ever being added to Storybook

Why is that, if I may ask?
What is the concern (security, or other) over which writing the code as:

const MyComponent = (props: MyComponentProps) => (/* ... */);

const meta: Meta<MyComponentProps> = {
  component: MyComponent,
  decorators: [/* ... */],
  parameters: {/* ... */}
};

export default meta;

is considered ok, while

const metaFactory = (Component: React.ComponentType<MyComponentProps>): Meta<MyComponentProps> => ({
  component: Component,
  decorators: [/* ... */],
  parameters: {/* ... */}
});

const meta = metaFactory(MyComponent);

export default meta;

is not? I'm a bit stomped, seems to me like a perfectly legit pattern, especially since metaFactory could be reused to reduce boilerplate and keep things DRY. 🤔

@shilman
Copy link
Member

shilman commented May 9, 2024

@axedre we are statically analyzing the code, i.e. parsing the content of it without ever executing the code. We do this for performance reasons so that we can quickly generate the sidebar content without bundling all of your stories and the components etc that they depend on. This isn't possible when the contents of meta is the result of some arbitrary black box function

@domastic-nate
Copy link

agree that this is a big oversight @shilman.

is the maybe a thought for an annotation/hint that helps the static analyzer include other files?

@shilman
Copy link
Member

shilman commented May 26, 2024

@domastic-nate the plan is those factory functions would be provided by Storybook so that we know what's happening inside the black box and can provide the correct types

@woahitsjc
Copy link

@domastic-nate the plan is those factory functions would be provided by Storybook so that we know what's happening inside the black box and can provide the correct types

Would love this change. Is there another issue that is opened for this plan?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests