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

はじめに
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 環境の場合、少しハマるポイントがあるためです。
まずはプロジェクトの雛形を作成します。
npm init @vitejs/app <project-name> -- --template preact-ts
cd <project-name>
npm i -D @mdx-js/preactのちに必要になる storybook-builder-vite の peerDependency もインストールしています。
つづいて、Storybook の雛形を生成します。 スクラッチでももちろん環境構築できますが、次のコマンドで雛形を生成できます。
npx sb init --builder storybook-builder-vitebuilder オプションに storybook-builder-vite を指定するとついでに storybook-builder-viteもインストールしてくれます。
さてこれで雛形は完成しました。
この時点でのディレクトリ構成は次の通りです。説明に不要なファイルは除いています。
.
├── .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また、package.json には次のコマンドが追加されています。
{
"scripts": {
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
}
}この段階で start-storybook コマンド������行��て���、Introduction.stories.mdxのレンダリングに失敗します。
preact で mdxファイルを扱う場合には設定が必要です。 プロジェクトにreact や vue を選択した場合、次の作業は不要です。
また、もし preact 環境の場合でも、mdxを扱う必要がなければ、Introduction.stories.mdx を削除することで。 次のセクションを呼び飛ばすことができます。
Preact で mdx ファイルを扱う
preact で mdx ファイルを Storybook で使うために、2つの変更が必要です。
Storybook の設定ファイルを変更する
続いて、.storybook/main.js ファイルを変更します。あとでこのファイルを .ts にして型安全にする方法を紹介します。
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-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です。 わたしは設定ファイルは何が何でも型安全にしたい病気なので、その方法を紹介します。
現在の状態は次の通りです。
.
└── .storybook
├── main.js
└── preview.jsmain.js と preview.js は利用するモジュールシステムが異なります。
File
Module
Ext
main.js
Commonjs
.js
preview.js
ES modules
.[j,t]sx?
main.js は Node.js での 利用を前提としているため、 Commonjs を採用しています。 一方、preview.js は ブラウザで実行されるため、 ES modules です。 こちらは TypeScript もサポートされています。
この両方を TypeScrit 化して、 ES modulesで書けるように変更します。 ちなみに、Gatsby なども似たような構成なので、同じ手法で型安全な設定ファイルを定義できます。
main.js を型安全にする
main.js はエントリーポイントなので、このファイルはそのまま残します。 新たに main.ts を作成し、 main.js の内容を ES modules に書き換えます。
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') {// [!code highlight]
config.build.chunkSizeWarningLimit = 1200// [!code highlight]
}// [!code highlight]
return config
}
}
export default configこのタイミングで chunkSizeWarningLimit を変更するコードを追加しています。 ビルド時のバンドルサイズの���告を抑える目的です。なくても問題ないです。
これに型注釈を与えます。型は storybook-builder-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
}Storybook の StorybookConfig 型を拡張する必要があります。 interface ですでに存在するプロパティを拡張する場合は、そのプロパティを一旦 any にする必要があります。
Weaken は私が作っている utilitypesというプロジェクトで提供している便利な型です。 指定したプロパティを any した型を返します。
この記事の作成時はまだ、 beta です。
npm i -D utilitypes@betaあとはこの型を型注釈で指定します。ファイル全体は次のようになります。
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続いて、 main.js から main.ts をインポートします。 main.jsは Commonjs なので、 通常 TypeScript も ES modules も処理できません。
これを実現するために、 esbuild-registerというパッケージを使います。 esbuild-registerは ts-node の esbuild 版です。型のチェックはありません。早いです。
npm i -D esbuild-registerbetamain.js は次のようになります。
const { register } = require('esbuild-register/dist/node')
register({
target: 'node15'
})
module.exports = require('./main.ts')main.js は単なるエントリーポイントで、実際の設定は型安全に main.ts に書けるというわけです。
preview.js を型安全にする
preview.js の方は、 ES modules ですし、 TypeScripts も処理できるのでやることはほぼありません。
- ファイルの拡張子を
.tsもしくは.tsxへ変更 - Storybook の型を型注釈する
こんな感じになります。
import { Parameters } from '@storybook/addons'
export const parameters: Parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/
}
}
}Storybook は大量のファイルを提供しているので、型を見つけるのが大変でした💦。
これで型安全な開発ができますね。
プロダクションコードをプレビューする
完全におまけです。ビルド済みのコードを確認したいことありますよね。 Webpackから vite へ切り替えるような場合はなおさらです。
Storybook にはプレビュー用のコマンドが用意されていません。 なので、静的ファイルサーバーを自分で建てなければなりません。といっても簡単ですが。
npx http-server storybook-static
これでプレビューができますね。