📕

結局 Next (TypeScript) にStorybookを入れるには何が必要なの?

2020/10/13に公開

なんかググったらいろいろ出てきますが、どれを試してもうまくいかなかったので最小構成を探ってみました

あなたのNextプロジェクトを汚さずにStorybookを導入しよう!

手順

依存関係のインストール

yarn add --dev @storybook/react @storybook/preset-typescript @storybook/builder-webpack5 @storybook/manager-webpack5 webpack ts-loader css-loader babel-preset-react-app babel-plugin-react-require 

追加するのは

  • @storybook/react
    • これがなきゃ始まらないだろう…たぶん
  • @storybook/preset-typescript
    • TypeScriptを解釈するためのプリセット
  • @storybook/builder-webpack5, @storybook/manager-webpack5
    • @storybook/preset-typescript を動かすために必要なWebpack 5を導入するためのパッケージたち
  • webpack
    • 上で散々導入の準備をしてきたWebpack本体
  • ts-loader
    • これがあれば babel-loader は要りません
  • css-loader
    • CSS Modulesに対応するために必要
  • babel-preset-react-app
    • たぶんbabelがJSXを解釈するために必要
  • babel-plugin-react-require
    • Nextのコンポーネント内ではReactを明示的にインポートする必要がないのでそれに合わせるためのbabel-plugin

npm scriptの定義

これは厳密には必須ではありませんが、なにかしらscriptsに登録しないとStorybookの起動に不便なので、こんな感じの追記をおすすめします

package.json
{
  "scripts": {
    "storybook": "start-storybook -p 6006",
    "build-storybook": "build-storybook"
  }
}

.babelrc をつくる

さっきインストールしたpresetとpluginをbabelに伝えます

.storybook/.babelrc
{
  "presets": [
    "react-app"
  ],
  "plugins": [
    "react-require"
  ]
}

main.js をつくる

しばらく前には config.js と呼ばれていたそうです (参考)
なんか .storybook/webpack.config.js を作っても読んでもらえなかったので、webpack configはここに書いたほうが良さそうです

.storybook/main.js
const path = require("path")

module.exports = {
  stories: ['../stories/**/*.stories.tsx'],  // どのstoryファイルを読み込むのか
  addons: ['@storybook/preset-typescript'],  // TypeScriptに対応するためのプリセット
  core: {
    builder: 'webpack5'  // アドオンがWebpack 5を要求するのでこれを指定
  },
  webpackFinal: async (config) => {
    config.module.rules = [
      // デフォルトのrulesに入っているCSS用の設定が悪さをするのでお帰りいただく
      ...config.module.rules.filter(rule => rule.test.source !== (/\.css$/).source),
      // css-loader を設定しなおす
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
    config.resolve.alias = {
      "@": path.resolve(__dirname, "..")      // こっちは私の趣味です
    }
    return config
  }
}

余談ですが、aliasの設定は

  webpackFinal: async (config) => ({
    ...config,
    resolve: {
      alias: {
        "@": path.resolve(__dirname, "..")
      }
    }
  })

って書くのも読んでもらえなかったので、あんまり融通がきかないようです
rulesは読めるのになんでだろうね

おわりに

インターネットの海に浮かんでいるミスリードたちに阻まれながら、私はここにたどり着くために今日1日を費やしてしまいました
悩んでいるあなたを救えたのなら私も苦労した甲斐があったというものです

なにか改善点などアドバイスがあればコメントいただけるとうれしいです

では、良いStorybookライフを!

2020/10/16 追記

CSS ModulesでインポートしていたclassNameがundefinedになっていることに気付いたので調査
依存関係に css-loader が必要になりました
それに伴って main.js も書き換わっています

2022/04/01 追記

久しぶりに触ったらTypeScriptがうまく解釈されず、devDependencies が4つほど増えることになりました
今回変更分は、TypeScript用のPresetの追加と、それがWebpack 5に依存しているためインストールとバンドラを指定する設定の追加です
なので、今後のアプデでWebpack 5対応がされれば不要になるかもしれません

GitHubで編集を提案

Discussion