[RFC] Proposal to add first-class support for monorepos in a probably easy way #22521
Replies: 16 comments 14 replies
-
Thanks so much for making this proposal! Monorepos are definitely one of the pain points for Storybook, especially since there are so many different monorepo setups to consider. I like the idea of better supporting a standalone Storybook package in a monorepo. Historically we've had various problems where Storybook's dependencies have conflicted with user dependencies, and this direction could potentially address that. To help make this proposal more concrete, could you create a sample monorepo using the new proposed structure (dummy packages/components are fine) and link it in the RFC? |
Beta Was this translation helpful? Give feedback.
-
I love this, I had the exact same idea today, that we should explore what it would take to make it possible for Storybook to be its separate package in a monorepo, to cleanly separate it from the rest. |
Beta Was this translation helpful? Give feedback.
-
Hi guys @shilman , @JReinhold, thanks for your feedback. I am glad this is of interest. Will try to create the repo during this week so maybe we can start working on it! Will keep you posted. |
Beta Was this translation helpful? Give feedback.
-
The biggest issue that I see with this issue is that Storybook and its associated tooling assumes access to your original component source code, e.g. your stories should import your component from When you import from a package, you're typically getting the transpiled code which often strips out:
This would make it hard to auto-generate docs/controls from your component's API. There are aspects of supporting this pattern that we might be able to fix with improvements/tweaks to Storybook, but I'm not sure how we'd get around this one. |
Beta Was this translation helpful? Give feedback.
-
I'm trying to set up a monorepo exactly as you describe, @ernestostifano, but I'm seeing issues with docgen working when stories live in their respective component packages as opposed to the storybook package. I would really appreciate seeing an example monorepo setup to try and troubleshoot against! |
Beta Was this translation helpful? Give feedback.
-
I'm also very interested in being able to separate storybook(s) from component source code in a monorepo. Here is our context in Airbus Design System
To be more concrete, let's say we need to support angular up to v11, then we need to share dependencies of angular11 with storybook in a single package.json file. With this context, moving storybooks to separate packages in the monorepo looked to be an escape hatch, allowing us to use latest storybook releases without worrying about conflicts with the libraries dependencies, that we can manage independently. We made an attempt few months ago, with storybook v6.5 at this time. We were also a bit confused by the fact that storybook documentation clearly does not recommend doing this kind of separation. |
Beta Was this translation helpful? Give feedback.
-
I've been able to get docgen working in my monorepo by specifying the the path to my lib source directory in the Storybook docgen options. My project is structured like:
// docs/storybook/.storybook/main.ts
const config: StorybookConfig = {
typescript: {
reactDocgenTypescriptOptions: {
include: ['../../src/**/*'],
},
},
};
export default config; I haven't been able to get the test-runner code coverage working however. I can't get it to include files from outside of the Storybook root directory. |
Beta Was this translation helpful? Give feedback.
-
@ernestostifano, thank you for starting this discussion. We have been playing with Storybook configuration for monorepos in our open source project Code Shaper for quite sometime. The goal of this project is to encapsulate best practices such as Storybook, Testing, Linting in customizable templates so that developers can create new projects conforming to team standards very easily. We started off with exactly your approach – a separate Turborepo workspace for Storybook and wiring it up to all the apps and component libraries in the monorepo. You can see an example here which has a mix of Vite & Next.js apps as well as a common component library. However, the process of adding dependencies to more and more workspaces becomes very cumbersome, especially with different apps having different wrapper & mocking requirements (REST vs. GraphQL, ...). Given this, we are contemplating to move to one storybook per app approach. This would allow storybooks to be configured nicely to the app requirements (e.g. Webpack vs Vite), but still allow inclusion of component libraries from other workspaces. This approach is working quite well, except for one minor issue (see this discussion). In any case, this is an important piece of the Storybook puzzle that we should try to nailed down. |
Beta Was this translation helpful? Give feedback.
-
Following up on my comment above, we concluded that one storybook per app approach is much cleaner than one storybook per repo approach. The main reason for this is that storybook usually requires the same configuration as the app, e.g. package dependencies, Next vs Vite app, mock service worker configuration, etc. Having them in different workspaces requires duplication of configuration and somewhat awkward ways of making Storybook work. Note that if the repo contains only component libraries, we create a dummy app just to host Storybook. So far this approach is working very well for us. Please see this example where we have configured one storybook for the |
Beta Was this translation helpful? Give feedback.
-
This is our project structure
the way we think about our repo and storybook is that we have packages that do nothing, and a apps that can be started and or deployed. In that way, Also, our I wonder how you feel about that way of thinking in regards to use-cases in the scope of this PR |
Beta Was this translation helpful? Give feedback.
-
I'm surprised nobody seems to have mentioned Storybook composition yet. I think it already solves most of the problems this RFC identifies, including decorators and multiple frameworks; and without the unresolved questions it raises. I think of stories the same way I think of tests. I don't particularly want to separate either from their source code! In our front end packages, every component lives together with its stories and tests, all in one place. We have one Storybook per app package and one Storybook per library package, and then we use composition:
This seems to work regardless of the differences between packages. Even HMR works well! Our biggest current pain points are:
What could help with this is some kind of instance orchestration in the Storybook CLI. If a parent Storybook could compose a bunch of children, and the whole thing could I have no idea how this would work under the hood, but maybe somebody else would know of some prior art in the ecosystem for this kind of approach. |
Beta Was this translation helpful? Give feedback.
-
👋 currently working in a setup where there is a storybook package with all of our stories but cannot get autodocs working. We just did also did some work to not allow direct imports in |
Beta Was this translation helpful? Give feedback.
-
Proably a bit unrelated, but I had success using the Just have a |
Beta Was this translation helpful? Give feedback.
-
In case anyone is interested, I successfully managed to get Storybook working as a separate project in a monorepo. Relevant parts: Remaining issue:
|
Beta Was this translation helpful? Give feedback.
-
In case someone is looking for a comprehensive monorepo setup of storybook - here is good example https://github.com/Financial-Times/origami. I ended up implementing similar setup in my workplace for storybook thankfully to this repo. P.S. they addressed an interesting problem of navigating between different versions of the same component by building custom viewer engine. I didn't attempt to mimic it, but it might be a useful reference example. |
Beta Was this translation helpful? Give feedback.
-
Some anecdotal experience around this: At my organisation I recently introduced storybook to two of our projects, both projects are monorepos. Project 1In the first project, I added it using an approach similar to what is described here (we have a /storybook folder in which storybook was created using the storybook cli). Directory structure looks like this:
We got most things working using this approach, but it felt a bit like pulling teeth getting everything working. We wanted the stories to live inline with the components in our packages, and getting everything to resolve properly was a pretty big ordeal to figure out. Additionally, we were able to use the react-typescript-docgen package, but only if we were building storybook using webpack. Project 2In the second project, we started with the same approach as above, but this time it was much more important to get vite working. Hot reload times in that project using webpack were unbearably slow and vite was an answer to that problem. I could get vite to work, but again with subpar doc generation (no react-docgen-typescript) and a bunch of stuffing around with tsconfigs/workspaces/lerna/etc. Eventually I gave up on separating out storybook into its own package, moving it to the root of the monorepo, and I had everything working within an hour or so. Not sure how useful those anecdotal stories are, but at this stage we're actually leaning more in favour of "storybook is just a build tool for the entire repo". We probably had an easier time with it being at the root of the project because we only have React libraries, but it definitely felt easier. In the first approach we also still ended up with @storybook/blocks and @storybook/react in the root package.json as it would be used by all packages in the monorepo |
Beta Was this translation helpful? Give feedback.
-
Status: in-review; championed by @shilman
Summary
Storybook's current setup/integration strategy (location, dependencies management and configuration interface) is well suited for standard repositories, but not for a monorepo.
Don't get me wrong, it works! And it is flexible enough to be installed in different ways. Still, there are no suggested, officially supported (documented) approaches. As a consequence, some related issues remain unsolved (like #16748) and some questions unanswered (like this).
I would like to share a recent experience I had on a big monorepo and how I solved some of the issues we encountered, with minimum impact.
Problem Statement
The most common way to integrate Storybook is to put it in the root of your project under a directory called
.storybook
.Dependencies are installed in the root's
package.json
file.At this point, Storybook behaves really well in a monorepo, as long as you configure it to find your stories across different packages/locations.
Integration with other common tools and technologies is also relatively good (
ESLint
,Prettier
,TypeScript
,Yarn 2
,Yarn PnP
,Yarn Zero-Installs
,Babel
,GitHub Actions
, etc.)Things started to go downside when we faced the need to add some decorators to insert various providers to our stories.
Some providers come from our own packages and some are even wrappers of other providers coming from external libraries (
JSS
,Redux
, etc.).If a story placed in package
B
imports a context from packageA
(likeimport {MyContext} from '@scope/package-a'
) and a decorator imports the same context with a relative import (likeimport {MyContext} from '../../packages/package-a'
), even if the resolved file is the same, both contexts will be bundled separately, and the story will not work (provider and consumer will not recognise each other).The above is only a fairly simple example of a wide range of issues that could arise because, in this configuration, Storybook is not inserted into the monorepo's packages loop.
The more your decorators rely on external dependencies or in your own packages, the worst it can get.
Another potential issue becomes relevant if your monorepo contains more than one framework (for example
React
andAngular
).Also, imagine that you have all your tools configured for your packages in a specific location and with a specific naming convention. Having Storybook outside of that structure and following different conventions forces you to write additional overrides and to take that into consideration when configuring tools like
EsLint
or evenTypeScript
.From all of these, objectively speaking, only the dependencies and the multiple frameworks situations are to be considered particularly important.
The good news are that probably all of these can be addressed at once.
Non-goals
N/A.
Implementation
Note: Everything was done using Storybook v7.
Once we identified the above issues as the source of our problems (especially the dependencies one), the first solution was to simply add our
.storybook
directory as a package of our monorepo. Something like this:Then, we immediately added a specific
package.json
file inside the.storybook
directory and moved all Storybook's dependencies into it. We also added some of our own internal packages as dependencies and this immediately fixed our problem. So also the issue (#16748) and question linked above. Great!The only caveat found so far is that for some strange reason
"@storybook/react-webpack5"
needs to remain in the root'spackage.json
file, while all other dependencies can be moved without issues (this is a first symptom of Storybook relying on being in the root of a project, which in any case, IMHO, should be avoided, especially if it does not take too much to do).Beside that, we also added its own
tsconfig.json
and.eslintrc.js
files, both extending the ones in the root, just like any other package. The integration is starting to feel more sound and monorepo friendly.At this point, since everything went fairly well, we wanted to test our luck by moving Storybook (which is now a package) into the
packages
directory. The objective was to stick to our own conventions and avoid having to write especial overrides when configuring our other tools (as explained above).So, we wrapped the
.storybook
directory inside another directory called something likereact-stories
and moved it into thepackages
directory. Also, package relative configuration files (like thepackage.json
) were moved out from the.storybook
directory. Finally, we renamed the.storybook
directory intosrc
. It ended being something like this:This turned to be just great... All the issues described above were gone! Now Storybook has its own reserved package, tracking its own dependencies, it has access to the monorepo's packages loop, so no issues with contexts nor anything related to double builded modules. The package contains its own scripts for
dev
andbuild
, no special rules, regarding other tools configuration, because the structure is the same as for all other packages and finally, if there was a need to support multiple frameworks, we could just add a new package with a completely separate Storybook. Also, the Storybook Static App gets builded inside its own package and in a more sound way.Unfortunately, this is not documented anywhere (not that I could find). But it would be great to officially support this pattern... because it really seems like first-class support for monorepos... could be advertised as that!
Benefits:
Yarn PnP
, thus avoiding issues like the ones explained above about separately building the same module. Also, dependencies on other packages can be tracked better.package.json
file which usually contains only development dependencies and scripts.package.json
will only contain needed dependencies, well distributed between production and development ones..ignore
files and tooling required (since Storybook could be installed following already existent project conventions in terms of file names, locations, etc.).Prior Art
N/A.
Deliverables
N/A.
Risks
N/A.
Unresolved Questions
However, the following minor doubts/issues remain:
babel.config.js
file in the root (as it should, per Babel's documentation). We had to add a local.babelrc.js
file in the package with something like:Which I don't really like.
"@storybook/react-webpack5"
is still required to be installed in the root (the only one).TypeScript
). This was fixed by addingconfigFile
option (alsorootMode
works) in thebabel-loader
configuration (usingwebpackFinal
property inmain.ts
file). Strangely it also works if either of these options is injected in the Babel configuration usingbabel
property inmain.ts
file.7.0.7
, if the.babelrc.js
file was present, the Storybook's manager was being transpiled using our Babel configuration (double log from our config file). If we removed the file, only the preview (and stories) were transpiled with our Babel configuration. After upgrading to version7.0.9
it does not matter the presence of the file, the manager does not use our configuration..storybook
, to avoid passing the-c
option each time, it would be cool to have astorybook.config.js
file (and/or similar) to contain the source path like:(maybe some additional options could by added/migrated in the future)
Alternatives considered / Abandoned Ideas
N/A.
Beta Was this translation helpful? Give feedback.
All reactions