Closed24

vite / webpack / Native ESM

Kazuhiro MimakiKazuhiro Mimaki

webpack(ウェブパック) とはモジュールバンドラー(module bundler) です。モジュールバンドラーとは、複数のモジュールの依存関係を解決して1つにまとめる、いわゆるバンドリング(bundling) するものであり、モジュールとはそれ単体というより、組み合わせて使う個々のプログラムのことです。

https://zenn.dev/antez/articles/58307946cf4f3e

Kazuhiro MimakiKazuhiro Mimaki

webpackは、ビルド時に全ての依存関係を解消した後、バンドルを行います。そのため、アプリケーション起動前にアプリケーション全体を走査してバンドルする必要があり、これは規模が大きくなればなるほど結構な時間がかかります。

これに対してViteは、開発時には依存関係の解決と多少のバンドル(pre-bundle)だけを行います。全てをバンドルするのではなく、ESModulesのimportでソースコードを読み込むことにより、高速な開発サーバを実現します。ちなみに、依存関係にはGo製のesbuildを使用して事前にバンドルするのですが、これが恐ろしく早い(従来の何十倍)らしいです。

この説明だけ読んでも全然分からない。

Kazuhiro MimakiKazuhiro Mimaki

モジュールバンドラを使いたいモチベーション

  • 開発時はモジュールを分けたい
  • デプロイ後は1つのファイルにまとめたい(複数ファイルのままだと転送に時間がかかるため)
Kazuhiro MimakiKazuhiro Mimaki

さらにwebpackならJSファイルだけでなくCSSや画像ファイルもJSのデータとしてバンドルできる。

Kazuhiro MimakiKazuhiro Mimaki
Kazuhiro MimakiKazuhiro Mimaki

ES Modules 形式のままブラウザからインポートする Dev サーバを搭載し、ソースコードをバンドルすることなく高速で動作させるのが特徴です。もちろん npm パッケージもブラウザから読み込み可能な ES Modules 形式に変換します。プロダクションビルド時は Rollup を使ってバンドルします。

本当?

ソースコードをバンドルすることなく高速で動作させる

分からない言葉いっぱい

  • ES Modules 形式とは
  • Rollup とは
Kazuhiro MimakiKazuhiro Mimaki
Kazuhiro MimakiKazuhiro Mimaki

Before ES modules were available in browsers, developers had no native mechanism for authoring JavaScript in a modularized fashion. This is why we are all familiar with the concept of "bundling": using tools that crawl, process and concatenate our source modules into files that can run in the browser.

ES Modules がブラウザで利用できるようになるまで事前にバンドルする必要があったけど、ES Modules をブラウザで利用できるようになって変わった、的な話につながるのかな?

Kazuhiro MimakiKazuhiro Mimaki

Vite improves the dev server start time by first dividing the modules in an application into two categories: dependencies and source code.

Dependencies

Dependencies are mostly plain JavaScript that do not change often during development. Some large dependencies (e.g. component libraries with hundreds of modules) are also quite expensive to process. Dependencies may also be shipped in various module formats (e.g. ESM or CommonJS).

Vite pre-bundles dependencies using esbuild. esbuild is written in Go and pre-bundles dependencies 10-100x faster than JavaScript-based bundlers.

依存関係の大部分は開発中にほぼ変更されない、にも関わらず巨大な依存関係解消には時間がかかるため、事前ビルド&キャッシュすることで、開発サーバの起動時間が速くなるのか。
初めて開発サーバを起動する時はキャッシュが存在しないので事前ビルドが走って多少重くなると見た。
とはいえ、Go製のesbuild使ってるから速いっぽい。

Source code

Source code often contains non-plain JavaScript that needs transforming (e.g. JSX, CSS or Vue/Svelte components), and will be edited very often. Also, not all source code needs to be loaded at the same time (e.g. with route-based code-splitting).

Vite serves source code over native ESM. This is essentially letting the browser take over part of the job of a bundler: Vite only needs to transform and serve source code on demand, as the browser requests it. Code behind conditional dynamic imports is only processed if actually used on the current screen.

ルーティングによるコード分割のような場合、変更していないルーティングは読み込む必要がないので無視されるのかな。

これの意味がちゃんと分かってない気がするけど、

Vite serves source code over native ESM. This is essentially letting the browser take over part of the job of a bundler

「必要なファイルをViteにリクエストする」という意味でブラウザに一部バンドラーの仕事を任せてるってことなのかな。

Vite only needs to transform and serve source code on demand, as the browser requests it.

Kazuhiro MimakiKazuhiro Mimaki

"HMR is performed over native ESM" が理解できない...
HMRがネイティブESM上で動くってどういうこと?

In Vite, HMR is performed over native ESM. When a file is edited, Vite only needs to precisely invalidate the chain between the edited module and its closest HMR boundary (most of the time only the module itself), making HMR updates consistently fast regardless of the size of your application.

Kazuhiro MimakiKazuhiro Mimaki

プロダクションではビルドする理由が書かれている。

Even though native ESM is now widely supported, shipping unbundled ESM in production is still inefficient (even with HTTP/2) due to the additional network round trips caused by nested imports. To get the optimal loading performance in production, it is still better to bundle your code with tree-shaking, lazy-loading and common chunk splitting (for better caching).

Ensuring optimal output and behavioral consistency between the dev server and the production build isn't easy. This is why Vite ships with a pre-configured build command that bakes in many performance optimizations out of the box.

Kazuhiro MimakiKazuhiro Mimaki

なぜ esbuild でバンドルしないのか?の理由が書かれている。将来的にはバンドルも esbuild で実行することになるかも。

