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

[Bug]: Storybook 7.5.3 -> Type [component] is part of the declarations of 2 modules: [module] and StorybookComponentModule! #24636

Closed
meriturva opened this issue Oct 31, 2023 · 16 comments · Fixed by #24798

Comments

@meriturva
Copy link

Describe the bug

I'm migrating our solution from mdx (1.x) stories to a new modern way (using stories.ts files).

My first attempt is to migrate a few stories about external customized components (es Kendo Components) to straightforward stories.

So basically I have a story like:

import { type Meta, type StoryObj, moduleMetadata } from '@storybook/angular';

import { DropDownListComponent, DropDownListModule } from '@progress/kendo-angular-dropdowns';

// More on how to set up stories at: https://storybook.js.org/docs/angular/writing-stories/introduction
const meta: Meta<DropDownListComponent> = {
  title: 'Kendo/DropDownList',
  component: DropDownListComponent,
  decorators: [
    moduleMetadata({
      imports: [DropDownListModule]})
  ],
  tags: ['autodocs'],
  render: (args: DropDownListComponent) => ({
    props: {
      ...args,
    },
  })
};

export default meta;
type Story = StoryObj<DropDownListComponent>;

// More on writing stories with args: https://storybook.js.org/docs/angular/writing-stories/args
export const Normal: Story = {
  args: {
    data: ['X-Small', 'Small', 'Medium', 'Large', 'X-Large', '2X-Large']
  },
};

Unfortunately, I got the error:
Error: Type DropDownListComponent is part of the declarations of 2 modules: DropDownListModule and StorybookComponentModule! Please consider moving DropDownListComponent to a higher module that imports DropDownListModule and StorybookComponentModule. You can also create a new NgModule that exports and includes DropDownListComponent then import that NgModule in DropDownListModule and StorybookComponentModule.

To Reproduce

Please clone repo:

https://github.com/meriturva/Storybook2ModulesError

just run: npm run storybook and go to page: http://localhost:6006/?path=/docs/kendo-dropdownlist--docs

System

No response

Additional context

The same issue is already described:

#23669
#22282
#19289

but no one in version 7.5x

@meriturva
Copy link
Author

Update repo to storybook v7.5.3 version -> same issue.

@meriturva meriturva changed the title [Bug]: Storybook 7.5.2 -> Type [component] is part of the declarations of 2 modules: [module] and StorybookComponentModule! [Bug]: Storybook 7.5.3 -> Type [component] is part of the declarations of 2 modules: [module] and StorybookComponentModule! Nov 7, 2023
@vanessayuenn
Copy link
Contributor

