はじめに
ついにStorybookのバージョン7がリリースされ最新版になりました。
この記事ではReact×Viteの環境でバージョン6から7にアップデートするまでに行ったことをまとめました。
公式が公開する移行方法はこちらになります。
これまでの書き方
今回私がアップデートしたプロジェクトの設定ファイルやStoryの書き方を紹介します。
アップデート前の設定ファイルは以下のようになっています。
const { loadConfigFromFile, mergeConfig } = require('vite');
const svgr = require('vite-plugin-svgr');
const path = require('path');
const configEnvServe = {
mode: 'development',
command: 'serve',
ssrBuild: false,
};
const configEnvBuild = {
mode: 'production',
command: 'build',
ssrBuild: false,
};
module.exports = {
stories: ['../src/components/**/*.stories.@(jsx|tsx)'],
addons: [
'@storybook/addon-a11y',
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
],
staticDirs: ['./public'],
framework: '@storybook/react',
core: {
builder: '@storybook/builder-vite',
},
features: {
storyStoreV7: true,
},
async viteFinal(config, { configType }) {
const isProduction = configType === 'PRODUCTION';
const { config: userConfig } = await loadConfigFromFile(
isProduction ? configEnvBuild : configEnvServe,
path.resolve(__dirname, '../vite.config.ts'),
);
return mergeConfig(config, {
...userConfig,
plugins: [svgr()],
build: {
...config.build,
sourcemap: !isProduction,
},
});
},
};
import React from 'react';
import { AppProvider } from './../src/providers/app';
export const decorators = [
(Story) => (
<AppProvider>
<Story />
</AppProvider>
),
];
Storyは基本的に以下のように書かれていました。
import type { ComponentMeta, ComponentStoryObj } from '@storybook/react';
import { SampleComponent } from './SampleComponent';
export default {
component: SampleComponent,
} as ComponentMeta<typeof SampleComponent>;
export const Default: ComponentStoryObj<typeof SampleComponent> = {};
バージョンアップ
まずは、storybookの一連のライブラリをアップデートしていきます。storybookにはアップデートを自動でおこなってくれるコマンドがあるのでそれを用いて行いました。
npx storybook upgrade
入力後Ok to proceed?
と聞かれるのでy
を返してパッケージのアップデートを進めていきます。
次にDo you want to run the 'storybook-binary' migration on your project?
と聞かれます。y
と返すと、storybook
という名前のライブラリがdevDependencies
に追加されます。
バージョン7ではstart-storybook
やbuild-storybook
が削除され、cli
はstorybook
から始まるコマンドに置き換わるためです。推奨される方法はstart-storybook
がstorybook dev
、build-storybook
がstorybook build
に変わりました。
そして、それらのコマンドの置き換えは次に聞かれるDo you want to run the 'sb-scripts' migration on your project?
でy
を返すと自動で置き換えられます。
Storybook6では一定のNodeバージョン以上で利用するとエラーが起き、Nodeのオプション--openssl-legacy-provider
を設定する必要がありました。そのため私のscriptsは以下のように設定されました。
"scripts": {
"storybook": "NODE_OPTIONS=--openssl-legacy-provider start-storybook",
"build-storybook": "NODE_OPTIONS=--openssl-legacy-provider build-storybook",
}
そのような設定が記載されていても検知してstorybook dev
とstorybook build
に置き換えてくれました。
"scripts": {
"storybook": "NODE_OPTIONS=--openssl-legacy-provider storybook dev",
"build-storybook": "NODE_OPTIONS=--openssl-legacy-provider storybook build",
}
置き換え後もNODE_OPTIONS=--openssl-legacy-provider
がついてきますが、Storybook7ではこれらのオプションが不要になったのでコマンド実行後に削除しました。
"scripts": {
"storybook": "storybook dev",
"build-storybook": "storybook build",
}
次に、Do you want to run the 'new-frameworks' migration on your project?
と聞かれますが、これもy
と返してください。これまではVite環境を構築するときに@storybook/builder-vite
を利用していましたが、これからはViteとReactと組み合わせた環境では@storybook/react-vite
を使うようになります(Vueだったら@storybook/vue-vite
のようにUIライブラリとビルドツールを組み合わたライブラリを利用するようになります)。
そのため質問にy
を返すことで@storybook/builder-vite
をアンインストールして@storybook/react-vite
をインストールします。さらに、バージョン7それらを用いた設定に.storybook/main.cjs
を自動で変更されます。
const {
loadConfigFromFile,
mergeConfig
} = require('vite');
const svgr = require('vite-plugin-svgr');
const path = require('path');
const configEnvServe = {
mode: 'development',
command: 'serve',
ssrBuild: false,
};
const configEnvBuild = {
mode: 'production',
command: 'build',
ssrBuild: false,
};
module.exports = {
stories: ['../src/components/**/*.stories.@(jsx|tsx)'],
addons: ['@storybook/addon-a11y', '@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-interactions'],
staticDirs: ['./public'],
framework: {
name: '@storybook/react-vite',
options: {}
},
features: {
storyStoreV7: true
},
async viteFinal(config, { configType }) {
const { config: userConfig } =
(await loadConfigFromFile(
isProduction ? configEnvBuild : configEnvServe,
path.resolve(__dirname, '../vite.config.ts'),
)) ?? {};
return mergeConfig(config, {
...userConfig,
plugins: [svgr()],
build: {
...config.build,
sourcemap: !isProduction
}
});
}
};
ここで行われた変更の調整は後ほど行います。これまではcore.builder
でviteを指定して、framework
ではreactを指定するようなバラバラな形式でしたが、バージョン7からはframework
で両者を組み合わせたライブラリを指定するようになりました。
その後、Do you want to run the 'github-flavored-markdown-mdx' migration on your project?
と聞かれます。今回移行したプロジェクトではmdxの利用を行っていなかったためn
としました。利用状況によってはy
とした方が良いと思います。
最後に、Do you want to run the 'autodocsTrue' migration on your project?
と聞かれます。これまでのバージョンではStoryのドキュメントは自動生成されてきましたが、バージョン7では自動生成をさせないことがデフォルトになりました。これまで通り生成して欲しい場合はy
を入力してください。以下のように.storybook/main.cjs
に追加されるはずです。
docs: {
autodocs: true
}
私はドキュメントを利用していなかったのでn
を入力しました(その場合何も変わりはありません)。'tag'
にすると、Storyごとに自動生成させるかどうかを指定させることができます。
以上がコマンドによる変更でした。
main.cjsの書き換え
コマンド実行後、main.cjs
は以下のようになりました。
const {
loadConfigFromFile,
mergeConfig
} = require('vite');
const svgr = require('vite-plugin-svgr');
const path = require('path');
const configEnvServe = {
mode: 'development',
command: 'serve',
ssrBuild: false,
};
const configEnvBuild = {
mode: 'production',
command: 'build',
ssrBuild: false,
};
module.exports = {
stories: ['../src/components/**/*.stories.@(jsx|tsx)'],
addons: ['@storybook/addon-a11y', '@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-interactions'],
staticDirs: ['./public'],
framework: {
name: '@storybook/react-vite',
options: {}
},
features: {
storyStoreV7: true
},
async viteFinal(config, { configType }) {
const { config: userConfig } =
(await loadConfigFromFile(
isProduction ? configEnvBuild : configEnvServe,
path.resolve(__dirname, '../vite.config.ts'),
)) ?? {};
return mergeConfig(config, {
...userConfig,
plugins: [svgr()],
build: {
...config.build,
sourcemap: !isProduction
}
});
}
};
バージョンが7になったのでこのファイルからfeatures.storyStoreV7
を消します。そして元の状態のように整形すると以下のようになります。
const { loadConfigFromFile, mergeConfig } = require('vite');
const path = require('path');
const configEnvServe = {
mode: 'development',
command: 'serve',
ssrBuild: false,
};
const configEnvBuild = {
mode: 'production',
command: 'build',
ssrBuild: false,
};
module.exports = {
stories: ['../src/components/**/*.stories.@(jsx|tsx)'],
addons: [
'@storybook/addon-a11y',
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
],
staticDirs: ['./public'],
framework: {
name: '@storybook/react-vite',
options: {},
},
async viteFinal(config, { configType }) {
const isProduction = configType === 'PRODUCTION';
const { config: userConfig } =
(await loadConfigFromFile(
isProduction ? configEnvBuild : configEnvServe,
path.resolve(__dirname, '../vite.config.ts'),
)) ?? {};
return mergeConfig(config, {
...userConfig,
build: {
...config.build,
sourcemap: !isProduction,
},
});
},
};
このプロジェクトではsvgを扱うために'vite-plugin-svgr'
を利用していて、StorybookではviteFinal
で改めて導入し直す必要があったのですが、このバージョンアップで不要になりました(むしろあったらエラーになります)。
Storybook7はここまでの変更で実行することが可能になります。npm run storybook
コマンドでこれまで通り実行することができます。
ここからは今後推奨される(であろう)書き方に直していきます。.storybook/main.cjs
はESM形式にTypeScriptで書けるようになりました。今後はdefault export
で必要な情報をexportすることが推奨されます。
import { StorybookConfig } from '@storybook/react-vite';
import { ConfigEnv, loadConfigFromFile, mergeConfig } from 'vite';
import path from 'path';
const configEnvServe: ConfigEnv = {
mode: 'development',
command: 'serve',
ssrBuild: false,
};
const configEnvBuild: ConfigEnv = {
mode: 'production',
command: 'build',
ssrBuild: false,
};
const config: StorybookConfig = {
stories: ['../src/components/**/*.stories.@(jsx|tsx)'],
addons: [
'@storybook/addon-a11y',
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
],
staticDirs: ['./public'],
framework: {
name: '@storybook/react-vite',
options: {},
},
async viteFinal(config, { configType }) {
const isProduction = configType === 'PRODUCTION';
const { config: userConfig } =
(await loadConfigFromFile(
isProduction ? configEnvBuild : configEnvServe,
path.resolve(__dirname, '../vite.config.ts'),
)) ?? {};
return mergeConfig(config, {
...userConfig,
plugins: [],
build: {
...config.build,
sourcemap: !isProduction,
},
});
},
};
export default config;
ファイル名の変更を行い、全てのimportをCommonJSの形式(require
)からESMの形式(import {} from ...
)に変更しました。さらにこれまでmodule.exports
で出荷していた部分をconfig
で宣言する様にしてそれをdefault export
するようにしました。
config
の型は'@storybook/react-vite'
などのframework
に渡すライブラリが提供するStorybookConfig
になります。その他、TypeScript化のためにconfigEnvBuild
など細かい点に型を持たせました。
以上がmain.cjs
の変更です。最低限の変更はStorybookの自動的な変更が行ってくれるので簡単に対応できました。
preview.tsxの書き換え
このファイルはバージョンアップに伴う動作の担保のための変更はありませんでした。
main.cjs
と同様にESM化、TypeScript化、export default
によるexportが推奨されるようになりました。ESM化、TypeScript化は元々行っていたので後者のみ行いました。
import React from 'react';
import { Preview } from '@storybook/react';
import { AppProvider } from './../src/providers/app';
const preview: Preview = {
decorators: [
(Story) => (
<AppProvider>
<Story />
</AppProvider>
),
],
};
export default preview;
'@storybook/react'
のPreview
型を持ったpreview
を詰め込んで、それをdefault export
します。
parameters
などの他の要素もこのpreview
に詰め込んで設定します。
Storyの書き換え
Storybook7ではStory
、ComponentStory
、ComponentStoryObj
、ComponentStoryFn
およびComponentMeta
が非推奨の型になりました。代わりに、Meta
、StoryObj
、StoryFn
を利用するようになります。私が変更したプロジェクトでは以下のようになります。
import type { Meta, StoryObj } from '@storybook/react';
import { SampleComponent } from './SampleComponent';
const meta: Meta<typeof SampleComponent> = {
component: SampleComponent,
};
export default meta;
export const Default: StoryObj<typeof SampleComponent> = {};
またはsatisfies
を使って書きます。
import type { Meta, StoryObj } from '@storybook/react';
import { SampleComponent } from './SampleComponent';
const meta = {
component: SampleComponent,
} satisfies Meta<typeof SampleComponent>;
export default meta;
export const Default: StoryObj<typeof meta> = {};
機能的な変更があるわけではないので、名前をひたすら変更するだけで対応することができます(ここで書いた例ではよりドキュメントに近い書き方に合わせています)。
今回移行したプロジェクトではCSF3で記述していたためStoryFn
は利用しませんでした。この型はCSF2の記法で使われます。
export const CSF2Story: StoryFn<ButtonProps> = (args) => <Button {...args} />;
Story
が非推奨になった理由にCSF3への移行があるのでこの機会にCSF2を用いている場合はCSF3に変更した方が良いと考えています。
CSF2からCSF3へ記法を変更したい場合は
npx sb@next migrate csf-2-to-3 --glob="**/*.stories.js"
を使って変換することができます。変更についてはこちらに書かれています。
さいごに
以上でStorybook7への移行は完了です。
型名の変更によって全てのStoryに影響があり移行が大変でしたが、コマンドの書き換えやmain.cjs
の書き換えなどは自動で実行されるため円滑に進められました。
コンポーネントごとに見た目を表示できるので実装が行いやすい点や、コンポーネントの管理がしやい点、UIに関するテストも合わせてかける点など、フロントエンドの開発で重宝される良いライブラリと考えているので、今後の発展を楽しみにしています(もちろん傍観しているだけではなく寄与していきたいと考えています)。