avatarJennifer Fu

Summary

The provided content outlines a detailed guide for upgrading multiple Storybook instances to version 7 within a monorepo setup, including troubleshooting and best practices.

Abstract

The article presents a comprehensive nine-step migration process for updating Storybook to version 7 in a monorepo environment. It covers the requirements for the upgrade, such as Node 16 or later and the discontinuation of support for IE11. The guide explains the use of migration scripts, the introduction of the new Storybook binary, and the necessity to configure frameworks and Babel settings. It also addresses the transition from MDX1 to MDX2, the adoption of the new autodocs feature, and the migration of configuration files to TypeScript. The author provides solutions to common issues encountered during the upgrade, such as fixing favicon and story decorators, handling TypeScript compilation errors, and ensuring compatibility with existing components and applications. The conclusion emphasizes the benefits of the migration process and acknowledges the contributions of team members to the successful upgrade of Storybook within the Domino products.

Opinions

  • The author conveys that the migration to Storybook 7 is a significant update that requires careful planning and execution, especially in a monorepo setup.
  • The new frameworks API and the removal of the start-storybook and build-storybook binaries are seen as positive changes that simplify the configuration and integration with other tools.
  • The author suggests that while the migration process can be complex, the Storybook team provides useful automigration tools and documentation to facilitate the transition.
  • There is an emphasis on the importance of reading the migration guide thoroughly and following the recommended steps to ensure a smooth upgrade experience.
  • The author expresses that maintaining legacy stories with the storyStoreV7 flag set to false is a temporary solution and that migrating to CSF3 is the preferred long-term approach.
  • The use of MDX2 is encouraged for its improved performance and syntax, despite the potential need for manual adjustments to existing stories.
  • The author indicates that some changes, such as the addition of .babelrc.json or babel.config.json, are crucial for TypeScript support and resolving compilation issues in a monorepo.
  • The removal of the composition Storybook suggests that the author believes in the value of simplicity and efficiency over the complexity of maintaining a combined Storybook instance.
  • The recommendation to use ZAI.chat, an AI service similar to ChatGPT Plus, reflects the author's opinion on the cost-effectiveness and utility of such tools for developers.

Problems and Solutions Upgrading to Storybook 7

Sharing the nine-step migration experience with multiple Storybooks in a monorepo

Photo by Dmitry Ratushny on Unsplash

Introduction

Storybook is a tool for UI development. It makes development faster and easier by isolating components. This allows us to work on one component at a time. It streamlines UI development, testing, and documentation.

Storybook 7 was released on April 3, 2023. It is the first major release in over two years and by far the largest ever. It brings a lot of features:

  • First-class Vite support
  • Zero-config support for NextJS and SvelteKit powered by the new frameworks API
  • Component Story Format 3 (CSF3) with improved type safety
  • MDX2 support and streamlined doc blocks
  • UI design refresh
  • Improved interaction testing and test coverage
  • Ecosystem CI for better stability and smoother upgrades

The Task

We were at Storybook 6 using React and Webpack, with three Storybooks defined in a monorepo, a software development strategy in which the code for several projects is stored in the same repository.

root
├── .storybook
│   ├── main.js
│   └── preview.jsx
├── node_modules
├── package.json
├── components
│   ├── .storybook
│   │   ├── main.js
│   │   ├── preview.jsx
│   │   └── style.css
│   └── package.json
└── apps
    ├── .storybook
    │   ├── main.js
    │   └── preview.jsx
    └── package.json
  • The repository has a root, where the global package.json is defined and node_modules are generated. components is a project for common components, and apps is a project for an enterprise application using common components via "@product/components": "link:../components". The packages in node_modules are shared by both components and apps.
  • We have three Storybooks in the monorepo, the root Storybook, the component Storybook, and the app Storybook. The root Storybook is the composition of the other two Storybooks.
  • .storybook is a folder for Storybook configuration. main.js configures story file location, add-ons, as well as custom Webpack and Babel configurations. preview.js sets the global setting for decorators, parameters, and global types. style.css is one of the six ways to define global styles.

The task is to upgrade Storybooks to version 7. With a complicated Storybook setup, our migration is not as smooth as previous versions. We share the nine-step migration experience with you, and hopefully, it might help your applications.

Here is the summary of the steps:

Meet the Requirements

Storybook 7 requires Node 16 or above, and it no longer supports IE11. The target browser version is chrome >= 100. It also requires Webpack 5.

Storybook 7 does not support stories that use storiesOf. However, these stories can continue to work by setting features.storyStoreV7: false in main.js, with the performance penalty.

Storybook 7 does not support MDX1. It is recommended to migrate to MDX2, but MDX1 can continue to work by setting features.legacyMdx1: true in main.js.

Writing stories directly in MDX has been deprecated in Storybook 7. It is recommended to document stories with simple .mdx, and write actual stories in CSF3, an improved version of CSF2 that requires writing stories with objects.

