おはようございます、こんにちは、こんばんわ。
今回はEJSでのStorybook導入とStorybookの簡単な使い方を書きます。
StorybookはUIコンポーネントとページを分離して構築するためのオープンソースツールです。
Storybook公式サイト
前置き
「Storybook 入門」とかで調べるとReactやらNuxtやらへの導入を前提とした記事が溢れています。
コンポーネント化を行うならばもちろんそれらのフレームワークを使うのが1番ですが、HTMLのテンプレートエンジンもフレームワークほどではないとはいえ、できない事もないです。
また、同時に2つのことを学ぶより1つのことを学ぶ方が楽だろうと思い、弊社で使われている「EJS」への導入を試みました。
Storybookはアドオンが強力なので、ぜひ最後まで読んで実践してみてください。
ファイル構成
root/
├ dest/ <!-- srcの出力ファイル -->
├ src/ <!-- コンパイル・トランスパイル前提で編集するファイル -->
│ ├ ejs/ <!-- .ejsを格納 -->
│ └ scss/ <!-- .scssを格納 -->
│
├ static/ <!-- コンパイルしない静的ファイル -->
├ node_modules/
└ package.json
Storybook導入
StorybookはVue.jsやReact環境下だとCLIでバコッと導入できたりするのですが、EJSの環境下での導入なので手動で導入を進めます。
まずはHTML用のStorybookをインストールします。
$ npm install @storybook/html
次はStorybook用の設定ファイル・フォルダを用意します。
root/直下に.storybookフォルダを作成します。
root/
├ .storybook/ <!-- new!! -->
├ dest/
├ src/
│ ├ ejs/
│ └ scss/
│
├ static/
├ node_modules/
└ package.json
作成した.storybookフォルダの中に設定ファイルを2ファイル(main.js,preview.js)を作成していきます。
const path = require('path');
module.exports = {
webpackFinal: async (config, { configType }) => {
config.module.rules.push();
return config;
},
stories: ['../stories/**/*.stories.@(js|mdx)']
};
import { addDecorator } from '@storybook/html'
もう一息です。
package.jsonに起動用のscriptを記述します。
"scripts": {
"storybook": "start-storybook -p 6006"
}
(6006のポート番号だと都合の悪い方は自由な番号で大丈夫です)
あとは正常に起動すれば一旦導入は完了です。
$ npm run storybook
コンポーネントを作成
コンポーネントの作成場所は以下になります。
root/
├ .storybook/
├ dest/
├ src/
│ ├ ejs/
│ │ └ _partials/
│ │ └ _components/ <!-- _components/配下に作成 -->
│ │
│ └ scss/
│
├ static/
├ node_modules/
└ package.json
試しにボタンコンポーネントを作成してみましょう。
<% //初期値を設定%>
<% if (typeof disabled === 'undefined') { var disabled = false; } %>
<% if (typeof type === 'undefined') { var type = 'button'; } %>
<% if (typeof label === 'undefined') { var label = ''; } %>
<% if (typeof modefireClass === 'undefined') { var modefireClass = ''; } %>
<% if (typeof jsClass === 'undefined') { var jsClass = ''; } %>
<% //初期値を設定%>
<button <%= disabled ? 'disabled' : '' %> type=<%= type %> class="<%= modefireClass %> <%= jsClass %>" ><%= label %></button>
以下の5つの設定をできるように作成しました。
- disabled
- type
- label
- modefireClass
- jsClass
余談ですが、初期値を設定を設定してあげないとEJSに怒られてしまうので、初期値は必須になります。
EJSを読み込めるようにする
EJSのローダーがあるので、インストールします。
$ npm install ejs-compiled-loader
インストール後は.storybook/main.jsにEJSローダー用の設定を記述するだけです。
const path = require('path');
module.exports = {
webpackFinal: async (config, { configType }) => {
config.module.rules.push(
{
test: /\.ejs$/,
loaders: ['ejs-compiled-loader'],
// 読み込む予定のEJSのディレクトリを指定する
include: path.resolve(__dirname, '../src/ejs/_partials/_components/')
}
);
return config;
},
stories: ['../stories/**/*.stories.@(js|mdx)']
};
完了!
storiesの作成
作成したボタンコンポーネントをStorybookに表示させるため、storie/に設定ファイル(button.stories.js)を作成します。
xxx.stories.js
xxx部分は自由ですが、わかりやすいように表示するコンポーネントと名前を統一します。
import button from '../src/ejs/_partials/_components/_button.ejs'; // コンポーネントを読み込む
export default {
title: 'button', // storybookでリストに表示されるタイトル名
component: button // importしたコンポーネント
};
export const nomarl = () => {
// コンポーネントに渡す変数を宣言
const label = 'Click Here';
const type = 'button';
const disabled = false;
const modefireClass = '';
const jsClass = '';
return button({ label, type, disabled, modefireClass, jsClass }); // 各変数を引数に入れてコンポーネントを表示
};
ここまで終わってStorybookを起動するとコンポーネントが表示されるはずです。

