ロードマップ 2021 (2020-12-08)

webpack 5の正式リリースから約2か月が経過しました。スポンサーの状況により、webpackに十分な時間を割くことができませんでした。私(@sokra)個人としては、短い休憩を楽しみ、いくつかのサイドプロジェクトに取り組みました。皮肉なことに、webpack 5とその最先端の機能(アセットモジュール、ワーカーサポート、永続キャッシング)を使用している間に、webpack 5にいくつかのバグを発見しました。これらのバグは、プロジェクトをwebpack 5にアップグレードする際に発生する可能性が高いため、バグ修正に多くの時間を費やしました。以下は簡単な概要です。

これまでの進捗

webpackから、型定義とランタイムの両方で、さらにいくつかの要素が公開されました。いくつかの低処理能力のパフォーマンス向上が行われました。セミコロンのないコードは、場合によっては無効/不正なコードを生成していましたが、これは修正されました。副作用のないコード + 連結されたモジュール + 再エクスポートの組み合わせにより、いくつかのエッジケースが発生しましたが、これらは修正されました(少なくとも既知のものについては)。

しかし、ユーザーから報告された1つのバグにより、まったく新しい内部機能が必要になりました。webpackの内部構造が退屈または複雑すぎると感じる場合は、これをスキップして次の章に進んでください。

バグを発生させるには、3つの要素が必要です

  • webpack 5以降、`production`モードの最適化では、使用済みエクスポート分析(ツリーシェイキング)が各ランタイム(多くの場合、エントリポイントと同じ)に対して実行されます。つまり、webpackは各ランタイム(resp。エントリポイント)を個別に最適化できます。
  • カスタムの`optimization.splitChunks`設定により、モジュールを強制的に単一のチャンクにマージできます。これは、`name`オプションを渡すことによって行われます。たとえば、`{ test: /node_modules/, name: "vendors" }`は、`node_modules`のモジュールを単一のチャンクにマージします。これは一般的には推奨されませんが、可能であり、場合によっては理にかなっています。すべてはトレードオフであり、すべてのベンダーを単一のチャンクにマージすることを選択すると、繰り返しアクセスする場合や複数のエントリポイント間でこのチャンクを長期キャッシングするのに役立ちます。
  • 副作用のないモジュールのエクスポートが使用されていない場合、モジュール全体がモジュールグラフから省略され、`import`ステートメントはランタイムコードをまったく生成しません。

2つのエントリポイントからのモジュールが単一のチャンクにマージされ、それらが共有チャンクにない副作用のないモジュールを参照する場合に、エッジケースで問題が発生します。これは、エントリポイントの1つだけが副作用のないモジュールからのエクスポートを使用しているためです。共有チャンク内のモジュールは両方のエントリポイントで使用されるため、エントリポイントのいずれかで使用されるエクスポートを含める必要があります。つまり、前述のエッジケースで他のエントリポイントのランタイムでは使用できない副作用のないモジュールを参照するコードが生成されます。これにより、ランタイムで`undefined is not a function`または`cannot read property 'call' of undefined`エラーが発生します。

考えられる修正策は、副作用のないモジュールをすべてのエントリポイントに含めることですが、このモジュールは実際には必要ないため、バンドルサイズが無駄になります。そのため、私たちは別のルートを選択しました。これには、新しい機能である「ランタイム依存コード生成」の開発が必要でした。これにより、実行されるランタイムに応じて動作が異なるコードを生成できます。

言い換えれば、生成されたコードの一部を`if`ブロックでラップしているため、1つのランタイムでのみ実行されます。この例では、これは副作用のないモジュールを参照する`import`ステートメントに影響します。インポートは、エントリポイントの1つに対してのみ実行されます。これにより、不要なモジュールを含めることを避け、使用可能な場合でも不要なコードを実行することを回避できます。そのため、すべてのコードを単一のチャンクにマージする場合でも、実際に使用されているコードのみが実行されます。

これまでのところ、退屈ではなかったことを願っています...

ロードマップ 2021

スポンサーの状況を整理できると仮定すると、2021年には以下が計画されています

さらなる安定化