Choose a Migration Script

A Storybook migration script accomplishes two tasks:

  1. Upgrade Storybook dependencies to the latest version.
  2. Run a collection of automigrations, which will:
  • Check for common upgrade tasks.
  • Explain the necessary changes with links to more information.
  • Ask for approval, and then perform the task.

There are three migration scripts provided, automigrate, upgrade, and prerelease.

Automigrate Script

Here is the command to run automigrate script:

$ npx storybook@next automigrate

It runs standard configuration checks, explains what is potentially out-of-date, and offers to fix it automatically. However, it does not upgrade Storybook packages.

Upgrade script

Here is the command to run the upgrade script:

$ npx storybook@latest upgrade

It upgrades the Storybook packages to the latest stable version, performs confidence checks of package versions, and executes automigrate to check the configuration.

Prerelease script

Here is the command to run the prerelease script:

$ npx storybook@latest upgrade --prerelease

Storybook is under constant development, and prerelease versions are published almost daily. It is best to try out new features before they are generally available.

Execute the Migration Script

The upgrade script is the most common way to upgrade Storybook, and we use it to walk through the migration process.

At root, execute the script:

$ npx storybook@latest upgrade

The script runs in a few steps.

Install Storybook 7 packages

The upgrade script checks for the latest versions of storybook packages and displays what needs to be upgraded:

info  @storybook/addon-actions     ^6.5.16  →  ^7.0.11
info  @storybook/addon-essentials  ^6.5.16  →  ^7.0.11
info  @storybook/addon-links       ^6.5.16  →  ^7.0.11
info  @storybook/addons            ^6.5.16  →  ^7.0.11
info  @storybook/react             ^6.5.16  →  ^7.0.11
info  @storybook/theming           ^6.5.16  →  ^7.0.11
info  eslint-plugin-storybook      ^0.6.11  →  ^0.6.12
info  storybook-dark-mode           ^2.1.1  →   ^3.0.0

It finds several outdated packages in yarn.lock, and prints out the warning messages:

WARN Found 5 outdated packages (relative to '@storybook/addon-actions@7.0.11')
WARN Please make sure your packages are updated to ensure a consistent experience.
WARN - @storybook/core-common@6.5.16
WARN - @storybook/core-events@6.5.16
WARN - @storybook/core@6.5.16
WARN - @storybook/node-logger@6.5.16
WARN - @storybook/telemetry@6.5.16
🔎 checking possible migrations..

The devDpendencies in package.json are upgraded to the following:

"devDependencies": {
  "@storybook/addon-actions": "^7.0.11",
  "@storybook/addon-essentials": "^7.0.11",
  "@storybook/addon-links": "^7.0.11",
  "@storybook/addons": "^7.0.11",
  "@storybook/core": "^6.5.16",
  "@storybook/react": "^7.0.11",
  "@storybook/theming": "^7.0.11",
  "eslint-plugin-storybook": "^0.6.12",
  "storybook-dark-mode": "^3.0.0"
}

Have you noticed that @storybook/core is not upgraded? We need to handle it manually.

Install storybook binary and upgrade scripts

Storybook 6 has binaries called start-storybook and build-storybook. In Storybook 7, these binaries are removed and replaced by a new CLI command, storybook. storybook dev starts a Storybook and storybook build builds a Storybook.

storybook looks for the framework field in main.js, and use it to determine how to start or build a Storybook. The benefit of this change is that it is now possible to install multiple frameworks in a project without worrying about hoisting issues.

The upgrade script first asks whether to install the storybook binary.

🔎 found a 'storybook-binary' migration:
╭ Automigration detected ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│                                                                                                                                            │
│   We've detected you are using Storybook 7.0.11 without Storybook's storybook binary. Starting in Storybook 7.0, it has to be installed.   │
│                                                                                                                                            │
│                                                                                                                                            │
│   More info: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#start-storybook--build-storybook-binaries-removed             │
│                                                                                                                                            │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
✔ Do you want to run the 'storybook-binary' migration on your project? (Y/n)

Type y, and it runs the storybook-binary migration.

storybook is added to devDpendencies in package.json:

"devDependencies": {
  "storybook": "^7.0.11"
}

Then, the upgrade script asks whether to fix Storybook scripts in package.json.

