💎

Next.jsベースの開発環境構築 及び GitHub ActionsによるStorybookの自動デプロイ

2021/03/15に公開

はじめに

個人開発だけでなく、チーム開発でも使えるように書きました。また、Webアクセシビリティを意識した開発をするためのStorybookアドオンの紹介や開発者だけに閉じがちな情報をオープンにするためのStorybook自動デプロイの方法なども書いてあります。

セットアップ内容

  • Next.js (記事作成時 v10.0.8)
  • TypeScript
  • SCSS
  • Jest
  • ESLint
  • Prettier
  • lint-staged
  • husky
  • Storybook
  • GitHub Actions

動作環境

$ sw_vers                                                           
ProductName:	Mac OS X
ProductVersion:	10.15.7
BuildVersion:	19H2

$ node -v
v14.15.0

$ yarn -v
1.22.4

Next.jsプロジェクトの作成

$ npx create-next-app --example with-typescript-eslint-jest next-app

next.js/examples用意されているこちらのボイラープレートを使わせていただきます。

このコマンドを実行するだけで、ESLint, Prettier, Jest, lint-staged, huskyが導入されたNext.jsプロジェクトを作成することができます。コマンド最後のnext-appがプロジェクトの名前(ディレクトリ名)になります。任意の名前で置き換えてください。

なお、上記コマンドで作成したプロジェクトはデフォルトでyarn.lockを作成します。package-lock.jsonを使用したい場合は、上記コマンドの最後に--use-npmをつけてください。もし、意図したlockファイルではない方が作成されていた場合はそのlockファイルとnode_modulesを削除して、使いたい方のコマンドでyarn install or npm installしてください。

また、初回作成時はインストールした各パッケージのバージョンが固定されていないので、必要に応じて固定しましょう。

husky + lint-staged について

husky: commitやpushを行う直前に任意のコマンドを実行することができるツールです。
lint-staged: gitのステージに上がっているファイルのみをターゲットできるツールです。

これらを組み合わせることで、例えば、commitコマンドを実行した際に、いきなりcommitせずに、Lintエラーのチェックとフォーマットを実行し、これらが正常に終了した場合にcommitさせる、ということが可能になります。

起動してみる

$ cd next-app
$ yarn dev

上記コマンドを実行したら、 http://localhost:3000 にアクセスしてみてください。以下の画像の通りに表示されていれば問題なく作成できています。

カスタマイズする

ここは必要性やお好みに合わせて設定してみてください。

  • 起動するポートを変更する
