🌨️

【解決談】React & ViteのstorybookをGitHub PagesにデプロイしたらCanvasが真っ白だった話

2022/09/24に公開

問題

個人でReactアプリを書くときによく使う汎用レイアウトコンポーネントを、どこからでも呼び出せるようにパッケージ化
意気揚々とstorybookでドキュメントも整備したのだが、ローカルが正常に表示されているものがデプロイした途端まったく表示されず。

表示されないと言えば語弊がある。
厳密に言えば、storybookのCanvas・Docs部分だけが綺麗に真っ白だった。

かなりトリッキーな方法で解決したので、同様の事例があるのかはわからないがとりあえずメモしておく。

問題が起きた環境

前にもstorybookのgh-pagesデプロイは経験しているのだが、その時は何事もなかった。
変わったことと言えば、@storybook/builder-viteを導入したくらい。

前回から変わったことと言えば、

しかし、storybook-staticに吐き出された静的サイトをnpx http-server storybook-staticでプレビューしたところ問題なく表示されたので、決してビルドに失敗しているわけではないのだ。

問題1.Failed to load resource

デプロイした真っ白サイトで開発者ツールを覗くと、なんらかのファイルに対してFailed to load resourceエラーが吐き出されていた。

これに関しては、storybook-static直下に.nojekyllという空ファイルを設置することで見事解決した。
https://www.bennadel.com/blog/3181-including-node-modules-and-vendors-folders-in-your-github-pages-site.htm

問題2. ERR_ABORTED 404

ところが、今度はERR_ABORTED 404エラーが吐き出されるように。

しかし状況は大きく進展していて、今度はどのファイルでエラーが発生しているのかが明示されるようになったので、ここで原因が判明する

原因

ERR_ABORTED 404エラーは、https://USERNAME.github.io/assets/iframe.*.jsに対して起こっていたのだが、まあ当たり前である。
GitHub PagesのルートURLはhttps://USERNAME.github.io/REPOなのだから、正しいiframe.*.jsのURLはhttps://USERNAME.github.io/REPO/assets/iframe.*.jsであるはずだ。

assets/iframe.*.jsiframe.htmlから呼び出されているため、iframe.html

<script type="module" crossorigin src="/assets/iframe.b0087190.js"></script>

の部分を、

<script type="module" crossorigin src="/REPO/assets/iframe.b0087190.js"></script>

に修正してやれば問題は解決するだろう。

ビルドの設定でなんとかならないものかと調べていろいろと試してみたものの、vite.config.ts.storybook/main.cjsの改変では状況がびくともしなかったので、最終的に諦めてパス修正スクリプトを書くことにした。

最終的なビルドスクリプト

bashで書くと癖の強い文法やpermission deniedが面倒なので、最近はTypeScriptでbashと同様のスクリプトが書けるzxがお気に入り。

script/deploy.zx.ts
#!/usr/bin/env zx

/* eslint-disable no-use-before-define */
/* eslint-disable no-undef */
import 'zx/globals'

const REPO_NAME = 'polym-generic-layout' // 今回の場合

// ビルド
await $`yarn build-storybook`

// ビルドされたものの修正
await within(async () => {
  cd('storybook-static')

  // .nojekyllファイルを作成
  await $`touch .nojekyll`

  // iframe.htmlがassets/iframe.*.jsを読み込めるように修正
  const iframehtml = await fs.readFile('iframe.html', 'utf-8')
  await fs.writeFile(
    'iframe.html',
    iframehtml.replace(/\/assets/g, `/${REPO_NAME}/assets`)
  )

  // assets内の*.jsや*.map.jsファイルにも同様のパス修正が必要
  const assetjs_path = await glob('assets/*.@(map|js)')
  await assetjs_path.forEach(async (path) => {
    const assetjs = await fs.readFile('./' + path, 'utf-8')
    await fs.writeFile(
      './' + path,
      assetjs.replace(/assets\//g, `${REPO_NAME}/assets/`)
    )
  })
})

// キャッシュ削除
await $`rm -rf node_modules/.cache/gh-pages`

// デプロイ
await $`gh-pages -d storybook-static`

動かすための依存関係やらスクリプトやらはこんな感じ。(...は割愛部分)

package.json
{
  ...
  "repository": {
    "type": "git",
    "url": "git+https://github.com/USER/REPO.git"
  },
  "bugs": {
    "url": "https://github.com/USER/REPO/issues"
  },
  "homepage": "https://USER.github.io/REPO",
  ...
  "scripts": {
    ...
    "build-storybook": "build-storybook -s STATIC_FILES_PATH",
    "deploy-story": "vite-node ./script/deploy.zx.ts"
  },
  ...
  "devDependencies": {
    ...
    "gh-pages": "^4.0.0",
    "vite-node": "^0.23.4",
    "zx": "^7.0.8"
  },
  ...
}

UpperCase(全大文字)で書かれているところは適宜置き換えてください。

  • USERはGitHubのユーザ名
  • REPOはリポジトリ名
  • STATIC_FILES_PATHは、storybookで表示したい画像等が置かれているフォルダのパス(例えば私の場合はsrc/stories/assets

yarn deploy-storyでデプロイしてみると、コンソールを開いても何一つ警告が表示されることなく、正常に表示されました✌️

https://tetracalibers.github.io/polym-generic-layout/?path=/story/layout-introduction--page

https://www.npmjs.com/package/@polym/generic-layout

追記(.storybook/main.*の設定)

.storybook/main.(js|cjs|ts)を次のように設定したら、パスの修正は不要になりました。

./storybook.main.ts
+ const path = require('path')
+ const { loadConfigFromFile, mergeConfig } = require('vite')

module.exports = {
  stories: [
    ...
  ],
  addons: [
    ...
  ],
  framework: '@storybook/react',
  core: {
    builder: '@storybook/builder-vite'
  },
  features: {
    storyStoreV7: true
  },
+ async viteFinal(config, { configType }) {
+   return mergeConfig(config, {
+.    // リポジトリ名を設定
+     base: '/REPO/',
+     plugins: []
+   })
+ }
}

しかし相変わらず.nojekyllという空ファイルは必要な模様。

そんなわけで、ビルドスクリプトはこれで良さそう。

script/deploy.zx.ts
#!/usr/bin/env zx

/* eslint-disable no-use-before-define */
/* eslint-disable no-undef */
import 'zx/globals'

// ビルド
await $`yarn build-storybook`

// storybook-staticディレクトリ内での処理
await within(async () => {
  cd('storybook-static')

  // .nojekyllファイルを作成
  await $`touch .nojekyll`
})

// gh-pages実行時にエラーが出た場合はアンコメント
//await $`rm -rf node_modules/.cache/gh-pages`

// デプロイ
await $`gh-pages -d storybook-static`

Discussion