全モダンブラウザで使えるJavaScriptのdynamic import（動的読み込み）

JavaScriptでは他のJSファイルを読み込みES Modulesという仕組みがあり、Chrome、Edge、Safariなどブラウザのサポートが拡大しつつあります（参考記事「[JavaScriptでモジュールを使う時代に！ ブラウザで覚えるES Modules入門](https://ics.media/entry/16511)」）。

さらに、モジュールを`import()`を使って「関数のように」呼び出すためのdynamic importという仕組みが2020年に公開されるECMAScriptの仕様で導入されます（[tc39/proposal-dynamic-import](https://github.com/tc39/proposal-dynamic-import)）。**Google Chrome、Firefox、Safariではではモジュールのdynamic importに対応しており**、ブラウザでその挙動を確認できます。

dynamic importのメリットは、任意のタイミングでモジュールを読み込めること。たとえば、ページの初期表示に必要なJavaScriptだけを読み込んでおき、コンテンツが展開する度に必要なモジュールを遅延ロードすれば、ページの初期表示時の処理負荷が軽減できます。

本エントリーでは次のような`Sub`クラスを定義した`sub.js`モジュールの読み込みを通して、dynamic importの使い方について解説します。

```js:sub.js
export class Sub {
  subMethod() {
    alert("this is Sub Class Method");
  }
}
```

各サンプルは、Google Chrome、Safari、Firefox、Edgeで動作します。


# これまでのimport
`import`はJavaScriptのトップレベルでしか用いることができず、モジュールは即座に読み込まれる仕様でした。

```js:main1.js
 // 即座にsub.jsが読み込まれる
import {Sub} from './sub.js'

const sub = new Sub();
sub.subMethod(); // 「this is Sub Class Method」というアラートが表示される。
```

- [従来のimportのデモ](https://tonkotsuboy.github.io/kanobox/171122_dynamic_import/sample1.html)

# dynamic import
Dynamic importでは、 `import(モジュール名)`を用いてモジュールを動的に読み込みます。返り値は`Promise`（[Promise - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise)）です。

```js:main2.js
// import()のタイミングでsub.jsが読み込まれる
import('./sub.js')
  .then(module => {
      // 動的に読み込まれたSubクラス
      const sub = new module.Sub();
      sub.subMethod();  
    });
```

`resolve()`時の引数にモジュールを受け取るため、`new module.Sub()`とすれば`sub.js`内の`Sub`クラスにアクセスできます。

- [dynamic importのデモ](https://tonkotsuboy.github.io/kanobox/171122_dynamic_import/sample2.html)

※ dynamic importに対応したブラウザでご確認ください。

![](https://qiita-image-store.s3.amazonaws.com/0/67884/3dce9f6c-3c6d-76f9-1d39-9d2a832eff9f.png)

## await/asyncを用いてimport
返り値が `Promise` なので、 [await](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/await) と [async](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/async_function)を用いて、次のようにモジュールの読み込みを待つことも可能です。

```js:main3.js
async function main() {
  const module = await import('./sub.js');
  const sub = new module.Sub();
  sub.subMethod();
}

main();
```


- [dynamic import / await / asyncのデモ](https://tonkotsuboy.github.io/kanobox/171122_dynamic_import/sample3.html)

※ dynamic importに対応したブラウザでご確認ください。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/67884/a97c0752-0358-2e55-360a-159e5a2c0944.png)

## 一定時間後にimport
モジュールが動的に読み込まれていることをわかりやすくするために、次のように2秒後にモジュールの読み込み・実行を行う処理を書きました。

```main4.js
setTimeout(() => {
  import('./sub.js')  // 2秒後にモジュールを読み込み
    .then(module => {
        // 読み込み完了後にSubを使用する
        const sub = new module.Sub();
        sub.subMethod();
      });
}, 2000);
```

- [2秒後にモジュールを読み込むデモ](https://tonkotsuboy.github.io/kanobox/171122_dynamic_import/sample4.html)

※ dynamic importに対応したブラウザでご確認ください。

Chromeの開発者ツールで確認すると、ページ表示時にはモジュールは読み込まれず、2秒経って読み込まれることが確認できます。

![](https://qiita-image-store.s3.amazonaws.com/0/67884/a3df765c-1c3b-3345-efa0-4ea46d414c98.gif)

## SafariやFirefox、Edgeでも動作
Safariは11.1から、Firefoxでは67から、Edgeでは79からdynamic importに対応済みです。ちなみにiOS Safariでも動作します。

▼ [Can I use](https://caniuse.com/#feat=es6-module-dynamic-import)より

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67884/cad02f88-747a-f506-1c00-2d4ec8456d60.png)





Firefox、Safariで動作している様子は次のとおりです。

▼ Firefoxで動作している様子

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67884/c074d579-f5db-b4db-58ae-03abbce10ba8.gif)

▼ Safariで動作している様子

![](https://qiita-image-store.s3.amazonaws.com/0/67884/cd85e30d-82a7-0fa5-5798-073e8723893d.gif)

# オブジェクト形式でモジュールを展開する
前述の例では `.then(module => `のように`module`変数にモジュールを展開していましたが、次のようにすると`new Sub()`で`Sub`をインスタンス化できるようになります。

この方法は「[tc39/proposal-dynamic-import](https://github.com/tc39/proposal-dynamic-import)」に記載はありませんでしたが、Safari 11、Chrome 64 (canary)で動作しました。

```js:main5.js
import('./sub.js')
  .then(({ Sub }) => {
  // 動的に読み込まれたSubクラス
  const sub = new Sub();
  sub.subMethod();
});
```

- [オブジェクト形式でモジュールを展開](https://tonkotsuboy.github.io/kanobox/171122_dynamic_import/sample5.html)

`await / async`を使うとこのようになります。

```js:main6.js
async function main() {
  const { Sub } = await import('./sub.js');

  const sub = new Sub();
  sub.subMethod();
}

main();
```

- [オブジェクト形式 ・ await/async](https://tonkotsuboy.github.io/kanobox/171122_dynamic_import/sample6.html)

※ dynamic importに対応したブラウザでご確認ください。

## 複数クラスのimport
オブジェクト形式で展開する場合、複数のクラスを`import`するならば次のようにします。

```main.js
async function main() {
  const { Sub, Sub2 } = await import('./sub.js');

  const sub = new Sub();
  sub.subMethod();

  const sub2 = new Sub2();
  sub2.subMethod2();
}

main();
```

- [オブジェクト形式 ・ 複数クラス](https://tonkotsuboy.github.io/kanobox/171122_dynamic_import/sample7.html)

※ dynamic importに対応したブラウザでご確認ください。

# ますます強力になるJavaScriptのモジュール

JavaScriptの大規模開発においてはモジュールによる処理の分割は必須。従来は[webpack](https://webpack.js.org/)（参考記事「[最新版で学ぶwebpack 3入門](https://ics.media/entry/12140)」）や[Browserify](http://browserify.org/)を用いて実現していた仕組みが、ブラウザの標準機能として実装されつつあり、さらに動的読み込みもというのは明るい話題ではないでしょうか。


## 参考記事
- [JavaScriptの次の仕様ES2020で追加されることが決定した新機能まとめ \- ICS MEDIA](https://ics.media/entry/200128/)
- [tc39/proposal-dynamic-import](https://github.com/tc39/proposal-dynamic-import)
- [Dynamic import()  |  Web  |  Google Developers](https://developers.google.com/web/updates/2017/11/dynamic-import)