Open13

Docker + Laravel + Vite + Vue3環境へStorybookを導入

ピン留めされたアイテム
webdevwebdev

既存のInertiaの開発環境でStorybookも使えるようにしたい

構成

バックエンド(LaravelやRails) + モダン JavaScriptライブラリの構築を一つのリポジトリでまかなえるInertiaというのを使っていて、開発環境全体がDockerコンテナにいる。
Viteがすでに動作しているので、ここへ@storybook/builder-viteを追加したい。

  • Laravel Sail
    • Docker
    • Larabel
  • Inertia
    • Vite
    • Vue3

達成したいこと

  • Storybookに、Vueコンポーネントをimportしてカタログにしたい。
  • HerokuかNetlifyでbuild-storybookをデプロイしたい。
webdevwebdev

Viteは既にうごいているので、Storybookと@storybook/builder-viteの導入から。

Docker Desktopを起動し、Sailを立ち上げるコマンドを実行。

sail up -d

続いてDockerコンテナ内で bashを起動する

sail shell

準備ができたので、パッケージを導入する。

npx sb init --builder storybook-builder-vite

initまでは無事に実行でき、ルートへ.storybookstoriesのディレクトリとファイルが生成された。

webdevwebdev

パッケージのバージョンが噛み合わないエラー

initは成功したが、storybookの起動はエラーで失敗。
※このエラーの解決は、もうすこし後ろの「パッケージのバージョンが噛み合わないエラー:解決

npn run storybook

> storybook
> start-storybook -p 6006

info @storybook/vue3 v6.5.13
info 
info => Loading presets

    ERR! TypeError: vite.createFilter is not a function
    ERR! at viteReact (/var/www/html/node_modules/@vitejs/plugin-react/dist/index.cjs:231:21)
    ERR! at pluginConfig (/var/www/html/node_modules/@storybook/builder-vite/dist/vite-config.js:82:36)
    ERR! at processTicksAndRejections (node:internal/process/task_queues:96:5)
    ERR! at async commonConfig (/var/www/html/node_modules/@storybook/builder-vite/dist/vite-config.js:66:18)
    ERR! at async createViteServer (/var/www/html/node_modules/@storybook/builder-vite/dist/vite-server.js:10:24)
    ERR! at async Object.start (/var/www/html/node_modules/@storybook/builder-vite/dist/index.js:64:14)
    ERR! at async Promise.all (index 0)
    ERR! at async storybookDevServer (/var/www/html/node_modules/@storybook/core-server/dist/cjs/dev-server.js:203:28)
    ERR! at async buildDevStandalone (/var/www/html/node_modules/@storybook/core-server/dist/cjs/build-dev.js:120:31)
    ERR! at async buildDev (/var/www/html/node_modules/@storybook/core-server/dist/cjs/build-dev.js:174:5)

このIssueが、まさにそれっぽい。

https://github.com/storybookjs/builder-vite/issues/436

パッケージどうしのバージョンが噛み合っていないことが原因らしい。
(まだ、どれとどれが対象なのかは、わかっていない)

webdevwebdev

DockerコンテナのNode.jsを18へアップデート

パッケージバージョンかみ合わせ問題、TypeError: vite.createFilter is not a functionの調査で行き詰まった。
何かあったときはNodeのバージョンが原因のことが多いので
ためしにv16.5を LTEの 18.12 までアップデートしてみることにした。

NODEで16と記載されているデータを探すと、/docker/8.1/Dockerfileに記述がみつかった。
これを変更して再構築してみる。[1]

sail build --no-cache

実行すると、しばらくいろんなコマンドが流れた後…失敗

(省略)
#7 1.031 Ign:1 http://security.ubuntu.com/ubuntu impish-security InRelease
#7 1.075 Ign:2 http://archive.ubuntu.com/ubuntu impish InRelease
#7 1.270 Err:3 http://security.ubuntu.com/ubuntu impish-security Release
#7 1.270   404  Not Found [IP: 91.189.91.39 80]
#7 1.315 Ign:4 http://archive.ubuntu.com/ubuntu impish-updates InRelease
#7 1.678 Ign:5 http://archive.ubuntu.com/ubuntu impish-backports InRelease
#7 1.910 Err:6 http://archive.ubuntu.com/ubuntu impish Release
#7 1.910   404  Not Found [IP: 185.125.190.39 80]
#7 2.166 Err:7 http://archive.ubuntu.com/ubuntu impish-updates Release
#7 2.166   404  Not Found [IP: 185.125.190.39 80]
#7 2.487 Err:8 http://archive.ubuntu.com/ubuntu impish-backports Release
#7 2.487   404  Not Found [IP: 185.125.190.39 80]
#7 2.495 Reading package lists...
#7 2.519 E: The repository 'http://security.ubuntu.com/ubuntu impish-security Release' does not have a Release file.
#7 2.520 E: The repository 'http://archive.ubuntu.com/ubuntu impish Release' does not have a Release file.
#7 2.520 E: The repository 'http://archive.ubuntu.com/ubuntu impish-updates Release' does not have a Release file.
#7 2.520 E: The repository 'http://archive.ubuntu.com/ubuntu impish-backports Release' does not have a Release file.

