Next.js & StorybookにEmotionが効かない問題の解決メモ

# TL;DR
- Next.js⇄Storybook間の設定共有は [storybook-addon-next](https://storybook.js.org/addons/storybook-addon-next) がほぼ全部解決してくれる。まずはこれを使え。
- ただし `Emotion` などのJSXを拡張するライブラリを使う場合、アドオンが自動で解決してくれない。Storybook側の設定でも記述する必要がある
- Storybookの設定でドキュメント構文である `MDX` をv1→v2に上げないと `Emotion` を入れたときに落ちる


# 経緯

個人開発でフロントエンドを作る必要があったので、
簡単にサクッと作りつつ、かつある程度はしっかりした構成にしよう、
ということで以下のような形にセットアップすることを目標に作業を始めた。

- React (フロントフレームワーク)
- TypeScript (言語)
- Next.js (ディレクトリ構成とオートルーティングが使いたかったので)
- Storybook (コンポーネント単位の実装と確認を楽にする)
- Emotion (描きやすいCSSinJS)

## Next.js と Storybook　の導入まで
とりあえずcreateコマンドを使ってNextの雛形をTypeScript構成で作成。対話形式で色々聞いてくるので適当に入力する。
```sh
$ yarn create next-app --typescript
```

次にStorybookを導入する。こっちもCLI形式で導入するのでよしなに選択。
```sh
$ npx storybook init
```

で、ここにNext.js側の設定（画像インポートの取り扱いやページルーティングの挙動など）をStorybookに展開してくれるアドオン [storybook-addon-next](https://storybook.js.org/addons/storybook-addon-next)　を導入する。

```sh
$ yarn add -D storybook-addon-next
```

```.storybook/main.js
module.exports = {
  ...
  addons: [
    'storybook-addon-next',         // ← インストールしたNext.js連携用アドオンを追加
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-interactions'
  ]
  ...
}
```

これでNext.jsとStorybookのセットアップは完了。
ほんと最近の環境構築は楽である。ノーコンフィグ・ゼロコンフィグ思想万歳！


## Emotionの導入

JSXの基本的な機能（要素のprops）を拡張するためここはちょっと手間。
まずはパッケージを入れる。

```sh
$ yarn add @emotion/react
$ yarn add -D @emotion/babel-plugin
```

次にプロジェクトルートにBabelの設定ファイルを作成。個人的な好みで `babel.config.js` とした。
ここにNextの標準設定をインポートし、その上でEmotionの拡張処理が注入されるように設定する。
```babel.config.js
module.exports = {
  presets: [
    presets: [
      [
        'next/babel',
        {
          'preset-react': {
            runtime: 'automatic',
            importSource: '@emotion/react'
          }
        }
      ]
    ],
    plugins: ['@emotion']
  ]
}
```

またTypeScript側でもエラーが出ないよう、Emotionを注入する形に設定を書き換える。
```tsconfig.json
{
  ...
  "types": ["@emotion/react/types/css-prop"],
  "jsxImportSource": "@emotion/react"
  ...
}
```

`types` と `jsxImportSource` はもしかしたらどっちかでよかったかもしれない。
がまあめんどいのでどっちも入れておくことに。検証は後回し。

これでNext側では見事にEmotionによるスタイリングが効くようになった。
しかしStorybookで起動してみるとスタイルをあてたはずのCSS要素に以下のような文面が。

```html
<span css="You have tried to stringify object returned from css function. It isn't supposed to be used directly (e.g. as value of the className prop), but rather handed to emotion so it can handle it (e.g. as value of css prop).">
```

なんか効いてないっぽいメッセージが出てるわね

### 対策１.Storybook側にBabelの設定を記述

Storybookの設定ファイルである `main.js` から強引にログを吐き出させてみると、
どうやらNext側のBabel設定が一部しか読み込まれておらず、Storybook側にEmotionの注入設定が渡っていないことがわかった。

ならばStorybookでも個別に設定してやるまで。

```.storybook/main.js
module.exports = {
  ...
  babel: async (options) => {
    // emotion用の注入設定
    // preset-react ローダーを取得してくる
    const presetReact = options.presets.find((p) => /preset-react/.test(p[0]));
    // preset-react　ローダのオプションを設定
    presetReact[1] = {
      ...presetReact[1],
      runtime: 'automatic',
      importSource: '@emotion/react'
    }
    // Emotionプラグインを追加
    options.plugins.push(require.resolve('@emotion/babel-plugin'));
    
    return options;
  }
}
```

とりあえずこれで理屈上は動くはずなので、Storybookを起動してみる。
ところが今度は以下のようなエラーが発生して落ちてしまった。
```
src/stories/Introduction.stories.mdx: importSource cannot be set when runtime is classic.
```

通常のコンポーネントに対するビルドは通っているようで、ドキュメントファイルである `.mdx` を削除すれば正常に起動した。

### 対策2. MDXのバージョンを上げる

とはいえドキュメントファイルだけ落ちるのも気持ち悪いので原因を探してみることに。
`preset-react` ライブラリにおいて、 `runtime: classic` はEmotionなどの外部ライブラリの注入に対応していないため `automatic`　に設定する必要がある（[Babel公式Docs](https://babeljs.io/docs/en/babel-preset-react)参照）。
しかし、そもそもローダー側では `runtime` をしっかり `automatic` に設定しているのに、 `classic` として扱われているのが妙にひっかかる。

ざっと調べてみたら以下のissueにたどり着いた。
https://github.com/mdx-js/mdx/issues/1441

どうやらMDXのビルドプラグインが `runtime: classic` のみに対応していたようで、
最近出た `v2` 以降だと `runtime: automatic` に対応するようになったようだ。
Storybook標準で採用しているMDXのビルドプラグインは `v1.6.22` となっており、これを上げれば解決しそう。

調べたらまだ実験的昨日の段階だが、[MDX2を有効にする機能がStorybook内にあるらしい](https://storybook.js.org/docs/react/writing-docs/mdx#mdx-2)。
これを使ってバージョンを上げてみる。

```sh
$ yarn add -D @storybook/mdx2-csf
```

```.storybook/main.js
module.exports = {
  ...
  features: [
    previewMdx2: true
  ]
  ...
}
```

これでStorybookを起動してみる…よし、動いた！ スタイリングもちゃんと反映されている！

サクッとセットアップを済ませるはずだったが予想外に手間取ってしまった。
やはりノーコンフィグといえども新しめのライブラリへの対応は限界があるわけで、
その辺りはちゃんとやらないと動かないんだな…と痛感。
妙に疲れたので実装は明日にしよう…