🔎 found a 'sb-scripts' migration:
╭ Automigration detected ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│                                                                                                                                               │
│   We've detected you are using Storybook 7.0.11 with scripts from previous versions of Storybook.                                             │
│   Starting in Storybook 7, the start-storybook and build-storybook binaries have changed to storybook dev and storybook build respectively.   │
│   In order to work with Storybook 7.0.11, your storybook scripts have to be adjusted to use the binary. We can adjust them for you:           │
│                                                                                                                                               │
│   build-storybook                                                                                                                             │
│   from:                                                                                                                                       │
│   build-storybook                                                                                                                             │
│   to:                                                                                                                                         │
│   storybook build                                                                                                                             │
│                                                                                                                                               │
│   storybook                                                                                                                                   │
│   from:                                                                                                                                       │
│   start-storybook -p 6006                                                                                                                     │
│   to:                                                                                                                                         │
│   storybook dev -p 6006                                                                                                                       │
│                                                                                                                                               │
│   In case this migration did not cover all of your scripts, or you'd like more info:                                                          │
│   https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#start-storybook--build-storybook-binaries-removed                           │
│                                                                                                                                               │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
✔ Do you want to run the 'sb-scripts' migration on your project? (Y/n)

Type y, and it runs the sb-scripts migration.

scripts in package.json are updated to use the new CLI command.

"scripts": {
  "build-storybook": "storybook build",
  "storybook": "storybook dev -p 6006",
}

Configure frameworks

Storybook 7 introduces the concept of frameworks, which abstracts configuration for renderers (React, Vue, etc.), builders (Webpack, Vite, etc.), and defaults to make integrations easier.

In Storybook 7, framework combines a renderer and a builder, with the exception of a few packages that do not contain multiple builders, such as @storybook/angular, which only has Webpack 5 support.

Here is the list of frameworks:

  • @storybook/angular (same as Storybook 6)
  • @storybook/ember (same as Storybook 6)
  • @storybook/html-vite
  • @storybook/html-webpack5
  • @storybook/preact-vite
  • @storybook/preact-webpack5
  • @storybook/react-vite
  • @storybook/react-webpack5
  • @storybook/nextjs
  • @storybook/server-webpack5
  • @storybook/svelte-vite
  • @storybook/svelte-webpack5
  • @storybook/sveltekit
  • @storybook/vue-vite
  • @storybook/vue-webpack5
  • @storybook/vue3-vite
  • @storybook/vue3-webpack5
  • @storybook/web-components-vite
  • @storybook/web-components-webpack5

The upgrade script asks whether to set up frameworks:

🔎 found a 'new-frameworks' migration:
╭ Automigration detected ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│                                                                                                                                                             │
│   We've detected your project is not fully setup with Storybook's 7 new framework format.                                                                   │
│                                                                                                                                                             │
│   Storybook 7 introduced the concept of frameworks, which abstracts configuration for renderers (e.g. React, Vue), builders (e.g. Webpack, Vite) and        │
│   defaults to make integrations easier.                                                                                                                     │
│                                                                                                                                                             │
│   Your project should be updated to use Storybook's framework: @storybook/react-webpack5. We can attempt to do this for you automatically.                  │
│                                                                                                                                                             │
│   Here are the steps this migration will do to migrate your project:                                                                                        │
│   - Add the following dependencies:                                                                                                                         │
│   - * @storybook/react-webpack5                                                                                                                             │
│   - Update or specify the framework field in .storybook/main.js with the value of "@storybook/react-webpack5".                                              │
│                                                                                                                                                             │
│                                                                                                                                                             │
│   To learn more about the new framework format, see: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#new-framework-api                      │
│                                                                                                                                                             │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
? Do you want to run the 'new-frameworks' migration on your project? › (Y/n)

Type y, and it runs the new-frameworks migration.

Since this is a React application, @storybook/react-webpack5 is added to devDpendencies in package.json:

"devDependencies": {
  "@storybook/react-webpack5": "^7.0.11"
}

main.js has been updated with the framework field:

module.exports = {
  ...
  framework: {
    name: "@storybook/react-webpack5",
    options: {}
  }
};

Migrate MDX1 to MDX2

MDX allows using JSX inside markdown content. It can import components, such as interactive charts or alerts, and embed them within a markdown file. Version 2 of MDX (MDX2) was released on February 1, 2022, with better performance and improved syntax. It supports any JSX runtime, including React, Preact, Vue, Emotion, etc.

Storybook 7 uses MDX2 instead of MDX1 to write .stories.mdx files that define and document stories. The upgrade script detects that we have 84 .stories.mdx files, and asks whether to migrate them to MDX2:

