Closed43

Storybook 7 + Vue 3 を試していく

shingo.sasakishingo.sasaki

SB7 は現状まだβで、リリースまで数ヶ月は掛かりそうなペースだし、リリースされるより先に Book を公開して基本的な使い方とか Tips を紹介できるといいな。

shingo.sasakishingo.sasaki

Vite でプロジェクト作る

$ yarn create vite vue-3-storybook-7 --template vue
$ cd vue-3-storybook-7
$ yarn install

一旦TSはナシで、そのうち触れるかな。

$ yarn dev

http://127.0.0.1:5173/ 開いて Vue 3 アプリケーションが動いていればOK。
開発環境はもう二度と使わないのでこれで終了。

e1ccf8c2fe880abd361f41091bb5799c184b36c3

shingo.sasakishingo.sasaki

公式ドキュメントだと
npx storybook init でセットアップしろというけど、個人的には推奨アドオン勝手に入ってくるし、いらんサンプルストーリーも作成されて好きじゃないので、手動セットアップをしていこうと思う。

shingo.sasakishingo.sasaki

そもそもスキャフォルド使うとまだ v6 がインストールされるっぽい。(オプションで v7 指定できるのかな?)

shingo.sasakishingo.sasaki

一旦コレで動かせるかな。 core-server は初めて見たので本当に必要かまだわからん。

$ yarn add -D @storybook/vue3@7.0.0-beta.14 @storybook/core-server@7.0.0-beta.14 @storybook/builder-vite@7.0.0-beta.14

28666130f53847ec8c7a58cd46ff15f0f1b251ed

shingo.sasakishingo.sasaki

@storybook/vue3-vite を入れるだけでこの辺も Dependencies になってるから一発で済むっぽい。

shingo.sasakishingo.sasaki

バイナリコマンドがなくなって CLI からサーバー起動できるようになってるみたい。

SB6.x framework packages shipped binaries called start-storybook and build-storybook.

In SB7.0, we've removed these binaries and replaced them with new commands in Storybook's CLI: > storybook dev and storybook build

https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#start-storybook--build-storybook-binaries-removed

なのでそもそも CLI が必要。
https://github.com/storybookjs/storybook/tree/next/code/lib/cli-storybook

$ yarn add -D storybook@7.0.0-beta.14

6d76035a65ce57562513a316af37555314e1a8f2

shingo.sasakishingo.sasaki

CLI が動く。 SB7 では CLI から色々できるっぽいな。 codemod 使ってマイグレーションもできそう。

Usage: storybook <command> [options]

Options:
  --disable-telemetry                     disable sending telemetry data
  --debug                                 Get more logs in debug mode (default: false)
  --enable-crash-reports                  enable sending crash reports to telemetry data
  -V, --version                           output the version number
  -h, --help                              display help for command

Commands:
  init [options]                          Initialize Storybook into your project.
  add [options] <addon>                   Add an addon to your Storybook
  babelrc                                 generate the default storybook babel config into your current working directory
  upgrade [options]                       Upgrade your Storybook packages to the latest
  info                                    Prints debugging information about the local environment
  migrate [options] [migration]           Run a Storybook codemod migration on your source files
  extract [location] [output]             extract stories.json from a built version
  repro [options] [outputDirectory]       Create a reproduction from a set of possible templates
  repro-next [options] [filterValue]      Create a reproduction from a set of possible templates
  link [options] <repo-url-or-directory>  Pull down a repro from a URL (or a local directory), link it, and run storybook
  automigrate [options] [fixId]           Check storybook for known problems or migrations and apply fixes
  dev [options]
  build [options]
  help [command]                          display help for command
shingo.sasakishingo.sasaki

とりあえずサーバー起動してみる。

$ yarn storybook dev
@storybook/cli v7.0.0-beta.14

ERR! Starting in 7.0, react and react-dom are now required peer dependencies of Storybook.
ERR! https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#react-peer-dependencies-required
ERR! 
ERR! It seems that you haven't run Storybook's CLI to upgrade to the latest version.
ERR! The upgrade command will install the required peer dependencies for you and will take 
ERR! care of other important auto migrations as well.
ERR! 

