Storybook の addon 作ってパネルを表示する
はじめに
Storybook の addon を作りたくなった。
最近触っているバージョンが 7.0-alpha なので、以降では Storybook 7.0-alpha.28 のバージョンで作っていく。
ただ、すでに非推奨な実装をしない限りは、7 でも 6 でも同様に動くと思う。
リポジトリのセットアップ
$ npm init -y
package.json
{
"name": "test-storybook-addon",
"version": "1.0.0",
"description": "storybook の addon の実装練習",
"type": "module",
"main": "index.js",
"scripts": {
},
"repository": {
"type": "git",
"url": "git+https://github.com/sterashima78/test-storybook-addon.git"
},
"license": "MIT"
}
とりあえず Storybook が動くようにする
パッケージの追加をする。
{
"name": "test-storybook-addon",
"version": "1.0.0",
"description": "storybook の addon の実装練習",
"type": "module",
"main": "index.js",
"scripts": {
+ "dev": "storybook dev -p 6006",
+ "build:storybook": "storybook build"
},
+ "devDependencies": {
+ "@types/react": "18.0.18",
+ "react": "18.2.0",
+ "react-dom": "18.2.0",
+ "storybook": "7.0.0-alpha.28",
+ "@storybook/react": "7.0.0-alpha.28",
+ "@storybook/react-vite": "7.0.0-alpha.28",
+ "@tsconfig/vite-react": "1.0.1",
+ "vite": "3.0.9",
+ "@vitejs/plugin-react": "2.0.1"
+ },
+ "peerDependencies": {
+ },
"repository": {
"type": "git",
"url": "git+https://github.com/sterashima78/test-storybook-addon.git"
},
"license": "MIT"
}
諸々の設定ファイルら追加
export default {
framework: "@storybook/react-vite",
stories: [
"../src/**/*.stories.tsx",
],
}
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [react()],
})
{
"extends": "@tsconfig/vite-react/tsconfig.json"
}
テスト用のコンポーネントと Story 追加
export type TestProps = { msg: string }
export const Test = ({ msg }: TestProps)=> <div>{msg}</div>
import { Meta, Story } from "@storybook/react";
import { Test, TestProps } from "./Test";
export default {
title: "test",
component: Test
} as Meta<TestProps>
export const testStory: Story<TestProps> = {
args: {
msg: "hoge"
}
}
これでOK
npm run dev
addon を作る
基本的には以下に書いてある。
ビルド設定など
jsx は React.createElement に変換されなくてはいけないみたいなので (上の説明で、@babel/preset-react
を使っている)、tsconfig を修正する。
{
"extends": "@tsconfig/vite-react/tsconfig.json",
"compilerOptions": {
"jsx": "react"
}
}
前に上げたドキュメントではトランスパイルを babel
で行っているが、ここでは tsup
で行う。個人的にライブラリを作るならこれを使うことが多い。
依存関係に必要なパッケージを追加し、ビルド用のタスクを追加する。
以下で、tsup
と以降で用いる Storybook のパッケージを追加しておく。
{
"name": "test-storybook-addon",
"version": "1.0.0",
"description": "storybook の addon の実装練習",
"type": "module",
"main": "index.js",
"scripts": {
+ "build": "tsup",
"dev": "storybook dev -p 6006",
"build:storybook": "storybook build"
},
"devDependencies": {
"@types/react": "18.0.18",
"react": "18.2.0",
"react-dom": "18.2.0",
"storybook": "7.0.0-alpha.28",
"@storybook/react": "7.0.0-alpha.28",
"@storybook/react-vite": "7.0.0-alpha.28",
"@tsconfig/vite-react": "1.0.1",
"vite": "3.0.9",
"@vitejs/plugin-react": "2.0.1",
+ "tsup": "6.2.3",
+ "@storybook/addons": "7.0.0-alpha.28",
+ "@storybook/components": "7.0.0-alpha.28",
+ "@storybook/api": "7.0.0-alpha.28"
},
"peerDependencies": {
+ "react": ">= 16",
+ "react-dom": ">= 16",
+ "@storybook/components": "7.0.0-alpha.28",
+ "@storybook/addons": "7.0.0-alpha.28",
+ "@storybook/api": "7.0.0-alpha.28"
},
"repository": {
"type": "git",
"url": "git+https://github.com/sterashima78/test-storybook-addon.git"
},
"license": "MIT"
}
ここで、peerDependencies
フィールドにもいくつかのパッケージを追加した。
これは、addon を使うユーザが自身でインストールするべきパッケージを記載する。
また、tsup
は dependencies
と peerDependencies
フィールドのパッケージを外部化し、バンドルしないでくれる。
これによって、Storybook 本体で使われている react とこの addon にバンドルされた react と2つの react がランタイムに存在することを回避できる。
tsup の設定を書く。
設定値はキーの名前から大体予想がつくと思う。詳しくはドキュメントを見てほしい。
今回は esm のみあればいいので、この形式で出力し、エントリポイントについては後述する。
import { defineConfig } from 'tsup'
export default defineConfig({
outDir: "libs",
entry: ['addon/index.ts', "addon/addon.tsx"],
splitting: false,
sourcemap: true,
clean: true,
format: ["esm"],
})
addon の実装
addon のエントリポイントでは、読み込ませるファイルらを設定する必要がある。
読み込ませるファイルらは、require.resolve
で解決させるようにする。
.storybook/main.js
で利用するようなものは、それを返す関数を managerEntries
という名前で公開し、.storybook/preview.js
で利用するようなものは config
という名前で公開するらしい。今回はパネルを増やすので、前者となる。
エントリーポイントは、 package.json
の main
フィールドや export
フィールドで決まるものなのでファイル名はなんでもいい。ここでは addon/index.ts
とする。
export const managerEntries = (entry = []) => {
return [...entry, require.resolve("./addon.js")];
}
addon 本体も実装していく。
前に出したドキュメントと同じ内容を書く。
addon/index.ts
で require.resolve("./addon.js")
を読ませるように書いているので、同じディレクトリに addon.js
という名前にトランスパイルされるファイルを作る。
import React from 'react';
import { addons, types } from '@storybook/addons';
import { AddonPanel } from '@storybook/components';
const ADDON_ID = 'myaddon';
const PANEL_ID = `${ADDON_ID}/panel`;
const MyPanel = () => <div>MyAddon</div>;
addons.register(ADDON_ID, () => {
addons.add(PANEL_ID, {
type: types.PANEL,
title: 'My Addon',
render: ({ active, key }) => (
<AddonPanel active={!!active} key={key}>
<MyPanel />
</AddonPanel>
),
});
});
@storybook/addons
から addons
オブジェクトを取得し、いくつかのメソッドを実行している。
これによって、Story の下部に表示されるパネルを追加することができる。
各メソッドについては以下での説明で十分理解できると思う。
動作確認
動作確認をする。
といってもそれほど難しくはなく、とりあえずビルドする。
$ npm run build
> test-storybook-addon@1.0.0 build
> tsup
CLI Building entry: addon/index.ts, addon/addon.tsx
CLI Using tsconfig: tsconfig.json
CLI tsup v6.2.3
CLI Using tsup config: /workspaces/test-storybook-addon/tsup.config.ts
CLI Target: node14
CLI Cleaning output folder
ESM Build start
ESM libs/index.js 550.00 B
ESM libs/addon.js 640.00 B
ESM libs/index.js.map 297.00 B
ESM libs/addon.js.map 992.00 B
ESM ⚡️ Build success in 21ms
次に、addon を読み込ませるための設定を .storybook/main.ts
に追加する。
export default {
framework: "@storybook/react-vite",
stories: [
"../src/**/*.stories.tsx",
],
+ addons: ["../libs/index.js"]
}
起動してみよう。
$ npm run dev
パネルが追加されている。
パラメータの取得
このままだとどの Story でも同じ内容が表示されるだけであまりおもしろくない。
Storyの記述フォーマットの CSF には parameters というフィールドがある。
この値を取得できるようにすることで、Story ごとに振る舞いを変えることができる。
値の取得には useParameter
という hook を用いる。
import React from 'react';
import { addons, types } from '@storybook/addons';
import { AddonPanel } from '@storybook/components';
+ import { useParameter } from '@storybook/api';
const ADDON_ID = 'myaddon';
const PANEL_ID = `${ADDON_ID}/panel`;
+ const PARAM_KEY = 'myAddon';
- const MyPanel = () => <div>MyAddon</div>;
+ const MyPanel = () => {
+ const value = useParameter<{ data: string } | null>(PARAM_KEY, null);
+ const item = value ? value.data : 'No story parameter defined';
+ return <div>{item}</div>;
+ };
addons.register(ADDON_ID, () => {
addons.add(PANEL_ID, {
type: types.PANEL,
title: 'My Addon',
render: ({ active, key }) => (
<AddonPanel active={!!active} key={key}>
<MyPanel />
</AddonPanel>
),
});
});
Story にもパラメーターを追加する。
import { Meta, Story } from "@storybook/react";
import { Test, TestProps } from "./Test";
export default {
title: "test",
component: Test
} as Meta<TestProps>
export const testStory: Story<TestProps> = {
args: {
msg: "hoge"
+ },
+ parameters: {
+ myAddon: {
+ data: 'this data is passed to the addon',
+ },
}
}
addon を再ビルドして確認する。
$ npm run build
$ npm run dev
パラメータに記載した値が表示されている
あとは普通にパッケージを公開すれば外部からも使え、中に表示するものは好きに React コンポーネントを実装すればいい。
終わりに
他にも Toolbar や Tab にも自分で機能を埋め込むことができる。
これらを含めた説明は以下にもあり、
その説明のベースであり、addon を作るためのベースとなるのが、以下となっている。
そのため、手っ取り早く手を動かしたい人はこれをイジるのがいいと思う。
個人的には更地から足していきながら API などを理解したいので、引き続き別の機能に触れていきたいと思った。
ここまでの内容は以下となっている。
続き
Discussion