🔎 found a 'mdx1to2' migration:
╭ Automigration detected ──────────────────────────────────────────────────────────────────────────────────────────╮
│                                                                                                                  │
│   We've found 84 '.stories.mdx' files in your project.                                                           │
│                                                                                                                  │
│   Storybook has upgraded to MDX2 (https://mdxjs.com/blog/v2/), which contains breaking changes from MDX1.        │
│   We can try to automatically upgrade your MDX files to MDX2 format using some common patterns.                  │
│                                                                                                                  │
│   After this install completes, and before you start Storybook, we strongly recommend reading the MDX2 section   │
│   of the 7.0 migration guide. It contains useful tools for detecting and fixing any remaining issues.            │
│                                                                                                                  │
│   https://storybook.js.org/migration-guides/7.0                                                                  │
│                                                                                                                  │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
? Do you want to run the 'mdx1to2' migration on your project? › (Y/n)

Type y, and it runs the mdx1to2 migration.

Actually, for these 84 files, no changes have been made.

Configure autodocs option

Writing stories directly in MDX (.stories.mdx files) has been deprecated in Storybook 7. It is recommended to document stories with simple .mdx, and write stories in CSF3, an improved version of CSF2, that requires writing stories with objects.

Previously, Docs is a tab next to Canvas, which renders each story in docs view mode.

Image by author

In Storybook7, autodocs adds additional sidebar entries for stories.

Image by author

autodocs can be configured in main.js, and the folder name is configurable.

module.exports = {
  docs: {
    autodocs: true, 
    defaultName: 'Docs'
  }
};

autodocs can take the following three values:

  • true: It automatically creates docs for every story file.
  • false: It never creates docs.
  • tag: It only creates docs for story files with the autodocs tag. Here is an example:
export default {
  component: MyComponent,
  tags: ['autodocs']
}

The upgrade script asks whether to set autodocs to true:

🔎 found a 'autodocsTrue' migration:
╭ Automigration detected ────────────────────────────────────────────────────────────────────────────╮
│                                                                                                    │
│   We've changed the configuration of autodocs (previous docsPage), so now the value:               │
│     - docs.autodocs: true -- means automatically create docs for every CSF file                    │
│     - docs.autodocs: 'tag' -- means only create autodocs for CSF files with the 'autodocs' tag     │
│     - docs.autodocs: false -- means never create autodocs                                          │
│                                                                                                    │
│   Based on your prior configuration,  we can set the `docs.autodocs` to keep your old behaviour:   │
│                                                                                                    │
│   docs: { autodocs: true }                                                                         │
│                                                                                                    │
│   More info: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#autodocs-changes      │
│                                                                                                    │
╰────────────────────────────────────────────────────────────────────────────────────────────────────╯
? Do you want to run the 'autodocsTrue' migration on your project? › (Y/n)

Type y, and it runs the autodocsTrue migration.

main.js has been updated with the docs field:

module.exports = {
  ...
  framework: {
    name: "@storybook/react-webpack5",
    options: {}
  },
  docs: {
    autodocs: true
  }
};

Create root’s babel settings

Babel is a toolchain that is mainly used to convert ECMAScript 2015+ and/or TypeScript code into a backward-compatible version of JavaScript in current and older browsers or environments. Storybook 7 now uses Babel mode v7 exclusively. By default, babelModeV7 is set to true in main.js. Because it is the default value, the setting is not necessary.

module.exports = {
  features: {
    babelModeV7: true
  }
}

Storybook reads the project’s babel configuration, .babelrc.json, babel.config.js, etc.

The upgrade script asks whether to create a .babelrc.json file with some basic configuration and add any necessary packages to devDependencies.

🔎 found a 'missing-babelrc' migration:
╭ Automigration detected ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│                                                                                                                                                             │
│   We detected that your project does not have a babel configuration (.babelrc, babel.config.js, etc.).                                                      │
│                                                                                                                                                             │
│   In version 6.x, Storybook provided its own babel settings out of the box. Now, Storybook re-uses your project's babel configuration, with small,          │
│   incremental updates from Storybook addons.                                                                                                                │
│                                                                                                                                                             │
│   If your project does not have a babel configuration file, we can generate one that's equivalent to the 6.x defaults for you. Keep in mind that this can   │
│   affect your project if it uses babel, and you may need to make additional changes based on your projects needs.                                           │
│                                                                                                                                                             │
│   Note: This automatic setup doesn't work in a monorepo, see the babel documentation for how to setup babel manually:                                       │
│   https://babeljs.io/docs                                                                                                                                   │
│                                                                                                                                                             │
│   We can create a .babelrc.json file with some basic configuration and add any necessary package devDependencies.                                           │
│                                                                                                                                                             │
│   Please see the migration guide for more information:                                                                                                      │
│   https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#babel-mode-v7-exclusively                                                                 │
│                                                                                                                                                             │
│                                                                                                                                                             │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
? Do you want to run the 'missing-babelrc' migration on your project? › (Y/n)

Type y, and it runs the missing-babelrc migration that asks whether to add @babel/preset-env, @babel/preset-typescript, and @babel/preset-react.

✔ Do you want to add the TypeScript preset? … yes
✔ Do you want to add the React preset? … yes
info Writing file to root/.babelrc.json
✔ Shall we install the required dependencies now? (@babel/preset-env, @babel/preset-typescript, @babel/preset-react) … yes
  • @babel/preset-env: It is a smart preset to use the latest JavaScript without micromanaging syntax transforms.
  • @babel/preset-typescript: It is a recommended preset for TypeScript.
  • @babel/preset-react: It is a recommended preset for React. Starting from Babel mode v7, @babel/preset-react does not include @babel/preset-flow.

Here is the generated .babelrc.json:

{
  "sourceType": "unambiguous",
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "chrome": 100
        }
      }
    ],
    "@babel/preset-typescript",
    "@babel/preset-react"
  ],
  "plugins": []
}

