I'm exploring Nx with Angular (relatively new to both) and trying to figure out how to generate a component library that:
- Can run Storybook, and
- Can be imported one component at a time, rather than dragging the whole module/all the components into an app that requires a single component. (To avoid adding unnecessary code/bloat to my bundle).
So the ideal setup is sort of like this:
repo
|--apps
|--normal-app (can import just card or button, without pulling in both)
|--libs
|--ui (lists individually exportable components, and has a servable Storybook)
|--.storybook
|--card
|--button
Following both a general Nx tutorial that showed how to create a shared component lib, as well as the guide for setting up Storybook in Nx, I generated a UI lib, and set up a Storybook schematic (if that's the right way to say it) for that lib directory.
The hope was to have all my shared components in one lib, and have Storybook set up to autogenerate and serve stories for each component -- but then be able to individually pull components from that lib into other applications without pulling in the whole, bulky UI library.
I successfully built out a UI lib and two components in it. I successfully set up Storybook in that lib. I then imported the UIModule
from the index.ts
file (the public API) in the UI lib, and used one of the two components in my template.
But then when I built the app that was importing the lib, the production build contained both components, although I had used one. This makes sense, since I'm importing the UiModule
. This is, however, non-ideal.
I'm hoping to create a setup that allows for collocated components in a lib with Storybook setup, which can be imported individually.
Is there a way to do this without drastically altering the NX setup? I've explored two main options so far. One is to break all the components into their own UI libs, import them all into a separate app that is set up for Storybook, and import them individually into a main app. Like this (attempt #1):
repo
|--apps
|--storybook-app (imports all)
|--other-app (imports just button)
|--libs
|--button
|--card
That's non-ideal, though, for several reasons:
- It doesn't group the components.
- It involves a lot more lib boilerplate.
- It doesn't autogenerate stories for each component.
- It requires devs to remember to add a story to the Storybook for each component.
As a second attempt (#2), I tried to generate each of the components as separate libs in a shared directory:
repo
|--apps
|--storybook-app (imports all)
|--other-app (imports just button)
|--libs
|--ui (just a directory; not a "project")
|--button (module; has separate component dir beneath)
|--card
This has a number of drawbacks of its own:
- Still can't autogenerate stories.
- Unclear where my Storybook should be. Is it a self-standing app, as in the above tree diagram? That seems non-idiomatic. I tried to put it in the
ui
directory, but since that's not a "project", I get aCannot find project 'ui'
when I attempt to do so using the VSCode NX extension. - I have to generate both a lib and a component within a lib for each UI component. This is extra boilerplate and extra code and extra opportunity for mistakes.
- It seems to me that the Storybook should not be an app, since it can be served normally, but has its own serving commands.
I could build out one of the versions described above (#2, probably), but I suspect there are best practices for what seems like a common use case. It seems that much of my confusion stems from the Angular dependency-injection. (In a React app, you could just import a component, but in Angular, each component belongs to a module and that, specifically, needs to be injected as well. Is it a bad practice to have component-specific modules?)
Does anyone know of an idiomatic/best-practice way to meet these ideal specifications (shared storybook library with individual exported components, or split components with automatically generated stories in NX)?
import { } from '@example/mylib/navbar';
) or else, as you're stating, loading a single module from the library drags your entire library into the main bundle. I managed to reduce my main bundle from 970kB to 501kB by using sub-entrypoints in my library – Catalog