React NativeでStorybookを使う(Expo使わないパターン)

React Native

世の中の React Native + Storybook の記事がExpoのパターンばっかりだったので、Expo使わないパターンでのStorybookの導入方法についてまとめてみました。(とは言っても、あまり手順は変わりませんが、、、)

事前準備

以下のページを見て、開発環境を構築してください。

Get Started with React Native · React Native
React Native allows developers who know React to create native apps. At the same time, native developers can use React Native to gain parity between native plat...

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 androidyarn ios としてやればいいのですが、、、

Androidエミュレータ特有のバグ

このままやってもAndroidエミュレータだとうまく動きません。ここでだいぶつまづきました。
どうやら、AndroidのエミュレータとStorybookの連携の部分でバグがあるようです。

Stories not loaded for new generated react native application by expo cli. · Issue #91 · storybookjs/react-native
Describe the bug After adding storybook for new expo react-native blank project and run command "npm run storybook", browser opens up but no stories displayed a...

今は、この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 scriptsstorybook タスクを修正してやります。

"storybook": "start-storybook -p 7007 -h 192.168.0.95"

この状態で、 Storybookが起動した後に、Androidのエミュレータを起動してやると、、、

$ yarn storybook

# ストーリーブックを起動した状態で、別ターミナルにて
$ yarn android

無事動きました!

タイトルとURLをコピーしました