Summary and followup

At the end, the upgrade script prints out a summary:

╭ Migration check ran successfully ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│                                                                                                                                                             │
│   Successful migrations:                                                                                                                                    │
│                                                                                                                                                             │
│   storybook-binary, sb-scripts, new-frameworks, mdx1to2, autodocsTrue, missing-babelrc                                                                      │
│                                                                                                                                                             │
│   ─────────────────────────────────────────────────                                                                                                         │
│                                                                                                                                                             │
│   If you'd like to run the migrations again, you can do so by running 'npx storybook@next automigrate'                                                      │
│                                                                                                                                                             │
│   The automigrations try to migrate common patterns in your project, but might not contain everything needed to migrate to the latest version of            │
│   Storybook.                                                                                                                                                │
│                                                                                                                                                             │
│   Please check the changelog and migration guide for manual migrations and more information: https://storybook.js.org/migration-guides/7.0                  │
│   And reach out on Discord if you need help: https://discord.gg/storybook                                                                                   │
│                                                                                                                                                             │
│   ─────────────────────────────────────────────────                                                                                                         │
│                                                                                                                                                             │
│   Attention: The following dependencies are duplicated which might cause unexpected behavior:                                                               │
│                                                                                                                                                             │
│   @storybook/core-server:                                                                                                                                   │
│   7.0.11, 6.5.16                                                                                                                                            │
│                                                                                                                                                             │
│   @storybook/core-common:                                                                                                                                   │
│   7.0.11, 6.5.16                                                                                                                                            │
│                                                                                                                                                             │
│   @storybook/csf-tools:                                                                                                                                     │
│   7.0.11, 6.5.16                                                                                                                                            │
│                                                                                                                                                             │
│   @storybook/node-logger:                                                                                                                                   │
│   7.0.11, 6.5.16                                                                                                                                            │
│                                                                                                                                                             │
│   @storybook/telemetry:                                                                                                                                     │
│   7.0.11, 6.5.16                                                                                                                                            │
│                                                                                                                                                             │
│   You can find more information for a given dependency by running yarn why <package-name>                                                                   │
│                                                                                                                                                             │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

This shows the scripts executed: storybook-binary, sb-scripts, new-frameworks, mdx1to2, autodocsTrue, and missing-babelrc.

There is a warning about @storybook 6.5.16 packages, and we need to remove @storybook/core from devDpendencies in package.json.

"devDependencies": {
  "@storybook/addon-actions": "^7.0.11",
  "@storybook/addon-essentials": "^7.0.11",
  "@storybook/addon-links": "^7.0.11",
  "@storybook/addons": "^7.0.11",
  "@̶s̶t̶o̶r̶y̶b̶o̶o̶k̶/c̶o̶r̶e̶":̶ "^̶6̶.5̶.1̶6̶",
  "@storybook/react": "^7.0.11",
  "@storybook/theming": "^7.0.11"
}

Re-execute npx storybook@latest upgrade, and skip the mdx1to2 migration:

╭ Migration check ran successfully ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│                                                                                                                                                             │
│   Skipped migrations:                                                                                                                                       │
│                                                                                                                                                             │
│   mdx1to2                                                                                                                                                   │
│                                                                                                                                                             │
│   ─────────────────────────────────────────────────                                                                                                         │
│                                                                                                                                                             │
│   If you'd like to run the migrations again, you can do so by running 'npx storybook@next automigrate'                                                      │
│                                                                                                                                                             │
│   The automigrations try to migrate common patterns in your project, but might not contain everything needed to migrate to the latest version of            │
│   Storybook.                                                                                                                                                │
│                                                                                                                                                             │
│   Please check the changelog and migration guide for manual migrations and more information: https://storybook.js.org/migration-guides/7.0                  │
│   And reach out on Discord if you need help: https://discord.gg/storybook                                                                                   │
│                                                                                                                                                             │
│   ─────────────────────────────────────────────────                                                                                                         │
│                                                                                                                                                             │
│   You can find more information for a given dependency by running yarn why <package-name>                                                                   │
│                                                                                                                                                             │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

The summary shows no more warnings.

Fix MDX2 Issues

Storybook 7 uses MDX2 by default for rendering Docs. MDX1 can be used, but it is not recommended.

However, enabling the legacy MDX1 can be a quick fix, if you see the canvas displays Template.bind() or ReferenceError: action is not defined.

Install the package @storybook/mdx1-csf:

% yarn add @storybook/mdx1-csf

Set the flag legacyMdx1 to be true in main.js:

module.exports = {
  features: {
    legacyMdx1: true
  }
}