ubuntuのアップデートやdocker-compose.ymlの編集も必要だった

お手上げだったので、このプロジェクトのDockerコンテナを設定してくれたエンジニアさんに助けてもらった。

差分をみると、/docker/8.1/Dockerfileだけでなく/docker-compose.ymlも編集が必要だったり、ubuntuのバージョンも関係していたよう。

更新してもらったデータで改めてsail build --no-cacheすると、成功。
sail up -dからのsail shellでコンテナのbashを起動し確認。

node -v
v18.12.0
脚注
  1. 行き詰まって色々試したので、この時点で、Sailを立ち上げるsail upは終了しており、Dockerコンテナ内で bashを起動するsail shellexitで終了している ↩︎

webdevwebdev

パッケージのバージョンが噛み合わないエラー:解決

パッケージバージョンかみ合わせ問題、TypeError: vite.createFilter is not a functionの原因が見つかった。

https://github.com/storybookjs/storybook/issues/19151#issuecomment-1268329475

IanVSさんによると、@storybook/builder-viteとviteのバージョンが噛み合ってないからエラーになっているとのこと。
最新版の@storybook/builder-vitevite ver.3未満で実行しようとしたら発生する。
解決策は、このいずれか。

  • @storybook/builder-viteをダウングレードする
  • viteをver3以上にアップデートする

さっそくViteのバージョンを確認してみたら、2.8だった!

Viteを3.2にアップデートして解決

viteとその依存関係にあるパッケージをuninstallし、改めてinstall。最新版にアップデートした。

その後、SailやDockerコンテナを起動する一連のコマンドを実行してからStorybookを起動すると…

npn run storybook

...別のエラーで失敗した。

ともかく、@storybook/builder-viteviteの依存関係については解消できた。(つづく)

webdevwebdev

Node.js 17以上にした際のOpenSSL互換エラー対応

Storybookの起動コマンドを実行すると、エラー。

> storybook
> start-storybook -p 6006

info @storybook/vue3 v6.5.13
info 
info => Loading presets

info => Ignoring cached manager due to change in manager config
(node:633) ExperimentalWarning: The Fetch API is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
i 「wdm」: wait until bundle finished: 
node:internal/crypto/hash:71
 this[kHandle] = new _Hash(algorithm, xofLen);
 ^

Error: error:0308010C:digital envelope routines::unsupported
 at new Hash (node:internal/crypto/hash:71:19)
 at Object.createHash (node:crypto:133:10)
 at module.exports (/var/www/html/node_modules/webpack/lib/util/createHash.js:135:53)
 at NormalModule._initBuildHash (/var/www/html/node_modules/webpack/lib/NormalModule.js:417:16)
 at handleParseError (/var/www/html/node_modules/webpack/lib/NormalModule.js:471:10)
 at /var/www/html/node_modules/webpack/lib/NormalModule.js:503:5
 at /var/www/html/node_modules/webpack/lib/NormalModule.js:358:12
 at /var/www/html/node_modules/loader-runner/lib/LoaderRunner.js:373:3
 at iterateNormalLoaders (/var/www/html/node_modules/loader-runner/lib/LoaderRunner.js:214:10)
 at Array.<anonymous> (/var/www/html/node_modules/loader-runner/lib/LoaderRunner.js:205:4) {
 opensslErrorStack: [ 'error:03000086:digital envelope routines::initialization error' ],
 library: 'digital envelope routines',
 reason: 'unsupported',
 code: 'ERR_OSSL_EVP_UNSUPPORTED'
}

Node.js v18.12.0

Node.jsを18にアップデートした影響だった

Node.js 17からOpenSSLが3.0になったため、3.0で非奨励の処理が差し止められていた。
環境変数でOpenSSLの設定をレガシーに設定すると解消できるとわかった。

