kubell Creator's Note

ビジネスチャット「Chatwork」のエンジニアのブログです。

ビジネスチャット「Chatwork」のエンジニアのブログです。

読者になる

Storybookで任意のテンプレートエンジンを利用する

kubell Advent Calendar 2024 シリーズ 1の12/9の記事です。

qiita.com

こんにちはkubellのフロントエンド開発部の末竹(magcho)🐧です。

今日においてフロントエンドのUIにおけるカタログ・テスト基盤としてStorybookが採用されることがあるかと思います。 特に最近のStorybookではコンポーネントテスト・UIテスト・振る舞いテストとしての機能が充実しています。

Storybookは公式にReact, Vue, Angular, ...をサポートしており少しの設定でこれらのフレームワーク(テンプレートエンジン)で作られたコンポーネントを掲載できますがそれ以外のフレームワークで作られたものは利用ができません。

本記事では任意のフレームワーク(テンプレートエンジン)で書かれたマークアップをStorybookに掲載する方法について紹介します。

今回は以下のbuttonタグを10個表示するSmartyのマークアップを題材に扱っていきます。

{$lang = "smarty"}
<div>
  {for $i=1 to 10}
    <button>{$i}</button>
  {/for}
</div>
<b>This is {$lang}</b>

今回はSmaryをJavaScriptでも扱えるようにjSmartというパッケージを利用しました。

github.com

まずばStorybookのセットアップです。frameworkは一旦HTMLとして初期化しておきましょう。このままではもちろんSmartyではなくHTMLしか掲載できません。

npm init -y && npx storybook@latest init

任意のテンプレートエンジンを簡単に掲載する方法としては CSFv3のrenderを使う方法があります。

import jSmart from 'jsmart'

const smartyTempalte = `
{$lang = "smarty"}
<div>
  {for $i=1 to 10}
    <button>{$i}</button>
  {/for}
</div>
<b>This is {$lang}</b>
`;


const smartyRenderer = (template) => {
  const compiled = new jSmart(template);
  return compiled.fetch()
};

export default {
  title: "Smarty/Page",
  render: () => smartyRenderer(smartyTempalte),
};

export const Sample = {};

CSFv3のrenderに対してHTMLを文字列で返す関数を渡すことで、任意のHTMLを利用できます。今回はjSmartを用いてSmartyテンプレートをHTML文字列に変換する処理を行い、HTML 文字列をStorybookに渡しています。

Storybookのargsから値を渡し、テンプレート中の表示のバリエーションを確認したいケースもあるかと思います。

CSFv3のrenderの型を確認すると以下のようになっており、第一引数にてargsが渡されそうであることが推測できます。

type ArgsStoryFn<TRenderer extends Renderer = Renderer, TArgs = Args> = (args: TArgs, context: StoryContext<TRenderer, TArgs>) => (TRenderer & {
    T: TArgs;
})['storyResult'];

実際に以下のように記述することでStorybook側からテンプレートに値を渡すことができました。

import jSmart from "jsmart";

const smartyTempalte = `
<div>
{for $i=1 to 10}
  <button>{$i}</button>
{/for}
</div>
<b>I like {$food}</b>
`;

const smartyRenderer = (template, args) => {
  const compiled = new jSmart(template);
  return compiled.fetch(args);
};

export default {
  title: "Smarty/Page",
  render: (args) => smartyRenderer(smartyTempalte, args),
  args: {
    food: "sushi",
  },
};

export const Sample = {};

さて、これでは肝心のテンプレートをJavaScriptの変数として持たなければなりません。Smartyは本来的PHPで利用するものであり*.tpl拡張子のファイルで運用することが多いでしょう。

また、Storybookとしてはテンプレート(コンポーネント)とstoriesファイルを分離した方が管理しやすい側面もあります。

storiesファイル内でfsなどを利用してファイルを読み込ませようとするとエラーになります。これはstorybookのrender処理はブラウザ上で実行されるためです。

ブラウザで実行する前にテンプレートをバンドルすることで回避が可能です。これにはviteのraw saffix機能で簡単に解決ができます。import smartyTempalte from "./Sample.tpl?raw"; のように後ろにrawをつけるとviteはテキストファイルの場合はsmartyTempalteという変数に内容を文字列としてインライン展開してくれます。これを利用することで*.tplファイルを分けた状態で利用ができました。

以上でStorybookにSmartyを掲載することができました。

今回はSmartyを利用しましたがブラウザ上にてHTML文字列が組み立てできれば他のテンプレートエンジンであっても同様の方法で解決ができます。公式に対応していないテンプレートエンジンをStorybookに載せたくてたまらない方は是非お試しください。