😵

Dockerで構築したNext.JS環境にStorybook導入したら、思いの他苦労した件🤮

2023/09/15に公開

経緯

本業の方で、Next.JS 13(App RouterはまだBeta版)を用いて開発、というか既存のレガシーな作りを取っ払ってNextにreplaceしちゃおうぜ計画を行っている。
その際に技術的に気になったことをメモしていく。できれば継続的に🐟

元々Storybook自体はインストールしていたのだが、当時はNext Replaceの期限が近々に設定されていたこともあり、実装は後回しにしていた。

Replace実装が進むに連れ、「やっぱりコンポーネント管理が大変だなぁ」という気持ちと、「将来的にVRTも導入してしっかりUIテストできるようにしたいなぁ」と思うようになった & 色々あって期限がかなーり先延ばしになったこともあり、今のうちにサラッとStorybookも実装しておこうとしたのだが・・・

npm run storybookで画面が立ち上がらない

本来なら上記コマンドでStorybookの画面が立ち上がるはずなのだが、

あれ・・・

storybook自体の起動はできているのになあぜなあぜ?

と思い、記事を漁ったりなんなりしていて、以下の記事を見ていたら、
https://qiita.com/fsd-ssk/items/9f02ea8d4d14a7978af0

単純にdocker.ymlファイルでポート番号を指定してなかっただけだったことに気づいた😵

Storybookのデフォルトのポートは6006。
Docker環境下でStorybookを使用する際は、コンテナ内のアプリケーション(この場合はStorybook)が使用するポートと、ホストマシンに公開したいポートをマッピングする必要があって、これにより、ホストマシンからコンテナ内のStorybookにアクセスできるようになる。

というわけで追加(container_nameは記事用に仮の名前にしておきます)

docker-compose.yml
services:
  app:
    build: ./
    container_name: 'shibuyajihen'
    volumes:
      - ./src:/app
    ports:
      - "3000:3000" # Next開発環境のport
      - "6006:6006" # Storybookのport

無事立ち上がリーノ🤞🏻

loader関連エラーに遭遇

Storybookの画面が立ち上がったので、早速どこかのコンポーネントのstoryファイルを作ってみる。

対象のコンポーネントはこれ👇🏻

index.tsx
import style from './index.module.scss';

export default function H2(props: Title) {
  return <h2 className={style['h2-title']}>{props.title}</h2>;
}

interface Title {
  title: string;
}

最初だったので、h2要素だけをまとめた簡単なコンポーネントのstoryをとりあえず作ってみることに。

index.stories.tsx
import { ComponentMeta, ComponentStory } from "@storybook/react";
import { H2 } from ".";

export default {
  title: "components/atoms/H2",
  component: H2,
} as ComponentMeta<typeof H2>;

export const Template: ComponentStory<typeof H2> = (args) => (
  <H2 {...args} />
);

よし、これで再度storybook起動させよう!!

You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders

グェホ🤮

今度はloaderエラー。
Webpackが特定のファイルタイプを処理するためのローダーが設定されていないとのこと。

Storybookも内部的にWebpackを使用しているらしい。
確かにmain.jsファイルにも、

.storybook/main.js
core: {
   builder: "@storybook/builder-webpack5",
 },

このように記載ある。
今回の場合だと、sassを読み込んでいるので、これを処理するためのローダーをインストールする必要がありそうだ。

ということで、👇🏻の記事を参考に、必要なloader達をインストールしていく。
https://zenn.dev/toono_f/articles/3aa990971975cb

npm install sass-loader css-loader style-loader --save-dev

そしてmain.jsファイルに、カスタマイズしたWebpack設定を返す関数を定義するためのプロパティを記載。
ついでにpublic配下の画像を表示させる記述も追加。

.storybook/main.js
module.exports = {
  stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-interactions",
  ],
  framework: "@storybook/react",
  core: {
    builder: "@storybook/builder-webpack5",
  },
+ staticDirs: ["../public"], // public配下の画像を表示させる
+ webpackFinal: async (config) => {
+   config.module.rules.push({
+     test: /\.scss$/,
+     use: [
+       "style-loader",
+       {
+         loader: "css-loader",
+         options: {
+           modules: {
+             auto: true, // *.module.scssファイル全てを対象
+           },
+           url: false, // cssのbackgroundで設定した画像へのパスがプロジェクトルートからの絶対パスになるように設定
+         },
+       },
+       "sass-loader",
+     ],
+     include: path.resolve(__dirname, "../src/"),
+   });
+    return config;
+ },
};

これで再度起動してみると、今度は

ReferenceError: path is not defined

と出たので、main.js上部に下記記載

.storybook/main.js
const path = require('path');

その後、また起動したらまたloader関連エラー出て1時間くらい沼り、単純な記述ミスだということが発覚(なんだよもううううう!!!! またかヨォぉぉぉぉぉ!!!!!)。
一度獣の巨人になりかけたものの、何とか耐えて無事再起動することに成功した😆

喜びを胸に、起動した画面を見ると、何やら緑のお知らせが。

下に続く👇🏻

Storybookのバージョンを6系 → 7系にアップデートさせる

通知内容は、「現在Storybookの最新バージョンは7.4.1です。アップデートしてね!」的な内容だった。(僕らの環境のバージョンは6系)