export NODE_OPTIONS=--openssl-legacy-provider

https://qiita.com/kamada_math/items/4df6f153bb2d0159a4ff
https://zenn.dev/yogarasu/articles/425732ff408d06

Dockerコンテナで環境変数を設定する方法がわからなかったので、
Storybookの起動コマンドに直書きして解消するか確認。

NODE_OPTIONS=--openssl-legacy-provider start-storybook -p 6006

(省略)

╭─────────────────────────────────────────────────╮
│                                                 │
│   Storybook 6.5.13 for Vue3 started             │
│   24 s for manager and 31 s for preview         │
│                                                 │
│    Local:            http://localhost:6006/     │
│    On your network:  http://172.22.0.6:6006/    │
│                                                 │
╰─────────────────────────────────────────────────╯

起動できた!

webdevwebdev

Dockerコンテナの環境変数を設定

Storybookの起動コマンドへNODE_OPTIONSを残したままにするのは不本意なので、
Dockerコンテナの環境変数が設定したい。

docker-compose.ymlbuild
environmentという環境変数用のオプションがあることがわかった。

https://www.cloudnotes.tech/entry/react_docker

ここにNODE_OPTIONSを設定すると...

docker-compose.yml
    build:
        environment:
+            NODE_OPTIONS: '--openssl-legacy-provider'

package.jsonのscriptsに直書きしていた環境変数を取り除けた。

package.json
  "scripts": {
-    "storybook": "NODE_OPTIONS=--openssl-legacy-provider start-storybook -p 6006",
+    "storybook": "start-storybook -p 6006",
  },
webdevwebdev

Dockerコンテナで6006ポートを使えるようにする

start-storybookが成功し起動したのに、
http://localhost:6006がブラウザで「このサイトにアクセスできません」となり表示できない。

Docker環境であらたにポートが使いたいときは設定が必要だった

docker-compose.ymlへ6006を追加。

docker-compose.yml
    build:
        ports:
            - '${APP_PORT:-80}:80'
            - '3000:3000'
+           - '6006:6006'

あらためてStorybookを起動すると、アクセスできた。🎊

webdevwebdev

stories.tsを開発ソースと同じディレクトリに設置できるようにする

さいしょに、Storybookの設定ファイル/.storybook/main.js
/.storybook/main.tsへ変更しTypeScript化した。

Inertiaの環境では、開発ソースは/resources/views/配下にあり
ここに置いているコンポーネント名.vueと同じディレクトリ内へ、コンポーネント名.stories.tsをならべて設置したいため
main.tsのstories/resources/views/配下も設定[1]