hi @meriturva, in two of the issues you link, each has a similar discovery (#22282 (comment), #23669 (comment)) -- it looks like, for some reasons unknown to us, the error Type X is part of the declarations of 2 modules: StorybookComponentModule and StorybookComponentModule! ... tends to be a side effect of another "real" error. Are you able to investigate to see what the actual error message is?

@Marklb
Copy link
Member

Marklb commented Nov 7, 2023

I think I see the problem, but I would need to debug further to know if this can be fixed, without affecting the more common use case.

You are creating a story for a dependency's component, which is not what Storybook expects. Storybook expects you to be adding stories for your project's components. Storybook iterates the NgModules, to see if you imported a module that already declared the component, when creating the Story's NgModule, so the Story's NgModule doesn't declare it a second time. Without looking into it more, I don't know why, but the reflection utility that is parsing the metadata is returning the declarations as declarations: [[DropDownListComponent]] for DropDownListModule and Storybook is expecting declarations: [DropDownListComponent]. I will take a look and see if we can update the code that searches for declarations to handle components from dependencies, but Storybook isn't expecting users to document external dependencies components. Even if the multiple declarations error is fixed, you still won't get any of the benefits, such as auto-generated controls for inputs and actions for outputs, since that info is parsed from the source files.

This is typically is solved by using the dependency component in a component from your project, like dependencies should be used.

Workaround if you still want to do it directly, even though I don't recommend it:
You are probably missing out on most of the benefits of specifying a component in your Meta that isn't from your sources, since the types data is currently gathered by compodoc and it isn't generating for anything in node_modules. So, you could exclude the component property and just render the component yourself.

The following is how I made your code work:

// NOTE: I also added `import '@angular/localize/init';` to the top of `preview.ts`, but I am not using localize in my projects, so I don't know if that is how that should be done.

const meta: Meta<DropDownListComponent> = {
  title: 'Kendo/DropDownList',
  // NOTE: The component property is expecting a component from your project.
  // component: DropDownListComponent,
  decorators: [
    // NOTE: The component was expecting animations module to be added so I also added this, also.
    applicationConfig({
      providers: [
        provideAnimations(),
      ],
    }),
    moduleMetadata({
      imports: [DropDownListModule]
    }),
    // You could pass a template string to this function or set the `template` property
    // in the render function's return, also, if you need more control.
    componentWrapperDecorator(DropDownListComponent)
  ],
  tags: ['autodocs'],
  render: (args: DropDownListComponent) => ({
    props: {
      ...args,
    },
  })
};

@meriturva
Copy link
Author

Thanks @Marklb yesterday I implemented the Workaround 1 solution from: #23867 it is quite similar to the one you have proposed here.

Why do we document an external component? Because we do a lot of style customization (scss) we need to check them and show even third-party components.

@meriturva
Copy link
Author

I have tried componentWrapperDecorator approach but it lacks correct args binding for the class property.
Doing something like:

import { type Meta, type StoryObj, moduleMetadata, applicationConfig, componentWrapperDecorator } from '@storybook/angular';
import { provideAnimations } from '@angular/platform-browser/animations';

import { DropDownListComponent, DropDownListModule } from '@progress/kendo-angular-dropdowns';

const meta: Meta<DropDownListComponent> = {
  title: 'Kendo/DropDownList',
  decorators: [
    applicationConfig({
      providers: [
       provideAnimations(),
      ],
    }),
    moduleMetadata({
      imports: [DropDownListModule]
    }),
   componentWrapperDecorator(DropDownListComponent, ({ args }) => ({
    ...args
  }))
  ]
};

export default meta;

type Story = StoryObj<DropDownListComponent & { class : string }>;
export const Small: Story = {
  args: {
    data: ['X-Small', 'Small', 'Medium', 'Large', 'X-Large', '2X-Large'],
    class: 'k-dropdownlist-sm'
  },
};

bind correctly data property to component but not class one (it is HTMLElement property but not directly present on DropDownListComponent class).

So my approach still:

import { type Meta, type StoryObj, moduleMetadata, applicationConfig, componentWrapperDecorator } from '@storybook/angular';
import { provideAnimations } from '@angular/platform-browser/animations';

import { DropDownListComponent, DropDownListModule } from '@progress/kendo-angular-dropdowns';

const meta: Meta<DropDownListComponent> = {
  title: 'Kendo/DropDownList',
  decorators: [
    applicationConfig({
      providers: [
       provideAnimations(),
      ],
    }),
    moduleMetadata({
      imports: [DropDownListModule]
    }),
   render: (args: DropDownListComponent) => ({
    template: `<kendo-dropdownlist ${argsToTemplate(args)}></kendo-dropdownlist>`,
    props: {
      ...args
    },
  })
  ]
};

export default meta;

type Story = StoryObj<DropDownListComponent & { class : string }>;
export const Small: Story = {
  args: {
    data: ['X-Small', 'Small', 'Medium', 'Large', 'X-Large', '2X-Large'],
    class: 'k-dropdownlist-sm'
  },
};

argsToTemplate doesn't care about component prototype but I guess it just prints all args before template rendering.
Do you have any advice @valentinpalkovic or @Marklb to avoid render? I have a lot of stories to migrate and I would like to find the simplest approach.
Thanks

@Marklb
Copy link
Member

Marklb commented Nov 10, 2023

@meriturva I am not sure what you mean by avoiding render. argsToTemplate is just a convenience to avoid manually adding the bindings. So, if args are { data: 'Small', class: 'k-dropdownlist-sm' } then it should return [data]="data" [class]="class". It is new, so there are probably improvements that can still be made to that function.

@valentinpalkovic I think this just pointed out a possible problem that, maybe is known and I just hadn't thought about it, yet. I don't know if there is a good way to catch and warn about it, though. If args are passed to argsToTemplate, but not also passed to props then the snippet will generate inputs for properties that aren't in the context.

@valentinpalkovic
Copy link
Contributor

@Marklb, Good point! We should add an appropriate warning. Feel free to open an issue and label it with help wanted. It should be easy to implement

@valentinpalkovic
Copy link
Contributor

valentinpalkovic commented Nov 10, 2023

@meriturva Can you install the following snapshot release for all your Storybook packages:
0.0.0-pr-24798-sha-f16de2fb?

And then please try out your original approach:

import { type Meta, type StoryObj, moduleMetadata } from '@storybook/angular';

import { DropDownListComponent, DropDownListModule } from '@progress/kendo-angular-dropdowns';

// More on how to set up stories at: https://storybook.js.org/docs/angular/writing-stories/introduction
const meta: Meta<DropDownListComponent> = {
  title: 'Kendo/DropDownList',
  component: DropDownListComponent,
  decorators: [
    moduleMetadata({
      imports: [DropDownListModule]})
  ],
  tags: ['autodocs'],
  render: (args: DropDownListComponent) => ({
    props: {
      ...args,
    },
  })
};

export default meta;
type Story = StoryObj<DropDownListComponent>;

// More on writing stories with args: https://storybook.js.org/docs/angular/writing-stories/args
export const Normal: Story = {
  args: {
    data: ['X-Small', 'Small', 'Medium', 'Large', 'X-Large', '2X-Large']
  },
};

I am curious, whether it works then!

@valentinpalkovic
Copy link
Contributor

@meriturva I made it work with the proposed snapshot release and I tiny adjustments around polyfilling and BrowserAnimations: Please take a look: meriturva/Storybook2ModulesError#1

@meriturva
Copy link
Author

@valentinpalkovic error running version 0.0.0-pr-24798-sha-f16de2fb:

<i> [webpack-dev-middleware] wait until bundle finished
<i> [webpack-dev-middleware] wait until bundle finished: /__webpack_hmr
ERROR in ./src/stories/Button.stories.ts
Module build failed (from ./node_modules/@storybook/builder-webpack5/dist/loaders/export-order-loader.js):
TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator))
    at Object.loader (xxx\Storybook2ModulesError\node_modules\@storybook\builder-webpack5\dist\loaders\export-order-loader.js:1:1255)
 @ ./src/ lazy ^\.\/.*$ include: (?:[\\/]src(?:[\\/](?%21\.)(?:(?:(?%21(?:^%7C[\\/])\.).)*?)[\\/]%7C[\\/]%7C$)(?%21\.)(?=.)[^\\/]*?\.stories\.(js%7Cjsx%7Cmjs%7Cts%7Ctsx))$ chunkName: [request] namespace object ./stories/Button.stories.ts ./stories/Button.stories
 @ ./storybook-stories.js 22:11-24:29
 @ ./storybook-config-entry.js 4:0-50 21:2-10 25:2-30:4 25:58-30:3 28:6-14

