ES6 Moduleには、import ~;
と書く静的なものだけでなく、import()
と関数っぽく書く、動的なものも登場しようとしています。すでに一部のブラウザに実装されていますが、Webpackでも利用可能です。
事前準備
Webpack本体はバージョン3でもすでにimport()
を使えるようになっていますが、周辺ツールは対応するために事前準備が必要となります。
-
babel
-import()
をシンタックスエラーとせずスルーさせるために、babel-plugin-syntax-dynamic-importが必要です。 -
eslint
- 標準ではimport()
を文法エラーとしてしまうので、"parser": "babel-eslint"
とする必要があります(もちろん、babel-eslint
のインストールも必要です)。
コードを書いてみる
あとは特に設定は必要なく、import()
で分割するファイルのエントリポイントを読み込めば、ファイル分割まで自動で行われます(もちろん、他の箇所で静的にimport
されているものを動的にimport()
しても、すでにバンドルされているものが使われるので、改めてファイルが発生することはありません)。
import()
はPromise
を返しますので(IE11などではPolyfillが必要です)、単純に.then
をつなげるなり、Promise.all
で全部揃うまで待つなり、async
-await
で待たせるなり、ロード後のタイミングでの実行も問題ありません。
なお、Webpackが読み込むものを決められる必要があるので、import()
の引数は完全に変数ではなく、文字列リテラルを含む式など、パスの手がかりが得られるものでないといけません。
分割後のファイル名を指定する
デフォルトでは、「0.js」のようなファイル名になってしまって、何が何だか分からないし、キャッシュパージも効かないので扱いづらいです。
これを改善するには、まずimport()
の側にコメントを追加します。
import(/* webpackChunkName: "qiita" */'path/to/qiita').then(...);
こうすることでチャンクに名前が入るので、あとはwebpack.config.js
のoutput.chunkFilename
(リファレンス)で名前のパターンを指定できます。
挙動を確認する
まず、全体のエントリポイントの側に、webpackJsonp
というグローバル関数が用意されます(名前は設定で変更可能です)。そして、import()
の実行は以下のように処理されます。
- まず、ロード済みなら適切なライブラリを
require
して成功したPromise
を返す - すでにロードが進行中なら、生成済みの
Promise
を返す - タイムアウトしたら失敗、ロードするファイルの
webpackJsonp
が実行されれば成功、というようなPromise
を作成する -
<script>
タグを<head>
に追加 -
Promise
を返す
ということで、
- 複数回
import()
しても、重複してロードが走ることはありません。 - 分割後のファイルを事前に
<script>
で読み込んであったような場合、読み込みは済むけど、import()
実行まで中身は実行されません。