Closed36

yarn workspace で各 workspace の storybook の依存を root で管理したい

nbstshnbstsh

現状と課題

現状

  • yarn workspace を使った monorepo 環境で、各 workspace で storybook をセットアップしている。(= 全ての package.json に storybook 関連の依存が入った状態。)
  • yarn v3 使ってる

課題

  • storybook の version が workspace ごとに異なると、たびたびエラーが起きる。
  • そのため、storybook の version は全ての workspace で固定。
  • version を上げる際に全ての workspace で version を上げるのがめんどい。
nbstshnbstsh

project root でまとめてみる

とりあえず、何を project root にまとめたらいいかわからんので、storybook の初期化コマンド叩いて何が install されるのか調べる。

ちなみに、project 自体はシンプルな React の library。(CRA でも Vite でもない。)

npx storybook init

https://storybook.js.org/docs/react/get-started/install

install されたもの一覧↓

  "@babel/core": "^7.20.12",
  "@mdx-js/react": "^1.6.22",
  "@storybook/addon-actions": "^6.5.15",
  "@storybook/addon-docs": "^6.5.15",
  "@storybook/addon-essentials": "^6.5.15",
  "@storybook/addon-interactions": "^6.5.15",
  "@storybook/addon-links": "^6.5.15",
  "@storybook/builder-webpack4": "^6.5.15",
  "@storybook/manager-webpack4": "^6.5.15",
  "@storybook/react": "^6.5.15",
  "@storybook/testing-library": "^0.0.13",
  "babel-loader": "^8.3.0"

これらを project root の package.json に加える。

nbstshnbstsh

workspace から storybook の依存を取り除いて実行してみる

試しに、storybook を使っている一つの workspace から storybook 関連の依存を取り除いて動くか確かめる。

=> 動かなかった....

storybook を実行しようとすると、

yarn storybook
command not found: start-storybook

start-storybook 見つかりません、と出てくる...

nbstshnbstsh

そもそも start-storybook ってなんだ...?

start-storybook を叩いた時、何が起きてるのかわからんので調べる。

nbstshnbstsh

yarn bin で、コマンド叩いた時に実際に実行されてる binary がわかるのでこれで調べる。

https://yarnpkg.com/cli/bin

yarn bin 
path/to/my-project-root/node_modules/@storybook/react/bin/index.js

どうやら、node_modules 下の @storybook/react/bin/index.js が実行されてるみたいだな。

というか、start-storybook を叩くと @storybook/react/bin/index.js が実行されるのはなんでだ...?
どこかで設定してんのか...?
蛇足だけど調べるか...

nbstshnbstsh

command not found: start-storybook の原因を考える

とりあえず現状整理だな

  • workspace から storybook の依存を削除したことで、start-storybook コマンドを実行するための @storybook/react が workspace の node_modules から削除された
  • project root の node_modules には @storybook/react が存在する

package.json bin に設定されているコマンドに関しては、project root まで見てくれてないっぽいな...

やっぱりそうみたい↓

This command will run a tool. The exact tool that will be executed will depend on the current state of your workspace:

  • If the scripts field from your local package.json contains a matching script name, its definition will get executed.

  • Otherwise, if one of the local workspace's dependencies exposes a binary with a matching name, this binary will get executed.

  • Otherwise, if the specified name contains a colon character and if one of the workspaces in the project contains exactly one script with a matching name, then this script will get executed.

https://yarnpkg.com/cli/run

ということは、各 workspace から project root の node_modules に install されてる @storybook/react を利用するように指定できれば良さそう

できるのか...?

nbstshnbstsh

こちらを参照、とのこと

https://yarnpkg.com/getting-started/qa#how-to-share-scripts-between-workspaces

Little-known Yarn feature: any script with a colon in its name (build:foo) can be called from any workspace. Another little-known feature: $INIT_CWD will always point to the directory running the script. Put together, you can write scripts that can be reused this way:

: 使ってる script はどの workspace からも使えるんか...!
そして、$INIT_CWD で実行中の directory を指定できると。

project root で、

