Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NX - UI Library with Storybook and individually exported components

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 a Cannot 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)?

like image 672
Sasha Avatar asked Mar 30 '20 21:03

Sasha


2 Answers

I haven't found a totally satisfactory solution for this problem, but here's what I ended up doing. It's close to #2 above:

  1. Created a libs/ui directory (not a project)
  2. Built a generator script that creates each component as its own separate module within that directory, and auto-creates a story scaffold in it.
  3. Created a component-lib lib. (Rather than an app, because Storybook has its own separate run commands).
  4. Set up a Storybook config in there using the Nx CLI.
  5. Altered the Storybook config to grab all .stories.ts files under the libs/ui directory.

That way, the component-lib directory automatically adds each new component as its added, and I don't have to worry about a bunch of boilerplate whenever I add a component. Also, each component can be individually imported, without dragging in the bundle.

I won't share my whole component-generator script, but it's a basic JS file that

  1. Runs the Nx library generator to generate a lib within the ui dir
  2. Runs the Nx component generator to generate a component within that lib.
  3. Uses fs to write an appropriate .stories.ts file in that lib.

Then, in my component-lib directory, I modified the final line in .storybook/config.js to require all stories under the ui lib:

configure(require.context('../../ui', true, /\.stories\.tsx?$/), module);

It all feels a little hacky, particularly the generator script. But it works!

like image 52
Sasha Avatar answered Oct 09 '22 14:10

Sasha


You could also use your initial approach and generate a module (not a lib) for each component set (or even only one component) you want to provide. Then export all modules in your index.ts

A angular lib in nrwl/nx must contain at least one module (Exceptions are data-libs), like your normal angular project must contain the app.module. But that doesn't mean that you cannot have multiple modules exportet throughout the lib. With multiple modules you can import them independently from your lib.

Example

import { module1 } from '@mylib'
import { module2 } from '@mylib'
like image 39
J. S. Avatar answered Oct 09 '22 13:10

J. S.