StorybookでバンドラーにViteを使う

StorybookをViteで開発する方法を紹介します。preactプロジェクトではハマりどころがあるため、その点を丁寧に解説しています。また、Storybookの設定ファイルを型安全にする方法も紹介しています。

2021/10/104 min read
..
hero image

はじめに

Storybook で Vite を使ってビルドできるようになったので紹介します。

iframe 内のビルドを Webpack から vite に切り替えることで次の利点があります。

  • ビルド速度の改善
  • HMR の高速化
  • アセット処理の自動化
  • Vite プロジェクト設定との互換性
  • Vite のプラグインエコシステムへのアクセス

コンポーネントが少ない場合、速度の恩恵はあまり感じられない可能性があります。 Webpack と比較すると、ブラウザが表示されるまでのスピードは劇的に向上しますが、 ブラウザ上での読み込みに時間がかかるためです1

一方、コンポーネントが増えた場合に増加する時間は、抑えられると思います。 また、HMR での再ビルドは明らかな速度の差を感じられると思います。

個人的に vite に切り替える最大の利点は、アセット処理の設定が不要になることです。

Webpack ベースの場合、例えば sass を使うには、それ用の loader を設定する必要があります。 viteであれば、TypeScript、CSS プリプロセッサ、Static Assets の処理などは標準で備えているため設定不要です。

その反面、viteに切り替えることで、一部の Storybook アドオンが使用できなくなる可能性があります。 これは Storybook で webpack 5 を使用する場合にも同じことが言えます。 多くの Storybook アドオンを利用しているプロジェクトでは、十分な検証をしてから切り替えることをおすすめします。

ちなみに以下のアドオンは動作確認しました。

Storybook の準備

説明にはpreactを用います。preact を例にする理由は、preact 環境の場合、少しハマるポイントがあるためです。

まずはプロジェクトの雛形を作成します。

1
2
3
yarn create @vitejs/app <project-name> --template preact-ts
cd <project-name>
yarn add -D @mdx-js/preact
bash

のちに必要になる storybook-builder-vitepeerDependency もインストールしています。

つづいて、Storybook の雛形を生成します。 スクラッチでももちろん環境構築できますが、次のコマンドで雛形を生成できます。

1
npx sb init --builder storybook-builder-vite
bash

builder オプションに storybook-builder-vite を指定するとついでに storybook-builder-viteもインストールしてくれます。

さてこれで雛形は完成しました。

この時点でのディレクトリ構成は次の通りです。説明に不要なファイルは除いています。

1
2
3
4
5
6
7
8
9
10
11
12
13
.
├── .storybook
│ ├── main.js
│ └── preview.js
├── package.json
├── src
│ ├── stories
│ │ ├── Button.jsx
│ │ ├── Button.stories.jsx
│ │ └── Introduction.stories.mdx
│ └── vite-env.d.ts
├── tsconfig.json
└── vite.config.ts
bash

また、package.json には次のコマンドが追加されています。

1
2
3
4
5
6
{
"scripts": {
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
}
}
package.jsonjson

この段階で start-storybook コマンドを実行しても、Introduction.stories.mdxのレンダリングに失敗します。

preactmdxファイルを扱う場合には設定が必要です。 プロジェクトにreactvue を選択した場合、次の作業は不要です。

また、もし preact 環境の場合でも、mdxを扱う必要がなければ、Introduction.stories.mdx を削除することで。 次のセクションを呼び飛ばすことができます。

Preact で mdx ファイルを扱う

preactmdx ファイルを Storybook で使うために、2つの変更が必要です。

Storybook の設定ファイルを変更する

続いて、.storybook/main.js ファイルを変更します。あとでこのファイルを .ts にして型安全にする方法を紹介します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module.exports = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
core: {
builder: 'storybook-builder-vite'
},
viteFinal: (config) => {
config.plugins = [
...config.plugins,
require('@preact/preset-vite').default()
]
return config
}
}
.storybook/main.jsjs

storybook-builder-vite によって、viteFinalというフックが追加されます。 このフックから vite の設定を変更できます。 プラグインで @preact/preset-vite を使うように変更します。 ちなみに元々の構成では、以下のプラグインが有効になっています。

  • storybook-vite-code-generator-plugin
  • mock-core-js
  • vite-plugin-mdx
  • mdx:transclusion
  • storybook-vite-inject-export-order-plugin

これらも引き続き使う必要があるので、Destructuring assignmentによって、plugins にプラグインの配列を代入します。

jsx inject に対応する

@preact/preset-vite によって、[j,t]sxファイルには自動的に import { h, Fragment } from 'preact' が挿入されます。 雛形で Storybook を生成した場合、 生成されたファイルにはすでに import { h, Fragment } from 'preact'が宣言されています。  自動挿入により宣言が重複するので、雛形のすべてファイルから上の宣言を削除します。

これで準備は完了したので、start-storbybook コマンドを実行しましょう。 無事、正常にレンダリングされていると思います。

Storybook の設定ファイルを型安全にする

Storybook の設定ファイルは、デフォルトでは .jsです。 わたしは設定ファイルは何が何でも型安全にしたい病気なので、その方法を紹介します。

