コード分割

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

大規模なアプリケーションにおいて、全てのコードを1つのファイルに含めてしまうのは得策とは言えません。 特に、特定のコードのブロックがある階層下でしか必要とされないケースでは、それが顕著に言えるでしょう。 webpackは必要に応じて、読み込む"chunks"(チャンク/固まり)毎にコードを分割する機能を備えています。 別のバンドラーでは、これを"layers"、"rollups"、"fragments"などと呼んでいます。 この機能は、"code splitting"(コード分割)と呼ばれています。

これは、任意で選択する機能になります。 開発者がコードに分割ポイント(split points)を定義することになります。 webpackは出力されるファイルと実行時の依存性を考慮して処理してくれます。

よくある誤解が起きないように、コード分割とは単に共通するコードを共有されるチャンクに抜き出すだけでは無い、ということを明らかにしておきます。 それよりも、コード分割は読み込まれるチャンクに応じてコードを分割できるということが重要です。 これは初回のダウンロードサイズを小さくし、アプリケーションによるリクエストに応じてコードをダウンロードさせることを可能にします。

分割ポイントの定義

AMDとCommonJsで、必要なコードを読み込むメソッド(方法)が異なりますが、 どちらにも分割ポイントがサポートされています。

CommonJsの場合: require.ensure

require.ensure(dependencies, callback)

require.ensureメソッドは、dependenciesの全ての依存性がcallbackが呼びだされた際に同期的にrequireされることを保証します。 callbackは、パラメーターとしてrequire関数が指定された状態で呼び出されます。

//例: 
require.ensure(["module-a", "module-b"], function(require) {
    var a = require("module-a");
    // ...
});

注意: require.ensureは、あくまでモジュールを読み込むだけで、それらを評価するわけではありません。

AMDの場合: require

AMDの仕様で、これを記述するための非同期のrequireメソッドが定義されています。

require(dependencies, callback)

これが呼び出されると、全てのdependencies指定したモジュールが読み込まれ、 callbackは読み込まれたdependenciesのexport付き(引数として)で呼びだされます。

//例:
require(["module-a", "module-b"], function(a, b) {
    // ...
});

注意: AMDの場合、requireはモジュールの読み込みと評価を行います。 webpackではモジュールは左から右へ評価されます。

注意: コールバック(callback)は省略することができます。

ES6モジュール

要約: webpackはes6モジュールをサポートしないため、 require.ensureまたはrequireを、 使用するトランスパイラが作成するモジュールフォーマットの依存性に直接使用してください。

webpack 1.x.x(2.0.0ではサポート)は、ES6モジュールをサポートしません。 ただし、Babelのようなトランスパイラを使用して、ES6のimport文をCommonJSまたはAMDモジュールに変換することで、これを可能にすることも出来ます。 このアプローチは有効ではあるものの、動的読み込みにおいては1つ重大な注意点があります。

追加されたモジュール文法(import x from 'foo')は、意図的に静的解析されるように設計されているため、 動的なimportは不可能ということになります。

// 無効!!!!!!!!!
['lodash', 'backbone'].forEach(name => import name )

幸い、JavaScriptのAPIの"loader"の仕様には、動的なユースケースに対する扱いが書かれています。(System.load、またはSystem.import) このAPIは、ネイティブにおける上記のrequireに相当するものになるでしょう。 しかしながら、ほとんどのトランスパイラはSystem.load呼び出しのrequire.ensureへの変換をサポートしないため、 もし動的なコード分割をさせたければ、開発者が直接これを行う必要があります。

// 静的なimport
import _ from 'lodash'

// 動的なimport
require.ensure([], function(require) {
  let contacts = require('./contacts')
})

チャンクの内容

分割ポイントの全ての依存性は、新しいチャンクに入れられます。 また、依存関係は再帰的に追加されます。

もし、分割ポイントへコールバックとして関数式(または関数式へ紐付けされたもの)を渡す場合、 webpackはその関数式でrequireされる全ての依存性も、自動的にチャンクへ追加します。

チャンクの最適化

2つのチャンクが同じモジュールを含む場合、それらは1つにマージされます。 これにより、チャンクが複数の親を持つ可能性があります。

もし、あるモジュールがあるチャンクの全ての親で利用される場合、それはそのチャンクから削除されます。

もし、あるチャンクが別のチャンクの全てのモジュールを含む場合、それは保存され、複数のチャンクで必要とされます。

チャンクの読み込み

設定でのオプションのtargetに応じて、チャンク読み込みのための実行ロジックが、バンドル(出力ファイル)へ追加されます。 例えば、targetがwebであれば、チャンクはjsonpを介して読み込まれます。 チャンクは一度だけ読み込まれ、並列なリクエストは1つにマージされます。 読み込まれたチャンクに対して、それらが複数のチャンクで必要とされているか否かが実行時にチェックされます。