Then, your MDX1 stories continue working.

MDX2 stories are preferred over MDX1 stories. The upgrade from MDX1 to MDX2 is not fully automated due to many changes between versions. Execute the following command to detect errors:

$ npx @hipster/mdx2-issue-checker

Here are some examples of what to fix:

"Actions for {object name}" => "Actions for \{object name\}" 
The number cannot exceed 7 (<7) => The number cannot exceed 7 (`<7`)
const count = 1; => export const count = 1;

There is also this warning during migration.

info => Loading presets
WARN The "@storybook/addon-mdx-gfm" addon is meant as a migration assistant for Storybook 7.0; and will likely be removed in a future version.
WARN It's recommended you read this document:
WARN https://storybook.js.org/docs/react/writing-docs/mdx#lack-of-github-flavored-markdown-gfm
WARN 
WARN Once you've made the necessary changes, you can remove the addon from your package.json and storybook config.

@storybook/addon-mdx-gfm is meant as a migration assistant for Storybook 7.0, providing Github Flavored Markdown support to Storybook docs. Once the necessary changes are made, this addon can be removed from package.json and storybook configuration. The removal needs to be done manually.

Migrate main.js and preview.js to TypeScript

Storybook 7 supports TypeScript for main and preview files. We manually migrate them to TypeScripts.

Here is root/.storybook/main.ts:

import { StorybookConfig } from '@storybook/react-webpack5';

const config: StorybookConfig = {
  stories: ['./*.story.@(js|jsx|ts|tsx)'],
  addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-mdx-gfm'],
  refs: {
    components: {
      title: 'components',
      url: 'http://localhost:6007'
    },
    apps: {
      title: 'apps',
      url: 'http://localhost:6008'
    }
  },
  framework: {
    name: '@storybook/react-webpack5',
    options: {fastRefresh: true}
  },
  docs: {
    autodocs: true
  }
};

Here is root/.storybook/preview.ts:

import { Preview } from '@storybook/react';

const preview: Preview = {
  parameters: {
    actions: {argTypesRegex: '^on[A-Z].*'},
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/,
      },
    },
  }
};

export default preview;

At this point, we have finished the migration at root.

Upgrade the Component Storybook

With the monorepo, we have to make manual changes to the Storybook in root/components.

It takes a few steps to make the changes.

Rename and modify main.ts

Rename root/components/.storybook/main.js to root/components/.storybook/main.ts. Adopt the changes in root/.storybook/main.ts to root/components/.storybook/main.ts.

import { StorybookConfig } from '@storybook/react-webpack5';

// find stories
const stories = ...

const config: StorybookConfig = {
  stories,
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    'storybook-dark-mode'
  ],
  webpackFinal: async (config) => ({
    ...config,
    devtool: 'eval-source-map'
  }),
  framework: {
    name: "@storybook/react-webpack5",
    options: { fastRefresh: true },
  },
  docs: {
    autodocs: true
  },
  features: {
    storyStoreV7: false
  }
};

export default config;

In the configuration above, storyStoreV7 is set to false, as we still have legacy stories that use storiesOf.

There are still warnings, but these stories work for the time being.

In SB7, we use the next-generation `storyStoreV7` by default, which does not support `storiesOf`. 
More info, with details about how to opt-out here: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#storystorev7-enabled-by-default
Unexpected `storiesOf` usage: (line 38, col 0).

Rename and modify preview.jsx

Rename root/components/.storybook/preview.jsx to root/components/.storybook/preview.tsx. Adopt the changes in root/.storybook/preview.ts to root/components/.storybook/preview.tsx:

import * as React from 'react';
import { Preview } from '@storybook/react';
import 'antd/dist/antd.css';
import '@ant-design/compatible/assets/index.css';
import { useDarkMode } from 'storybook-dark-mode';
import ProductThemeProvider from '../src/styled/ProductThemeProvider';
import './style.css';

const preview: Preview = {
  parameters: {
    darkMode: {
      stylePreview: true,
      classTarget: 'body',
      darkClass: 'darkClass',
      lightClass: 'lightClass',
    },
    actions: {argTypesRegex: '^on[A-Z].*'},
    controls: {
      expanded: true,
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/,
      },
    },
    options: {
      storySort: {
        order:
          [
            // ordered stories
          ]  
      },
    },
  }
};

export default preview;

const withThemeProvider = (Story, context) => {
  const [someState, useSomeState] = React.useState();
  return (
    <ProductThemeProvider isDarkMode={useDarkMode()} isStorybook={true} someState={someState}>
      <Story {...context} useSomeState={useSomeState}/>
    </ProductThemeProvider>
  );
};
export const decorators = [withThemeProvider];

Upgrade scripts in root/components/package.json

Update scripts in root/components/package.json with the CLI command, storybook, to start and build a Storybook.

