React + Storybook + styled-componentsをTypeScriptで動かす

設定するところが多く調べるところが多かったため、備忘録として書きます。

## ツールのバージョン
### @storybook/react
最新版を使いましょう。

```
"@storybook/react": "^4.0.0-alpha.9",
```

### styled-components
自分がcssはstyled-componentsを使っていたので一応書いておきます。
これも最新版を使わないとエラーが発生しました。

```
"styled-components": "^3.3.2"
```

## 型定義ファイル
インストールしたものを抜粋します。

```json:package.json
"devDependencies": {
    "@types/jest": "^23.0.2",
    "@types/react": "^16.3.17",
    "@types/react-dom": "^16.0.6",
    "@types/storybook__addon-actions": "^3.0.3",
    "@types/storybook__addon-links": "^3.3.1",
    "@types/storybook__addon-notes": "^3.3.1",
    "@types/storybook__addon-storyshots": "^3.4.2",
    "@types/storybook__react": "^3.0.7",
}
```

## webpack.config.js
小さい構成なので以下のようになりました。

```js:webpack.config.js
const path = require('path');
const env = process.env.NODE_ENV;

const config = {
    mode: env || 'development',
    target: 'node',
    entry: {
        index: [path.resolve(__dirname, 'src/index.js')],
    },
    output: {
        path: path.resolve(__dirname, './public/'),
        publicPath: './public/',
        filename: '[name].js',
    },
    module: {
        rules: [
            {
                test: /\.(ts|tsx)$/,
                loader: 'babel-loader!awesome-typescript-loader',
            },
        ],
    },
    resolve: {
        extensions: ['.ts', '.tsx'],
    },
};

module.exports = config;
```

## storybook
storybook側も設定が必要です。

### webpack
webpackの設定を追加します。

```js:.storybook/webpack.config.js
const path = require("path");

module.exports = (baseConfig, env, defaultConfig) => {
    defaultConfig.module.rules.push({
        test: /\.(ts|tsx)$/,
        include: path.resolve(__dirname, "../src"),
        loader: require.resolve("awesome-typescript-loader")
    });

    // addon-storysource使うときだけ
    defaultConfig.module.rules.push({
        test: /\.stories\.jsx?$/,
        loaders: [
            {
                loader: require.resolve("@storybook/addon-storysource/loader"),
                options: { parser: "typescript" }
            }
        ],
        enforce: "pre"
    });

    defaultConfig.resolve.extensions.push(".ts", ".tsx");

    return defaultConfig;
};
```

### config.js
`.ts`か`.tsx`の拡張子を持つファイルを対象にします。

```js:.storybook/config.js
import React from 'react';
import { configure } from "@storybook/react";
const req = require.context("../src/Components", true, /.stories.(ts|tsx)$/);

function loadStories() {
    req.keys().forEach((filename) => req(filename));
}

configure(loadStories, module);
```

## styled-components
テーマファイルを作成しつつ、モジュールをラップするようにしてTypeScriptに適用させます。
共通で使用するカラー変数などもここで設定することも可能です。

```js:Theme/index.ts
import * as styledComponents from "styled-components";

export interface ThemeInterface {
    primaryColor: string;
}

export const Theme = {
    primaryColor: "#e9e9e9"
};

const {
    default: styled,
    css,
    injectGlobal,
    keyframes,
    ThemeProvider
} = styledComponents as styledComponents.ThemedStyledComponentsModule<ThemeInterface>;

export default styled;
export { css, injectGlobal, keyframes, ThemeProvider };
```

## Component
実際のコンポーネントは以下のようになります。
ポイントは`node_modules`から`styled-components`を直接読み込まず、上で作成したTypeScriptに適用させたものを読み込むところです。

```tsx:Caption/index.tsx
import * as React from "react";
import styled from "../Theme";

interface CaptionInterface {
    children?: React.ReactChild;
}

const StyledCaption = styled.h1`
    margin: 0 0 12px 0;
    color: black;
    font-size: 2rem;
    font-weight: bold;
    line-height: 1.4;
`;
const Caption:React.SFC<CaptionInterface> = ({...props}) => <StyledCaption {...props} />;
export default Caption;
```

当然storybookも`.tsx`で作成します。

```tsx:Caption/index.stories.tsx
import React from 'react';
import { storiesOf } from '@storybook/react';

import Caption from './index';

storiesOf('Atoms/Caption', module).add('default', () => <Caption>Caption is here!</Caption>);
```

## おわり

多分これで動くと思います！
お役に立っていたら嬉しいです。