{
  "dependencies": {
    "typescript": "^3.8.0"
  },
  "scripts": {
    "g:tsc": "cd $INIT_CWD && tsc"
  }
}

g:tsc を定義していたら他の workspace で

{
  "scripts": {
    "build": "yarn g:tsc"
  }
}

このように実行できると。

他にも run -T で project root の node_modules を実行できるみたい。

{
  "scripts": {
    "build": "run -T tsc"
  }
}
nbstshnbstsh

project root の package.json で storybook 起動用のコマンドを準備して対応してみる

package.json
{
  "name": "root",
  "scripts": {
    "g:storybook": "cd $INIT_CWD && start-storybook"
  }
}

これで、別の workspace で yarn g:storybook とすれば、project root の package.json に記載した cd $INIT_CWD && start-storybook が実行される。

実際に試してみる。
packageA で yarn g:storybook を実行するコマンドを用意して実行。

packages/packageA/package.json
{
  "name": "packageA",
  "scripts": {
    "storybook": "yarn g:storybook",
  }
}
yarn storybook

いけた!!!!

nbstshnbstsh

他の storybook を利用する workspace の package.json stoybook コマンドを上記のものに差し替えたら完了!

nbstshnbstsh

v8 では project root に react, react-dom ないとエラーになる

project root の storybook を利用して workspace 内で start-storybook すると "Cannot find module 'react-dom/client'" のエラーが出る。

project root に react と react-dom を install してあげると治る。

