インポートの変更でvite devが数分から数秒になった

こんにちは。トグルホールディングス、AIエンジニアのマーカスです。
トグルホールディングスエンジニアアドベントカレンダーの23日目の記事です！ 

# 概要

```diff_typescript
- import mapboxgl from 'mapbox-gl'
+ import mapboxgl from '../lib/mapboxgl'
```

っていう謎の変更で`vite dev`のビルド時間が2分から18秒までに短縮したについての話です。

# 背景

開発する途中で動作確認したいのはよくある例で、そこでJavaScriptツールで[HMR](https://vite.dev/guide/api-hmr)という機能で変更点をすぐ見えるようになったはずなのに、何故か2分かかったのは謎です。また、`vite build`を試したとき15秒しかかからないのはもっと謎です。

そこで`git bisect`で悪いコミットを探し、インポートの変更を見つけました。

理由を知らないまま進むと今後同じことを起きるかもしれないため、徹底的に調査しました。

# 推測

TypeScriptの型検査でビルド時間かかるのはよくある話。

そこで[mapbox-gl](https://www.npmjs.com/package/mapbox-gl)のインポートを一つのファイル(`../lib/mapboxgl.ts`)にまとめて、型検査を1回でしか実行しないようにした、のが推測です。それを再現するために小さなプロジェクトを建ててみました。

（※ TypeScriptとは関係ないのビルドツール（rollupなど）が遅いの可能性もあるが、一旦ここで放っておきます）

```ts
// ../lib/mapboxgl.ts
import mapboxgl from 'mapbox-gl'

// mapboxglの処理

export default mapboxgl
```

# 再現

## プロジェクトを建てる

発見したプロジェクトがHonoXを使ってるから同じものにしました。

```sh
pnpm create hono

# ? Target directory my-app
# ? Which template do you want to use? x-basic
# ? Do you want to install project dependencies? yes
# ? Which package manager do you want to use? pnpm
```

ディレクトリの構造

```diff
my-app
├── app
|  ├── client.ts
|  ├── global.d.ts
|  ├── islands
|  |  └── counter.tsx
|  ├── routes
|  |  ├── _404.tsx
|  |  ├── _error.tsx
|  |  ├── _renderer.tsx
|  |  └── index.tsx
|  └── server.ts
├── package.json
├── pnpm-lock.yaml
├── public
|  └── favicon.ico
├── tsconfig.json
├── vite.config.ts
└── wrangler.toml
```

## 再現するファイルを作る

```
pnpm add mapbox-gl
```

```tsx
// app/routes/index.tsx
import { createRoute } from 'honox/factory'
import Slow from '../islands/Slow'

export default createRoute((c) => {
  return c.render(<Slow />)
})
```

```tsx
// app/islands/Slow.tsx
import mapboxgl from 'mapbox-gl'

export default function Slow() {
  const dummy = typeof mapboxgl
  return (
    <div>
      {dummy}
    </div>
  )
}
```

```
my-app
├── app
|  ├── client.ts
|  ├── global.d.ts
|  ├── islands
|  |  └── Slow.tsx
|  ├── routes
|  |  ├── _404.tsx
|  |  ├── _error.tsx
|  |  ├── _renderer.tsx
|  |  └── index.tsx
|  └── server.ts
├── package.json
├── pnpm-lock.yaml
├── public
|  └── favicon.ico
├── tsconfig.json
├── vite.config.ts
└── wrangler.toml
```

よし！プロジェクトを建てられたので早速実験を実行する。

## 初期ビルド速度を試す

```sh
pnpm vite dev
```

```sh
curl -o /dev/null -w "%{time_total} seconds\n" -s http://localhost:5173
# 3.998502 seconds
```

## わざと遅くさせる

先ほどちょっと言ったが、mapbox-glを何倍インポートしてたら遅くなるはず

```mermaid
graph TD;
    mapbox-gl-->A;
    mapbox-gl-->B;
    mapbox-gl-->C;
    A-->routes;
    B-->routes;
    C-->routes;
```

```tsx
// app/islands/Slow.tsx
import mapboxgl from 'mapbox-gl'
import mapboxgl2 from 'mapbox-gl'
import mapboxgl3 from 'mapbox-gl'
import mapboxgl4 from 'mapbox-gl'
import mapboxgl5 from 'mapbox-gl'
import mapboxgl6 from 'mapbox-gl'
import mapboxgl7 from 'mapbox-gl'
import mapboxgl8 from 'mapbox-gl'
import mapboxgl9 from 'mapbox-gl'
import mapboxgl10 from 'mapbox-gl'

export default function Slow() {
  const dummy = typeof mapboxgl ||
    typeof mapboxgl2 ||
    typeof mapboxgl3 ||
    typeof mapboxgl4 ||
    typeof mapboxgl5 ||
    typeof mapboxgl6 ||
    typeof mapboxgl7 ||
    typeof mapboxgl8 ||
    typeof mapboxgl9 ||
    typeof mapboxgl10
  return (
    <div>
      {dummy}
    </div>
  )
}
```

```sh
pnpm vite dev # キャッシュを使わないように再起動（速度を測る前に実行するが今後略する）
```

```sh
curl -o /dev/null -w "%{time_total} seconds\n" -s http://localhost:5173
# 30.474034 seconds
```

よし！遅くなった。

# 修正

## 仮説から

ここで前の仮説からやってみよう。

```tsx
// app/islands/Slow.tsx
import mapboxgl from '../lib/mapboxgl'
import mapboxgl2 from '../lib/mapboxgl'
import mapboxgl3 from '../lib/mapboxgl'
import mapboxgl4 from '../lib/mapboxgl'
import mapboxgl5 from '../lib/mapboxgl'
import mapboxgl6 from '../lib/mapboxgl'
import mapboxgl7 from '../lib/mapboxgl'
import mapboxgl8 from '../lib/mapboxgl'
import mapboxgl9 from '../lib/mapboxgl'
import mapboxgl10 from '../lib/mapboxgl'

export default function Slow() {
  const dummy = typeof mapboxgl ||
    typeof mapboxgl2 ||
    typeof mapboxgl3 ||
    typeof mapboxgl4 ||
    typeof mapboxgl5 ||
    typeof mapboxgl6 ||
    typeof mapboxgl7 ||
    typeof mapboxgl8 ||
    typeof mapboxgl9 ||
    typeof mapboxgl10
  return (
    <div>
      {dummy}
    </div>
  )
}
```

```sh
curl -o /dev/null -w "%{time_total} seconds\n" -s http://localhost:5173
# 17.119245 seconds
```

あれ？ちょっと早くなったがインポート1回みたいの速度じゃないな・・・
`../lib/mapboxgl`を1回インポートしてやってみよう

```tsx
// app/islands/Slow.tsx
import mapboxgl from '../lib/mapboxgl'

export default function Slow() {
  const dummy = typeof mapboxgl
  return (
    <div>
      {dummy}
    </div>
  )
}
```

```sh
curl -o /dev/null -w "%{time_total} seconds\n" -s http://localhost:5173
# 4.015881 seconds
```

うん。mapbox-glがまだ数回読まれてる。

## 前のディレクトリ構造から

色んな試行錯誤してroutesのディレクトリ構造が原因なのは分かったんでここで試してみる。

### 作り直し

```tsx
// app/routes/foo/index.tsx
import { createRoute } from 'honox/factory'
import Slow from '../../islands/Slow'

export default createRoute((c) => {
  return c.render(<Slow />)
})
```

```tsx
// app/islands/Slow.tsx
import mapboxgl from 'mapbox-gl'

export default function Slow() {
  const dummy = typeof mapboxgl
  return (
    <div>
      {dummy}
    </div>
  )
}
```

```
my-app
├── app
|  ├── client.ts
|  ├── global.d.ts
|  ├── islands
|  |  └── Slow.tsx
|  ├── lib
|  |  └── mapboxgl.ts
|  ├── routes
|  |  ├── _404.tsx
|  |  ├── _error.tsx
|  |  ├── _renderer.tsx
|  |  └── foo
|  |     └── index.tsx
|  └── server.ts
├── package.json
├── pnpm-lock.yaml
├── public
|  └── favicon.ico
├── tsconfig.json
├── vite.config.ts
└── wrangler.toml
```

```sh
curl -o /dev/null -w "%{time_total} seconds\n" -s http://localhost:5173/foo
# 3.860002 seconds
```

よし！また4秒

### 遅くさせる（2回目）

```tsx
// app/islands/Slow.tsx
import mapboxgl from 'mapbox-gl'
import mapboxgl2 from 'mapbox-gl'
import mapboxgl3 from 'mapbox-gl'
import mapboxgl4 from 'mapbox-gl'
import mapboxgl5 from 'mapbox-gl'
import mapboxgl6 from 'mapbox-gl'
import mapboxgl7 from 'mapbox-gl'
import mapboxgl8 from 'mapbox-gl'
import mapboxgl9 from 'mapbox-gl'
import mapboxgl10 from 'mapbox-gl'

export default function Slow() {
  const dummy = typeof mapboxgl ||
    typeof mapboxgl2 ||
    typeof mapboxgl3 ||
    typeof mapboxgl4 ||
    typeof mapboxgl5 ||
    typeof mapboxgl6 ||
    typeof mapboxgl7 ||
    typeof mapboxgl8 ||
    typeof mapboxgl9 ||
    typeof mapboxgl10
  return (
    <div>
      {dummy}
    </div>
  )
}
```

```sh
curl -o /dev/null -w "%{time_total} seconds\n" -s http://localhost:5173/foo
# 29.796043 seconds
```

また30秒

### 修正

```tsx
// app/islands/Slow.tsx
import mapboxgl from '../lib/mapboxgl'
import mapboxgl2 from '../lib/mapboxgl'
import mapboxgl3 from '../lib/mapboxgl'
import mapboxgl4 from '../lib/mapboxgl'
import mapboxgl5 from '../lib/mapboxgl'
import mapboxgl6 from '../lib/mapboxgl'
import mapboxgl7 from '../lib/mapboxgl'
import mapboxgl8 from '../lib/mapboxgl'
import mapboxgl9 from '../lib/mapboxgl'
import mapboxgl10 from '../lib/mapboxgl'

export default function Slow() {
  const dummy = typeof mapboxgl ||
    typeof mapboxgl2 ||
    typeof mapboxgl3 ||
    typeof mapboxgl4 ||
    typeof mapboxgl5 ||
    typeof mapboxgl6 ||
    typeof mapboxgl7 ||
    typeof mapboxgl8 ||
    typeof mapboxgl9 ||
    typeof mapboxgl10
  return (
    <div>
      {dummy}
    </div>
  )
}
```

```sh
curl -o /dev/null -w "%{time_total} seconds\n" -s http://localhost:5173/foo
# 2.516782 seconds
```

4秒すらかからなかった！

### 余談

`import mapboxgl from '../lib/mapboxgl'`とは別

- 絶対パス（`import mapboxgl from '/home/my-app/app/lib/mapboxgl'`）
- Viteの[resolve.alias](https://ja.vite.dev/config/shared-options#resolve-alias)（`import mapboxgl from '@/lib/mapboxgl'`）
 
をやる時も`import mapboxgl from 'mapbox-gl'`みたいな速度、数回読まれてる。

# 分かったこと

- HonoXが使ってるviteのバージョンか設定がバグっている
- ファイルのまとめ方によってビルド時間が変わる
- インポートのやり方によってビルド時間が変わる

今度ビルドが遅くなった時インポートのやり方を変えるべきかもしれないです。
また、viteのバグか、HonoXのみの問題かはまた別の記事に記載したいと思います！