@Marklb I mean argsToTemplate works also with class property in contrast to componentWrapperDecorator that skip class binding.

Thanks.

@valentinpalkovic
Copy link
Contributor

@meriturva Can you start Storybook lets say 5 times? Does the error always occur? I know the issue but I have troubles to reproduce it.

@meriturva
Copy link
Author

@valentinpalkovic 5 times out of 5 ... same error.

@valentinpalkovic
Copy link
Contributor

valentinpalkovic commented Nov 13, 2023

The error is resolved together with the initial Angular problem. Storybook 7.6.0-alpha.5 will incorporate the changes as soon as it is released

@meriturva
Copy link
Author

@valentinpalkovic thanks! now it works as expected!

@Marklb @valentinpalkovic I have updated the repo adding a new story.
So we have the standard one:
https://github.com/meriturva/Storybook2ModulesError/blob/main/src/stories/Dropdownlist.stories.ts

and the new one using argsToTemplate:
https://github.com/meriturva/Storybook2ModulesError/blob/main/src/stories/Dropdownlist.argsToTemplate.stories.ts

The main difference is that the class property in the first example is ignored in the second works correctly.
Why? Any advice here? Do I have to open a different issue?

Thanks.
Diego

@valentinpalkovic
Copy link
Contributor

Unfortunately, I have no clue :/ Needs some further debugging and investigation.

@Marklb
Copy link
Member

Marklb commented Nov 14, 2023

@meriturva The class property should be excluded in the first story, because the class property is undefined in args. In your case, you are passing args and without class defined the function has no way of knowing about it. Even if the property was known, such as the prop being in the includes option, the following line is where it would be filtered out:

.filter(([key]) => args[key] !== undefined)

That sort of limitation is why I haven't actually switched to argsToTemplate in my project, yet. I had started working on a smarter implementation a few years ago, but never finished it. I sort of felt like I was overthinking the problem, but I started using it again last weekend and I am trying to come up with something using more than just the args and is still simple to use. My implementation has different limitations that I want to try and avoid before I would propose it as a solution, though, so I wouldn't say I have something better, yet.

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

Successfully merging a pull request may close this issue.

4 participants