package.json
...
  "scripts": {
-    "dev": "next dev",
+    "dev": "next dev -p 3001",
...
  • 末尾コロンつけないようにする

設定変更したらyarn formatを実行してフォーマットしておきます。

.prettierrc
{
  "semi": false,
  "singleQuote": true,
+  "trailingComma": "none"
}
  • .eslintrc.jsonの設定の表記を数字から英語にする

数字だと設定がわかりにくいです。

0 --> "off"
1 --> "warn"
2 --> "error"
  • npm installコマンドの実行を禁止する

yarn.lockが作成された状態でnpm installするとpackage-lock.jsonが作成されてlockファイルが2種類になってしまいます。

プロジェクトのルートディレクトリに.npmrcを作成

.npmrc
engine-strict=true
package.json
...
+    "engines": {
+      "npm": "npmではなくyarnを使ってください"
+    },
    "dependencies": {
...

SCSS導入

$ yarn add sass

sassパッケージをインストールするだけで終わりです。Next.jsでは大変ありがたいことにこれだけでScssを使用することが可能になります。また、Next.jsでは{任意の名前}.module.scssというファイル名にすることでSCSS Modulesとしてscopedにスタイルを当てることができます。

なぜ、Tailwind CSSではなく、CSS Modulesの形式を採用したのかといいますと、デザイナーの開発への参加しやすさを考慮しています。CSS(SCSS)ファイルとしてTSXファイルから分離されていることで、スタイルのコーディングのハードルを下げています。また、TSX自体もスタイルの定義が減って読みやすくなります。ただ、気持ち的な理由ではあるので、どちらを採用するかはお好みで選べば良いと思っています。

Storybook導入

$ npx sb init

上記のコマンドを実行するだけでStorybookが起動できるようになります。このコマンドは多くの人が使うであろうアドオンも一緒にセットアップしてくれます。詳しくはこちら

$ yarn storybook

上記のコマンドを実行することでStorybookが起動します。コマンドを実行するだけで自動的にStorybookのページをブラウザで表示してくれるはずです。自動的に表示されない場合は、 http://localhost:6006 にアクセスしてください。以下の画像のように表示されていればOKです。

しかし、このままだとESLintの設定に引っかかるので修正していきます。

まずは、フォーマットしましょう。

$ yarn format

自動整形されなかったところは手動で直してESLintに引っかからないようにしましょう。exampleが不要であれば、storiesディレクトリごと削除してしまっても良いです。その場合は、.storybook/main.jsstoriesに設定しているパスを削除しておきましょう。逆に、componentsディレクトリを作成してそこにストーリーファイルを置きたいという場合はstoriesにパスを設定する必要があります。

以下は修正の例です。良しなに対応してもらえればと思います。

stories/Page.tsx
...
export interface PageProps {
-  user?: {}
+  user?: Record<string, unknown>
...
-          such data from the "args" of child component stories
+	  such data from the {'"args"'} of child component stories
...
stories/header.tsx
...
export interface HeaderProps {
-  user?: {}
+  user?: Record<string, unknown>
...

StorybookでScssを読み込めるようにする

$ yarn add -D css-loader sass-loader style-loader

必要なローダーをインストールします。そして、インストールしたローダーをStorybookで使うように設定します。

.storybook/main.js
+const path = require('path')
...
  addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
+  webpackFinal: async (config) => {
+    config.module.rules.push({
+      test: /\.scss$/,
+      use: ['style-loader', 'css-loader', 'sass-loader'],
+      include: [
+        path.resolve(__dirname, '../styles'),
+        path.resolve(__dirname, '../components'),
+        path.resolve(__dirname, '../node_modules')
+      ]
+    })
+
+    return config
+  }
}

next/imageがStorybookで使えないので置き換える設定を追加

.storybook/preview.js
+import * as nextImage from 'next/image'
...
+Object.defineProperty(nextImage, 'default', {
+  configurable: true,
+  value: (props) => {
+    const { width, height } = props
+    const ratio = (height / width) * 100
+    return (
+      <div
+        style={{
+          paddingBottom: `${ratio}%`,
+          position: 'relative'
+        }}
+      >
+        <img
+          style={{
+            objectFit: 'cover',
+            position: 'absolute',
+            width: '100%',
+            height: '100%'
+          }}
+          {...props}
+        />
+      </div>
+    )
+  }
+})

カスタマイズする

  • Storybook上で任意の画面サイズのプレビューを見れるようにする
.storybook/preview.js
export const parameters = {
-  actions: { argTypesRegex: '^on[A-Z].*' }
+  actions: { argTypesRegex: '^on[A-Z].*' },
+  viewport: {
+    viewports: {
+      iphone5S: {
+        name: 'iPhone 5S・SE1',
+        styles: {
+          width: '320px',
+          height: '568px'
+        }
+      },
+      iphone8: {
+        name: 'iPhone 8・SE2',
+        styles: {
+          width: '375px',
+          height: '667px'
+        }
+      }
+    }
+  }
}
  • Storybookででwebアクセシビリティの確認をできるようにする
$ yarn add -D @storybook/addon-a11y

アドオンのパッケージをインストールします。インストールしたアドオンをStorybookに適用します。

.storybook/main.js
...
-  addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
+  addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-a11y'],
...

@storybook/addon-a11yはコンポーネントがWebアクセシビリティを準拠しているか確認することができます。昨今、提供するプロダクトがWebアクセシビリティを準拠していることを求められるようになりつつあるので、このアドオンを使ってコンポーネントに警告が出たら、都度対応してみることから始めてみると良いのかもしれません。

GitHub Actionsを用いたStorybookの自動デプロイ設定

これを行うためには、GitHubにリポジトリを作成して、ここまでで作成したコードをpushする必要があります。GitHub Actionsを用いてmainブランチにマージされたタイミングでGitHub PagesにStorybookを自動デプロイさせます。

Storybookの自動デプロイの設定によって、Storybookが埃を被りにくくなり、開発環境をセットアップしていない人にも、すぐに開発状況を視覚的に共有することが可能です。

つまり、効率的な開発を持続するメリットと開発者に閉じがちな情報をオープンにしてコミュニケーションを円滑にするメリットがあります。

Storybookのビルドコマンドを設定

package.json
...
-    "build-storybook": "build-storybook"
+    "build-storybook": "build-storybook -o build -s ./stories/assets"
...

-oはビルドするディレクトリを指定していて、-sは静的なファイルをビルドに含めるためにパスの指定をしています。

GitHub Actionsを用いたCIによるStorybookの自動デプロイ設定

.github/workflows/deploy.ymlというファイルを作成してください。

.github/workflows/deploy.yml
name: deploy

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: install packages
        run: yarn install
      - name: build storybook
        run: yarn build-storybook
      - name: deploy storybook
        uses: JamesIves/github-pages-deploy-action@4.1.0
        with:
          branch: gh-pages
          folder: build

GitHub Actionsによるデプロイを行うためにJamesIves/github-pages-deploy-actionを使用しています。

重要なポイントとして、build-storybookする時のディレクトリ指定とGitHub Actionsでのdeploy storybookのフォルダー指定は同じである必要があります。今回は両方ともbuildディレクトリを指定しています。

.github/workflows/deploy.yml
on:
  push:
    branches:
      - main

上記の設定はmainブランチにpush(merge)した時のみGitHub Actionsのjobが実行される設定です。この設定をしておかないとPRを作成するため、もしくはPRにcommitを積むためのpushを行うたびにbuildが実行されて非常に冗長な処理が発生します。

GitHubリポジトリのGitHub Pagesの設定

リポジトリのSettingsタブを押して最初に表示されているOptionsのページをスクロールすると以下の画像のようにGitHub Pagesという項目があるので画像のようにBranchをgh-pages、パスは/(root)を選択してください。

一度mainブランチにPRをマージしてgh-pagesブランチを生成した後でないとBranchにgh-pagesを選択できないかもしれません。

GitHub Actions設定のyamlファイルでbranch: gh-pagesを指定したので、gh-pagesブランチでビルドされます。これによって、mainブランチにはビルドされたファイルが置かれないので、ローカルにpullされることもありません。

GitHub Pagesの項目の設定をするとすぐ上にURLが表示されるので、リポジトリのホーム画面にこのURLを設定しましょう。

画面右上のAboutの歯車アイコンをクリックしてURLを設定してください。これでリポジトリのホーム画面にStorybookをデプロイしたURLが表示されて、1クリックで最新のStorybookを見ることが可能になりました。

今回実装したGitHubのリポジトリ

https://github.com/yukishinonome/next-storybook-github-actions-deploy

Discussion