現在の状態は次の通りです。

1
2
3
4
.
└── .storybook
├── main.js
└── preview.js
bash

main.jspreview.js は利用するモジュールシステムが異なります。

FileModuleExt
main.jsCommonjs.js
preview.jsES modules.[j,t]sx?

main.jsNode.js での 利用を前提としているため、 Commonjs を採用しています。 一方、preview.js は ブラウザで実行されるため、 ES modules です。 こちらは TypeScript もサポートされています。

この両方を TypeScrit 化して、 ES modulesで書けるように変更します。 ちなみに、Gatsby なども似たような構成なので、同じ手法で型安全な設定ファイルを定義できます。

main.js を型安全にする

main.js はエントリーポイントなので、このファイルはそのまま残します。 新たに main.ts を作成し、 main.js の内容を ES modules に書き換えます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import preact from '@preact/preset-vite'
const config = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
core: {
builder: 'storybook-builder-vite'
},
viteFinal: (config) => {
config.plugins = [...config.plugins, preact()]
if (process.env.NODE_ENV === 'production') {
config.build.chunkSizeWarningLimit = 1200
}
return config
}
}
export default config
.storybook/main.tsts

このタイミングで chunkSizeWarningLimit を変更するコードを追加しています。 ビルド時のバンドルサイズの警告を抑える目的です。なくても問題ないです。

これに型注釈を与えます。型は storybook-builder-vite からは提供されていないので、自作する必要があります。

1
2
3
4
5
6
7
8
9
10
11
import { StorybookConfig, CoreConfig, Options } from '@storybook/core-common'
import { UserConfig } from 'vite'
import { Weaken } from 'utilitypes'
interface CustomizedCoreConfig extends Weaken<CoreConfig, 'builder'> {
builder: CoreConfig['builder'] | 'storybook-builder-vite'
}
interface CustomizedStorybookConfig extends Weaken<StorybookConfig, 'core'> {
core: CustomizedCoreConfig
viteFinal?: (config: UserConfig, options: Options) => UserConfig
}
.storybook/main.tsts

Storybook の StorybookConfig 型を拡張する必要があります。 interface ですでに存在するプロパティを拡張する場合は、そのプロパティを一旦 any にする必要があります。

Weaken は私が作っている utilitypesというプロジェクトで提供している便利な型です。 指定したプロパティを any した型を返します。

この記事の作成時はまだ、 beta です。

1
yarn add -D utilitypes@beta
bash

あとはこの型を型注釈で指定します。ファイル全体は次のようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import preact from '@preact/preset-vite'
import { StorybookConfig, CoreConfig, Options } from '@storybook/core-common'
import { UserConfig } from 'vite'
import { Weaken } from 'utilitypes'
interface CustomizedCoreConfig extends Weaken<CoreConfig, 'builder'> {
builder: CoreConfig['builder'] | 'storybook-builder-vite'
}
interface CustomizedStorybookConfig extends Weaken<StorybookConfig, 'core'> {
core: CustomizedCoreConfig
viteFinal?: (config: UserConfig, options: Options) => UserConfig
}
const config: CustomizedStorybookConfig = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
core: {
builder: 'storybook-builder-vite'
},
viteFinal: (config) => {
config.plugins = [...config.plugins, preact()]
if (process.env.NODE_ENV === 'production') {
config.build.chunkSizeWarningLimit = 1200
}
return config
}
}
export default config
.storybook/main.tsts

続いて、 main.js から main.ts をインポートします。 main.jsCommonjs なので、 通常 TypeScript も ES modules も処理できません。

これを実現するために、 esbuild-registerというパッケージを使います。 esbuild-registerts-nodeesbuild 版です。型のチェックはありません。早いです。

1
yarn add -D esbuild-register
bash

main.js は次のようになります。

1
2
3
4
5
const { register } = require('esbuild-register/dist/node')
register({
target: 'node15'
})
module.exports = require('./main.ts')
.storybook/main.jsjs

main.js は単なるエントリーポイントで、実際の設定は型安全に main.ts に書けるというわけです。

preview.js を型安全にする

preview.js の方は、 ES modules ですし、 TypeScripts も処理できるのでやることはほぼありません。

  • ファイルの拡張子を .ts もしくは .tsx へ変更
  • Storybook の型を型注釈する

こんな感じになります。

1
2
3
4
5
6
7
8
9
10
import { Parameters } from '@storybook/addons'
export const parameters: Parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/
}
}
}
.storybook/preview.tsts

Storybook は大量のファイルを提供しているので、型を見つけるのが大変でした💦。

これで型安全な開発ができますね。

プロダクションコードをプレビューする

完全におまけです。ビルド済みのコードを確認したいことありますよね。 Webpackから vite へ切り替えるような場合はなおさらです。

Storybook にはプレビュー用のコマンドが用意されていません。 なので、静的ファイルサーバーを自分で建てなければなりません。といっても簡単ですが。

npx http-server storybook-static

これでプレビューができますね。


  1. Storybook のバンドルサイズはかなり大きいです。

Edit this page on GitHub

Other Article

Comments