【CSF3.0対応】Next.js + CSS Modules(Sass)にStorybookを導入し、諸々のセットアップを済まそう
Next.jsプロジェクトの作成
今回は、TypeScriptに対応したNext.jsを基盤として利用するため、以下コマンドを実行します。
yarn create next-app --typescript
インストール完了後、pagesとstylesをsrcディレクトリに移動してください。この先の手順は、src配下にpagesやstylesディレクトリが配置されていることを前提に進めています。
Prettier + Stylelint + ESLintを含めたNext.jsの環境構築は以下を参考にしてみてください🌟
Storybookのインストール
プロジェクトのルートディレクトリで以下コマンドを実行します。
npx sb init
途中でeslintPlugin
関連の質問を聞かれるので、「y」を入力してください。
? Do you want to run the 'eslintPlugin' migration on your project? › (y/N)
インストール完了後、以下コマンドを実行し、Storybookの画面が立ち上がるか確認しましょう。
yarn storybook
http://localhost:6006/ でStorybookが問題なく起動することを確認したら、以下設定をあらかじめ追加しておきましょう。
eslintにルール設定を追加
.eslintrc.json
に以下記述を追加します。
{
"extends": [
"next/core-web-vitals",
+ "plugin:storybook/recommended"
]
}
public配下の画像を表示させる
publicディレクトリ配下の画像をStorybook内で読み込む場合、.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"],
};
Sass(SCSS)を対応させる
StoryBook側で特に何か設定していなくともCSSModulesには対応していますが、Sass(SCSS)を使いたい場合は以下コマンドを実行し、必要パッケージをインストールします。
yarn add -D css-loader sass sass-loader style-loader
上記実行後、.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"],
+ 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;
},
};
上記のポイントはurl: false
の設定です。本設定を行わなければ、ルート相対パスで記載されているCSSでの背景画像を取得できなくなくなるので注意してください。
global.css(scss)を適用させる
プロジェクトのglobal.css
をStorybookで読み込ませたい場合、.storybook/preview.js
に以下記述を追加します。
+ import "../src/styles/globals.css";
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
};
next/routerを対応させる
以下コマンドを実行し、必要パッケージをインストールします。
yarn add -D storybook-addon-next-router
上記実行後、.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",
+ "storybook-addon-next-router",
],
framework: "@storybook/react",
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/"),
});
config.resolve.alias["@"] = rootPath;
return config;
},
};
さらに、.storybook/preview.js
に以下記述を追加します。
+ import { RouterContext } from "next/dist/shared/lib/router-context";
import "../src/styles/globals.scss";
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
+ nextRouter: {
+ Provider: RouterContext.Provider,
+ },
};
next/imageを対応させる
.storybook/preview.js
に以下記述を追加します。
+ import * as nextImage from "next/image";
import { RouterContext } from "next/dist/shared/lib/router-context";
import "../src/styles/globals.scss";
+ Object.defineProperty(nextImage, "default", {
+ configurable: true,
+ value: (props) => {
+ return <img {...props} />;
+ },
+ });
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
nextRouter: {
Provider: RouterContext.Provider,
},
};
Storyファイルで絶対パスが通るようにする
StoryファイルもしくはStoryファイル内でimportしたコンポーネント内で、絶対パスで何らかのファイルをimportしている場合、以下のようなエラーが発生します(ModuleNotFoundError)
ModuleNotFoundError: Module not found: Error: Can't resolve 'src/components/atoms/Button/Button.module.scss' in '/Users/~中略~/src/components/atoms/Button'
上記エラーを回避し絶対パスを通すために.storybook/main.js
に以下記述をそれぞれ追加します。
+ const path = require("path");
+ const rootPath = path.resolve(__dirname, "../src/");
module.exports = {
stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-interactions",
"storybook-addon-next-router",
],
framework: "@storybook/react",
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/"),
});
+ config.resolve.alias["@"] = rootPath; // srcを@と省略してパスを記載できるように設定
return config;
},
};
ウェブアクセシビリティチェックに便利なプラグインを導入する
以下コマンドを実行し、必要パッケージをインストールします。
yarn add -D @storybook/addon-a11y
上記実行後、.storybook/main.js
に以下を追加します。
const path = require("path");
const rootPath = path.resolve(__dirname, "../src/");
module.exports = {
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-a11y",
],
framework: "@storybook/react",
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/"),
});
config.resolve.alias["@"] = rootPath; // srcを@と省略してパスを記載できるように設定
return config;
},
};
Googleフォント(CDN)を利用する場合
.storybookディレクトリ配下にpreview-head.html
を作成し、以下のような記述を追加する。
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700;900&family=Noto+Sans+JP:wght@300;400;500;700&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" />
実際にStorybookにコンポーネントを追加してみる
今回は例として、Buttonコンポーネントを作成し、Storyファイルで読み込ませてStorybookで表示されるように実装を進めていきます。
Buttonコンポーネントを作成
src下にcomponentsディレクトリを作成し、その配下でButtonコンポーネントを作成します。
import Link from "next/link";
import styles from "./Button.module.scss";
type ButtonProps = {
href: string;
children: React.ReactNode;
};
export const Button = ({ href, children }: ButtonProps) => {
return (
<Link href={href}>
<a className={styles.button}>
{children}
</a>
</Link>
);
};
CSSModules(Sass)を作成する
上記のButtonコンポーネントを配置したディレクトリ下にButton.module.scss
を作成します。今回はscss記法を実現したかったので、cssファイルではなくscssファイルをCSSModulesとして利用しています。
.button {
display: inline-block;
padding: 16px 32px;
font-weight: 500;
color: #fff;
background-color: #3ea8ff;
border-radius: 10px;
box-shadow: 0px 0px 15px -5px #0f83fd;
&:hover {
opacity: 0.7;
}
}
Storyファイルを作成する
上記のButtonコンポーネントを配置したディレクトリ下にButton.stories.tsx
を作成し、以下のように実装します。今回はCSF3.0による実装方法を採用しています。
import { ComponentMeta, ComponentStoryObj } from "@storybook/react";
import { Button } from "./Button";
export default { component: Button } as ComponentMeta<typeof Button>;
export const Index: ComponentStoryObj<typeof Button> = {
args: {
href: "/",
children: "Button",
},
};
Storybookを起動する
以下コマンドを実行し、Storybookを起動します。
yarn storybook
http://localhost:6006/ でStorybookが立ち上がったら、以下のような画面で作成したButtonコンポーネントが確認できるはずです。
StoryShotsを導入する
続きは以下からどうぞ。
以上です。お疲れ様でした🙏
Discussion