Vue3 + Storybook の Vite 移行

# はじめに
我々[ユニークビジョン株式会社](https://qiita.com/organizations/uniquevision)では、フロントエンドは Vue3 + Storybook を使用しています。
メインのアプリも Storybook も **Webpack** でビルドしていました。
昨今の vite の普及に伴い、**Webpack から vite への移行を行いました。**
この記事では、その手順やハマりポイントをまとめたいと思います。

# メインアプリの vite 移行
## package の置き換え
vue-cli 関連の package を全てアンインストールします。以下が削除対象となりました。
- @vue/cli-plugin-babel
- @vue/cli-plugin-eslint
- @vue/cli-plugin-router
- @vue/cli-plugin-typescript
- @vue/cli-plugin-unit-jest
- @vue/cli-service
- vue-cli-plugin-storybook
- vue-cli-plugin-tailwind


vite 関連の package をインストールします。
```bash
npm install -D @vitejs/plugin-vue vite
```

## index.html の移動と修正
vite ルート直下にある index.html をエントリーポイントとするのでディレクトリを移動します。

my-app/public/index.html → my-app/index.html

```diff_html:index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
-    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+    <link rel="icon" href="/favicon.ico">
    <title>My App</title>
  </head>
  <body>
    <noscript>
-      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+      <strong>We're sorry but MyApp doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
+    <!-- main.js ではなく main.ts であることに注意 -->
+    <script type="module" src="/src/main.ts"></script>
    <!-- built files will be auto injected -->
  </body>
</html>
```

## html loader を使用している処理の書き換え
我々のプロジェクトでは、SVG ファイルを文字列として読み込んで html としてレンダリングするという処理があります。
Webpack のプラグイン html-loader を使って実現していました。

```js:
const icon = require(`!!html-loader!./assets/icon.svg`);
``` 

vite では次のように書けます。
```js
import icon from '../assets/icon.svg?raw'
```
[[参考]アセットを文字列としてインポートする](https://ja.vitejs.dev/guide/features.html#glob-%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%88)


複数ファイルをまとめて取得したい時は以下のように書けます。
```js
const modules = import.meta.glob('./dir/*.js')
```

ただし、この方法は実行時に動的に**非同期に行われる処理**なので、取得したデータにアクセスするには下記のようにしなくてはいけません。
```js
for (const path in modules) {
  modules[path]().then((mod) => {
    console.log(path, mod)
  })
}
```

**ビルド時に取得しておきたい場合は`eager: true` オプションを付けます。**
```js
const modules = import.meta.glob('./dir/*.js', { eager: true })
```
ビルド時に以下のように変換されます。
```js
import * as __glob__0_0 from './dir/foo.js'
import * as __glob__0_1 from './dir/bar.js'
const modules = {
  './dir/foo.js': __glob__0_0,
  './dir/bar.js': __glob__0_1
}
```

この方法ならコードの中では `module` はテキストを値に持つオブジェクトとして扱うことができます。

[[参考]Glob のインポート](https://ja.vitejs.dev/guide/features.html#glob-%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%88)

# Storybook の vite 移行
## plugin のインストール
```bash
npm install -D @vitejs/plugin-vue
```

## main.js の修正
```diff_javascript:.storybook/header.html
const path = require('path');

module.exports = {
  core: {
-    builder: "webpack5"
+    builder: '@storybook/builder-vite',
  },
  stories: ['../src/**/*.stories.ts'],
  addons: [
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
    '@storybook/addon-links',
    '@storybook/addon-postcss',
  ],
  framework: '@storybook/vue3',
  staticDirs: [
    { from: '../src/assets', to: 'assets' }
  ],
-  webpackFinal: async config => {
-    config.resolve.alias['@'] = path.resolve(__dirname, '../src');
-    config.resolve.fallback['path'] = require.resolve('path-browserify');
-    return config;
-  },
+  async viteFinal(config) {
+    config.resolve.alias['@'] = path.resolve(__dirname, "../src");
+    
+    return {
+      ...config,
+      build: {
+        ...config.build,
+        sourcemap: false,
+        minify: false,
+      },
+    };
+  },
};
```

`config.build.sourcemap = false;` `config.build.minify = false;` の設定を追加しました。
ユニークビジョン株式会社では Storybook を Gitlab CI でビルドし、Chromatic に publish して VRT を行っています。
Webpack よりも Vite でのビルドサイズが大きく、 Gitlab CI メモリ制限 4GB に引っ掛かってしまいました。
回避策として、前述の設定を追加しました。

https://github.com/storybookjs/builder-vite/issues/409#issuecomment-1152848986

## header.html の追加
ここまでの状態で storybook を起動すると、
```
global is not defined
```

のようなエラーが表示されたので、下記のファイルを追加することで回避しました。

```.storybook/header.html
<script>
  window.global = window;
</script>

```

下記を参考にしました。
https://github.com/storybookjs/storybook/issues/15391

# Vite に移行してみた感想
**もう Webpack には戻れないなというのが正直な感想です。** 
コードの修正の確認がリアルタイムでできるようになりました。
移行して 2 カ月ほど運用しましたが、特に困ったことも無いので社内標準にしていきたいと考えています。


と言っている側からさらに速い Turbopack なるものが出てきたりしていますが...

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">viteの10倍速い？？<br><br>Vercelがwebpackの後継を発表しました<a href="https://t.co/qOAZQsqVSy">https://t.co/qOAZQsqVSy</a></p>&mdash; ユニークビジョン技術 (@uv_technology) <a href="https://twitter.com/uv_technology/status/1585198679593742336?ref_src=twsrc%5Etfw">October 26, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# おしらせ
フロントエンド周りのことをよく呟いています。よければフォローお願いします。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ユニークビジョンという会社のエンジニアです。<br>フロントエンド開発と品質向上に取り組んでいるので、そのあたり興味ある方はフォローしていただけると嬉しいです。<br><br>品質の高いサービスを提供し続ける会社であるために／品質向上チーム <a href="https://t.co/vI7YsKL6QW">https://t.co/vI7YsKL6QW</a></p>&mdash; シライシ@ユニークビジョン (@punksy2) <a href="https://twitter.com/punksy2/status/1576934123670630404?ref_src=twsrc%5Etfw">October 3, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>