TL;DR
- Next.js⇄Storybook間の設定共有は storybook-addon-next がほぼ全部解決してくれる。まずはこれを使え。
- ただし
Emotion
などのJSXを拡張するライブラリを使う場合、アドオンが自動で解決してくれない。Storybook側の設定でも記述する必要がある - Storybookの設定でドキュメント構文である
MDX
をv1→v2に上げないとEmotion
を入れたときに落ちる
経緯
個人開発でフロントエンドを作る必要があったので、
簡単にサクッと作りつつ、かつある程度はしっかりした構成にしよう、
ということで以下のような形にセットアップすることを目標に作業を始めた。
- React (フロントフレームワーク)
- TypeScript (言語)
- Next.js (ディレクトリ構成とオートルーティングが使いたかったので)
- Storybook (コンポーネント単位の実装と確認を楽にする)
- Emotion (描きやすいCSSinJS)
Next.js と Storybook の導入まで
とりあえずcreateコマンドを使ってNextの雛形をTypeScript構成で作成。対話形式で色々聞いてくるので適当に入力する。
$ yarn create next-app --typescript
次にStorybookを導入する。こっちもCLI形式で導入するのでよしなに選択。
$ npx storybook init
で、ここにNext.js側の設定(画像インポートの取り扱いやページルーティングの挙動など)をStorybookに展開してくれるアドオン storybook-addon-next を導入する。
$ yarn add -D storybook-addon-next
module.exports = {
...
addons: [
'storybook-addon-next', // ← インストールしたNext.js連携用アドオンを追加
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions'
]
...
}
これでNext.jsとStorybookのセットアップは完了。
ほんと最近の環境構築は楽である。ノーコンフィグ・ゼロコンフィグ思想万歳!
Emotionの導入
JSXの基本的な機能(要素のprops)を拡張するためここはちょっと手間。
まずはパッケージを入れる。
$ yarn add @emotion/react
$ yarn add -D @emotion/babel-plugin
次にプロジェクトルートにBabelの設定ファイルを作成。個人的な好みで babel.config.js
とした。
ここにNextの標準設定をインポートし、その上でEmotionの拡張処理が注入されるように設定する。
module.exports = {
presets: [
presets: [
[
'next/babel',
{
'preset-react': {
runtime: 'automatic',
importSource: '@emotion/react'
}
}
]
],
plugins: ['@emotion']
]
}
またTypeScript側でもエラーが出ないよう、Emotionを注入する形に設定を書き換える。
{
...
"types": ["@emotion/react/types/css-prop"],
"jsxImportSource": "@emotion/react"
...
}
types
と jsxImportSource
はもしかしたらどっちかでよかったかもしれない。
がまあめんどいのでどっちも入れておくことに。検証は後回し。
これでNext側では見事にEmotionによるスタイリングが効くようになった。
しかしStorybookで起動してみるとスタイルをあてたはずのCSS要素に以下のような文面が。
<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).">
なんか効いてないっぽいメッセージが出てるわね
対策1.Storybook側にBabelの設定を記述
Storybookの設定ファイルである main.js
から強引にログを吐き出させてみると、
どうやらNext側のBabel設定が一部しか読み込まれておらず、Storybook側にEmotionの注入設定が渡っていないことがわかった。
ならばStorybookでも個別に設定してやるまで。
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参照)。
しかし、そもそもローダー側では 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内にあるらしい。
これを使ってバージョンを上げてみる。
$ yarn add -D @storybook/mdx2-csf
module.exports = {
...
features: [
previewMdx2: true
]
...
}
これでStorybookを起動してみる…よし、動いた! スタイリングもちゃんと反映されている!
サクッとセットアップを済ませるはずだったが予想外に手間取ってしまった。
やはりノーコンフィグといえども新しめのライブラリへの対応は限界があるわけで、
その辺りはちゃんとやらないと動かないんだな…と痛感。
妙に疲れたので実装は明日にしよう…