VISASQ Dev Blog

ビザスク開発ブログ

【Vue.js】Histoire 設定のつまずきポイント & 対処方法!

【Vue.js】Histoire 設定のつまずきポイント & 対処方法!

お試しでセットアップしてみた Histoire のスクリーンショット

はじめに

はじめまして!開発部 横断チーム の足立です!

株式会社ビザスクでは Vue.js でフロントエンドの実装が行われております。
たくさんの内製の AtomMolecules 単位のコンポーネントを活用し画面を実装してますが、
それらを最新の状態でレンダリングして一望できるようにしたかったので今回は Storybook...
ではなくあえて https://histoire.dev を手元で試してみました。
(Vue.js に特化していて比較的導入コストが低いとのこと)

その際に dev (ホットリロードプレビュー) や build preview (ビルドしたものの閲覧) の周りの設定で少しつまずいたり、
Story ファイルの作成を GitHub Copilot Agent に任せてみたので、こちらの記事としてまとめてみました 📝
ご参考になれば幸いです! 🥳


ビザスクは Vue Fes Japan 2025 に今年もスポンサーとして参加します!

vuefes.jp

ご参加される方はぜひ、ビザスクブースで Vue.js について語り合いましょう! 🙌


別のチームで Storybook を使った事例もあるのでよければこちらもご確認ください! tech.visasq.com


前提

  • Vue.js を使ったプロジェクト ✅️
  • vite を使ったプロジェクト ✅️
  • Histoire のバージョンは 0.17.17 ✅️
  • https://histoire.dev/guide/config.html (公式) に沿った設定を実施済 ✅️
  • Story (TargetComponent.story.vue) を src 内に 1 個以上作成済 ✅️

npm run story:buildcode: 'PLUGIN_ERROR', plugin: 'commonjs--resolver' で失敗する

対象の vite / vue プロジェクトの構成次第ですが上記のような vite 関連のエラーが生じる場合がございます。
そこで process.env.HISTOIRE環境変数で vite の設定を動的に切り替えることで回避できました!

Before (例)

// vite.config.ts (例)
import { defineConfig } from 'vite';

export default defineConfig({
// ...省略...
  build: {
    rollupOptions: {
      input: {
        main: path.resolve(__dirname, 'src/main.ts'),
      },
    },
    // ...省略...
  },
});

After (例)

// vite.config.ts (例)
import { defineConfig } from 'vite';

export default defineConfig({
  // ...省略...
  build: {
    rollupOptions: {
      // `npm run story:build` のときだけ `input` プロパティを無かったことにして回避
      input: process.env.HISTOIRE
        ? undefined
        : {
            main: path.resolve(__dirname, 'src/main.ts'),
          },
      },
    },
    // ...省略...
  },
});

npm run story:build の成果物が見当たらない

デフォルトでは .histoire というディレクトリが作られそこにビルドの成果物が出力されるのですが、見当たらない場合は histoire.config.ts 上で outDir の明記をご検討ください!(指定した path への出力がされると思います)

// histoire.config.ts (例)
import { defineConfig } from 'histoire';
import { HstVue } from '@histoire/plugin-vue';
import * as path from 'path';
import * as fs from 'fs';

// 明記!!!
const outDir = path.resolve(__dirname, 'dist', '.histoire');

export default defineConfig({
  plugins: [HstVue()],
  // repository-dir/src/histoire.setup.ts が読み込まれる
  setupFile: 'histoire.setup.ts',
  outDir, // 明記!!!
  // ...省略...
});

npm run story:preview で URL に謎の . が入る

Preview server listening on http://localhost:6006./

とターミナルに表示される場合があるかと思います。
. の部分は vite の設定 (例 vite.config.ts 等) の base プロパティを参照しているようなのでこちらも process.env.HISTOIRE で解決できます!

// vite.config.ts (例)
import { defineConfig } from 'vite';

export default defineConfig({
  base: process.env.HISTOIRE ? undefined : './',
  // ...省略...
});

npm run story:dev で 画像や fontawesome のアイコンが表示されない

