おはようございます、こんにちは、こんばんわ。
今回は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を目指しましょう!