package.json
  "devDependencies": {
    //...
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    //...
nbstshnbstsh

workspace の react version が project root と異なるとエラーになる

"Cannot read properties of null (reading 'useMemo')"

のようなエラーが起こる。hooks を使ってる story が軒並みエラーになる。

project root で install した react と同一 version にすれば解決する。

共通の ui package みたいな dependencies に react を持たず peerDependencies に react を指定しているような package では、開発時は project root の react が参照されるためこの問題は起きないはず。

nbstshnbstsh

別の方法

要は、project root の node_module 内の binary を叩ければいいので、以下のように直接叩いてあげてもいけた。

  "name": "packageA",
  "scripts": {
    "storybook": "$(yarn workspace root bin start-storybook)",
  }

yarn workspace ${workspace name (package.json の name)} で root の workspace を指定して、yarn bin start-storybook で start-storybook の binary の path を取得している。

https://yarnpkg.com/cli/workspace

https://yarnpkg.com/cli/bin

nbstshnbstsh
nbstshnbstsh

monorepo root の package.json に最新版の @storybook 関連の dependencies を追加する。
react + webpack5 の project で npx storybook@latest init を実行し、install されたものを参考にした。

    "@storybook/addon-essentials": "^7.0.24",
    "@storybook/addon-interactions": "^7.0.24",
    "@storybook/addon-links": "^7.0.24",
    "@storybook/blocks": "^7.0.24",
    "@storybook/react": "^7.0.24",
    "@storybook/react-webpack5": "^7.0.24",
    "@storybook/testing-library": "^0.0.14-next.2",
    "storybook": "^7.0.24",

storybook は storybook の start/dev を実行するための cli みたい。
今までは、@storybook/react の中に start-storybook, build-storybook 用の bin が含まれていたが、v7 からはこれらが削除され代わりに cli 用の package が用意された模様

詳しくはこちら↓
https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#start-storybook--build-storybook-binaries-removed

start-storybook command も置き換える。

{
  "name": "root",
  "scripts": {
-    "g:storybook": "cd $INIT_CWD && start-storybook"
+    "g:storybook": "cd $INIT_CWD && storybook dev"
  }
}
nbstshnbstsh

実行してみる

エラーでた

ERR! Error: Could not find a 'framework' field in Storybook config.
ERR!
ERR! Please run 'npx storybook@next automigrate' to automatically fix your config.
ERR!
ERR!   See the migration guide for more information:
ERR!   https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#new-framework-api
nbstshnbstsh

In SB7.0, we've removed these binaries and replaced them with new commands in Storybook's CLI: storybook dev and storybook build. These commands will look for the framework field in your .storybook/main.js config--which is now required--and use that to determine how to start/build your Storybook. The benefit of this change is that it is now possible to install multiple frameworks in a project without having to worry about hoisting issues.

https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#start-storybook--build-storybook-binaries-removed

Storybook 7 introduces the concept of frameworks, which abstracts configuration for renderers (e.g. React, Vue), builders (e.g. Webpack, Vite) and defaults to make integrations easier. This requires quite a few changes, depending on what your project is using. We recommend you to use the automigrations, but in case the command fails or you'd like to do the changes manually, here's a guide:

https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#new-framework-api

  • 今までは framework ごとの package (e.g. @storybook/react) に start と build の binary が梱包されていた
  • v7 から start とbuild を実行する binary は分離された

=> 別途 framework ごとの package を install して、config でどの framework を利用するのか指定してあげる必要がある

てな感じか

nbstshnbstsh

framework を設定する

いつの間にか storybook の config が ts で書けるようになっていたので、main.ts に設定していく。

.storybook/main.ts

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

const config: StorybookConfig = {
  framework: '@storybook/react-webpack5',
  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: ['@storybook/addon-essentials'],
  docs: {
    autodocs: 'tag',
  },
  staticDirs: ['../public'],
};

export default config;

@storybook/react-webpack5 から import した StorybookConfig 使うと、framework がちゃんと型付けされる↓

type FrameworkName = '@storybook/react-webpack5';
type BuilderName = '@storybook/builder-webpack5';
nbstshnbstsh

再度実行

実行自体はできた!

が、babel-loader の SyntaxError でたぞ
TypeScript を parse できてな感あるな...

nbstshnbstsh

That's correct. We changed the way Babel is handled. During the 7.0 automigration if you don't have a babelrc, the migration tool can automatically create one for you with Typescript support built in. If you don't choose that option, we don't support TS.

https://github.com/storybookjs/storybook/issues/22357#issuecomment-1532548058

v7 から storybook デフォの babelrc がなくなって、自前のものを参照してくれるようになったらしい。
そのかわり自前の babelrc で TS 用の setup をしておく必要がある。

ただし、babelrc がない状態で automigration をしてれば、自動で v6 系時の babelrc と同等のものを作成してくれるみたい。

nbstshnbstsh

automigrate command 実行

npx storybook@next automigrate

prompt が立ち上がって、いくつかの質問に答える必要がある。
今回は、babelrc の作成だけ行う。

✔ Do you want to run the 'missing-babelrc' migration on your project? … yes

これで、.babelrc.jsonが生成される↓

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

ついでに、babel 関連の以下の dependencies が install される↓

    "@babel/preset-env": "^7.22.5",
    "@babel/preset-react": "^7.22.5",
    "@babel/preset-typescript": "^7.22.5",
nbstshnbstsh

再度実行する

いけた!
v6 と比べると dev server の立ち上がりが段違いに早いな

╭──────────────────────────────────────────────────╮
│                                                  │
│   Storybook 7.0.24 for react-webpack5 started    │
│   1.09 s for manager and 1.7 min for preview     │
│                                                  │
│    Local:            http://localhost:6006/      │
│    On your network:  http://100.64.1.25:6006/    │
│                                                  │
╰──────────────────────────────────────────────────╯

nbstshnbstsh

まとめ

  1. project root の @storybook 関連の dependencies を更新
  2. 各 workspace の storybook config (main.js or main.ts) に framework を追加
  3. .babelrc.json を作成
  4. babel 関連の dependencies がない場合は install
nbstshnbstsh
nbstshnbstsh

init してみる

今回も、新規の react project 作って、npx storybook@latest init して様子を見る。

npx storybook@latest init

install された packages↓

"@chromatic-com/storybook": "^1.6.1",
"@storybook/addon-essentials": "^8.2.7",
"@storybook/addon-interactions": "^8.2.7",
"@storybook/addon-links": "^8.2.7",
"@storybook/addon-onboarding": "^8.2.7",
"@storybook/addon-webpack5-compiler-swc": "^1.0.5",
"@storybook/blocks": "^8.2.7",
"@storybook/react": "^8.2.7",
"@storybook/react-webpack5": "^8.2.7",
"@storybook/test": "^8.2.7",
"eslint-plugin-storybook": "^0.8.0",
"storybook": "^8.2.7",
nbstshnbstsh

v8 から新しく追加されてる packages

"@chromatic-com/storybook": "^1.6.1",
"@storybook/addon-onboarding": "^8.2.7",
"@storybook/addon-webpack5-compiler-swc": "^1.0.5",
"@storybook/test": "^8.2.7",

v8 からなくなっている packages

"@storybook/testing-library": "^0.0.14-next.2",
nbstshnbstsh

v8 から追加された新規 packages について

v8 init で新規追加されてた以下 packages についてみていく。

"@chromatic-com/storybook": "^1.6.1",
"@storybook/addon-onboarding": "^8.2.7",
"@storybook/addon-webpack5-compiler-swc": "^1.0.5",
"@storybook/test": "^8.2.7",
nbstshnbstsh

@chromatic-com/storybook

storybook で chromatic を利用するための addon。

https://www.chromatic.com/docs/visual-tests-addon/

"Visual Test" の addon panel が追加される↓

利用には chromatic でのアカウントが必要。
必須ではないので、chromatic を利用しないなら決して問題ない。

nbstshnbstsh

@storybook/test

The @storybook/test package contains utilities for testing your stories inside play functions.

storybook の play function による interaction test のための package。

https://storybook.js.org/docs/writing-tests/interaction-testing

In Storybook 8, @storybook/testing-library has been integrated to a new package called @storybook/test, which uses Vitest APIs for an improved experience. When upgrading to Storybook 8 with 'npx storybook@latest upgrade', you will get prompted and will get an automigration for the new package. Please migrate when you can.

https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#deprecated-storybooktesting-library-package

@storybook/testing-library の後釜。Vitest ベースになって experience 向上したとのこと。

nbstshnbstsh

方向性一旦整理

今回自分の project (webpack ベース) で行う v8 への migration でやること整理しておく。

  • @chromatic-com/storybook, @storybook/addon-onboarding は必須ではないので今回は無視
  • @storybook/testing-library は削除
  • @storybook/test 追加
  • @storybook/addon-webpack5-compiler-swc 追加
  • その他の storybook 関連の package は latest に upgrade
  • storybook config addons に "@storybook/addon-webpack5-compiler-swc"追加
nbstshnbstsh

"React is not defined" エラー

一通り上記の点を行ったのち、storybook を実行すると "React is not defined" エラー出た

利用している React は v18 で babel で transpile している。
storybook では @storybook/addon-webpack5-compiler-swc 利用しているので、swc で babel 相当の設定が必要そう。

babel では、@babel/preset-react で runtime "automatic" に設定してる。

.babelrc
{
  "presets": [
    "@babel/preset-typescript",
    ["@babel/preset-react", { "runtime": "automatic" }],
    "@babel/preset-env"
  ],
  //...
}

swc jsc.transform.react.runtime

Possible values: automatic, classic. This affects how JSX source code will be compiled.
Defauts to classic.
Use runtime: automatic to use a JSX runtime module (e.g. react/jsx-runtime introduced in React 17).
Use runtime: classic to use React.createElement instead - with this option, you must ensure that React is in scope when using JSX.

デフォが classic。"automatic" に設定すれば良さげ。

https://swc.rs/docs/configuration/compilation#jsctransformreactruntime

storybook config で swc の設定を変更

.storybook/main.ts
import type { Options } from '@swc/core';
import type { StorybookConfig } from '@storybook/your-framework';
//...

const config: StorybookConfig = {
  //...
    swc: (config: Options): Options => {
    return {
      ...config,
      jsc: {
        ...config.jsc,
        transform: {
          ...config.jsc?.transform,
          react: {
            runtime: 'automatic',
          },
        },
      },
    };
  },
};

export default config;

https://storybook.js.org/docs/api/main-config/main-config-swc

このスクラップは2023/01/16にクローズされました