Angularで巨大なライブラリを動的に読み込む
オリジナルはこちら
基本的にコードサンプルなどはオリジナルを参照してください。この記事では込み入った事情の部分だけを日本語で補足します。
tsconfig.jsonの準備
tsconfig.jsonの module 設定は、TypeScript内で記述したモジュールのimport/exportをどのように解決するかを指定します。
Angular CLIのデフォルトでは module: es2015 を指定しているので、静的な import ... from はそのまま残しますが、import() はサポートしていません。
tsconfig.jsonで module: esnext を指定すると、import() をJavaScriptにそのまま残すようになります。
import() がサポートされたブラウザ上であれば、webpackを通さなくてもそのままブラウザ上でモジュール解決できる状態になっています。
ところがまだ import() はTC39のProposalとしてはStage 3で、未サポートのブラウザが多くあります。
現実的には、webpackを使ってbundleする必要がありますが、webpackはこの import() をwebpackがもつ動的モジュール読み込みの仕組み ( require.ensure ) で置き換えてくれます。
つまり、 import() のpolyfillのように振る舞ってくれます。
webpackを通すことでbundle後のJavaScriptには import ... from も import() も残らないため、 target: es5 のままトランスパイルしても問題ありません。つまりブラウザ互換性には影響しません。
Promiseがないブラウザではes2015のpolyfillが必要ですが、Angular CLI v7.2からはデフォルトで es2015非サポートなブラウザでだけ自動的に適用されるpolyfillを吐き出すので、我々がes2015のpolyfillについて気にすることはありません。
normalizeCommonJSImport について
これは TypeScriptの import() の型定義でCommonJSとの互換性に問題があるための処置です。
import() では名前付きインポートをサポートしておらず、ES Moduleにおける default export だけをサポートしています。
webpackではその互換性のために、CommonJSで書かれたモジュールを import() でインポートするときには、module.exports オブジェクトを default exportに見立てて、 import() で読み込まれるオブジェクトの default プロパティに格納しています。
TypeScriptの import() は賢いので、 静的にimportしたときに import * as Chart from 'chart.js' で得られる Chart の型と、 import('chart.js').then(result => result) で得られる result の型は同じに扱うのですが、実際は result.default が Chart に相当するので、素直に書くとTypeScriptのコンパイルが通りません。
そのために normalizeCommonJSImport でラップしています。