"scripts": {
  "storybook": "../../node_modules/.bin/storybook dev -p 6007",
  "build-storybook": "../../node_modules/.bin/storybook build"
}

Create .babelrc.json and fix the build

We have upgraded main, preview, and scripts.

Execute yarn storybook, and it is not able to compile TypeScript:

ModuleBuildError: Module build failed (from ../../node_modules/@storybook/builder-webpack5/node_modules/babel-loader/lib/index.js):
SyntaxError: root/components/src/components/ProductComp.tsx: Unexpected token, expected "," (12:25)

  10 |   }
  11 | `;
> 12 | const getOptions = (products: Product[]) => {
     |                          ^

.babelrc.json is the rescue. Copy root/.babelrc.json to root/components/.babelrc.json.

{
  "sourceType": "unambiguous",
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "chrome": 100
        }
      }
    ],
    "@babel/preset-typescript",
    "@babel/preset-react"
  ],
  "plugins": []
}

The Babel configuration has resolved the TypeScript compilation issue, but it shows a new error:

ModuleNotFoundError: Module not found: Error: Can't resolve 'stream' in 'root/node_modules/csv-parse/lib/es5'

BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.

If you want to include a polyfill, you need to:
        - add a fallback 'resolve.fallback: { "stream": require.resolve("stream-browserify") }'
        - install 'stream-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
        resolve.fallback: { "stream": false }

It suggests installing stream-browserify. In fact, we have stream-browserify installed:

$ yarn why stream-browserify
...
=> Found "[email protected]"
info Reasons this module exists
   - "webpack#node-libs-browser" depends on it
   - Hoisted from "webpack#node-libs-browser#stream-browserify"

Let’s add stream-browserify into the fallback configuration in main.ts:

import { StorybookConfig } from '@storybook/react-webpack5';

// find stories
const stories = ...

const config: StorybookConfig = {
  stories,
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    'storybook-dark-mode'
  ],
  webpackFinal: async (config) => {
    const fallback = config.resolve.fallback || {};
    Object.assign(fallback, {
      stream: require.resolve("stream-browserify"),
    });
    config.resolve.fallback = fallback;
    return ({
      ...config,
      devtool: 'eval-source-map'
    })
  },
  framework: {
    name: "@storybook/react-webpack5",
    options: { fastRefresh: true },
  },
  docs: {
    autodocs: true
  },
  features: {
    storyStoreV7: false
  }
};

export default config;

In addition, we add @types/webpack-env, the type definition for Webpack, to devDpendencies in package.json to resolve further type errors that come from ../../node_modules/@storybook/builder-webpack5/node_modules/babel-loader/lib/index.js.

"devDependencies": {
  "@types/webpack-env": "^1.18.0"
}

Define types in tsconfig.json to include webpack-env:

"types": ["node", "webpack-env", ...]

The build is fixed. The component Storybook starts.

╭──────────────────────────────────────────────────╮
│                                                  │
│   Storybook 7.0.11 for react-webpack5 started    │
│   214 ms for manager and 48 s for preview        │
│                                                  │
│    Local:            http://localhost:6007/      │
│    On your network:  http://169.254.1.1:6007/    │
│                                                  │
╰──────────────────────────────────────────────────╯

Fix favicon and story decorators

Go to http://localhost:6007, and view the component Storybook:

Image by author

There are two issues:

  • The Storybook does not have a favicon, a small icon associated with a particular website or web page.
  • The Button component does not have associated styles.

We create the Storybook favicon at root/public/storybook.ico, and add root/components/.storybook/manager-head.html to include this icon:

<link rel="shortcut icon" href="/storybook.ico">

This fixes the favicon.

We have used decorators to apply styles by decorators = [withThemeProvider]. However, the decorators syntax has been changed in Storybook 7. Instead, decorators becomes a field of root/components/.storybook/preview.tsx:

const preview: Preview = {
  ...
  decorators: [
    (Story, context) => {
      const [someState, useSomeState] = React.useState();
      return (
        <ProductThemeProvider isDarkMode={useDarkMode()} isStorybook={true} someState={someState}>
          <Story {...context} useSomeState={useSomeState}/>
        </ProductThemeProvider>
      );
    }
  ],
};

This fixes the Button component styling.

At root/components, execute yarn storybook, and it works well.

Image by author

There is another issue for development. fetch-mock, a JavaScript library to mock HTTP requests, throws an error during hot reload: fetch-mock: No fallback response defined for GET to /runtime_main.b83bdb3a8714964066e2.hot-update.json.

Image by author

This can be fixed by setting fetchMock.config.fallbackToNetwork = true, although the setting was not needed for Storybook 6.

Because preview.tsx sets the global setting for decorators, parameters, and global types, we add the fetchMock setting in it:

import * as React from 'react';
import { Preview } from '@storybook/react';
import 'antd/dist/antd.css';
import '@ant-design/compatible/assets/index.css';
import { useDarkMode } from 'storybook-dark-mode';
import ProductThemeProvider from '../src/styled/ProductThemeProvider';
import './style.css';
import fetchMock from 'fetch-mock';
fetchMock.config.fallbackToNetwork = true;

const preview: Preview = {
  parameters: {
    darkMode: {
      stylePreview: true,
      classTarget: 'body',
      darkClass: 'darkClass',
      lightClass: 'lightClass',
    },
    actions: {argTypesRegex: '^on[A-Z].*'},
    controls: {
      expanded: true,
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/,
      },
    },
    options: {
      storySort: {
        order:
          [
            // ordered stories
          ]  
      },
    },
  },
  decorators: [
    (Story, context) => {
      const [someState, useSomeState] = React.useState();
      return (
        <ProductThemeProvider isDarkMode={useDarkMode()} isStorybook={true} someState={someState}>
          <Story {...context} useSomeState={useSomeState}/>
        </ProductThemeProvider>
      );
    }
  ],
};

export default preview;

Now, it works well for dev mode as well.

Upgrade the App Storybook

With the monorepo, we have to make manual changes to the Storybook in root/apps.

Similar to the component Storybook, we apply the following changes:

  • Rename and modify main.ts + Add the fallback configuration.
  • Rename and modify preview.jsx + Fix Story decorators + Add the fetchMock setting.
  • Upgrade scripts in root/components/package.json.
  • Create .babelrc.json.
  • Fix favicon.
  • Fix story decorators.

Execute yarn storybook, and we are not able to compile TypeScript. .babelrc.json needs to be renamed to babel.config.json:

{
  "sourceType": "unambiguous",
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "chrome": 100
        }
      }
    ],
    "@babel/preset-typescript",
    "@babel/preset-react"
  ],
  "plugins": []
}

What are the differences between .babelrc.json and babel.config.json?

  • .babelrc.json is a configuration that only applies to a single part of the project.
  • babel.config.json is a configuration for monorepo and it compiles node_modules.

Our application is hosted by a monorepo. Since root/components acts like a single repository, .babelrc.json is still working. However, root/apps links to common components via "@product/components": "link:../components. It has multiple package.json files, and babel.config.json should be used instead, although the content is the same.

At root/apps, execute yarn storybook.

╭──────────────────────────────────────────────────╮
│                                                  │
│   Storybook 7.0.11 for react-webpack5 started    │
│   306 ms for manager and 45 s for preview        │
│                                                  │
│    Local:            http://localhost:6008/      │
│    On your network:  http://169.254.1.1:6008/    │
│                                                  │
╰──────────────────────────────────────────────────╯

The Storybook works, and here’s what it looks like:

Image by author

Remove the Composition Storybook

Storybook composition allows us to browse components from any Storybook accessible via static URL or local URL.

Image by author

Here is root/.storybook/main.ts that we use to reference two local Storybooks:

import { StorybookConfig } from '@storybook/react-webpack5';

const config: StorybookConfig = {
  stories: ['./*.story.@(js|jsx|ts|tsx)'],
  addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-mdx-gfm'],
  refs: {
    components: {
      title: 'components',
      url: 'http://localhost:6007'
    },
    apps: {
      title: 'apps',
      url: 'http://localhost:6008'
    }
  },
  framework: {
    name: '@storybook/react-webpack5',
    options: {fastRefresh: true}
  },
  docs: {
    autodocs: true
  }
};

It does not work with the upgraded component Storybook and app Storybook.

We have built the composited Storybook to put multiple Storybooks into a single pane. However, it requires all local Storybooks to run. It is inconvenient and resource intensive. We have decided to remove it instead of fixing it.

Write CSF3 stories

From Storybook 7, it is recommended to write stories in CSF3, although MDX2 and CSF2 continue to work. As time goes on, we should migrate all existing stories to CSF3.

Here is an example of CSF3 story:

export default {
  title: 'Components/MyStory', // story path
  component: MyComponent, // React component
  ArgTypes: {
    [disabledName]: { // hide specific controls
      table: {
        disable: true,
      },
    },
  },
};

export const MyStory = { // define story
  render: (args: Props) => { 
    fetchMock.restore().mock(...); // configure mocks if needed
    return <MyComponent {...args}/>;
  },
  args: {
    [controlName]: {...} // configure all controls
  }
};

Conclusion

We have walked through the nine-step migration process on how to upgrade multiple Storybooks to version 7 in a monorepo. There are problems and solutions. Hopefully, our experience is helpful to your applications.

Thanks for reading.

Thanks, Sushmitha Aitha, S Sreeram, Rajasekhar Gandavarapu, Urian Chang, and Richard Tom for working with me on the Domino products.

Want to Connect?

If you are interested, check out my directory of web development articles.
Programming
Storybook
Babel
React
Web Development
Recommended from ReadMedium