何故、webpackが必要なのか?

このサイトは作成途中のものを公開しています。 また、Webpackのバージョンが1.13.0の頃に作成されたものですが、2017年2月現在Webpackは1系を非推奨として2の使用を勧めています。

今日のWebサイトは、サイトというよりWebアプリケーションに進化しつつあります。

  • ページ内に占めるJavaScriptのコード量は増加する一方です。
  • 多くの事が、モダンブラウザ上で行えるようになりました。
  • ページを全て再読み込みして、少しのことを行っていましたが、1ページで多くのコードで処理を行うようになりました。

結果として、クライアント側のコード量が増大しました。

大規模なコードは、その構成を考えて整理する必要があります。 モジュール・システムは、コードをモジュールに分割するための手段を提供してくれます。

モジュール・システムの形式

モジュール・システムには依存性の定義と値をexportをするための、標準となる幾つかの形式が存在します。

  • <script>タグ形式(モジュール・システムを使用しない)
  • CommonJS
  • AMD(もしくは、その派生)
  • ES6 modules
  • それ以外…

<script>タグ形式

モジュール・システムを使用したことが無ければ、モジュール化したコードはこの方法で扱っているでしょう。

<script src="module1.js"></script>
<script src="module2.js"></script>
<script src="libraryA.js"></script>
<script src="module3.js"></script>

モジュールはグローバル・オブジェクト(例えば、windowオブジェクト)にインターフェースをexportします。 モジュールは、グローバル・オブジェクト越しに依存関係にあるインターフェースにアクセスすることが出来ます。

問題点
  • グローバル・オブジェクト内での衝突。
  • importする順番が重要になります。
  • 開発者がモジュール/ライブラリの依存性を解決しなくてはいけません。
  • 大規模なプロジェクトでは読み込みのリストが肥大化し、管理が困難になります。

CommonJs: 同期require

この形式は、依存性を読み込むために同期requireメソッドを使用して、exportされたインターフェースを返します。 モジュールは、exportsオブジェクトにプロパティを追加するか、 module.exportsの値に設定することで、exportの指定が可能になります。

require("module");
require("../file.js");
exports.doStuff = function() {};
module.exports = someValue;

これは、node.jsによってサーバーサイドで使用されています。

メリット
  • サーバーサイドのモジュールは、再利用される事があります。
  • 既に多くのモジュールがこの形式に対応しています。(npm)
  • シンプルで使用しやすい形式です。
デメリット
  • ブロッキング呼び出しは、ネットワーク上でうまく適用されません。ネットワークのリクエストは非同期です。
  • 複数のモジュールを並列にrequireすることが出来ません。
実装

AMD: 非同期require

非同期モジュール定義 (Asynchronous Module Definition)

他の(ブラウザ用の)モジュール・システムは、同期require (CommonJS)と、非同期版の導入(そして、モジュール定義と値のexport)の問題を抱えてしました。

require(["module", "../file"], function(module, file) { /* ... */ });
define("mymodule", ["dep1", "dep2"], function(d1, d2) {
  return someExportedValue;
});
メリット
  • ネットワーク内の非同期リクエスト形式に適合します。
  • 複数のモジュールを並列に読み込みます。
デメリット
  • コーディングが煩雑になり、コードの読み書きが難しくなります。
  • いわゆる、ワークアラウンド(回避策)です。
実装

詳細は、CommonJs とAMD を参照して下さい。

ES6 modules

EcmaScript6は幾つかの言語構造をJavaScriptに追加しましたが、そのモジュール・システムはまた別の形式となります。

import "jquery";
export function doStuff() {}
module "localModule" {}
メリット
  • 静的解析が容易
  • 将来のEcmaScript標準
デメリット
  • ネイテイブ・ブラウザへのサポートにはまだ時間が掛かります。
  • この形式をサポートしているモジュールは非常に少数です。

先入観を持たない公平な解決策の提供

webpackはモジュール形式の選択を、開発者に委ねます。 既存のコードを動作させたまま、カスタムされたモジュール形式を追加しやすい仕組みを提供します。

モジュールの受け渡し

モジュールはクライアント側で実行されるため、サーバーからブラウザに受け渡されなければいけません。

モジュールの受け渡しには、2つの両極端な方法が使用されています。

  • モジュール毎に、1リクエストを送信・取得
  • 全モジュールを、1リクエストで送信・取得

どちらも、そこかしこで使用されている方法ですが、最善策とは言い難いものです。

モジュール毎に、1リクエストを送信・取得
  • メリット: 必要なモジュールだけが受け渡されます。
  • デメリット: 多くのリクエストが発生し、ネットワークに負荷が掛かります。
  • デメリット: リクエストのレイテンシ(待機時間)により、アプリケーションの起動が遅くなります。
全モジュールを、1リクエストで送信・取得
  • メリット: リクエスト数とレイテンシ(待機時間)を減らすことが出来ます。
  • デメリット: 不要な(まだ必要としない)モジュールも受け渡されます。

モジュールの固まり(chunk)の受け渡し

より柔軟なモジュールの受け渡し方法があれば、それに越したことはありません。 前述した両極端の手法が互いに歩み寄ることで、多くのケースでより望まれる手法になることでしょう。

全てのモジュールをコンパイルする代わりに、複数の小さな固まり(chunk)にモジュールの集まりを分割することが出来ればどうでしょう?

複数のより小さなリクエストを送り、モジュールの固まりは初回時にはrequireせず、必要な時にのみrequireすることが出来ればどうでしょう? 初回のリクエストでは全コードを対象としない、より小さなリクエストにすることが出来るでしょう。

この「分割点(split points)」は開発者が任意に定めることになります。大規模なプロジェクト(コード)に適した仕組みです。

このアイディアは、GoogleのGWTによるものです。

詳細は、コード分割を参照して下さい。

何故、JavaScriptだけ?

何故、モジュール・システムはJavaScriptしか解決してくれないのでしょうか? 扱うべき静的リソースは他にもたくさん存在します。

  • スタイルシート
  • 画像
  • Webフォント
  • テンプレート用のHTML
  • その他

また、

  • coffeescript → javascript
  • elm → javascript
  • less スタイルシート → css スタイルシート
  • jade テンプレート → HTMLを生成するjavascript
  • i18n(国際化)ファイル → 何か
  • その他

同じように簡単に扱えてもいいはずです。

require("./style.css");
require("./style.less");
require("./template.jade");
require("./image.png");

詳細は、ローダーの使用方法とローダーを参照して下さい。

静的解析

全てのモジュールをコンパイルする際に、静的な解析(static analysis)はその依存性を見つけようとします。

通常、式を伴わない単純な検索しか行われませんが、 例えば、require("./template/" + templateName + ".jade")のような書き方は、よくある一般的なものでしょう。

多くのライブラリは異なる形式で書かれており、それらの幾つかには非常に奇妙なものがあります。

方針

webpackのclever(賢い)パーサーであれば、ほとんどの既存コードを動くようにしてくれるでしょう。 もし、その奇妙なものが含まれていたとしても、このパーサーは最も適した解決策を探しだそうとするでしょう。

 Back to top

© 2010 - 2017 STUDIO KINGDOM