Webpack の仕組み
クシル・ミストリー
序章
Webpackは、最新のフロントエンド ベースのアプリケーションで一般的に使用されるライブラリです。これは、人気のある JavaScript バンドラーの 1 つです。これは現在、10 年前に実戦でテストされたライブラリです。NextJSやGatsbyなどの本格的なフロントエンド フレームワークの多くデフォルトでは、バンドルとコンパイルの目的で webpack を使用します。webpack とは何かと尋ねると、javascript バンドラーであると答えるでしょう。もう少し深く掘り下げて、なぜ JavaScript バンドラーが必要なのか、どのように機能するのかを尋ねると、好奇心旺盛な人だけが十分に答えられるかもしれません。それは誰のせいでもありません。最新のフロントエンド ライブラリは、内部で何が起こるかを心配する必要がないようにパッケージ化されています。create-react-appライブラリを介して構成された ReactJS アプリケーションの例を挙げると、バンドラーとトランスパイラーの構成を処理します。開発者は、 ReactJSの知識を通じて、すぐにアプリケーションの構築を開始できます。. 高速でパフォーマンスの高いフロントエンド アプリケーションを構築する技術を習得したい場合は、これらすべてのライブラリの機能について明確なメンタル モデルを用意する必要があります。この記事では、そもそも JavaScript バンドラーが必要な理由について説明し、最も人気のある JavaScript バンドラーの 1 つである webpack が内部でどのように機能するかを探ります。
なぜ JavaScript バンドラーが必要なのですか?
JavaScript バンドラーは、Web アプリケーションでの JavaScript ファイルの配信を最適化するのに役立つツールです。以下は、バンドラーを使用する主な利点です。
- モジュールのマージ、分割、および後処理:バニラの JavaScript または対応する最新の JavaScript フレームワークを使用している場合は、開発過程でコードを複数のファイルに分割することをお勧めします。JavaScript バンドラーの概念は、今では 10 年前のものです。10 年前、ブラウザーが HTTP1 プロトコルを介して単一のファイルにアクセスすることは、負荷の高い操作でした。したがって、すべてのコードを 1 つのファイルにまとめてから、1 つの HTTP 要求を作成する方が適切でした。しかし、ほとんどのブラウザはそれ以来大幅に改善されており、現在、HTTP2 プロトコルを使用してネットワーク リクエストを送信しているため、ファイルにアクセスするために複数のリクエストを送信してもボトルネックは発生しません。代わりに、最初にコードベースを結合し、次に分割して、バンドラーを介して要求されたルートに必要最小限のリソースをロードすることを開始しました。また、使用されていないコードの削除も開始しました(ツリーシェイク)、および結合されたファイル間の空白/空白は、要求されたリソースを最小限に抑えます(縮小)。
- 循環依存の問題の解決:バンドラーがない場合、「定義されていない」エラーを回避するために、javascript ファイルのインポート順序に注意する必要があります。このファイルをチェックしてください。開発者は、エラーが発生しやすいスクリプト タグのインポート順序を自分で処理する必要があります。バンドラーは、必要なすべてのファイルをトラバースして、読み込み順序を決定できます。
- モジュールでのトランスパイルと前処理のアンカー: 現在、Javascript には、開発の旅を改善するための最新のルールがいくつか追加されています。しかし、最新のブラウザーはこれらの機能をまだサポートしていないため、最新の JavaScript をブラウザーが理解できる JavaScript に変換するには、トランスレーター (一般にトランスパイラーと呼ばれます) が必要です。バンドラは、最初にトランスパイラ モジュールの助けを借りてファイルを変換し、ブラウザが直接ロードできるようにそれらを結合するこのプロセスに役立ちます。
Webpack の仕組み
Webpack は、イベント駆動型のプラグイン ベースのコンパイラです。つまり、webpack にはファイルをバンドルするためのライフサイクルがあり、ライフサイクルの各ステップはイベントとして想像できます。これらのさまざまなイベントをリッスンし、それに応じて動作するプラグインを追加できます。デフォルトの機能は、プラグインを介して挿入することもできます。つまり、一部のコア機能を処理するデフォルトのプラグインがいくつかあります。
ライフサイクルに次の 5 つのメソッドがあるとします。
コンパイル開始 → 解決 → パース → バンドル → コンパイル終了
プラグインを統合して、これらのイベントをリッスンし、ソース コード ファイルで操作を実行できます。コア機能を実行するために統合されたデフォルトのプラグインがいくつかあります。
プラグインベースのアーキテクチャはすべて同様に設計でき、特定の機能のライフサイクルがあり、ライフサイクルのさまざまなステップで動作するカスタムイベントハンドラー (プラグインとして想像できる) を追加できます。
では、webpack のライフサイクルについて見ていきましょう。
webpack の内部で何が起こっているのですか?
いくつかのキーワードを理解し、それらをつなぎ合わせてストーリー全体を形成しましょう。
- コンパイラ:通常のコンパイラと同様に、webpack ライフ サイクルのポイントを開始および停止します。これは、イベントの中心的なディスパッチャーと考えることができます。
- Compilation AKA The Dependency Graph:脳のようなものです。この webpack を通じて、コードベースで使用しているソースを理解します。ディペンデンシー グラフ トラバーサル アルゴリズムが含まれています。コンパイラによって作成されます。
- リゾルバー:部分パスを絶対パスに変換します。これは、ファイルが存在するかどうかを確認するために使用され、存在する場合は情報を提供します。
- モジュール ファクトリ:リゾルバーによって正常に解決された要求を取得し、リゾルバーによって受信されたソース ファイルといくつかの情報を使用してモジュール オブジェクトを作成します。
- パーサー:モジュール オブジェクトを受け取り、解析するために AST (ソース コードのツリー表現) を返します。すべてのrequireとimportを見つけて、バンドル用に解析するツリーを作成します。
- テンプレート: ディペンデンシーグラフのデータ バインディングに使用されます。ツリー オブジェクトをモジュール内の実際のコードにバインドします。
まず、webpack は構成ファイルを探し、前述のエントリ ポイントを探します。コンパイラはそのファイルからコンパイルを開始します。相対ファイル パスがリゾルバーに送信されます。リゾルバは相対パスを絶対パスに変換します。その要求はモジュール ファクトリに送られます。モジュール ファクトリはモジュール オブジェクトを作成し、ファイルの種類、サイズ、絶対パス、割り当てられた ID などの情報を追加します。
ここで、モジュール オブジェクトに記述されているファイル タイプに応じて、コンパイラはトランスパイラーを探して、コードをブラウザーで読み取り可能な形式に変換します。コードを変換した後、パーサーはファイルを解析し、'require' または 'import' ステートメントを探し、以下のような依存情報でオブジェクトを更新します。
// example module object
{
id: 0,
absolutePath: '/path',
fileType: '.jsx',
dependency: [{ id1, relativePath }, { id2, relativePath }]
// some more information
}
依存関係グラフを作成した後、コンパイラはすべての依存関係をトポロジ的に並べ替えます。
トポロジカル ソート: 有向非巡回グラフ (DAG) のトポロジカル ソートは、すべての有向エッジ uv について、頂点 u が v の順序で前になるような頂点の線形順序付けです。
次に、webpackは、維持されているハッシュ マップの助けを借りて、これらのトポロジ的に並べ替えられた依存関係をすべてマージして、バンドル ファイルを作成します。プラグインとも呼ばれるイベント リスナーを介した後処理手段として、これらのバンドル ファイルに対してミニフィケーションまたはツリー シェーキングを実行できるようになりました。それでおしまい!Bundler は、これらの簡単な手順で仕事を完了しました :)
次のコードについて、上記のバンドル プロセスをドライランしてみましょう。
// cat.js ES Module
export default "cat";
// bar.js CommonJS
const cat = require("./cat");
const bar = "bar" + cat;
module.exports = bar;
// foo.js ES Module
import catString from './cat';
const fooString = catString + "foo";
export default fooString;
// index.js - entry point
import fooString from './foo';
import barString from './bar';
import './tree.jpeg';
console.log(fooString, barString);
// example module object for index.js
{
"id":0,
"absolutePath":"$home/index.js",
"bundledFile": "bundledFile0.js",
"fileType":".js",
"dependency":[
{ "id": 1, "path": "./foo" },
{ "id": 2, "path": "./bar" },
{ "id": 3, "path": "./tree.jpeg"}
]
}
すべてのモジュール オブジェクトをトポロジー順に並べ替え、変換されたチャンクをマージして、ブラウザーが直接実行するためのバンドル ファイルを形成します。
webpack の構成オプション
これらは、webpack が提供する基本的な構成オプションです。
- エントリ: webpack が依存関係グラフを作成し、このグラフの開始点がエントリまたはエントリ ポイントとして知られていることは誰もが知っています。依存関係グラフの開始点から、すべての依存関係を追跡して、バンドルする必要があるものを認識します。
- 出力: 出力は、作成したバンドルを配置する場所とその形式を webpack に通知します。
- ローダー: ローダーは、画像や CSS などのさまざまな種類のファイルをモジュールに変換してから、依存関係グラフに追加します。
- プラグイン: プラグインは機能を提供します。webpack の実行時に何かを印刷する、縮小する、バンドルを最適化するなど、多くの機能を提供できます。
上記の概念を理解するために、webpack の構成ファイルを見てみましょう。
// webpack config
const path = require("path");
const ExamplePlugin = require("./ExamplePlugin.js");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.join(__dirname, "build"),
},
module: {
rules: [
{
test: /\.js$/,
use: "babel-loader"
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
]
},
]
},
plugins: [
new ExamplePlugin(),
]
}
// Example of a loader: DoTranspile.js
// You will see a majority of loaders being transpilers.
const doTranspile = require('do-transpile');
module.exports = function(devlopmentSourceCode) {
const browserCode = doTranspile(devlopmentSourceCode);
return browserCode;
}
// Example of a plugin.
// ExamplePlugin.js
class ExamplePlugin {
apply(compiler) {
compiler.plugin("afterCompile", (compiler, callback) => {
console.log("Webpack is Running!!");
callback();
})
}
}
module.exports = ExamplePlugin;
ローダーは通常、バンドル プロセス中に使用され、主にファイルをブラウザで読み取り可能な形式に変換または変換します。ここでは、babel-loader、styles-loader、およびcss-loaderを追加しました。babel-loader は、最新の JavaScript ファイルをブラウザーで読み取り可能な JavaScript モジュールに変換するのに役立ちます。スタイル ローダーは、sass や less などの CSS フレームワークを通常の CSS モジュールに変換するのに役立ちます。css-loader は、最新の CSS 機能をブラウザーで読み取り可能な CSS スタイルに変換するのに役立ちます。package-manager には、このようなローダーが多数用意されています。例のDoTranspile.jsのような独自のローダーを作成することもできます。
プラグインは、ロギング、変更などの前処理段階または後処理段階でバンドルを微調整するために使用されます。 Package-manager には、 SourceMapDevToolPluginやIgnorePluginなど、多くのプラグインが用意されています。これをチェックして、さらに探索してください。Webpack はリッスンするさまざまなイベント フックを提供します。ExamplePlugin.jsなどのバンドルに作用する独自のプラグインを追加できます。
バンドラーの次は?
webpack の主な不満の 1 つは、そのパフォーマンス時間が比較的長いことです。これは、開発の旅にとってイライラするだけでなく、アプリを運用環境にデプロイするときに非常に遅くなることがあります。
Webpack は、コンパイル、解決、モジュール オブジェクトの作成、解析、テンプレート作成など、シングル スレッドの JavaScriptで非常に多くのことを行っています。これらはすべて、言語に依存しないタスクです。これらすべてのタスクを実行するために別の下位レベルのマルチスレッド言語を使用すると、ビルド時間は指数関数的に改善されます。Turbopack (Rust で作成) やESBuild (GO で作成) など、webpack よりも高速であると主張する言語で作成された最新の JavaScript バンドラーはほとんどありません。
Turbopack は JavaScript と TypeScript 用に最適化されたインクリメンタル バンドラーであり、Vercel の Webpack と Next.js の作成者によって Rust で記述されています。
大規模なアプリケーションでは、Turbopack は Webpack よりも 700 倍速く更新されます。
Webpack は実戦でテスト済みの JavaScriptバンドラーですが、パフォーマンス時間は依然として問題であり、低レベルのマルチスレッド言語を使用することで解決できます。近い将来、Webpack がどこに向かっているのか見てみましょう!
次の記事では、トランスパイラーについて説明します。Lyearnで私たちをチェックアウトできます。Lyearnは、従業員エクスペリエンス プラットフォームです。チームが連携し、学び、実行し、関与し、その可能性を最大限に引き出すための単一の場所です。
参考文献
https://www.youtube.com/watch?v=H3g0BdyVVxA
https://webpack-academy.teachable.com/courses/104961/lectures/2951138

![とにかく、リンクリストとは何ですか?[パート1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































