storybook for htmlがあまりにも使いづらいのでpugを使って上手く利用する方法を考えてみた


storybook for htmlを利用する場合ってvueとか使わないと思うのですが、実制作で素のHTMLで書くケースなんてほぼ無いですよね。pugで書かれる事が多いかと思います。
そんな中でstorybookを利用したい場合【コード補完の利かない環境でHTMLを書く】か【ビルドしてコピペ】、しかも書いたところでpugとして再利用できる内容ではないのです。
下手したら仕事が増えるだけでハッキリ言って使えません。検索にも殆ど引っかからない状態です。
かと言ってstorybookを使うためにnuxtを利用するってのも違うと思うので、上手く使う方法を考えてみました。

20/02/26
環境作ってみました。gitも置いてあるのでこちらの方を参考にしてください。
静的なWEBサイト用にも使えるstorybookのpug環境を作ってみた

前準備

今回説明する内容はpugの利用方法を説明するだけなので、特別な環境は作りません。
サンプルを作るのが面倒なので初期インストール+bootstrapのcssを使用して進めます。

npx -p @storybook/cli sb init --type html

インストールしたら、とりあえずこんな感じの構成で準備します。

├── .storybook
│   ├── config.js
│   └── preview-head.html
└── stories
    └── sample
        ├── button.pug
        └── sample.stories.js
storybook/preview-head.html
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
stories/sample/button.pug
  button.btn.btn-primary(type="button") Primary
  button.btn.btn-secondary(type="button") Secondary
  button.btn.btn-success(type="button") Success
  button.btn.btn-danger(type="button") Danger
  button.btn.btn-warning(type="button") Warning
  button.btn.btn-info(type="button") Info
  button.btn.btn-light(type="button") Light
  button.btn.btn-dark(type="button") Dark

stories.jsでpugから直接表示する方法

pugを読み込めるようにするには、以下を追加インストールします。

npm i pug pug-plain-loader html-loader -D

pugを読み込んで表示するだけだったら実は簡単だったりします。
pug-plain-loaderhtml-loaderを経由したものを出力するだけです。

import { storiesOf } from '@storybook/html';
import Pug2HtmlBtn from '!html-loader!pug-plain-loader!./button.pug'

storiesOf('sample', module)
.add('pug2html', () => Pug2HtmlBtn ) //htmlタグに変換されたものを表示するだけ

Pugのソース表示&コピーが出来るようにする

pugからhtmlを表示できたとしても、ページ上でpugのソース表示やコピーが出来ないと微妙なのでコピー出来るようにします。
コードのコピーに関しては、storybookのaddonsにある【Copy code block】を使います。

npm i -D @pickra/copy-code-block

pugソースの表示方法は、html-loaderのみで読み込むことでpugのソースとして読み込むことができます。

stories/sample/sample.stories.js
import { storiesOf } from '@storybook/html';
import copyCodeBlock from '@pickra/copy-code-block';
import Pug2HtmlBtn from '!html-loader!pug-plain-loader!./button.pug'
import PugSrcBtn from '!html-loader!./button.pug'

storiesOf('sample', module)
.add('pug2html', () => Pug2HtmlBtn ) //htmlタグに変換されたものを表示するだけ
.add('pug&html', () => Pug2HtmlBtn + copyCodeBlock(PugSrcBtn) )

サンプルは載せませんが下のイメージのようにpugのレイアウトやmixinなども反映することができます。

さらに使いやすくする方法を考える

nuxt開発者レベルであれば、このくらいのことが出来ていれば十分活用できると思うのですが、マークアップの人がメインで使うことを考えるとまだちょっと複雑な感じです。
あとimportの記述が多くなってしまう点も問題としてあります。
その辺りを改善していきます。

importを動的化する

require.contextとかで一気に読み込んでも良いのですが、HTMLとpugは基本セットで使用するのでcopyCodeBlockをラッパー化してその中で読み込むようにします。

.storybook/config.js
import { configure } from '@storybook/html';
import copyCodeBlock from '@pickra/copy-code-block';

// 指定したパスのhtml表示とpugソースのコピーボタンを表示する
global.code = (path,title=false) => {
  const pug = require(`!html-loader!../stories/${path}.pug`);
  const html = require(`!html-loader!pug-plain-loader!../stories/${path}.pug`);
  return `
  <div class="container mt-4">
    ${title ? '<h1 class="mb-5">'+title+'</h1>':''}
    ${html}
    <div class="mt-5">
    ${copyCodeBlock(pug)}
    </div>
  </div>`
}

// automatically import all files ending in *.stories.js
configure(require.context('../stories', true, /\.stories\.js$/), module);

sample.stories.jsに記述していたような内容をconfig.jsに記述し、global関数として登録することで全てのstories.jsから使用することができます。

storiesOf('ボタン', module)
.add('ノーマルボタン', () => code('sample/button','ノーマルボタン') )

こんな感じのシンプルな記述を追加をするだけで【見出し+HTML+pug 】の表示が出来るようになります。

さらに楽をする

const path = require('path')
const req = require.context('../stories', true, /\.stories\.pug$/)
req.keys().forEach((pathstr) => {
  const dir = path.dirname(pathstr).replace('./','')
  const fpath = `${dir}/${path.basename(pathstr,'.pug')}`
  const fname = path.basename(pathstr,'.stories.pug')
  storiesOf( dir , module)
  .add( fname , () => code( fpath , fname) )
})

マークダウンも同じような考え方で作れます

今までの内容にマークダウンでの記述を追加すれば、編集のしやすいページが出来ると思います。
マークダウンも今回のpugと同じ考え方でhtml-loadermarkdown-loaderで読み込むだけで同様の処理が出来ます。

const md2html = !html-loader!markdown-loader!パス名

メソッドを同じように作成すればより記述を少なくして、自由度の高いページ作成が出来ると思います。
参考にしつつ色々お試しください。