【バージョン7.0先取り】Beta版になったStorybook 7.0のVite×React環境における変更

# はじめに
[Storybook](https://storybook.js.org/)を用いてフロントエンドの開発を行うことはとても体験がよく重宝しています。そんなStorybookの最新バージョンである7.0のベータ版が[昨夜(2022/12/8)リリース](https://github.com/storybookjs/storybook/releases/tag/v7.0.0-beta.0)されました。
この記事ではVite×Reactに関係するStorybook 7.0の機能について解説を行います。
Storybookの7.0への移行ガイドは[こちら](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#from-version-65x-to-700)になります。
この記事はBeta版での解説ですのでリリース時には異なる内容になっている可能性があります。

# Viteを使ったStorybookの環境構築
storybookでvite環境を作るには一つのコマンドだけが必要です。以下のコマンドを入力すると最新バージョンの環境が手に入ります。
```
npx sb@latest init --builder=vite
```
これによって`.storybook`フォルダが作成され、`main.cjs`と`preview.cjs`と`preview-head.html`が生成されます。またサンプルとして`src`配下に`stories`ディレクトリが生成され、ボタンなどのコンポーネントとstoryが準備されます。どの様なファイルが含まれていて、内容がどうなっているかはこの記事には大きく関係しないので自分で構築して確かめて下さい。

# 7.0と比較
先ほどのコマンドによって生成されたファイルを7.0のものと比較します。執筆当時の最新バージョンである6.5.14と最新のベータバージョンである7.0.0-beta.0比較を行います。最新のベータバージョンはnextを指定することで得られます。
```
npx sb@next init --builder=vite
```
変更は3点です。main.cjsと各storiesファイル、storybookのscriptsコマンドです。

## main.cjs
6.5.14では以下のようになっています。
```js:latest
module.exports = {
  "stories": [
    "../src/**/*.stories.mdx",
    "../src/**/*.stories.@(js|jsx|ts|tsx)"
  ],
  "addons": [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-interactions"
  ],
  "framework": "@storybook/react",
  "core": {
    "builder": "@storybook/builder-vite"
  },
  "features": {
    "storyStoreV7": true
  }
}
```
7.0.0-beta.0では以下のようになっています。pathは使っていないので以後考えないことにします(消しても良いと思います)。
```js:next
const path = require('path');
module.exports = {
  "stories": [
    "../src/**/*.mdx",
    "../src/**/*.stories.@(js|jsx|ts|tsx)"
  ],
  "addons": [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-interactions"
  ],
  "framework": {
    "name": "@storybook/react-vite",
    "options": {}
  },
  "docs": {
    "docsPage": true
  }
}
```
変更はこの部分です。
```diff
-  "framework": "@storybook/react",
-  "core": {
-    "builder": "@storybook/builder-vite"
-  },
-  "features": {
-    "storyStoreV7": true
-  }
+  "framework": {
+    "name": "@storybook/react-vite",
+    "options": {}
+  },
+  "docs": {
+    "docsPage": true
+  }
```
フレームワークが`@storybook/react`から`@storybook/react-vite`になり、`core`の設定が消えました。これはframworkにUIライブラリとbuilderを組み合わせた値を渡せるようになり、まとめて記述することになったからです。
次に`features`が消えました。7.0の機能を先取りする機能みたいなものなので、7.0になったら消えるのが当然のものです。
最後に`docs`が追加されています。7.0ではDocs周りの機能が大きく変わっています。これまではStoryを記述すれば自動的にDocsも記述されて、CanvasとDocsを切り替えることでDocsの確認をすることができました。
![スクリーンショット 2022-12-08 18.41.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591069/d6dbf183-451a-a46c-4a54-1cff56f510ba.png)
7.0ではDocsの自動生成がデフォルトではされなくなりました。従来通り記述させたい場合は`docsPage`を`"automatic"`に設定することで自動的に生成されるようになります。さらに、画面上部で行うDocsとCanvasの切り替えではなくなりコンポーネントの切り替えと同じように切り替えるようになりました。
![スクリーンショット 2022-12-08 18.44.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591069/0ab9a74a-841b-6b0d-6d21-fd540cb82779.png)
`docsPage`を`false`にすると完全にDocsが生成されなくなるので注意して下さい。デフォルトになった`true`の時のDocsの生成方法はstoriesファイルの説明の時に行います。

## stories
storyの書き方は大きく変わりました。CSF2の記法だったものからCSF3に移行しました。まずは見比べてみましょう。まずは6.5.14の書き方です。
```tsx:Button.stories.tsx
import { ComponentStory, ComponentMeta } from '@storybook/react';

import { Button } from './Button';

export default {
  title: 'Example/Button',
  component: Button,
} as ComponentMeta<typeof Button>;

const Template: ComponentStory<typeof Button> = (args) => <Button {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: 'Button',
};
```
次に7.0.0-beta.0の書き方です。
```ts:Button.stories.ts
import type { Meta, StoryObj } from '@storybook/react';

import { Button } from './Button';

const meta: Meta<typeof Button> = {
  title: 'Example/Button',
  component: Button,
};
export default meta;

export const Primary: StoryObj<typeof Button> = {
  args: {
    primary: true,
    label: 'Button',
  },
};
```
あまりにも変更が大きいですね。7.0.0-beta.0の方が記述が簡素になっていることがわかると思います。
まず、7.0ではComponentMetaやStory、ComponentStory、ComponentStoryFn、ComponentStoryObjが非推奨になってしまいました。それぞれMeta、StoryFn、StoryObjに置き換える必要があります(ComponentMeta=>Meta、Story=>StoryFn、ComponentStory=>StoryObj、ComponentStoryFn=StoryFn、ComponentStoryObj=StoryObj)。
次に`meta`のdefault exportの方法が変わりました。これは元の記法でも動きますので変更する必要はないです。私はasを使わない新記法が好みです。型はComponentMetaからMetaに変える必要があります。
最後にStoryの記法が大きく変わりました。TemplateのようにComponentStoryを別で定義する必要はなくなり、必要なオブジェクトを定義した`StoryObj`をnamed exportするだけでStoryの定義が可能になりました。例ではargsだけをキーに持ってますが、`play`や`decorators`などこれまで別々に宣言していたものが一つにまとめて宣言できるようになりました。
これらはCSF2からCSF3に移行するための変更ですので下のコマンドを用いてアップグレードできます。ぜひご利用ください。
```
npx sb@next migrate csf-2-to-3 --glob="**/*.stories.js"
```
`main.cjs`の設定で`docsPage`を`true`にした場合はStoryの設定ファイルでDocsの定義ができます。`meta`を以下のようにすることでそのStoryのDocsを生成させることができます。各Storyで定義するのが億劫でこれまで通りを望んでいる場合は`docPage`を`"automatic"`に変更して下さい。
```ts
const meta: Meta<typeof Button> = {
  title: 'Example/Button',
  component: Button,
  tags: ['docsPage'],
};
```

## コマンド
storybookではこれまで`start-storybook`、`build-storybook`コマンドを用いて起動、ビルドを行っていました。package.jsonにはこの様に書かれていました。
```json
{
  "scripts": {
    "storybook": "start-storybook -p 6006",
    "build-storybook": "build-storybook",
  }
}
```
7.0ではこれらのコマンドが一つに統一されます。
```json
{
  "scripts": {
    "storybook": "storybook dev -p 6006",
    "build-storybook": "storybook build"
  }
}
```
さらにNode17以降でStorybookを利用する際に必要だった`NODE_OPTIONS`である`--openssl-legacy-provider`を設定する必要がなくなりました。`NODE_OPTIONS=--openssl-legacy-provider npm run storybook`とする手間がなくなったので大変助かります。
## その他
他に関係ありそうな変更を紹介します。
### ルートタグのidの変更
storybookのコンポーネント表示を開始するタグのidをrootからstorybook-rootに変更されました。`button`のコンポーネントは下の様に変化しました。
```html:従来
<div id="root">
  <button>Submmit</button>
</div>
```

```html:7.0
<div id="storybook-root">
  <button>Submmit</button>
</div>
```
これによってidの競合による動作不良の頻度が減少しました(storybook-rootをidに指定することはほとんどアプリではないので)。

### IE１１非対応
Viteも基本非対応ですが`@vitejs/plugin-legacy`を使うなどして対応している場合もあるので書きました。storybookはこれまでIE11にも対応していましたが、IE11も含めレガシーバージョンブラウザへの対応をやめました。React18でもIE11の対応をやめているので今後も対応し続けるのがかなり難しくなってきた印象です。

### Color Mode
どこにも書いてませんでが、7.0からOSのLight/Dark Modeを読み取って自動でModeを設定してくれるようになりました。
同じ環境でも7.0よりも前と後のもので比べると以下のようになります。
![スクリーンショット 2022-12-08 21.26.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591069/6f82f4c6-07a3-1450-656f-6162463f5036.png)
![スクリーンショット 2022-12-08 21.26.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591069/df79e688-734e-e097-c464-f4d63f436c51.png)
実装しているアプリケーションによってはColor Modeを実装しておらず白背景に表示することを前提としているためにコンストラストの関係でコンポーネントが見えにくくなることあります。そうした場合は`preview.cjs`の`parameters`オブジェクトに下のように追加して下さい。表示されるコンポーネントの背景が白になるのでアプリケーションの表示通りに開発を行うことができます。
```js:preview.cjs
export const parameters = {
  backgrounds: {
    default: 'light'
  },
};
``` 

# ベータ版へのアップデート
これまでは新しく環境を作成してバージョンによる差分を紹介して比較してきました。しかし、多くのユーザーはすでに利用しているstorybookからのアップデートを行うことがほとんどです。アップデートを行う場合は以下のコマンドを入力します。
```
npx sb@next upgrade --prerelease
```
入力するとコマンドラインでファイルの更新についていくつか聞かれます。実行コマンドの書き換え、`main.cjs`の`framework`の書き換え、mdxを使っている場合はmd1からmd2への書き換え、Docsの自動生成のための設定(`docsPage: 'automatic'`)を行なってくれます。
このコマンドによる変化は`package.json`でstorybook類のバージョンアップとscriptsの書き換え、`main.cjs`の書き換えを行なってくれます。書き換え後の`main.cjs`は以下の通りです。
```js:main.cjs
module.exports = {
  "stories": ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
  "addons": ["@storybook/addon-links", "@storybook/addon-essentials", "@storybook/addon-interactions"],
  "framework": {
    name: "@storybook/react-vite",
    options: {}
  },
  "core": {},
  "features": {
    "storyStoreV7": true
  },
  docs: {
    docsPage: "automatic"
  }
};
```
coreやfeaturesは意味をなしていないので消しても構いません。自動で色々設定してくれるのでとても助かります。
削除後は`migrate csf-2-to-3`などでstoriesファイルをCSF2からCSF3に移行すれば完了です。