世の中の React Native + Storybook の記事がExpoのパターンばっかりだったので、Expo使わないパターンでのStorybookの導入方法についてまとめてみました。(とは言っても、あまり手順は変わりませんが、、、)
事前準備
以下のページを見て、開発環境を構築してください。
yarn android
もしくは yarn ios
にて、エミュレーターが起動する状態にしておいてください。
念の為、プロジェクトを作成するコマンドを記述しておきます。今後は以下のプロジェクトで作業をする前提とします。
$ npx react-native init AwesomeProject
$ cd AwesomeProject
Storybookの初期化
以下のコマンドで、プロジェクト内でStorybookを使えるようにします。
$ npx -p @storybook/cli sb init --type react_native
この sb init
コマンドによって、
- 必要なライブラリのインストール
- プロジェクトルートに
storybook
フォルダを作成(中に色々とファイルがあります) - Storybookに関わる
npm scripts
タスクの追加
が行われます。
アプリのエントリポイントの変更
sb init
時に以下のようなメッセージが出てくると思います。
NOTE: installation is not 100% automated.
To quickly run Storybook, replace contents of your app entry with:
export {default} from './storybook';
要するには、手動で行うインストール作業が残っているということです。
具体的には、アプリのエントリポイントの内容を書き換えることになります。
現状のアプリのエントリポイントはプロジェクトルートの index.js
なので、それを以下の1行だけにしてください。(それ以外の部分はコメントアウトとかにしておくといいと思います。)
export { default } from './storybook';
ストーリーの作成
適当なコンポーネントを1つ作ります。
今回は src/components/Hello.js
としました。
import React from 'react';
import { View, Text } from 'react-native';
function Hello({ name }) {
return (
<View>
<Text>Hello {name}</Text>
</View>
);
}
export default Hello;
同じフォルダにストーリーを作成していきます。
src/components/Hello.stories.js
というファイルを作成し、以下のように記述してください。
import React from 'react';
import { storiesOf } from '@storybook/react-native';
import Hello from './Hello';
storiesOf('Hello', module).add('with hoge', () => <Hello name={'hoge'} />);
storiesOf('Hello', module).add('with fuga', () => <Hello name={'fuga'} />);
動的にストーリーを読み込む
もう少し下準備が続きます。
storybook/index.js
をよく見ると、以下のような記述があります。
// import stories
configure(() => {
require('./stories');
}, module);
これは、 storybook/stories
の中にあるストーリーの記述をインポートする形になっています。
ですが、前述のようにストーリーを記述するディレクトリは自由に決めたいものです。
そこで、 react-native-storybook-loader
というものを使います。
ストーリーのimport文を自動生成してくれるものです。(だと思っています)
まずは、 react-native-storybook-loader
をインストールします。
$ yarn add -D react-native-storybook-loader
そして、 package.json
を以下のように書き換えます。(中略部は適宜補完してください)
{
"scripts": {
// 〜 中略 〜
"prestorybook": "rnstl"
// 〜 中略 〜
},
"config": {
"react-native-storybook-loader": {
"searchDir": [
"./src/components"
],
"pattern": "**/*.stories.js",
"outputFile": "./storybook/storyLoader.js"
}
}
}
最後に、 storybook/index.js
を以下のように編集します。
// if you use expo remove this line
import { AppRegistry } from 'react-native';
import { getStorybookUI, configure, addDecorator } from '@storybook/react-native';
import { withKnobs } from '@storybook/addon-knobs';
import { name as appName } from '../app.json';
import { loadStories } from './storyLoader';
import './rn-addons';
// enables knobs for all stories
addDecorator(withKnobs);
// import stories
configure(() => {
loadStories(); //ここ重要
}, module);
// Refer to https://github.com/storybookjs/react-native/tree/master/app/react-native#getstorybookui-options
// To find allowed options for getStorybookUI
const StorybookUIRoot = getStorybookUI({});
// If you are using React Native vanilla and after installation you don't see your app name here, write it manually.
// If you use Expo you should remove this line.
AppRegistry.registerComponent(appName, () => StorybookUIRoot);
export default StorybookUIRoot;
ここで、少し解説です。
npm scripts
に追加した prestorybook
タスクですが、この処理は yarn storybook
の処理が実行される前に行われます。
prestorybook
タスクでは storybook/storyLoader.js
というファイルを自動生成し、その中で、 package.json
で定義した searchDir
内のストーリーのインポートを行うための loadStories
関数を定義してくれます。
それを、アプリのエントリポイントである storybook/index.js
ファイルで、ストーリーを読み込む部分の処理に挿入してあげる、という形になります。
Storybookの起動
では、ここでやっとStorybookを起動してみることにしましょう。以下のコマンドで起動できます。
$ yarn storybook
起動するとこんな感じの画面になると思います。
このままずっと待っても、画面左側のストーリーの部分は更新されません。
ブラウザに表示されている説明文のように、エミュレーターを起動させることで、左側のストーリーの部分が出現します。
なので、Storybookを起動させたままで、別ターミナルで yarn android
や yarn ios
としてやればいいのですが、、、
Androidエミュレータ特有のバグ
このままやってもAndroidエミュレータだとうまく動きません。ここでだいぶつまづきました。
どうやら、AndroidのエミュレータとStorybookの連携の部分でバグがあるようです。
今は、このIssueに従い、以下のような修正を行います。
(iOSの場合はこの作業はいらないかもです。動作確認していないのでわかりませんが、、、)
まずは、 storybook/index.js
です。
// if you use expo remove this line
import { AppRegistry } from 'react-native';
import { getStorybookUI, configure, addDecorator } from '@storybook/react-native';
import { withKnobs } from '@storybook/addon-knobs';
import { name as appName } from '../app.json';
import { loadStories } from './storyLoader';
import './rn-addons';
// enables knobs for all stories
addDecorator(withKnobs);
// import stories
configure(() => {
loadStories();
}, module);
// Refer to https://github.com/storybookjs/react-native/tree/master/app/react-native#getstorybookui-options
// To find allowed options for getStorybookUI
const StorybookUIRoot = getStorybookUI({
host: '192.168.0.95', // replace with your ip address
port: '7007',
});
// If you are using React Native vanilla and after installation you don't see your app name here, write it manually.
// If you use Expo you should remove this line.
AppRegistry.registerComponent(appName, () => StorybookUIRoot);
export default StorybookUIRoot;
そして、 npm scripts
の storybook
タスクを修正してやります。
"storybook": "start-storybook -p 7007 -h 192.168.0.95"
この状態で、 Storybookが起動した後に、Androidのエミュレータを起動してやると、、、
$ yarn storybook
# ストーリーブックを起動した状態で、別ターミナルにて
$ yarn android
無事動きました!