最優先事項は、webpack 5の安定化です。これまでのところ、状況はかなり良好に見えます。最近報告されたほとんどの重大なバグは、いくつかのエッジケースに影響を与えます。そのため、webpack 5は一般的なケースでは機能するはずです。しかし、エッジケースの処理はwebpackの強みの1つであり(そしてそうあるべきです)、そのため、これらを修正するために引き続き努力していきます。多くのwebpackユーザーは、ビルドのためにカスタムのものが必要だと考えており、webpackは設定可能性と豊富なプラグインシステムを通じてそれを提供しています。

EcmaScript モジュール

EcmaScriptモジュール(ESM)は、徐々に広く採用されています。オーサリング側では、すでにコードを記述するための事実上の標準となっています。ブラウザのサポートについても、かなり良好に見えます(IE11といくつかの古いモバイルブラウザを除く)。ブラウザは、WebWorkersのESMのサポートにおいてはまだ少し不足しています。

`type=module`スクリプトタグで実行されるバンドルを生成することもできますが、現時点では利点はほとんどありません。

webpackには、ESMのサポートを改善できる領域が複数あります

チャンクロードメカニズムとしてのESM

現在、webpack はターゲット環境に応じてチャンクの読み込み方法を変えています。Web の場合は script タグ、Node.js の場合は require または fs + vm、WebWorkers の場合は importScripts を使用します。

近い将来、これらの環境はすべて ESM と、さらに重要な動的 import() 関数をサポートするようになるでしょう。そのため、import() に基づくチャンク読み込みメカニズムは、これらのすべての環境を統一し、ランタイムコードの量を削減することができます。

自己実行チャンク

現在、webpack でオンデマンドでロードされるチャンクは、常にモジュールのコンテナであり、モジュールコードを直接実行することはありません。モジュール内で import("./module") を記述すると、__webpack_load_chunk__("chunk-containing-module.js").then(() => __webpack_require__("./module")) のようなコードにコンパイルされます。これは変更できない場合も多いですが(複数のチャンクを読み込む場合や CSS も読み込む場合など)、webpack が含まれるモジュールを直接実行するチャンクを生成できる場合があります。これにより、生成されるコードが少なくなり、チャンク内の関数ラッピングを回避できます。

現時点では、これが価値があるかどうかはわかりませんが、少なくとも調査する価値はあります。

ESM エクスポート

現在、output.library.type: "module" を使用してバンドルに ESM エクスポートを生成することはできません。これは、webpack バンドルを ESM 読み込み環境やインラインスクリプトに統合する場合に役立ちます。

ESM エクスターナル (インポート)

Webpack では、バンドルされずに実行時に存在するモジュールである externals を定義できます。エクスターナルには、グローバル、CommonJs/AMD/System、従来のスクリプトタグからの読み込みなど、さまざまな種類があります。 import() (type: "import") を使用してエクスターナルを読み込むこともできますが、import (type: "module") はまだ使用できません。

興味深いことに、type: "module" はまだサポートされていませんが、webpack は import x from "https://example.com/module.js" などを記述するときに既にデフォルトで使用しています。デフォルトでは、破壊的な変更を導入することなく、ESM エクスターナルのサポートをシームレスに追加することを選択しています。

import での絶対 URL は、ESM として API を提供する外部サービスを使用する場合などに意味があります。たとえば、import { event } from "https://analytics.company.com/api/v1.js" などです (import("https://analytics.company.com/api/v1.js") は、この外部サービスに依存する場合にエラーを適切に処理する方が理にかなっているかもしれませんが、モジュールグラフの上位でエラーをキャッチすることもできます)。

通常どおり、externals 設定では、任意のモジュール名をエクスターナルにマッピングできます。

export default {
  externalsType: 'module',
  externals: {
    analytics: 'https://analytics.company.com/api/v1.js',
    svelte: 'https://jspm.dev/svelte@3',
    react: 'https://cdn.skypack.dev/preact@10',
    'react-dom': 'https://esm.sh/[react,react-dom]/react-dom',
  },
};

ESM ライブラリ

ESM エクスポートとインポートがサポートされると、ライブラリのバンドルが理にかなっていると思うかもしれませんが、場合によっては正しいですが、多くの場合、ネイティブにバンドルすると結果が悪化します。最大の問題は、"sideEffects": false フラグです。これは、ファイルごとにモジュールに影響を与え、モジュール全体をスキップします。副作用のない複数のモジュールを連結すると、個々のモジュールをスキップできなくなり、ライブラリのすべてのエクスポートが使用されていない場合に最適化が悪化します。

