Next.jsのCSS周りをTailwind CSS + stylelint + Storybookで整備する

# Next.jsでTailwind CSSとStorybookをやりたいのだが
個別にTailwind CSSとStorybookをインストールしただけでは動きません。stylelintもTailwind CSSの`@apply`などでエラーを出してきます。

これらの問題を解決していい感じにしていきます。

- [Tailwind CSS](#tailwind-css)
- [stylelint](#stylelint)
- [Storybook](#storybook)

Next.jsのプロジェクトはこれで作成した想定で始めます。TypeScriptにしてますが必須ではないです。

```sh
yarn create next-app
mkdir src
mv pages styles src
touch tsconfig.json
yarn dev
```

# Changelog

- tailwindcss 3に対応しました(2021/01/02)。
- stylelint 14.4.0で追加された`function-no-unknown`に対応しました(2022/04/11)。


# Tailwind CSS

## Tailwind CSSとは
[Tailwind CSS](https://tailwindcss.com)はUtility指向のCSSフレームワークです。BootstrapやMaterial DesignではComponentが用意されているため同じような見た目になりがちですが、Tailwind CSSはComponentは自分で用意するため各サイトで見た目がバラバラです。BootstrapのUtilitiesとHelpersだけを集めて拡張した感じでしょうか。

## Tailwind CSSのインストールと設定
[Next.js用の公式の手順](https://tailwindcss.com/docs/guides/nextjs)に沿ってやるだけです。

```sh
yarn add -D tailwindcss@latest postcss@latest autoprefixer@latest
yarn tailwindcss init -p
```

Production Build時に不要なCSSを削除するために`tailwind.config.js`で設定します。

```diff_javascript:tailwind.config.js
module.exports = {
- content: [],
+ content: [
+   './src/pages/**/*.{js,ts,jsx,tsx}',
+   './src/components/**/*.{js,ts,jsx,tsx}',
+ ],
  theme: {
    extend: {},
  },
  plugins: [],
}
```

`globals.css`を書き直します。

```css:src/styles/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind variants;
```

これで終了です。Tailwind CSSが使えるはずです。

```js
export const Sample = () => (
  <div className="bg-red-500">Tailwind CSS</div>
);
```

## Tailwind CSSを使いやすくするためにVSCodeを設定する
[bradlc.vscode-tailwindcss](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)をインストールします。他の開発者のために推奨プラグインにも記載しておきます。

```json:.vscode/extensions.json
{
  "recommendations": [
    "bradlc.vscode-tailwindcss",
  ]
}
```

VSCodeの設定ファイルに色々と足します。

```json:.vscode/settings.json
{
  // CSSのチェック機能をオフ
  "css.validate": false,

  // JSXでもCSSクラスを補完できるように
  "editor.quickSuggestions": {
    "strings": true
  },

  // emmetでTailwind CSSのクラスを使えるように
  "tailwindCSS.emmetCompletions": true,

  // Tailwind CSSのDirectivesをLintする
  "tailwindCSS.validate": true
}
```

# stylelint

## stylelintとは
[stylelint](https://stylelint.io)はstylesのLinterです。ESLintのCSS版というところです。

## stylelintのインストールと設定
[stylelint-config-recommended](https://github.com/stylelint/stylelint-config-recommended)をextendしている[stylelint-config-standard](https://github.com/stylelint/stylelint-config-standard)を採用しています。

```sh
yarn add -D stylelint stylelint-config-standard
```

stylelintの設定ファイルを作成します。Tailwind CSS特有のdirectiveとfunctionがエラーにならないように`at-rule-no-unknown`と`function-no-unknown`に追加します。

```json:.stylelintrc
{
  "extends": [
    "stylelint-config-standard"
  ],
  "rules": {
    "at-rule-no-unknown": [
      true,
      {
        "ignoreAtRules": ["tailwind", "layer", "apply"]
      }
    ],
    "function-no-unknown": [
      true,
      {
        "ignoreFunctions": ["theme", "screen"]
      }
    ]
  }
}
```

stylelintはこのように実行します。

```sh
# チェック
yarn stylelint .

# 自動整形
yarn stylelint --fix .
```

## CSSだけをチェックする
`.json`とは`.tsx`でstylelintのエラーを出されても意味が無いので、`.css`以外を無視するように設定します。

```:.stylelintignore
/node_modules
/.next
*.*
!*.css
```

## Prettierと共存する
Prettierと競合するstylelintのルールをオフにするために`stylelint-config-prettier`をインストールします。

```sh
yarn add -D stylelint-config-prettier
```

```diff_json:.stylelintrc
{
  "extends": [
-   "stylelint-config-standard"
+   "stylelint-config-standard",
+   "stylelint-config-prettier"
  ],
  "rules": {
    "at-rule-no-unknown": [
      true,
      {
        "ignoreAtRules": ["tailwind", "layer", "apply"]
      }
    ],
    "function-no-unknown": [
      true,
      {
        "ignoreFunctions": ["theme", "screen"]
      }
    ]
  }
}
```

## CSS Propertyの順番を制御する

- Twitter Bootstrapインスパイアの[stylelint-config-recess-order](https://github.com/stormwarning/stylelint-config-recess-order)
- 関係性のある項目をまとめる[stylelint-config-rational-order](https://github.com/constverum/stylelint-config-rational-order)
- 自分で設定する/アルファベット順の[stylelint-order](https://github.com/hudochenkov/stylelint-order)

今回は`stylelint-config-rational-order`にします。

```sh
yarn add -D stylelint-config-rational-order
```

```diff_json:.stylelintrc
{
  "extends": [
    "stylelint-config-standard",
+   "stylelint-config-rational-order",
    "stylelint-config-prettier"
  ],
  "rules": {
    "at-rule-no-unknown": [
      true,
      {
        "ignoreAtRules": ["tailwind", "layer", "apply"]
      }
    ],
    "function-no-unknown": [
      true,
      {
        "ignoreFunctions": ["theme", "screen"]
      }
    ]
  }
}
```

## VSCodeでファイル保存時にstylelintで自動整形する
[stylelint.vscode-stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint)をインストールします。他の開発者のために推奨プラグインに記載しておきます。

```diff_json:.vscode/extensions.json
{
  "recommendations": [
    "bradlc.vscode-tailwindcss",
+   "stylelint.vscode-stylelint"
  ]
}
```

VSCodeの設定ファイルでファイル保存時にstylelintで自動整形するように設定します。

```diff_json:.vscode/settings.json
{
+ "editor.codeActionsOnSave": {
+   "source.fixAll.stylelint": true
+ },

  // CSSのチェック機能をオフ
  "css.validate": false,

  // JSXでもCSSクラスを補完できるように
  "editor.quickSuggestions": {
    "strings": true
  },

  // emmetでTailwind CSSのクラスを使えるように
  "tailwindCSS.emmetCompletions": true,

  // Tailwind CSSのDirectivesをLintする
  "tailwindCSS.validate": true
}
```


# Storybook

## Storybookとは

[Storybook](https://storybook.js.org)はComponentのカタログを作成するツールです。各Componentの開発を独立させられるので、Atomic Designと相性が良いです。

## Storybookのインストールと設定

```sh
yarn sb init
```

以下を実行すると通常ならStorybookが開きますが、PostCSSのバージョンがStorybookとTailwind CSSで違うためにエラーになるかもしれないです。

```sh
yarn storybook
```

## StorybookとTailwind CSSのPostCSSのバージョンを合わせる。
そのうちStorybookもPostCSS 8を使うようになるでしょうが、それまでは手動でPostCSS 8を使うように設定する必要があります。

```sh
Error: PostCSS plugin tailwindcss requires PostCSS 8.
```

Storybookでwebpack@5を使うようにすると、PostCSS 8を使うようになるのでTailwind CSSとのpostcssのバージョン違い問題は解消します。Webpack5でStorybookを作り直します。

```sh
yarn sb init --builder webpack5 --force
```

## StorybookをTailwind CSSに対応させる
このままだとStorybookでTailwind CSSのクラスが使えないので、Storybookの設定を変えます。

`.css`に対して`postcss-loader`を使うようにWebpackの設定を変えます。`postcss`の設定はTailwind CSSが自動生成した`postcss.config.js`を使います。

```diff_javascript:.storybook/main.js
const { resolve } = require('path');

module.exports = {
  "stories": [
    "../src/**/*.stories.mdx",
    "../src/**/*.stories.@(js|jsx|ts|tsx)"
  ],
  "addons": [
    "@storybook/addon-links",
    "@storybook/addon-essentials"
  ],
+ "webpackFinal": async (config) => {
+   config.module.rules.push({
+     test: /\.css$/,
+     use: [
+       {
+         loader: 'postcss-loader',
+         options: {
+           postcssOptions: {
+             sourceMap: true,
+             config: resolve(__dirname, '..', 'postcss.config.js'),
+           },
+         },
+       },
+     ],
+   });
+
+   return config;
+ },
  "core": {
    "builder": "webpack5",
  },
};
```

`preview.js`で`global.css`をimportします。

```diff_javascript:.storybook/preview.js
+ import '../src/styles/globals.css';

export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
}
```

今度そこStorybookが開くはずです。Tailwind CSSも使えるはずです。

```sh
yarn storybook
```

# さて
Next.js + Tailwind CSS + stylelint + Storybookの環境が整いましたが、大事なことを忘れていますね。Next.jsでCSSはどうやって書いてますか？

私はCSSはファイルを分けたいけど、Scoped CSSが欲しい人なので[CSS Modules](https://github.com/css-modules/css-modules)を使っています。Scoped CSSがサクッと手に入る世の中になることを切に願う。