はぁーん? SB 動かすための React が Dependencies から PeerDependencies に変わった臭いな。
Vue なのにこれ入れるのちょっと癪だけどほんと必要?

shingo.sasakishingo.sasaki

まぁ React が必要なのは事実なので入れようじゃないの。

$ yarn add -D react react-dom

d160a3181cabde94ebb2c9c81ddb9db786263510

shingo.sasakishingo.sasaki

React のエラーが出なくなった。次は設定ファイルがないよってエラー。これは期待通り。

$ yarn storybook dev

ERR! Error: No configuration files have been found in your configDir (/Users/shingo.sasaki/vue-3-storybook-7/.storybook).
ERR! Storybook needs either a "main" or "config" file.
shingo.sasakishingo.sasaki

設定ファイルを作る。とりあえずこんなんでいいかな。

.storybook/main.js
module.exports = {
  framework: "@storybook/vue3",
  stories: ["../src/**/*.stories.@(js|mdx)"],
};

なんだとー

$ yarn storybook dev

ERR! Error: Invalid value of @storybook/vue3 in the 'framework' field of Storybook config.
ERR! 
ERR! Please run 'npx sb@next automigrate'
ERR! 
ERR! See the v7 Migration guide for more information: 
ERR! https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#framework-field-mandatory
ERR!     at validateFrameworkName (/Users/shingo.sasaki/vue-3-storybook-7/node_modules/@storybook/core-common/dist/index.js:18:3976)
ERR!     at buildDevStandalone (/Users/shingo.sasaki/vue-3-storybook-7/node_modules/@storybook/core-server/dist/cjs/build-dev.js:74:41)
ERR!     at async withTelemetry (/Users/shingo.sasaki/vue-3-storybook-7/node_modules/@storybook/core-server/dist/cjs/withTelemetry.js:65:5)
ERR!     at async dev (/Users/shingo.sasaki/vue-3-storybook-7/node_modules/@storybook/cli/dist/generate.js:441:440)
ERR!  Error: Invalid value of @storybook/vue3 in the 'framework' field of Storybook config.
shingo.sasakishingo.sasaki

うん?プリセットないじゃん。 @storybook/vue3 じゃ入ってこないのかな?

WARN   Failed to load preset: "@storybook/vue3-vite/preset"
ERR! Error: Cannot find module '@storybook/vue3-vite/preset'
shingo.sasakishingo.sasaki

npm のサイトで見るとαで止まってるように見えるけど、普通に最新版インストールできた。

yarn add -D @storybook/vue3-vite@7.0.0-beta.14

@storybook/vue-3 のほうはいらないっぽいな。
0982561c63e3fa22addc45a630a1b21f761b7a5c

設定ファイルはこう

.storybook/main.js
module.exports = {
  framework: "@storybook/vue3-vite",
  stories: ["../src/**/*.stories.@(js|mdx)"],
};

403363d9af11777e49c59421bccc55a2a0c955be

shingo.sasakishingo.sasaki

よっしゃ Storybook 起動したぞ。当然ストーリーを1個も用意してないのでエラーは想定どおり。

空だからなんとも言えないけど爆速差を感じる。

shingo.sasakishingo.sasaki

Storybook で使用するコンポーネントを作ろう。
HelloWorld コンポーネントがあるから、これをシンプルなカウンターに書き換えてみる。

HelloWorld.vue
<script setup>
import { ref } from "vue";

const props = defineProps({
  initialCount: {
    type: Number,
    default: 0,
  },
});

const count = ref(props.initialCount);
</script>

<template>
  <div class="hello-world">
    <button @click="count--">-</button>
    <span>{{ count }}</span>
    <button @click="count++">+</button>
  </div>
</template>

<style scoped>
.hello-world {
  text-align: center;
  color: #2c3e50;
}
</style>

こんなん。
最初の例でだけ使うからシンプルに。

259366b31fdbd09ae3928f85a1859e89343f6d0a

shingo.sasakishingo.sasaki

Story を作る。どこに Story ファイルを作るかは派閥が分かれそうだけど、今回は自作のコンポーネントだけじゃなくて、外部UIライブラリのコンポーネントに対して試してみるってのをやりたいから、 src/stories ってディレクトリを作ろうかな。

