※この記事はWIPです。また単なる調査した結果なのでこれをきっかけにご自身で調査することをお勧めします
rollupとviteの関係をまとめる
viteはdev build時にESBuildを使っている
viteはproduction build時にrollupを使っている
2つがなぜ分かれているかというと
それぞれのシーンで求められている仕事に違いがあるため
dev時はすぐに修正を更新しなくてはいけないため、ESbuildは素早くトランスパイルしてブラウザが持つNative ES Moduleにわたし、ESModuleは高速に依存関係を動的に解決していく。viteはすべての依存をESModuleにして一度すべての依存をbuildして.viteないのキャッシュから読み込んでいる
viteは開発時にはバンドルしていない
yarn.lockを参照にしていてそこが変われば再ビルドされキャッシュされる
index.htmlには直接type=moduleのscriptから参照される
webpackは変更を検知したら全てbundleし直してjsを読み込むのでviteより遅い
production buildでrollupを使っている理由はビルドスピードを重視する開発時とは違い実行時のパフォーマンスを考慮する必要があるためで、一つのjsにまとめる必要がある(webpackと同じ) 一つにまとめる時にはCommonJSとESMの依存を解決しながらバンドルする
-
入力モジュール、最初のエントリーポイントを検出する
-
依存関係の解決。各依存が他の依存関係を作っている場合それを読み込み、解決していく
-
トラバース(どのように依存グラフが作られているか)
-
依存の最適化。グラフから不要な依存の除去、重複するモジュールの合併、して生成されるファイルを最適化する
-
バンドルの生成
これらは開発時にする必要がないのでビルドの方法が分けられている
今viteはCSSの依存解決がESBuildが対応していないのでrollupを採用しているが将来はproduction buildもESBuildにしたいロードマップがある
バンドルファイルはheaderにあるscriptタグにハッシュ付きで埋め込まれる
-
bundle base dev server (webpack) コールドスタート、依存が膨らむと更新が遅くなる
-
Native ESM base dev server (ESBuild) 頻繁に更新されない依存をあらかじめバンドルしている(ESBuildはGolangでビルドするのでJSバンドラーより10~100倍早い)。コードを分割していてどこをロードするべきかも知っているので変更箇所だけ対応する。全体を再バンドルする必要がない
rollupはビルド時にCommonJSライブラリに出会うと@rollup/plugin-commonjsのようなプラグインを使ってESMに変換する。ESMに出会うとそれをそのまま使ってバンドルする
package.jsonにあるmainはそのライブラリがrequireで呼ばれた時のエントリーポイントでmodule(が記述されている場合)はimportで呼ばれた時のエントリーポイント
-
viteはnode_modules内のライブラリをデフォルトでトランスパイルする
-
commonJS.includeに明示的に書かない場合でnode_modules内のライブラリがcommonJS形式で提供されていない場合viteはESModulesとして扱い、トランスパイルされない
-
viteを使っているライブラリが最終的に提供するのはESModulesのjsだが、Node環境で使われる場合にはCommonJS形式で提供されることがあるその場合ビルド設定を変更してesModulesにする必要がある
-
ライブラリBがreactのバージョン16.3を使っていて、それに依存するライブラリAがReactバージョン18を使っている場合、Aからviteでビルドするとproductionで実行時エラーが起きる可能性がある。ライブラリのバージョンを合わせる必要がある
-
peerDependencesで依存するバージョンを指定して、resolutionsでそれを使うように強制する
-
viteがproductionビルド時に生成するvender.**.jsが毎回違うファイルサイズになるのは圧縮、依存パッケージの変更など
-
optimizeDeps.include・・・デフォルトではnode_modules(リンクされたパッケージ)外のパッケージを最適化することはできない。そのようなライブラリをここに含めることで強制的に事前バンドルする必要がある、ESModulesで提供しているライブラリもネストされたライブラリがCommonJSを使っている場合ここに含める必要がある、CommonJSのライブラリはここに含めないといけない
-
productionとdevelopmentで事前バンドルの使用を制御する。(下はproduction時のみ使う) module.exports = { optimizeDeps: { include: [], exclude: [] }, buildOptions: { mode: process.env.NODE_ENV, optimizeDeps: process.env.NODE_ENV === "production" } };
-
optimizeDeps.include オプションは、依存関係グラフ最適化と事前バンドルの両方を指定することができる
-
includeオプションに渡す順番について、多くの場合は重要じゃないが、特定のパッケージが特定のパッケージに依存している場合、例えばvueとvue-routerがあり、vue-routerがvueに依存している場合、vueを先に書く必要がある。じゃないとアプリケーション実行時にエラーになるケースがある reactはincludeにもcommonJs.includeにも含める必要がある
-
optimizeDeps.includeは事前バンドルを指定するところで、commonJS.includeはcommonJSで出力されている依存を指定するところ
-
モノレポの場合、リンクするパッケージが何かCommonJSのライブラリを読み込むときに最適化する必要がある。includeに含める必要がある。問題があったが解決された vitejs/vite#1108