/.storybook/main.ts
module.exports = {
  stories: [
+    '../resources/views/components/**/*.stories.@(js|jsx|ts|tsx)',
  ],

storiesを移動して動作確認

前工程の npx sb init ...で生成した/storiesディレクトリを/resources/views/components/storiesに移動。
Storybookを起動したら、EXAMPLES配下のサンプルが正常に表示されていた。

ここから自作のVueコンポーネントをimportしたstories.tsを作っていく。

自作のstoriesを追加

ためしに、単純なコンポーネントのstoriesを設置してみる。

静的コンテンツだけで構成されたフッター/resources/views/components/footer.vueにした。
サンプルのHeader.stories.jsを複製し/resources/views/components/footer.stories.tsを設置。

footer.stories.ts
import LFooter from './footer.vue';

export default {
  title: 'Layout/Footer',
  component: LFooter,
  parameters: {
    // More on Story layout: https://storybook.js.org/docs/vue/configure/story-layout
    layout: 'fullscreen',
  },
};

const Template = () => ({
  // Components used in your story `template` are defined in the `components` object
  components: { LFooter },
  // Then, the spread values can be accessed directly in the template
  template: '<l-footer />',
});

export const Default = Template.bind();

追加できた!けれど、スタイルとSVGスプライトが効いていない。(つづく)

脚注
  1. /resources/views/componentsまでが、Inertiaのデフォルト設定で設置されるディレクトリ、/resources/views/componentsは、このプロジェクトのvueコンポーネント置き場として設置したディレクトリ ↩︎

webdevwebdev

tsconfigのalias対応をすすめる

開発環境では、tsconfigで@をルートのresourcesへ置き換えるよう設定している。

@/views/components/layouts/footer.vue

/resources/views/components/layouts/footer.vue

tsconfig.json
{
    "compilerOptions": {
        "baseUrl": ".",
        "paths": {
            "@/*": [
                "resources/*"
            ]
        }
    },
    "include": [
        "resources/**/*"
    ]
}

このままでは自作のVueコンポーネントをimportしたstories.tsでパスが通らないため
Storybookのmain.tsにもaliasを設定する。

この時点で、ためしにコンポーネントのimportをaliasでの参照に書き換えると
Failed to fetch dynamically imported module: エラーになる。

footer.stories.ts
-import LFooter from './footer.vue';
+import LFooter from '@/views/components/layouts/footer.vue';

webdevwebdev

storybook-builder-viteでviteの設定を上書きするときはviteFinalを使う

storybook-builder-vite用のフックviteFinal
viteのresolve.aliasに@のルールを追加した。

main.ts
+const { mergeConfig } = require('vite');
+const path = require('path');

module.exports = {
  stories: [
    '../resources/views/components/**/*.stories.@(js|jsx|ts|tsx)',
  ],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
  ],
  framework: '@storybook/vue3',
  core: {
    builder: '@storybook/builder-vite',
  },
  features: {
    storyStoreV7: true,
  },
+  viteFinal: async (config, { configType }) => {
+    return mergeConfig(config, {
+      resolve: {
+        alias: { '@': path.resolve(__dirname, '../resources') },
+    });
+  },
};

aliasを使ったコンポーネントのimportができるようになったので
スタイルとSVGスプライトが読み込めない問題に対応する。(つづく)

webdevwebdev

Storybookでスタイルを再現する

/.storybook/preview.tsは、Viteの起点になっているmain.ts[1]のような位置づけなので
ここへViteのmain.tsでimportしていたスタイルシートを追加。

/.storybook/preview.ts
+import 'normalize.css';
+import '@/styles/common.scss';
export const parameters = {
  actions: { argTypesRegex: '^on[A-Z].*' },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
};

Sassの中でglob対策

preview.tsを編集後にStorybokを起動するとエラー発生。

[sass] Can't find stylesheet to import.
   ╷
12 │ @import '_variable/**/*.scss';
   │         ^^^^^^^^^^^^^^^^^^^^^
   ╵
  resources/styles/common.scss 12:9  root stylesheet

プロジェクト本体ではvite.config.tsvite-plugin-sass-glob-importでglabを使えるよう設定しているけれど、Storybookにはまだ設定がなく解釈できないことが原因。

vite.config.ts(当該箇所だけ抜粋)
import sassGlobImports from 'vite-plugin-sass-glob-import';

export default defineConfig(({ command, mode }) => {
  const config: UserConfig = {
    plugins: [
      sassGlobImports(),
    ],
  };

  return config;
});

preview.tsのimportを、ためしにnormalize.cssのみにしたらStorybookは正常に起動した。
globの対応は必要だけど、スタイルシートを参照するところまでは解決できている。

脚注
  1. /.storybook/main.tsではありません、プロジェクト本体のViteの起点ファイルです。(紛らわしい…)
    対応中のプロジェクトではInertiaを導入しているため/resources/scripts/main.tsに設置しています。 ↩︎

webdevwebdev

vite.config.tsの設定を/.storybook/main.tsに取り込む

/.storybook/main.tsではimportで外部ファイルを読み込めないので[1]
loadConfigFromFilevite.config.tsを参照してみる。

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

module.exports = {
  stories: [
    '../resources/views/components/**/*.stories.@(js|jsx|ts|tsx)',
  ],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
  ],
  framework: '@storybook/vue3',
  core: {
    builder: '@storybook/builder-vite',
  },
  features: {
    storyStoreV7: true,
  },
+  async viteFinal(config, { configType }) {
+    const { config: userConfig } = await loadConfigFromFile(
+      path.resolve(__dirname, '../vite.config.ts')
+    );
+
+    return mergeConfig(config, {
+      ...userConfig,
+      resolve: {
+        alias: { '@': path.resolve(__dirname, '../resources') },
+      },
+      plugins: [],
+    });
+  },
-  viteFinal: async (config, { configType }) => {
-    return mergeConfig(config, {
-      resolve: {
-        alias: { '@': path.resolve(__dirname, '../resources') },
-    });
-  },
};

https://zenn.dev/longbridge/articles/13e65ef71455e4

が、Sass内のglobを認識できないエラーは継続されている。
この設定ではプラグインは効いていなかった。(つづく)

脚注
  1. vite.config.tsでは、プラグインをimportしてから設定している。 ↩︎