shingo.sasakishingo.sasaki

ドキュメントに Vue2/3 JS/TS それぞれの CSF での記法が書いてあるのでそれに追従する。
https://storybook.js.org/docs/7.0/vue/get-started/whats-a-story

src/stories/HelloWorld.stories.js
import HelloWorld from "../components/HelloWorld.vue";

const meta = {
  title: "HelloWorld",
  component: HelloWorld,
};

export default meta;

export const Default = {
  render: () => ({
    components: { HelloWorld },
    template: "<HelloWorld />",
  }),
};

一応できた。今更だけど、デフォルトでダークモードなのか。

shingo.sasakishingo.sasaki

Story に render 関数定義させるんだ。
同じレンダーで複数のストーリーを用意したいときのお作法みたいなのあるかな。

shingo.sasakishingo.sasaki

addon-controls 入れてく。
https://www.npmjs.com/package/@storybook/addon-controls?activeTab=readme

$ yarn add -D @storybook/addon-controls@7.0.0-beta.14

アドオンを有効化

.storybook/main.js
module.exports = {
  framework: "@storybook/vue3-vite",
  stories: ["../src/**/*.stories.@(js|mdx)"],
  addons: ["@storybook/addon-controls"],
};

ストーリー側で args の注入。

src.stories.HelloWorld.stories.js
import HelloWorld from "../components/HelloWorld.vue";

const meta = {
  title: "HelloWorld",
  component: HelloWorld,
};

export default meta;

export const Default = {
  render: (args) => ({
    components: { HelloWorld },
    setup() {
      return { args };
    },
    template: '<HelloWorld v-bind="args" />',
  }),
  args: {
    initialCount: 0,
  },
};

Controls から注入できるように

shingo.sasakishingo.sasaki

一つのコンポーネントに対して複数のストーリーを定義する場合は、メタ側にコンポーネントを定義して、各ストーリーでオーバーライドすれば良いみたい。

これ CSF のどのバージョンから出来たんだろ。知らなかった。

import HelloWorld from "../components/HelloWorld.vue";

const meta = {
  title: "HelloWorld",
  component: HelloWorld,
  render: (args) => ({
    components: { HelloWorld },
    setup() {
      return { args };
    },
    template: '<HelloWorld v-bind="args" />',
  }),
};

export default meta;

export const Default = {};

export const WithInitialValue = {
  args: {
    initialCount: 5,
  },
};

shingo.sasakishingo.sasaki

最初から TS で書いたほうが良いなと薄々感じてきたので、TS のセットアップを先にやっちゃう。
多分 TS と vue-tsc さえ入れればあとは ボイラーがどうにかしてくれる。

$ yarn add -D typescript vue-tsc

tsconfig も vite が生成するやつに従っておく。

{
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "lib": ["ESNext", "DOM"],
    "skipLibCheck": true
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
  "references": [{ "path": "./tsconfig.node.json" }]
}
{
  "compilerOptions": {
    "composite": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "allowSyntheticDefaultImports": true
  },
  "include": ["vite.config.ts"]
}

.vue の型定義も追加

/// <reference types="vite/client" />

declare module '*.vue' {
  import type { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}

ここまで、最初からやるなら yarn create vite [name] --template vue-ts にすれば自動でセットアップされると思う。

コンポーネントもTSにする。

<script setup lang="ts">
import { ref } from "vue";

const props = defineProps({
  initialCount: {
    type: Number,
    default: 0,
  },
});
const count = ref(props.initialCount);
</script>

<template>
  <div class="hello-world">
    <button @click="count--">-</button>

    <span>{{ count }}</span>