調べてみると、7系ではコンパイルも早くなりパフォーマンスも向上するだけでなく、既存の記述方法より短いコードでstory作成できる点、コード・カバレッジ・レポートによるテストとの掛け合わせ幅の広がりなど、メリットしかなかった😆

アップデートするならこのタイミングでやらないと後々とても面倒なことになりそうな予感がしたので、
公式によるアップデートガイドを読んで、今の環境に入れても特に問題ないことを確認し、実際にアップデートを行った。
https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#from-version-65x-to-700

storybookのアップデートコマンドが優秀すぎる

まずは公式に言われるがまま、アップデートコマンドを叩く

npx storybook upgrade

そうすると、色んな質問が出てきて、Yesと回答していくだけで、pakage.jsonやmain.js、preview.jsなどで必要な変更箇所が、結構自動でどんどん書き変わってくれた😳

同じくアップデートした方の記事によると、一部書き変わってくれない箇所もあるらしいが、僕の方ではそれも全部置き換わってくれていた。
(ちなみにどの部分がどう変更されるのかなども、記事で詳しく記載してくれていたので助かりました🙏🏻)
https://zenn.dev/sorinaji/articles/migration_storybook_from_65x_to_7x

変更した箇所

とはいえ、全く変更が必要ないわけではない、というより推奨される書き方がある(公式より)
ので、公式見ながら、一部その形式にしていく作業を行った。

preview.jsのts化とdefault export化

preview.ts
+ import { Preview } from '@storybook/react';

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

+ const preview: Preview = {
+  parameters: {
+   actions: { argTypesRegex: '^on[A-Z].*' },
+   controls: {
+    matchers: {
+     color: /(background|color)$/i,
+     date: /Date$/,
+    },
+   },
+  },
+ };

+ export default preview;

main.jsのts化とdefault export化(ここで無量空処ポイント)

main.ts
+ import { StorybookConfig } from '@storybook/nextjs';

- module.exports = {
+ const config: StorybookConfig = {
 stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
 addons: [
   "@storybook/addon-links",
   "@storybook/addon-essentials",
   "@storybook/addon-interactions",
-  "storybook-addon-next-router",
+  '@storybook/addon-mdx-gfm'
 ],
- framework: "@storybook/react",
+ framework: {
+   name: '@storybook/nextjs',
+   options: {},
+ },
- core: {
-  builder: "@storybook/builder-webpack5",
- },
 staticDirs: ["../public"],
- webpackFinal: async (config) => {
-  config.module.rules.push({
-    test: /\.scss$/,
-    use: [
-      "style-loader",
-      {
-        loader: "css-loader",
-        options: {
-          modules: {
-            auto: true, // *.module.scssファイル全てを対象
-          },
-         url: false, // cssのbackgroundで設定した画像へのパスがプロジェクトルートからの絶対パスになるように設定
-        },
-     },
-      "sass-loader",
-    ],
-    include: path.resolve(__dirname, "../src/"),
-  });
-  return config;
- },
+ docs: {
+   autodocs: true,
+ },
};

+ export default config;

上の差分を見て、もうお気づきだと思うが、
今までStorybookの環境を構築してくれてたframeworkは、@storybook/builder-webpack5だったのだが、7になってからは、、Next.jsとStorybookを使う場合は、@storybook/nextjsで行えるようになっている。
んで、この@storybook/nextjsには、もう既にcss-loader系の処理をやってくれているらしい笑
(開発者のPRを参照👇🏻)
https://github.com/storybookjs/storybook/releases/tag/v7.0.0-rc.11

つまり、バージョンあげる前に頑張ってやってたローダー系の処理がいらないということ・・・・・

・・・・情報が追いつかなくてしばらく思考停止した🙄

とはいえ、嬉しい誤算ではあるよね笑

CSF3になったことによる、stories.tsxファイルの記載方法

今までCSF2記法だった箇所がCSF3記法になることによって、記述量が少なく済むらしいので、その記法に変えていった。
具体的にどう変わったかは、この記事が参考になった
https://qiita.com/masaya82/items/86b03464116cbac4f2f4

とはいえ、ある程度はこのコマンドで自動でやってくれるからすごい😳

npx storybook@latest migrate csf-2-to-3 --glob="src/*.stories.tsx"

直してもらったら、argsなどを設定して少し修正する

index.stories.tsx
import { Meta, StoryObj } from '@storybook/react';
import { H2 } from ".";

const meta: Meta<typeof H2> = {
  argTypes: {
    title: {
      control: {
        type: 'text',
      },
    },
  },
  component: H2,
  tags: ['autodocs'],
  title: 'atoms/H2',
};

export default meta;

export const Default: StoryObj<typeof H2> = {
  render: (arg) => <H2 {...arg} />,
};

これで、再度立ち上げる。

キタァァァァ🤩

7系から、lightモードとdarkモードも選べるようになっているらしく、ビジュアルもよりかっこよくなっていて中々良きだね!

まとめ

前に導入は済ませてるから、実装くらいちゃっちゃとできるっしょと思ったら思わぬ落とし穴があったなあw
でもこれをきっかけにバージョンアップできたからよかったかもしれぬ!

運用もシマっていくぞ!💪🏻

Discussion