Viteのライブラリモードでビルドする際、フォーマットごとにディレクトリを分ける


## この記事の概要

Viteには[ライブラリモード](https://vitejs.dev/guide/build.html#library-mode)があります。
ライブラリを配布するためのバンドル設定は`vite.config.js`に記載をします。
その際、フォーマットにあわせてビルドアセットのディレクトリを分けるのに手こずったので記事にしました。

## 環境

Viteのバージョンは4.2.1です。

## やりたかったこと

以下のような構成で配布をしようとしていました。
ES ModulesとCommonJSの両方で配布する、いわゆるdual packageのUIライブラリです。

```sh:ビルド前
src
├── components
│   ├── Foo
│   ├── Bar
│   └── Baz
└── index.js
```

```sh:ビルド後
dist
├── cjs
│   ├── index.js
│   └── components
│       ├── Foo
│       │   └── Foo.js
│       ├── Bar
│       │   └── Bar.js
│       └── Baz
│           └── Baz.js
└── es
    ├── index.mjs
    └── components
        ├── Foo
        │   └── Foo.mjs
        ├── Bar
        │   └── Bar.mjs
        └── Baz
            └── Baz.mjs
```

## 完成形

若干省略していますが、最終的なconfigは以下のようになりました。

```javascript:vite.config.js
import { resolve } from 'path'
import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    lib: {
      entry: resolve(__dirname, 'src/index.js'),
      formats: ['es', 'cjs'],
      fileName: '[format]/[name]',
    },
    rollupOptions: {
      output: {
        preserveModules: true,
        exports: 'named',
      },
    },
  },
})
```

## ステップバイステップ

ライブラリモードを使うにあたって、1番簡単な（？）指定は次のようなイメージです。

```javascript:vite.config.js
import { resolve } from 'path'
import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    lib: {
      entry: resolve(__dirname, 'src/index.js'),
      formats: ['es', 'cjs'],
    },
  },
})
```

この状態だと、`dist/`への出力は次のようになります。

```sh
dist
├── package-name.js
└── package-name.mjs
```

1つのファイルにまとまっているので、コンポーネントごとにファイルを分けたいです。
そのため`rollupOptions`を指定します。

```diff_javascript:vite.config.js
  import { resolve } from 'path'
  import { defineConfig } from 'vite'

  export default defineConfig({
    build: {
      lib: {
        entry: resolve(__dirname, 'src/index.js'),
        formats: ['es', 'cjs'],
      },
+     rollupOptions: {
+       output: {
+         preserveModules: true,
+         exports: 'named',
+       },
+     },
    },
  })
```

`dist/`への出力は次のようになります。

```sh
dist
├── package-name.js
├── package-name.mjs
├── package-name2.js
├── package-name2.mjs
├── package-name3.js
├── package-name3.mjs
├── package-name4.js
└── package-name4.mjs
```

ファイル名がただの連番になってしまっているので、ちゃんとコンポーネント名をつけたくなります。
`build.lib.fileName`の欄を指定するとファイル名を変えられます。
ただし、単なる文字列を指定しても、結局その文字列+連番で出力されるだけです。

ここで次のような書式で指定すると、目当ての結果が得られます。

```diff_javascript:vite.config.js
  import { resolve } from 'path'
  import { defineConfig } from 'vite'

  export default defineConfig({
    build: {
      lib: {
        entry: resolve(__dirname, 'src/index.js'),
        formats: ['es', 'cjs'],
+       fileName: '[format]/[name]',
      },
      rollupOptions: {
        output: {
          preserveModules: true,
          exports: 'named',
        },
      },
    },
  })
```

```sh
dist
├── cjs
│   ├── index.js
│   └── components
│       ├── Foo
│       │   └── Foo.js
│       ├── Bar
│       │   └── Bar.js
│       └── Baz
│           └── Baz.js
└── es
    ├── index.mjs
    └── components
        ├── Foo
        │   └── Foo.mjs
        ├── Bar
        │   └── Bar.mjs
        └── Baz
            └── Baz.mjs
```

指定できる書式は以下の通りです。

- `[format]`
  - 以下の4つから選べます
    - `'es'`
    - `'cjs'`
    - `'umd'`
    - `'iife'`
- `[hash]`
  - ハッシュ値が付与されます
- `[name]`
  - ディレクトリ名、ファイル名などがすべて付与されます

これは、Viteの機能というより、Viteが内部で使っているRollupの機能です。
そのためViteのドキュメントには（恐らく）記載がなく、[Rollupのドキュメント](https://rollupjs.org/configuration-options/#output-entryfilenames)を見ないと分からないと思います。

ちなみにrollupOption内に記載をしても似たような動きをしますが、うまく実現できませんでした。

```diff_javascript:vite.config.js
  import { resolve } from 'path'
  import { defineConfig } from 'vite'

  export default defineConfig({
    build: {
      lib: {
        entry: resolve(__dirname, 'src/index.js'),
        formats: ['es', 'cjs'],
-       fileName: '[format]/[name]',
      },
      rollupOptions: {
        output: {
          preserveModules: true,
          exports: 'named',
+         entryFileNames: '[format]/[name]',
        },
      },
    },
  })
```

次のように、軒並み拡張子が消えてしまいました。

```sh
dist
├── cjs
│   ├── index # 拡張子無し
│   └── components
│       ├── Foo
│       │   └── Foo # 拡張子無し
│       ├── Bar
│       │   └── Bar # 拡張子無し
│       └── Baz
│           └── Baz # 拡張子無し
└── es
    ├── index # 拡張子無し
    └── components
        ├── Foo
        │   └── Foo # 拡張子無し
        ├── Bar
        │   └── Bar # 拡張子無し
        └── Baz
            └── Baz # 拡張子無し
```

`entryFileNames`の中でformatにあわせて拡張子を変えるのが自分では実現できなかったのもあり、`rollupOptions`ではなく`lib`の中で`fileName`を指定するに至りました。

## 最後に

過去に書いた記事とかなり近しい内容ですが、今回は今回で悩むポイントが変わりました。

https://qiita.com/xrxoxcxox/items/1fa0588ce06f69e43a12

Viteとしての動き、Rollupをラップしているだけの動き、どちらもあるので難しかったです。

探しても意外と出てこない情報だったので記事にしました。
ニッチな自覚はありますが、誰かの役に立場幸いです。

---

最後まで読んでくださってありがとうございます！
[Twitter](https://twitter.com/xrxoxcxox)でも情報を発信しているので、良かったらフォローお願いします！

[Devトーク](https://jobs.qiita.com/employers/qiita-inc/dev_talks/489)でお話してくださる方も募集中です！