出力が後でバンドラーによって処理されるライブラリになる場合は、これを考慮する必要があります。

チャンキングを適用せず、代わりに ESM インポートとエクスポート(または CommonJS require)を介して接続された生の(処理済み)モジュールを出力する特別なモードが考えられます。つまり、ローダー、モジュールグラフ、アセットの最適化は実行されますが、チャンクグラフは作成されず、モジュールグラフの各モジュールは個別のファイルとして出力されます。

厳格モードの警告

ESM バンドルを生成すると、含まれるすべてのコードが厳格モードに強制されます。多くのモジュールではこれは問題ありませんが、異なるセマンティクスに問題がある可能性のある古いパッケージがいくつかあります。これらの場合の警告を表示します。

より多くのファーストクラスシチズン

Webpack 4 と 5 は、JS 以外のモジュールタイプをサポートするために多くの作業を行いました。webpack 5 は、JS (ESM/CJS/AMD)、JSON、WebAssembly、Asset など、いくつかのモジュールタイプをデフォルトでサポートしています。webpack 5 以降、私たちの長期的な目標の 1 つは、ブラウザがサポートするすべてをサポートすることを目標とした、Web アプリケーションオプティマイザになることです。そのため、技術的には、バニラ Web アプリは webpack でそのまま動作するはずですが、その場で最適化されます。

webpack 5 の初期リリースでは、既にこの方向への大きな一歩を踏み出しました。new Worker はネイティブでサポートされています。new URL(...) はネイティブでサポートされています(アセット)。

WebAssembly と JSON は、提案がまだ完了していない場合でも、既にサポートされています。

しかし、完全なストーリーには、まだ 2 つのリソースタイプが欠けています。HTML と CSS です。

モジュールとしての CSS

現在、webpack は css-loaderstyle-loader、または mini-css-extract-plugin を介して CSS をサポートしています。これは非常にうまく機能していますが、webpack で CSS をネイティブモジュールタイプとしてサポートすることで、より多くのことができると思います。

大きな利点の 1 つは、開発エクスペリエンスです。mini-css-extract-plugin の設定は最も簡単ではなく、それをなくすことで開発者にとって多くのことが簡素化されます。とはいえ、その上に追加のカスタマイズを追加できないわけではありません。多くの開発者が生の CSS を使用せず、その上にプリプロセッサを使用しているのを目にします(ネイティブ CSS サポートでは、{ test: /\.sass$/, type: "stylesheet", use: "sass-loader" } のようになります)。

State of CSS 2020 によると、CSS Modules はモジュール化 CSS を記述する一般的な方法であり、webpack でネイティブモジュールタイプにすることで、Tree Shaking (Used Exports Optimization and Side-Effects optimization) などのモジュールグラフの最適化のメリットを活用できます。CSS Modules を使用する場合、結果の CSS には、アプリケーションから参照される CSS ルールのみが含まれます(JS Tree Shaking から使用されている場合と同様)。

webpack が持つアプリケーションのグローバルな知識があれば可能な、CSS Modules 固有の潜在的な最適化がいくつかあります。CSS ルールは、共通のプロパティの繰り返しを避けるために、より小さなルールに分割できます。これにより、出力 CSS に繰り返されるプロパティが少なくなるため、ペイロードが大幅に削減されます(Atomic CSS)。

ただし、ここには大きな「ただし」があります。WebComponents コミュニティでは、ブラウザでネイティブにサポートされる予定の別の「CSS Modules」提案に向けて作業が進められています。少なくともそれが提案の目標です。残念ながら、この提案は現在フロントエンドエコシステムで使用されているものとは異なりますが、同様の構文を使用しています。通常、webpack は提案に合わせて調整されるため、ここで考慮すべき点です。潜在的な競合を回避できるかどうかを確認する必要があります。

エントリポイントとしての HTML

Parcels の例に従って、HTML もネイティブでエントリポイントとしてサポートしたいと考えています。Web アプリは通常 HTML から始まるため、これをサポートすることは、Web アプリオプティマイザとしての目標と一致しています。また、HTML から多くのことを推測できるため、初心者にとって開発エクスペリエンスが大幅に向上します。