    <button @click="count++">+</button>
  </div>
</template>

<style scoped>
.hello-world {
  background-color: #fff;
  text-align: center;
  color: #2c3e50;
}
.hello-world span,
button {
  font-size: 4rem;
}
</style>

Story はこんな感じに。

import HelloWorld from "../components/HelloWorld.vue";
import type { Meta, StoryObj } from "@storybook/vue3";

type Story = StoryObj<typeof HelloWorld>;

const meta: Meta = {
  title: "HelloWorld",
  component: HelloWorld,
  render: (args) => ({
    components: { HelloWorld },
    setup() {
      return { args };
    },
    template: '<HelloWorld v-bind="args" />',
  }),
};

export default meta;

export const Default: Story = {};

export const WithInitialValue: Story = {
  args: {
    initialCount: 5,
  },
};
shingo.sasakishingo.sasaki
/// <reference types="vite/client" />

declare module "*.vue" {
  import type { DefineComponent } from "vue";
  const component: DefineComponent<{}, {}, any>;
  export default component;
}

のせいで props を型安全に指定できないなぁ。
SFC 間での型推論は Volar で出来るから十分といえば十分かもしれないけど。

shingo.sasakishingo.sasaki

typegen 的なのでコンポーネントから props の型を取り出す手段があった気がするけど、まぁそれはそのうち。

shingo.sasakishingo.sasaki

「ストーリー」とか CSFについてざっとおさらい。
https://storybook.js.org/docs/react/get-started/whats-a-story

  • 「ストーリー」はある状態のコンポーネントのレンダリングしたもの
  • 一つのコンポーネントに対して、複数のストーリーを定義することが可能
  • CSF (Component Story Format) は ESM の仕組みを使ってストーリーを記述するためのフォーマットで、 .stories.js .stories.ts の形式で書かれる
shingo.sasakishingo.sasaki

新バージョンの CSF 3

https://storybook.js.org/docs/7.0/vue/api/csf

  • CSF はストーリーを定義するために推奨されたフォーマットだが、ESM の仕組みに閉じているため、Storybook を超えて使用することができる
  • export deafult で、ストーリーにおける対象コンポーネントのメタデータ(title, parameters, decorators)を定義する
    • タイトルは全体でユニークである必要がある
    • 多くのフィールドはアドオンで使用されるため、すべて省略可能な場合が多い
  • export でストーリーを定義する
    • 明示しない限りは named export したオブジェクトはすべてストーリーとして扱う
    • ストーリー名は変数名をもとに機械的に決まる
      • 明示することも可能
    • default export で定義したメタデータを、ストーリーレベルで上書きできる

CSF 3 だとかなりシンプルに書けるようになってた。

import Counter from "../components/Counter.vue";
import type { Meta, StoryObj } from "@storybook/vue3";

type Story = StoryObj<typeof Counter>;

const meta: Meta<typeof Counter> = {
  component: Counter,
};

export default meta;

export const Default: Story = {};
shingo.sasakishingo.sasaki

アドオンについて
https://storybook.js.org/docs/7.0/vue/addons/introduction

  • アドオンは Storybook 本体には組み込まれていないが統合して Storybook を拡張できるツール
  • Storybook のほとんどの機能はアドオンによって提供される
    • 主要機能でさえも
      • ドキュメンテーション
      • アクセシビリティテスト
      • インタラクティブコントロール
    • アドオンは公式提供のものから、サードパーティのものまで多数ある
    • https://storybook.js.org/integrations
shingo.sasakishingo.sasaki

docs 普段全然使ってないので、改めて確認してみよう。

https://storybook.js.org/docs/7.0/vue/writing-docs/introduction

shingo.sasakishingo.sasaki

ドキュメントは

  • ストーリーから自動生成するもの
  • MDX で自由に記述するもの
  • Doc Blocks を使って Storybook に埋め込むもの

があるっぽいな。

shingo.sasakishingo.sasaki

メタデータに tags: ['autodocs'], を付与するとドキュメントページが自動生成される。
これ SB7 初登場か?

shingo.sasakishingo.sasaki

Cypress に魂を売ったせいで Play function もよくわかってないから確認する

https://storybook.js.org/docs/7.0/vue/writing-stories/play-function

shingo.sasakishingo.sasaki

Storybook's play functions are small code snippets that run once the story finishes rendering.

Play Function はストーリーのレンダリング後に自動で実行される。
だからテストを回すってイメージよりは、操作後の状態を作るってほうが正しいか。

フォームなら入力後とか、バリデーションエラーを出した後みたいな。

このスクラップは2023/01/18にクローズされました