Publishing Storybook: One Storybook instance per scope

This guide extends the Using Storybook in a Nx workspace - Best practices guide. In that guide, we discussed the best practices of using Storybook in a Nx workspace. We explained the main concepts and the mental model of how to best set up Storybook. In this guide, we are going to see how to put that into practice, by looking at a real-world example. We are going to see how you can publish one Storybook per scope (eg. theme, app, framework) for your workspace.

Sometimes, you have multiple apps and libraries, and each of these is associated with a specific scope. You can read more about grouping libraries and scoping them in the Library Types documentation page, and also in the Code Organization and Naming Conventions documentation section.

In this case, you can have one Storybook instance per scope. If you follow the folder organization convention described above, it is easy to configure Storybook to import all the stories under a specific folder, for example, which are associated with a specific app or scope.

Structure of the folders

Say, for example, that you have a client app, an admin app, and a number of UI libraries, organized under the name of each app. So you would have a folder structure that looks like this:

1happynrwl/ 2├── apps/ 3│ ├── client/ 4│ ├── client-e2e/ 5│ ├── admin/ 6│ ├── admin-e2e/ 7│ ├── client-ui-header-e2e/ 8│ ├── admin-ui-dashboard-e2e/ 9│ └── shared-ui-cta-e2e/ 10├── libs/ 11│ ├── client/ 12│ │ ├── feature/ 13│ │ ├── ui/ 14| │ │ ├── header/ 15| | | | ├── .storybook/ 16| | | | ├── src/ 17| | | | | ├──lib 18| | | | | | ├──my-header 19| | | | | | | ├── my-header.component.ts 20| | | | | | | ├── my-header.component.stories.ts 21| | | | | | | └── etc... 22| | | | | | └── etc... 23| | | | | └── etc... 24| | | | ├── README.md 25| | | | ├── tsconfig.json 26| | | | └── etc... 27│ │ └── utils/ 28│ ├── admin/ 29│ │ ├── feature/ 30│ │ ├── ui/ 31| │ │ ├── dashboard/ 32| | | | ├── .storybook/ 33| | | | ├── src/ 34| | | | | ├── etc.. 35| | | | ├── README.md 36| | | | ├── tsconfig.json 37| | | | └── etc... 38│ │ └── utils/ 39│ └── shared/ 40│ ├── ui/ 41| │ ├── cta/ 42| | | ├── .storybook/ 43| | | ├── src/ 44| | | | ├── etc.. 45| | | ├── README.md 46| | | ├── tsconfig.json 47| | | └── etc... 48│ └── utils/ 49├── tools/ 50├── nx.json 51├── package.json 52└── tsconfig.base.json 53

In this case you can see that we have two deployable applications, client and admin, and we have a number of UI libraries, each associated with a specific app. For example, client-ui-header is a UI library associated with the client app, and admin-ui-dashboard is a UI library associated with the admin app. We also have one more library, the shared-ui-cta library, which is shared between the two apps. The way we have structured our folders is such that any new library that is related to the client app will go in the libs/client folder, and in that folder we have a sub-folder to determine if the new library is related to ui or anything else. The same applies to the admin app. Any library shared between the two apps will live under a subfolder of the libs/shared folder.

Notice how we have already generated Storybook configuration and stories for all of our ui libraries. We have also generated a e2e application for each of these ui libraries, which is going to use the corresponding Storybook instance to run end-to-end tests.

Setting up the thematic Storybook instances

Now, we want to have one Storybook instance per thematic scope. This is quite easy to implement since we are following this specific folder structure.

First of all, we need to generate three new libraries (or as many as our "thematic scopes"), which will host all the stories for each specific scope. We can do this following the same steps described above.

It's important to note that if we want to combine stories from different libraries in the same Storybook instance, the stories need to use the same framework.

Let's assume in this case that all our libraries are using Angular.

Generate the libraries

Let's generate three Angular libraries, one for each scope, and let's call them storybook-host-client, storybook-host-admin, and storybook-host-shared. We can do this by running the following commands:

Directory Flag Behavior Changes

The commands below uses the as-provided directory flag behavior, which is the default in Nx 16.8.0. If you're on an earlier version of Nx or using the derived option, omit the --directory flag. See the workspace layout documentation for more details.

nx g @nx/angular:lib storybook-host-client --directory=libs/storybook-host-client

nx g @nx/angular:lib storybook-host-admin --directory=libs/storybook-host-admin

nx g @nx/angular:lib storybook-host-shared --directory=libs/storybook-host-shared

Nx 15 and lower use @nrwl/ instead of @nx/

Generate the Storybook configuration for the libraries

Now, we need to generate Storybook configuration for all these new libraries. We don't want to generate stories or a new Cypress project for these libraries, so we can run the following commands:

nx g @nx/storybook:configuration storybook-host-client --uiFramework=@storybook/angular

nx g @nx/storybook:configuration storybook-host-admin --uiFramework=@storybook/angular

nx g @nx/storybook:configuration storybook-host-shared --uiFramework=@storybook/angular

Nx 15 and lower use @nrwl/ instead of @nx/

Import the stories

Now that our Storybook configuration is ready for our new libraries, we can go ahead and import the stories!

Thanks to our folder structure, we can easily configure Storybook to import all the stories under a specific folder, for example, which are associated with a specific scope.

For example, libs/storybook-host-admin/.storybook/main.js:

libs/storybook-host-admin/.storybook/main.js
1module.exports = { 2 core: { builder: 'webpack5' }, 3 stories: ['../../admin/ui/**/src/lib/**/*.stories.ts'], 4 addons: ['@storybook/addon-essentials'], 5}; 6

And don't forget the libs/storybook-host-admin/.storybook/tsconfig.json:

libs/storybook-host-admin/.storybook/tsconfig.json
1{ 2 "extends": "../tsconfig.json", 3 "compilerOptions": { 4 "emitDecoratorMetadata": true 5 }, 6 "exclude": ["../**/*.spec.ts"], 7 "include": ["../../admin/ui/**/src/lib/**/*.stories.ts", "*.js"] 8} 9

Use cases that apply to this solution

  • Workspaces with multiple apps and libraries, all using a single framework

  • Workspaces that use scopes and follow the suggested folder structure

  • Workspaces that have multiple apps and libs divided by theme and by framework, that do not mind having more than one Storybook

Extras - Dependencies

In this example, you can still use the implicit dependencies to manually tell Nx which projects your new libraries depend on.