React Native(Expo) + NativeBase環境にStorybook v6(CSF3.0)を導入
課題
StorybookをReact Native環境に導入する際、公式のチュートリアルで紹介されている手順通りに進めると古いバージョンがインストールされてしまいます。
2022年3月現在、最新のStorybookは6.4系ですが、以下コマンドを実行することで導入されるStorybookは5.3系です。
npx -p @storybook/cli sb init --type react_native
(補足:公式の@storybook/react-native
リポジトリは若干動きがあり、2022年4月時点でv6.0.1-beta.5
がリリースされています。こちらの進捗次第で、本記事の内容は古くなるor意味がなくなる可能性があります)
+ "@storybook/addon-actions": "^5.3",
+ "@storybook/addon-knobs": "^5.3",
+ "@storybook/addon-links": "^5.3",
+ "@storybook/addon-ondevice-actions": "^5.3.23",
+ "@storybook/addon-ondevice-knobs": "^5.3.25",
+ "@storybook/react-native": "^5.3.25",
+ "@storybook/react-native-server": "^5.3.23",
Storybookはversion 6.3以降でComponent Story Format 3.0、通称CSF3.0を活用してこれまでと比較してシンプルなストーリーを記述できます。
React NativeであってもCSF3.0の恩恵を受けたいと思い、本記事の手順の通りセットアップしてみたので記事に残します。
本記事の環境情報
- Expo 42系
- NativeBase 3.2系
- Storybookおよび関連パッケージ 6.4系
免責事項
- 筆者は生まれて初めてStorybookを触ってみたガチ初心者なので、的外れなことを説明しているかもしれません
- 導入しただけなので、実際にコンポーネント開発に適用し始めるとどうなるかわかっていません
- 結論を言えば、 react-native-webを使うことでReact Native対応したことにしています
- 執筆時点のREADME.mdではReact Nativeのv6.4系のDemoが記載されていないなど、公式のサポート状況に疑問が残りますので、その点留意の上で導入などご判断ください
導入手順
reactをターゲットに指定してsb initする
react_native
ではなく、react
をターゲットにしてinitします。
npx -p @storybook/cli sb init --type react
これは相当時間が掛かるので、お茶でも飲みながら待ちます。
main.jsの書き換え
自動生成された.storybook/main.js
を書き換えます。
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',
// https://zenn.dev/himorishige/scraps/8cca98dc9120d2
webpackFinal: (config) => {
config.resolve.alias = {
'react-native$': 'react-native-web',
};
// https://github.com/react-native-svg/react-native-svg/issues/1553#issuecomment-1011487502
config.resolve.extensions.unshift('.web.js');
return config;
},
};
webpackFinal
の項目がポイントです。
react-native-webを使う
コメントに記載しているScrapを参考に、react-native
を使っている箇所を全てreact-native-web
としてWebpackが解決するようにAlias設定をします。
こういう立ち振る舞いができるのはReact Nativeの本領発揮というところですね。
※念のためですが、このアプローチを取っている時点で、ネイティブ独自の挙動をStorybook上で確認することは不可能になるはずなので、ご了承ください
react-native-svgのエラーを解消する
続いて、config.resolve.extensions.unshift('.web.js');
の部分についてです。
こちらの設定をしないと、以下のようなエラーが出ました。もしかしたらNativeBaseのコンポーネントを使ったStoryを含まない状態ではエラーが出ないかもしれません。
ModuleParseError: Module parse failed: Unexpected token (18:12)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| const AssetSourceResolver = require('./AssetSourceResolver');
|
> import type {ResolvedAssetSource} from './AssetSourceResolver';
|
| let _customSourceTransformer, _serverURL, _scriptURL;
この件についてググりまくっていたところ、上記の修正案を見つけたので対応できました。ざっくりいうと、react-native-webに変換することで、web.jsという拡張子に一部のコンポーネントが変換されるようなので、それをWebpackの解決対象に入れることが必要、ということみたいですね。
NativeBaseProviderを設定する
NativeBaseのコンポーネントを含んでいる場合は、Providerでラップしてあげる必要があるので、.storybook/preview.js
を以下のようにします。
import { NativeBaseProvider } from 'native-base';
import { theme } from '~/utils/themes/NativeBase';
import React from 'react';
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
};
const withThemeProvider = (Story, context) => {
return (
<NativeBaseProvider theme={theme}>
<Story {...context} />
</NativeBaseProvider>
);
};
export const decorators = [withThemeProvider];
僕の環境ではthemeだけを持ってくればOKでしたが、その他必要に応じて引数をセットアップしてください。
React Native Web addon for Storybookインストール
上記の設定時点でも簡単なコンポーネントであればStorybook上で確認できたのですが、Iconを使っているコンポーネントで失敗しました。
そこで、本記事の方法でreact-native-webを使っていることを活用し、React Native Web addon for Storybook
を追加で設定します。
yarn add babel-plugin-react-native-web @storybook/addon-react-native-web --dev
// see https://github.com/storybookjs/addon-react-native-web README
{
name: '@storybook/addon-react-native-web',
options: {
modulesToTranspile: ['react-native-vector-icons'],
babelPlugins: ['@babel/plugin-transform-react-jsx'],
},
},
babelPlugins
の設定はExpo42だったので必要なのかもしれません。みなさんの環境でもどの程度必要なのかは検証してみてください。
ここまでの設定をしたのち、yarn storybook
を実行すると以下のようにNativeBaseのButtonコンポーネントを使ったコンポーネントをStoryとして開くことができました。
補足
V7に備えた設定について
公式ドキュメントを見る限り、次のメジャーアップデートであるV7に備えてすでにFeature Flagが出ているみたいなので、これから導入する方は早めにチャレンジしてもいいかなと思いました。
Babelについて
Babelの設定をカスタムする必要があるのかなと思っていたのですが、そうでもなかったです。
CSF3.0について
以下のように型安全かつ超シンプルにStoryが書けます。便利ですね。タイトルはデフォルトでディレクトリ構造を反映したものになっているようです。
import { ComponentMeta, ComponentStoryObj } from '@storybook/react';
import ActionButton from '~/components/ui/button/ActionButton';
import Colors from '~/utils/themes/Colors';
export default {
component: ActionButton,
} as ComponentMeta<typeof ActionButton>;
export const Primary: ComponentStoryObj<typeof ActionButton> = {
args: {
size: 'md',
children: '送信する',
},
};
まとめ
Storybookはいつか運用してみたいなと思って定期的にウォッチしていたのですが、特にCSF3.0の書きかたが便利になっていたり、昨今はmswやtesting-libraryとのインテグレーションもできることから、フロントエンド開発において当たり前のように開発フローに含むことのできるツールに進化しているのかな?と初心者としては思っているところです。
もし運用面などについてお話してくれる方がいらっしゃればTwitterなどでご連絡ください!
参考文献
- https://zenn.dev/himorishige/scraps/8cca98dc9120d2
- https://storybook.js.org/blog/component-story-format-3-0/
- https://www.s-arcana.co.jp/blog/2021/01/19/4519
- https://webpack.js.org/configuration/resolve/#resolveextensions
- https://github.com/react-native-svg/react-native-svg/issues/1553#issuecomment-804089936
Discussion