Vite's current plugin API isn't compatible with using esbuild as a bundler. In spite of esbuild being faster, Vite's adoption of Rollup's flexible plugin API and infrastructure heavily contributed to its success in the ecosystem. For the time being, we believe that Rollup offers a better performance-vs-flexibility tradeoff.

That said, esbuild has progressed a lot in the past years, and we won't rule out the possibility of using esbuild for production builds in the future. We will keep taking advantage of new capabilities as they are released, as we have done with JS and CSS minification where esbuild allowed Vite to get a performance boost while avoiding disruption for its ecosystem.

Kazuhiro MimakiKazuhiro Mimaki
Kazuhiro MimakiKazuhiro Mimaki

Native ESM時代というのは、ビルド後にもES Modulesが利用されるようになった時代です。

Kazuhiro MimakiKazuhiro Mimaki

(開発)Native ESM時代における解決策は、そもそもバンドルをしないことです。つまり、ソースファイルにimportやexportがあれば、それをそのままブラウザに読み込ませるということです。ただし、Hot Module Replacementの対応のためにimport先をちょっと書き換える程度の変換はサーバーによって行われます。うまい仕組みによって、この変換はモジュール単位で行うことができます。モジュール単位で変換を行えるようにしたことで、バンドルというボトルネックの工程を省略することができ、並列性の向上や必要な変換処理の削減が達成できます。さらに、HMR(全体リロードではなく変更があったモジュールのみを差し替える最適化)を行う際も、部分バンドルの作成のような面倒な処理をする必要が無く効率的です。

Kazuhiro MimakiKazuhiro Mimaki

このように、開発Native ESM時代においてはプロジェクトのソースファイルたちが形成するモジュールグラフをそのままの形でブラウザに配信します。モジュールグラフの解決(import文たちをたどってプログラムの必要なファイルを全て読み込むこと)はブラウザ側に行なってもらう事で、複数ファイルをひとつにまとめるバンドルという行程を省くことができ、開発時のビルドの高速化に繋がります。

"HMRがネイティブESM上で動く" の意味がようやく分かった気がする。

Kazuhiro MimakiKazuhiro Mimaki

実行時パフォーマンスの問題とかあるんだ。

ES Modulesが使われたプロジェクトをバンドラによって1つの非ES Modulesなファイルにまとめることは、実行時パフォーマンスの問題もあります。現在のバンドラによる成果物には、元々のソースファイルにあったimportをエミュレートするためのランタイムが含まれています。つまり、モジュールグラフの情報がそのランタイムに隠されており、ブラウザは直接モジュールグラフを認識できないということです。これは、理想的な状態に比べてパフォーマンスが劣ると思われます。

Kazuhiro MimakiKazuhiro Mimaki

モジュールグラフのネストに伴ってネットワークの往復が増えるから、モジュールグラフをそのまま配信する、というのはローカル環境だからこそできる。

開発Native ESM時代では、開発時はブラウザにimportやexportを含むコードをそのまま、すなわちプロジェクトのモジュールグラフをそのままの形で配信していました。実は、これが許されるのは開発環境だからです。というのも、開発環境ではビルドの成果物を配信するサーバー(開発サーバー)は基本的に自分のPC上にあります。そのため、ブラウザとサーバーの間の通信にかかる時間を基本的に無視できます。実は、この事情があるからこそプロジェクトのモジュールグラフをそのまま配信できていました。

プロダクションビルドでもプロジェクトのモジュールグラフをそのまま配信するのは、やはりパフォーマンス上の大きな問題があります。それは、読み込みにとんでもなく時間がかかるということです。ES Modulesの仕様上、モジュールグラフを全て読み込んでからでないとプログラムの実行を開始できません。また、あるモジュールがどのモジュールをimportするかというのは、import元のモジュールを読み込まないと判断できません。つまり、モジュールグラフを完全に読み込むまでにサーバーとの間を何往復もする必要があります。サーバーとの間の往復に時間がかかるプロダクション環境では、無駄な往復をしてしまうことは大きな問題となります。

Kazuhiro MimakiKazuhiro Mimaki

もしプロジェクトのモジュールグラフをそのまま配信するならば、必要となるモジュールグラフ全体を2往復目に全部まとめて送る必要がありますが、それにもまだ問題があることが知られています。というのも、元のソースコードそのままのモジュールグラフを先読みしてもらおうとすると、一度に何百何千というファイルを読み込んでもらうことになります。これはパフォーマンス上良くないのです

Kazuhiro MimakiKazuhiro Mimaki

以下を両立させることができれば、プロダクション環境でもネイティブESMを利用できるのかー、なるほど。

  • ES Moduleのモジュールグラフをブラウザに直接解釈してもらう
  • ファイル数を減らす
Kazuhiro MimakiKazuhiro Mimaki

現状の認識メモ

  • webpackと比べた時のviteの特徴
    • webpackは開発時にもバンドルするのに対して、viteはバンドルなしで動作するため、初回起動が速い
    • viteは依存関係の解消をキャッシュできる
    • viteのHMRはモジュール単位でネイティブESMによって行われるため速い
    • viteは依存関係の事前ビルドに esbuild を利用し、プロダクションビルドに rollup を利用している
  • Native ESM時代
    • 開発環境においてはモジュールグラフをそのままブラウザに送信し、ブラウザがモジュールグラフを解析してくれる (Native ESM 開発環境)
    • 一方で本番環境においては、モジュールグラフをそのままブラウザに解析させるためには多量のファイルを転送する必要があり、パフォーマンス上の大きな問題が存在するため、ファイルをバンドルする必要がある
    • 本番環境でもNative ESMを利用するproposalが上がってきているが、まだ実用に移っていない
このスクラップは2023/07/10にクローズされました