生成された HTML を制御できるということは、デフォルトでより積極的に最適化できるということでもあります。現在、初期チャンクの名前変更や分割はデフォルトで防止しています。これは、HTML 生成に追加のインフラストラクチャが必要になるためです。

HTML エントリポイントは、CSS をモジュールとして、アセットモジュールとしても活用できます。これらのリソースは HTML からも参照できるためです(例:<link rel=stylesheet /><img src="..." /><link rel=icon />)。

HTML モジュール

ブラウザでの HTML のインポートのネイティブサポートに関する提案もあります。HTML エントリポイントと大きく重複しているため、特に注目しています。

ソースマップのパフォーマンス

webpack で(完全な)ソースマップを使用すると、ソースマップ処理のパフォーマンスが最適ではないため、現在のコストはかなり高くなっています。これは、webpack についてだけでなく、webpack でデフォルトのミニマイザーとして使用されている terser についても調査したいと考えています。

exports/imports package.json フィールド

Node.js 14 では、package.json に exports フィールドのサポートが追加され、パッケージのエントリポイントを定義できるようになりました。Webpack 5 はこれに倣い、production/development などの追加条件も追加しました。

その後まもなく、Node.js はさらに追加を行いました。たとえば、プライベートインポート用の imports フィールドも追加しました。

これも追加したいと考えています。

CommonJS 解析の改善

ESM は将来のものですが、npm と使用中の CommonJS パッケージはまだたくさんあります。Webpack 5 では、CommonJS モジュールの分析が追加され、これらのモジュールのほとんどで Tree Shaking が可能になりました。

しかし、もっとできることがあります。多くのエクスポートパターンがサポートされていますが、インポートパターンはごくわずかしかサポートされていません。CommonJS モジュールでもより多くの最適化を可能にするために、より多くのパターンのサポートを追加したいと考えています。

モジュールフェデレーションのホットモジュールリプレースメント

Webpack 5 には、「モジュールフェデレーション」と呼ばれる新機能が追加されました。これにより、実行時に複数のビルドを統合できます。現在、ホットモジュールリプレースメント (HMR) は一度に 1 つのビルドのみをサポートしており、更新はビルド間でバブルアップできません。ここで改善を行い、HMR 更新が異なるビルド間でバブルアップできるようにすることで、フェデレーションアプリケーションの開発を改善したいと考えています。

ヒントシステム

現在、webpack はユーザーに警告とエラーを表示します。ビルド中、潜在的な落とし穴や最適化の機会など、ユーザーに伝えられることがいくつかありますが、警告やエラーには当てはまらず、このすべての情報で出力をスパムしたくありません。そのため、別のカテゴリを追加したいと考えています。ヒントです。ビルド中にすべてのヒントを収集したいと考えていますが(プラグインもヒントを出力できます)、出力には限られた数のヒントのみを表示します(デフォルトでは 1 つのみ)。これにより、ユーザーにとってある種の「ご存知でしたか」体験が生まれるはずです。

マルチスレッディング

永続キャッシュにより、キャッシュされたビルドは「非常に」高速になりますが、永続キャッシュのない初期ビルドにはまだ改善の余地があります。Node.js での Javascript の実行はデフォルトではシングルスレッドですが、最近の追加により、WebWorkers に似た API である worker_threads を使用できるようになりました。

これは、すべての CPU に作業を分散するために使用できます。webpack 5 では、既にこれに向けて準備が行われています。たとえば、内部データ構造のシリアル化が可能であり、作業キューはプラグインをサポートしています。しかし、その一部はまだ不明瞭であり、実験が必要です。

これはしばらくの間投票リストに載っていますが、投票した人は多くありません。これは本当に人々が求めているものなのでしょうか?

WebAssembly

現在、WebAssembly は実験段階であり、デフォルトでは有効になっていません。提案がステージ 4 に達したら、デフォルトで有効にすることができます。

これは、エコシステムにおける WebAssembly のより広範な採用につながる可能性もあります。2021 年には、この分野でさらに多くのことが見られると予想しています。

免責事項

このリストは確定されたものではありません。Web エコシステムは非常に急速に変化するため、最終的には現時点では認識すらしていない、全く異なることを実装することになる可能性があります。現在のスポンサー状況を考慮すると、webpack にどれだけの時間を投資できるかさえもわかりません。

1 貢献者

sokra