Next.js(React) × TypeScript × Storybook の環境構築

## ■ 概要
### ・背景
<b>アトミックデザイン指向を考慮した、Next.jsとStyleBookを組み合わせた環境構築に少し手間取ったので、ここにその構築手順を記します。</b>
※ Storybookは最低限のアドオンしか入れません。色々便利なアドオンがあるのでぜひ調べてみてください<br>=>[リンク](https://storybook.js.org/addons)

### ・技術要件
|技術|雑な説明|URL|
|:-:|:-:|:-:|
|React|SPAフレームワーク。ほかにVue.jsやAngular.jsなどがある|[リンク](https://ja.reactjs.org/)|
|Next.js|ReactでSSR/SSG(ISR)などを使えたりより使いやすくしたもの<br>(Vue.jsでいうNuxt.jsのようなもの)|[リンク](https://nextjs.org/)|
|TypeScript|MSで開発された型安全なJavaScript言語|[リンク](https://www.typescriptlang.org/)|
|Storybook|既存のSPAコンポーネントをベースに<br>スタイルガイドを作成できるドキュメント生成ツール|[リンク](https://storybook.js.org/)|
|アトミックデザイン|フロントのプログラム設計手法の一つ。原子、分子、有機体、<br>テンプレ、ページと組み合わせていくことで設計する手法。|[リンク](https://www.codegrid.net/articles/2017-atomic-design-1/)|
|CSS Modules<br>CSS Loader<br>Style Loader|CSS Modulesは各モジュール単位でCSSを使えるようにしたもの。<br>Next.jsでは特定バージョン以降ビルトインされている。<br>CSS LoaderとStyle LoaderはStorybookにてそれを実現するためのもの|[リンク](https://github.com/css-modules/css-modules)|

### ・前提
##### 次がインストールされていること
+ npm(npx, yarn)
+ node.js

## ■ 手順
### 1. システム環境(React、Next.js、TypeScript)をインストールする
##### 1-1. 下記コマンドをたたいてインストールする
```sh
$ npm init
$ npm install react react-dom next typescript
```

### 2. ドキュメント環境(Storybook)をインストールする。
##### 1-1. 下記コマンドをたたいてインストールする
```sh
$ npm install -d @storybook/cli
```

##### 1-2. 下記コマンドをたたいてStorybookのベースを生成する
```sh
$ npx -p @storybook/cli sb init
```

##### 1-3. 下記コマンドをたたいて、サンプルを起動してみる

```sh
$ start-storybook -p 6006
```

### 3. アトミックデザインをベースに、コンポーネントを作成する
+ 名前は***.modules.cssにすること。また、構成は以下を想定。
+ サンプルはプロジェクト外に退避する(または削除する)

```sh
├─ .storybook
│  ├─ main.js
│  └─ preview.js
├─ components
│  ├─ atoms
│  │  └─ Button
│  │     ├─ Button.module.css
│  │     ├─ Button.tsx
│  │     └─ Button.stories.tsx
│  ├─ molecules
│  ├─ organisms
│  └─ templates
└─pages
   └─ buttonPage.tsx

```

##### 1-1. 下記のファイルを作成する

```css:components/atoms/Button/Button.module.css
.ukButtonKai {
    padding: 3.5px 10px 7px;
    border-radius: 3px;
    background-color:black;
    color:white;
}
```
```tsx:components/atoms/Button/Button.tsx
import React from 'react';
import styles from './Button.module.css';

export interface ButtonProps {
    /** className */
    classNameString?: string;
    /** Index contents */
    label: string;
    /** Optional click handler */
    onClick?: () => void;
}

/**
 * Primary UI component for user interaction
 */
export const Button: React.FC<ButtonProps> = ({label, classNameString, ...props}) => {
    return (
        <a
            className={"uk-button uk-button-default " + styles.ukButtonKai + " " + classNameString}
            {...props}
        >
            {label}
        </a>
    );
};

```
```tsx:pages/buttonPage.tsx
import React from "react";
import {Button} from "../components/atoms/Button/Button";
import Head from "next/head";

class buttonPage extends React.Component{
    render(){
        return (
            <>
                <Head>
                    <title>デモ</title>
                    {/* 例としてUIKitライブラリを入れている */}
                    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.5.5/css/uikit.min.css"/>
                </Head>
                <Button label={"Button"} />
            </>
        );
    }
}
export default buttonPage;
```

##### 1-2. 下記コマンドをたたいてnextアプリを起動する
```sh
$ set NODE_ENV=production && npx next build && npx next start
```
=> [http://localhost:3000/buttonPage](http://localhost:3000/buttonPage)


### 4. Storybookに読み込めるようにする
##### 1-1. 下記コマンドをたたいて、CSS Module群のパッケージをインストールする
```sh
$ npm install -d style-loader css-loader
```
##### 1-2. 下記のファイルを作成,修正
```.storybook/main.js
const path = require('path');

module.exports = {
  "stories": [
    //"../src/components/**/**/*.stories.mdx",
    "../components/**/**/*.stories.@(js|jsx|ts|tsx)"
  ],
  "addons": [
    '@storybook/addon-links',
    "@storybook/addon-essentials",
  ],

  webpackFinal: async (config) => {
    // remove default css rule from storybook
    config.module.rules = config.module.rules.filter((f) => f.test.toString() !== '/\\.css$/');

    config.module.rules.push({
      test: /\.css$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            modules: true,
          },
        },
      ],
    });

    return config;
  },
}
```
```html:preview-head.html
<!-- 例としてUIKitライブラリを入れている -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.5.5/css/uikit.min.css" />
```
```tsx:components/atoms/Button/Button.stories.tsx
import React from 'react';
// also exported from '@storybook/react' if you can deal with breaking changes in 6.1
import { Story, Meta } from '@storybook/react/types-6-0';
import {Button, ButtonProps} from "./Button";

export default {
    title: 'Components/Atoms/Button',
    component: Button
} as Meta;

const Template: Story<ButtonProps> = (args) => <Button {...args} />;

export const Sample = Template.bind({});
Sample.args = {
    classNameString: 'uk-text-small',
    label: 'ボタン',
};
```

##### 1-3. 下記コマンドをたたいて、サンプルを起動してみる

```sh
$ start-storybook -p 6006
```
=> [http://localhost:6006](http://localhost:6006)


## ■ ほか
### ・静的出力
```js:next.config.js
module.exports = {
    basePath: '/', // コンテキストパス
}
```


```sh
# アプリ出力
$ npx build-storybook -o 出力先 -c .storybook --quiet
# Storybook出力
$ set NODE_ENV=production && npx next build && npx next export -o 出力先
```
### ・デモ
-- [アプリサンプル](https://meww6m.github.io/nextjs_typescript_storybook_sample/dist/buttonPage)
-- [ドキュメントサンプル](https://meww6m.github.io/nextjs_typescript_storybook_sample/)
### ・リファレンス
+ [Writing your first React UI Library - Part 3: CSS Modules](https://dev.to/davixyz/writing-your-first-react-ui-library-part-3-css-modules-5929)