これにてstoriesの作成は完了になります。
コンポーネントが複数ある場合は1ファイルで完結する事もできると思いますが、コンポーネントごとに設定ファイルを作成するのをお勧めします。
StorybookにSCSSを読み込ませる
CSSを読み込んでもいいですが、コンポーネント用のSCSSは大体インポートして使われると思うので、そのまま読み込めるようにSCSSに対応します。
まずは各ローダーをインストール
$ npm install style-loader@2.0.0 css-loader@5.2.6 sass-loader@10.0.0
各ローダーが最新バージョンだと動かないらしいので、こちらのissueを参考にバージョン指定しています。(鬼ハマりポイント)
https://github.com/storybookjs/presets/issues/195
sass-loaderなどバージョンを下げているので、すでにnode-sass、sass(dart-sass)などをインストールされている方はバージョンの見直しをしてください。
今回はnode-sassを入れます。
$ npm install node-sass@4.14.1
インストール後は.storybook/main.jsにローダー用の設定を記述します。
const path = require('path');
module.exports = {
webpackFinal: async (config, { configType }) => {
config.module.rules.push(
{
test: /\.ejs$/,
loaders: ['ejs-compiled-loader'],
include: path.resolve(__dirname, '../src/ejs/_partials/_components/')
},
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
// 読み込む予定のSCSSのディレクトリを指定する
include: path.resolve(__dirname, '../src/scss/css/')
}
);
return config;
},
stories: ['../stories/**/*.stories.@(js|mdx)']
};
sass(dart-sass)を使っている方はsass-loaderでoptionの記述をする必要があるので、ご注意を。
これでSCSSをインポートできるようになりました!
各ファイルに必要なSCSSを読み込んでいきましょう。
ますはstorie/button.stories.js
import button from '../src/ejs/_partials/_components/_button.ejs';
import '../src/scss/css/_partials/_components/_button.scss'; // 必要SCSSを適宜読み込む
export default {
title: 'button',
component: button
};
export const nomarl = () => {
const label = 'Click Here';
const type = 'button';
const disabled = false;
const modefireClass = '';
const jsClass = '';
return button({ label, type, disabled, modefireClass, jsClass });
};
全てのコンポーネントへ共通で読み込みたいファイルがある場合は.storybook/preview.jsで読み込めばOKです。
import { addDecorator } from '@storybook/html';
import '../src/scss/css/bundle.scss';
Storybookを起動すると、SCSSが反映されているはずです。
Storybookにアドオンを入れる
アドオンを入れてないStorybookはネタの乗っていない寿司みたいなものです。
アドオンを入れることでさらに強力なツールになります。
今回は以下4つのアドオンを追加します。
-
@storybook/addon-a11y
- アクセシビリティチェックをしてくれます。
-
@storybook/addon-knobs
- コンポーネントに渡す値をUIを用いて変更できるようにしてくれます。
-
@storybook/addon-notes
- マークダウン形式で説明文を記述できるようになります。
-
@storybook/addon-storysource
-
stories.jsのソースコードをコンポーネントと合わせてブラウザから見ることができます。
-
まずはインストール
$ npm install @storybook/addon-a11y @storybook/addon-knob @storybook/addon-notes @storybook/addon-storysource
各ファイルに必要な記述をしていきます。
const path = require('path');
module.exports = {
webpackFinal: async (config, { configType }) => {
config.module.rules.push(
{
test: /\.ejs$/,
loaders: ['ejs-compiled-loader'],
include: path.resolve(__dirname, '../src/ejs/_partials/_components/')
},
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
include: path.resolve(__dirname, '../src/scss/css/')
}
);
return config;
},
stories: ['../stories/**/*.stories.@(js|mdx)'],
addons: [ // アドオンは追加したらここに記述する
'@storybook/addon-a11y',
'@storybook/addon-knobs',
'@storybook/addon-storysource',
'@storybook/addon-notes'
],
};
import { addDecorator } from '@storybook/html';
import { withA11y } from '@storybook/addon-a11y'; //全てのコンポーネントで反映させるため、ここでimport
import '../src/scss/css/bundle.scss';
addDecorator(withA11y);
@storybook/addon-knobsは覚えることがちょっとあるので、公式ドキュメントを参照しながら触ってみてください。
import { withKnobs, text, select, boolean } from '@storybook/addon-knobs'; // 渡す変数の指定方法によって記述が変わります。
import button from '../src/ejs/_partials/_components/_button.ejs';
import '../src/scss/css/_partials/_components/_button.scss';
const readme = ` // @storybook/addon-storysource用の記述
## Props
- label
- 初期値: ''
- type
- 初期値: 'button'
- disabled
- 初期値: false
- modefireClass
- modefire用のクラス付与
- 初期値: ''
- jsClass
- JavaScript用のクラス付与
- 初期値: ''
## 関連コンポーネント
なし
`;
export default {
title: 'button',
component: button,
decorators: [withKnobs],
parameters: {
notes: readme // readme読み込み
}
};
export const nomarl = () => {
const label = text('Text', 'Click Here'); // @storybook/addon-knobs用の記述
const type = select('type', { // @storybook/addon-knobs用の記述
button: 'button',
submit: 'submit',
reset: 'reset'
});
const disabled = boolean('disabled', false); // @storybook/addon-knobs用の記述
const modefireClass = text('modefireClass', ''); // @storybook/addon-knobs用の記述
const jsClass = text('jsClass', ''); // @storybook/addon-knobs用の記述
return button({ label, type, disabled, modefireClass, jsClass });
};
これでStorybookの画面から設定変数の値を触れたり、コンポーネントの説明を参照することができるようになりました。
お気に入りのアドオンでカスタマイズして最強のStorybookを目指しましょう!