MPA 構成などの場合、対象の Web プロダクト上で想定されている静的リソースへのパスと、Histoire の dev サーバ上でのパスが異なり、うまく読み込めないことがあります。
今回は静的リソースへのリクエストを利用可能なリソースへリダイレクトさせるだけのシンプルな vite プラグインを書き足すことで解決できました!

// histoire.config.ts (例)
import { defineConfig } from 'histoire';
import { HstVue } from '@histoire/plugin-vue';
import * as path from 'path';
import * as fs from 'fs';

const outDir = path.resolve(__dirname, 'dist', '.histoire');

export default defineConfig({
  plugins: [HstVue()],
  setupFile: 'histoire.setup.ts',
  outDir,
  // vite.config.ts の設定を一部上書き ただし、当ファイルに書いても上手く反映されなかった base や rollupOptions については本体に条件分岐の上で記述
  vite: {
    // ...省略...
    plugins: [
      {
        // `npm run story:dev` の際に `fontawesome` や `arrow_bottom.png` 関連のエラーが生じるのを防ぐ
        // Histoire開発時用のアセットリダイレクトミドルウェア
        name: 'histoire-asset-redirects',
        configureServer(server) {
          server.middlewares.use('/dashboard/v3/assets/', (req, res) => {
            const currentPath = req.url || '';
            const newUrl = `http://${req.headers.host}/${currentPath}`;
            res.writeHead(301, { Location: newUrl });
            res.end();
          });
        },
      },
      {
        // ...省略...
      },
    ],
  },
});

npm run story:preview で画像が表示されない

こちらも場合によっては Histoire の static サーバで閲覧した際にパスが異なり、上手く画像が表示できません。
今回はやや力技ですが、ビルドされた直後に成果物内のパスを利用可能なパスへ置換するプラグインを書くことで解決できました 💪

// histoire.config.ts (例)
import { defineConfig } from 'histoire';
import { HstVue } from '@histoire/plugin-vue';
import * as path from 'path';
import * as fs from 'fs';

const outDir = path.resolve(__dirname, 'dist', '.histoire');

export default defineConfig({
  plugins: [HstVue()],
  setupFile: 'histoire.setup.ts',
  outDir,
  // vite.config.ts の設定を一部上書き ただし、当ファイルに書いても上手く反映されなかった base や rollupOptions については本体に条件分岐の上で記述
  vite: {
    // ...省略...
    plugins: [
      {
        // ...省略...
      },
      {
        // CSS 内の arrow_bottom.png パスを置換(ビルド時のみ)
        name: 'css-path-replace',
        writeBundle(options, _bundle) {
          const outputDir = options.dir || outDir;
          const assetsDir = path.join(outputDir, 'assets');

          // assetsディレクトリの存在確認
          if (!fs.existsSync(assetsDir)) {
            return; // assetsディレクトリが存在しない場合はスキップ
          }

          const files = fs.readdirSync(assetsDir);
          const cssFiles = files.filter(file => file.startsWith('style-') && file.endsWith('.css'));

          for (const cssFile of cssFiles) {
            const cssFilePath = path.join(assetsDir, cssFile);

            // CSSファイルの存在確認
            if (!fs.existsSync(cssFilePath)) {
              continue; // ファイルが存在しない場合は次のファイルへ
            }

            let content = fs.readFileSync(cssFilePath, 'utf-8');

            content = content.replace(
              /\/dashboard\/v3\/assets\/img\/common\/arrow_bottom\.png/g,
              '/img/common/arrow_bottom.png',
            );

            fs.writeFileSync(cssFilePath, content, 'utf-8');
          }
        },
      },
      {
        // ...省略...
      },
    ],
  },
});

npm run story:previewfontawesome のアイコンが表示されない

こちらも場合によっては Histoire の static サーバで閲覧した際にパスが異なり、上手く画像が表示できません。
今回は対象の実装における fontawesomeの scss ファイルにて以下のように $fa-font-path という scss 変数が使われているため…

// src/scss/fontawesome/_variables.scss (例)

// Variables
// --------------------------

$fa-font-path: '/dashboard/v3/assets/fonts' !default;
$fa-font-size-base: 14px !default;
$fa-line-height-base: 1 !default;

