余白

https://blog.lacolaco.net/ に移転しました

Angularで巨大なライブラリを動的に読み込む

オリジナルはこちら

medium.com

基本的にコードサンプルなどはオリジナルを参照してください。この記事では込み入った事情の部分だけを日本語で補足します。

tsconfig.jsonの準備

tsconfig.jsonmodule 設定は、TypeScript内で記述したモジュールのimport/exportをどのように解決するかを指定します。 Angular CLIのデフォルトでは module: es2015 を指定しているので、静的な import ... from はそのまま残しますが、import() はサポートしていません。 tsconfig.jsonmodule: esnext を指定すると、import()JavaScriptにそのまま残すようになります。 import() がサポートされたブラウザ上であれば、webpackを通さなくてもそのままブラウザ上でモジュール解決できる状態になっています。

ところがまだ import() はTC39のProposalとしてはStage 3で、未サポートのブラウザが多くあります。 現実的には、webpackを使ってbundleする必要がありますが、webpackはこの import() をwebpackがもつ動的モジュール読み込みの仕組み ( require.ensure ) で置き換えてくれます。 つまり、 import() のpolyfillのように振る舞ってくれます。

webpackを通すことでbundle後のJavaScriptには import ... fromimport() も残らないため、 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.defaultChart に相当するので、素直に書くとTypeScriptのコンパイルが通りません。 そのために normalizeCommonJSImport でラップしています。