qrac / minista

Static site generator with 100% static export from React and Vite.
https://minista.qranoko.jp
165 stars 13 forks source link

Storybook風のビルトインコンポーネントを実装 #118

Closed qrac closed 7 months ago

qrac commented 1 year ago

SaaSのテンプレートコーディング案件でministaにStorybook (React + Vite)を加えて2ヶ月運用した。HTML + CSSだけの表示だったら問題ないが、ブラウザ用のJavaScriptは上手く動かない場合が多く、minista内でStorybookを再現する方が良い気がしてきた。

ブラウザ用JSの再現度を上げたい

即時実行でDOMを編集するJSはSPAのHTMLが間に合わないので失敗する。古い案件のjQueryやAlpine.js絡みとか。useEffectで実行すれば回避できるが、ダイナミックインポートで動かないものはデバッグ用にJSを書き直したりして良くなかった。

ブラウザ用のJSはコンポーネントの差し替えを想定していないのでイベントリスナーをアンマウントする処理がほぼない。そのまま使うとStorybookの遷移でJSが重複してフリーズする。クリーンアップ関数を書くこともできるが本番に不要なのでやはり良くない。

スタイルの取り回しを本番に近づけたい

PCとスマホで読み込むCSSが違う場合があって困った。importでCSSを読み込んでしまうと除去できないのでインラインスタイルを発行するか <link /> タグの切り替えとなる。preview.htmlに <link /> タグを書くのが筋としては良さそうだが条件分岐させる手段がない。

最終的にはデコレーターに <link /> タグを持たせて取り外すようにしたが、やはり本番とは違う仕組みになるので良くないと思った。コンポーネント切り替え時にFOUCも発生するし。

機能をSSG向けに用意しなおしたい

SaaSのテンプレート用の静的なコーディングだとpropsの概念がないのでprops表が微妙な感じになっている。Reactのコード通りに実装できないのでドキュメントはAuto docsではなく自由に書ける方が良いと思った。

コード表示をカスタマイズしてReactではなくHTMLを表示させられるようにはできたが、すべて表示するとHTMLは長くなりすぎる場合が多かったので不要かも。パネル内にドキュメント書ける方が実用的。

Storybook風のReactコンポーネントはPartial Hydrationで実装できた

Partial Hydrationを使ってiframeを切り替える実装をすればStorybookのような表現はできた。改良を加えながら実務で1ヶ月以上運用してみて問題もなかった。ただ、プロジェクトに開発用のコードが大量に増えるのでminista内に書いた方が良い。

minista内に実装しないといけないこと

React案件でStorybookに持ち替えた時にminista版のStorybookと使い勝手が違いすぎると困るので *.stories.tsx の書き方をほぼそのまま使って運用できるようにしたい。

qrac commented 1 year ago

ブランチ切ってアルファ版かv3.1で最低限の機能を持たせて公開したい。

qrac commented 1 year ago

名称は <Storyapp /> にする予定。

qrac commented 1 year ago

getStaticData() を一旦除外する。実際にはpagesのファイルをimport後、Storyファイルからexportすれば使用できるが、そこまで使うか現時点で不明なので。

qrac commented 1 year ago

一旦は裏側のdev(SSRで静的HTML配信)とbuild(SSG)だけを実装してビルトインコンポーネントの仕様は検討。

qrac commented 1 year ago

v3.1.0でSSRで静的HTML配信とSSGを試験的に実装した。コンフィグで storyapp.useImporttrue にすることで /src/**/*.stories.{js,jsx,ts,tsx,md,mdx} を読み込んで各ストーリーを静的HTMLに変換できる。以下のように、ほぼStorybookと同じ書き方ができる。

import type { StoryMeta as Meta, StoryObj } from "minista"

import Component from "."

const meta = {
  title: "Components/Button",
  component: Component,
  decorators: [
    (Story) => {
      return (
        <>
          <div className="decorator-1">
            <Story />
          </div>
        </>
      )
    },
    (Story) => {
      return (
        <>
          <div className="decorator-2">
            <Story />
          </div>
        </>
      )
    },
  ],
} satisfies Meta<typeof Component>

export default meta
type Story = StoryObj<typeof meta>

export const Primary: Story = {
  args: {
    text: "Primary",
    colorScheme: "cyan",
    variant: "solid",
  },
}
export const Secondary: Story = {
  args: {
    text: "Secondary",
    colorScheme: "blue",
    variant: "solid",
  },
  decorators: [
    (Story) => {
      return (
        <>
          <div className="decorator-3">
            <Story />
          </div>
        </>
      )
    },
  ],
}
qrac commented 7 months ago

121 v4 アルファ版にて実装。