切腹のイラスト

ViteなStorybookでemotionなライブラリを作る

{
  date: "",
}

概要

Storybookを使ってみようと思ったらwebpackが遅くてつらかったので,Vite版のbuilderを使おうとしたら沼った。

ここら辺はまだまだ発展途上っぽいのでそのうちこの記事も参考にならなくなりそう。

コード

説明することもないので実際のコードを晒す。

ターゲット

今回バンドルしたいファイルはsrc/main.ts。 こんな感じになっている。

export { default as Button } from "components/atoms/Button"

ちなみにButton.tsxはこんな感じ。

import { FC } from "react"

type ButtonProps = {
  text: string
  handler: React.MouseEventHandler<HTMLButtonElement>
  primary: boolean
}

const Button: FC<ButtonProps> = ({ text, handler, primary }) => {
  return (
    <button
      css={{
        backgroundColor: primary ? "red" : "blue",
      }}
      onClick={handler}>
      {text}
    </button>
  )
}

export default Button

package.json

{
  ...
  "exports": {
    ".": {
      "import": "./dist/main.js",
      "types": "./types/main.d.ts"
    }
  },
  "files": [
    "dist",
    "types"
  ],
  "scripts": {
    "build": "tsc && vite build",
    "build-storybook": "build-storybook",
    "storybook": "start-storybook -p 6006"
  },
  "type": "module"
}

tsconfig.json

型定義のみを出力するようにしている。 noEmitは無効化する。

{
  "compilerOptions": {
    ...
    "emitDeclarationOnly": true,
    "declaration": true,
    "declarationMap": true,
    "declarationDir": "./types",
    "jsx": "react-jsx",
    "jsxImportSource": "@emotion/react",
    "baseUrl": "./src"
  }
}

vite.config.js

https://github.com/emotion-js/emotion/issues/2853 が参考になった(というかこれが本質情報)。
本当にありがとうございます。

import { resolve } from "node:path"
import { defineConfig } from "vite"
import react from "@vitejs/plugin-react"
import tsconfigPaths from "vite-tsconfig-paths"
import { dependencies } from "./package.json"

const deps = Object.keys(dependencies)
const re = new RegExp(`^(${deps.join("|")})($|/)`)
const external = name => re.test(name)

export default defineConfig({
  plugins: [tsconfigPaths(), react({ jsxImportSource: "@emotion/react" })],
  build: {
    lib: {
      entry: resolve(__dirname, "src/main.ts"),
      formats: ["es"],
      fileName: "main",
    },
    rollupOptions: { external },
  },
})

main.cjs

Storybookのエントリー的なやつ。

Viteを使うのにvite.config.jsを読まないマジキチ仕様なライブラリなのでカオスなことになっている(2022年8月現在)。

const { resolve } = require("node:path")
const { loadConfigFromFile, mergeConfig } = require("vite")

const filter = [
  "vite:react-babel",
  "vite:react-jsx",
  "vite:react-refresh",
]

const filterPlugins = obj => {
  if(Array.isArray(obj))
    return obj
      .map(filterPlugins)
      .filter(v => Array.isArray(v) ? v.length : v)
  if(filter.includes(obj.name))
    return undefined
  return obj
}

module.exports = {
  ...
  core: {
    builder: "@storybook/builder-vite",
  },
  viteFinal: async orig => {
    const path = resolve(__dirname, "../vite.config.ts")
    const { config } = await loadConfigFromFile(path)
    orig.plugins = filterPlugins(orig.plugins)
    return mergeConfig(orig, config)
  },
}

まとめ

つらい