2019.7.29
以前、GatsbyJS × TypeScript製のサイトにStorybookを導入する ではGatsbyJSの環境でセットアップしました。今回は通常のTypeScript × React環境にStorybookをセットアップしたいと思います。
Storybook for React のAutomatic setup
に沿ってやっていきます。手動で入れたい場合はManual setup
を参照してください。
npx -p @storybook/cli sb init --type react
以下のライブラリが自動でインストールされます。
"@babel/core": "^7.5.5",
"@storybook/addon-actions": "^5.1.9",
"@storybook/addon-links": "^5.1.9",
"@storybook/addons": "^5.1.9",
"@storybook/react": "^5.1.9",
"@types/storybook__react": "^4.0.2",
"babel-loader": "^8.0.6",
以下のnpm scriptも自動で挿入されます。
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
以下の設定ファイルも自動で作成されます。
.storybook/addmins.js
.storybook/config.js
.storybook/addons.js
は以下の通りです。
import '@storybook/addon-actions/register';
import '@storybook/addon-links/register';
.storybook/config.js
は以下の通りです。
import { configure } from '@storybook/react';
// automatically import all files ending in *.stories.js
const req = require.context('../stories', true, /\.stories\.js$/);
function loadStories() {
req.keys().forEach(filename => req(filename));
}
configure(loadStories, module);
stories/index.stories.js
も自動生成されます。
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { linkTo } from '@storybook/addon-links';
import { Button, Welcome } from '@storybook/react/demo';
storiesOf('Welcome', module).add('to Storybook', () => <Welcome showApp={linkTo('Button')} />);
storiesOf('Button', module)
.add('with text', () => <Button onClick={action('clicked')}>Hello Button</Button>)
.add('with some emoji', () => (
<Button onClick={action('clicked')}>
<span role="img" aria-label="so cool">
😀 😎 👍 💯
</span>
</Button>
));
これでReact環境にStorybookをセットアップできました。昔に比べると導入がめちゃくちゃ簡単になっています。
npm run storybook
起動できました。
こちらの記事を参考に進めました。babel-loader
を使う方法が紹介されていますが、今回はts-loader
を使用しました。
Setting up TypeScript with babel-loader
型定義とts-loader
をインストールします。babel-loader
もインストールする必要があります。
npm i -D @types/storybook__react ts-loader babel-loader
Storybookでは.storybook/webpack.config.js
を自前で準備することができます。そこにTypeScriptを読み込む設定を加えます。
.storybook/webpack.config.js
はこのようにしました。ts-loader
を使用しています。
module.exports = {
module: {
rules: [
{
test: /\.tsx?$/,
use: "ts-loader"
}
]
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".json"]
}
};
.storybook/config.js
に.js
ではなく.tsx
ファイルを読み込むように書き換えます。
...
const req = require.context("../src", true, /\.stories\.tsx$/);
...
react-router-domを使用している場合のコンポーネントのPropsはRouteComponentProps
という型になります。
また<Link />
コンポーネントを使用している場合は<Router />
と<Route />
を使う必要があります。
これらをstoriesファイル内でエミュレートしてやる必要があります。
以下のissueに対応策がありました。
Component with react router · Issue #769 · storybookjs/storybook · GitHub
よくあるナビゲーションメニューのコンポーネントNavigation.tsx
。react-router-domの<Link />
を使用しています。これをそのままstoriesに書いてしまうとエラーが出てしまいます。
import React from "react";
import { Link, RouteComponentProps } from "react-router-dom";
const LINKS = [
{ to: "/", label: "Home" },
{ to: "/about", label: "About" },
{ to: "/profile", label: "Profile" }
];
const Navigation = (props: RouteComponentProps) => {
return (
<nav>
<ul>
{LINKS.map(l => {
return (
<li key={l.to}>
<Link to={l.to}>
{l.label}
</Link>
</li>
);
})}
</ul>
</nav>
);
};
export default Navigation;
<Router />
をaddDecoratior
で使うことでエミュレートできます。
Navigation.stories.tsx
はこんな感じです。RouteComponentProps
であるhistory
とlocation
とmatch
もエミュレートしてやる必要があります。
import React from "react";
import { storiesOf } from "@storybook/react";
import Navigation from "./Navigation";
import { Router, Route, match } from "react-router";
import { createMemoryHistory, createLocation } from "history";
const match: match<{}> = {
isExact: false,
path: "/",
url: "/",
params: {}
};
storiesOf("Navigation", module)
.addDecorator((story: () => JSX.Element) => (
<Router history={createMemoryHistory()}>
<Route path="*" component={() => story()} />
</Router>
))
.add("with text", () => {
return (
<Navigation
history={createMemoryHistory()}
location={createLocation("")}
match={match}
/>
);
});
これでひとまず起動することができました。