preprocessorOptions を使いビルド時に対象の scss 変数を上書きする設定を書き足すことで解決できました!

// histoire.config.ts (例)
import { defineConfig } from 'histoire';
import { HstVue } from '@histoire/plugin-vue';
import * as path from 'path';
import * as fs from 'fs';

const outDir = path.resolve(__dirname, 'dist', '.histoire');

export default defineConfig({
  plugins: [HstVue()],
  setupFile: 'histoire.setup.ts',
  outDir,
  // vite.config.ts の設定を一部上書き ただし、当ファイルに書いても上手く反映されなかった base や rollupOptions については本体に条件分岐の上で記述
  vite: {
    // `npm run story:build` の成果物をブラウザで閲覧した際に `fontawesome` 関連のエラーが生じるのを防ぐ
    css: {
      preprocessorOptions: {
        scss: {
          // Histoire環境でのフォントパス上書き
          additionalData: `
            // Histoire 用に変数上書き
            $fa-font-path: '/fonts';
          `,
        },
      },
    },
    plugins: [
      // ...省略...
    ],
  },
});

Atom や Molecules コンポーネントが多すぎて全部の Story を書くのが大変

対象となるコンポーネントは約 40 個ありこれらの Story を書き上げるのは大変です!🔥
今回は 1 個だけ参考となる Story を書いた上で GitHub Copilot Agent に以下のようなプロンプトを渡すことで、仕事の合間に 1 日で書き上げることができました 🤖 💪

- AtomXXX.story.vue を参考にする
- Layout は grid 200~300px
- 最初に Interactive な Variant
- init-state には型を意識し無名関数を用いて state を指定
- click が必要なときは Histoire の logEvent を使う
- 1 Variant 内に 1個 の 対象コンポーネントまで
- Variant は v-for などで効率的に書く
- docs の最後は Figma: todo:url と書く
- style タグや div タグは書き足さない style 属性も使わない

以上を踏まえ、 atoms と molecules ディレクトリ内のコンポーネントの Story を作成してください

最初に Interactive な Variant

こちらの指示を入れることで、プルダウンで値を変えたりしリアルタイムで見た目を確認できる Variant を書いてくれるようになります。

AtomAlert の level props の値を切り替える様子

click が必要なときは Histoire の logEvent を使う

console.logwindow.alert でなく logEvent を使わせるようにすることで実際に操作した際に Events タブ上でどのような値が取り扱われているのか確認することができます。

Events タブに値が流れる様子

docs の最後は Figma: todo:url と書く

Figma 上にデザインデータがあるコンポーネントの場合は対象の Figma の URL を掲載したいのでこのように指示してみました ✒️

末尾に関連 URL を掲載

今後やってみたいこと

  • GitHub Pages や Cloudflare Pages で前述のコマンド不要ですぐ Histoire を閲覧できる状態にしてみたいです
  • Devin などで atoms ディレクトリなどに更新があった際に自動で Story の追加・更新・削除を行う PR を立ててくれる仕組みとかも作っていきたいです

github.com

ビザスクでは様々なAI開発ツールをご利用いただけます

tech.visasq.com

最後に

以上、Histoire 設定のつまずきポイントと対処方法(+ GitHub Copilot Agent の活用)でした!

これまではコンポーネントの見た目を確認するために以下のように一手間二手間必要でしたが…

これらの手間がなくなり、見た目を確認したり、検索したり、 props をプルダウンやインプットボックスでリアルタイムで調整し見た目を確認したり、レンダリングしながらドキュメントも読めたりできる環境を手に入れることができました!
今のところ独り占めした状態なので社内でニーズがあれば前述のように社内向けで何処かにホスティングできたらと思います 💪

検索機能

今回はフロントエンドエンジニア向けなブログですが、ビザスクではフロントエンドに限らずリーダー候補やプロダクトマネージャーなど幅広くエンジニアやデザイナーの仲間を募集しています!
少しでもビザスク開発組織にご興味を持たれた方は、ぜひ一度カジュアルにお話ししましょう!

recruit.visasq.co.jp