チャンクのタイプ

エントリー・チャンク
エントリー・チャンクには、ランタイム(実行処理)とモジュールの集まりが含まれます。 もしこのチャンクがモジュール0が含まれる場合、ランタイムはそれを実行します。 そうでなければ、モジュール0を含むチャンクを待ち受けて、それを実行します。 (モジュール0のチャンクがある度に)
ノーマル・チャンク
ノーマル・チャンクはランタイムを含みません。 これには、モジュールの集まりのみが含まれます。 この構造は、チャンクの読み込みアルゴリズムに依存します。 例えば、jsonpのためのモジュールは、jsonpのコールバック関数でラップされます。 また、チャンクにはチャンクのIDのリストも含まれます。
イニシャル・チャンク(non-entry)
イニシャル・チャンクはノーマル・チャンクですが、 唯一の違いは初期化の読み込み時間(エントリー・チャンクのように)にカウントされるため、 最適化でより重要なものとして扱われる点です。 このタイプのチャンクは、CommonsChunkPluginと連携するような場合に使用される可能性があります。

アプリケーション(app)とベンダー(vendor)のコードを分割

アプリケーションをapp.jsvendor.jsの2つのファイルに分けようとした場合、 ベンダーのファイルをvendor.jsの中でrequireすることが考えられます。 その場合、そのベンダー名を下記のようにCommonsChunkPluginに渡します。

var webpack = require("webpack");

module.exports = {
  entry: {
    app: "./app.js",
    vendor: ["jquery", "underscore", ...],
  },
  output: {
    filename: "bundle.js"
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin(/* chunkName= */"vendor", /* filename= */"vendor.bundle.js")
  ]
};

これはappチャンクから、vendorチャンク内の全てのモジュールを削除します。 この場合、bundle.jsにはappのコードだけが含まれ、その依存性は含まれず、 代わりにvendor.bundle.jsに含まれることになります。

HTMLページでは、bundle.jsの前に、vendor.bundle.jsを読み込みます。

<script src="vendor.bundle.js"></script>
<script src="bundle.js"></script>

複数のエントリー・チャンク

複数のエントリー・ポイントを設定することが可能で、 この場合、複数のエントリー・チャンクを持つことになります。 エントリー・チャンクはランタイムを含み、ページには1つだけランタイムが無ければいけまん。(例外あり)

複数のエントリー・ポイントの実行

CommonsChunkPluginに指定されたランタイムは、共通(commons)チャンクに移されます。 その場合、このエントリー・ポイントは、イニシャル・チャンク内になります。 イニシャル・チャンクは1つだけ読み込むことが出来る一方で、エントリー・チャンクは複数読み込むことが出来ます。 これは、単一のページで複数のエントリー・ポイントを実行出来ることを意味します。

// 例:
var webpack = require("webpack");
module.exports = {
    entry: { a: "./a", b: "./b" },
    output: { filename: "[name].js" },
    plugins: [ new webpack.optimize.CommonsChunkPlugin("init.js") ]
}
<script src="init.js"></script>
<script src="a.js"></script>
<script src="b.js"></script>

共通(commons)チャンク

CommonsChunkPluginはモジュールを移すため、これにより複数のエントリー・チャンクが新しいチャンクになります。(共通(commons)チャンク) 同様に、ランタイムも共通チャンクに移されます。 これは、元々のエントリー・チャンクだったものが、イニシャル・チャンクになることを意味します。 詳細は、プラグイン一覧を参照してください。

最適化

特定の基準をもとにチャンクをマージすることが出来る、最適化プラグインが存在します。 プラグイン一覧を参照して下さい。

  • LimitChunkCountPlugin
  • MinChunkSizePlugin
  • AggressiveMergingPlugin

チャンクの名付け

require.ensure関数は、特別な3つ目のパラメーターが用意されています。 これは文字列でなければいけません。 もし2つの分割ポイントが同じ文字列を渡した場合、それらは同じチャンクを使用します。

require.include

require.include(request)

require.includeはwebpackの特別な関数で、現在のチャンクにモジュールを追加しますが、それは評価されません。 (バンドルから、その文を削除します。)

// 例:

require.ensure(["./file"], function(require) {
  require("./file2");
});

// 上記は下記と等価です。

require.ensure([], function(require) {
  require.include("./file");
  require("./file2");
});

require.includeは、モジュールが複数の子チャンク内にある場合に便利かもしれません。 親のrequire.includeがそのモジュールをincludeすることで、 子チャンク内で同じことをする手間が省かれます。

 Back to top

© 2010 - 2017 STUDIO KINGDOM