banseivlog

大器晩成型 - 仕事中の覚え書きや反省文を書く程度のブログです

TypeScriptでIsomorphicなCode Splittingをする

以前webpack2でDynamic importsが実装される前にWebpackのCode Splittingについて書きましたが、せっかくTypeScript 2.4でもDynamic Importsが実装されたので(結構経ちますが)TypeScript環境下でいかにしてCode Splittingを実現させるかを書き残しておきます。

webpack.ensureとbabelでハマりがちなこと - banseivlog

前提となる環境

  • webpack v3.4.1
  • TypeScript v2.4.2
  • Isomorphic

環境的にはwebpackでクライアントコードをビルドしてサーバーはtsc or webpackでビルドしてます。

TypeScriptにDynamic Imports入った→入れてみた→Code Splittingしない

結構な人が躓くと思うのですが、大抵の場合requre.ensureあたりをそのままDynamic ImportsにしてもCode Splittingしてくれません。

単純にTS側のトランスパイルの仕組みなのですが、tsc --initとかでtsconfig.jsonを作ると以下のようになります。

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    // ...
  }
}

普通に使ってる分には問題はないのですがこれだとDynamic ImportsをTypeScriptは以下のように変換してしまいます

import('./hoge');
Promise.resolve().then(function () { return require('./hoge'); });

というのも"module": "commonjs"と指定されているので、すべてCommonJS方式で出力されます。

三茶の蟹と海老屋さん美味

ESNextとmoduleResolution

最終的に欲しいアウトプットは、ESModules部分はcommonjs方式に、Dynamic Imports部分をクライアントサイドは無変換で、サーバーサイドは変換されて欲しい。

そこで新たに設定を追加します。

// サーバー側
{
  "compilerOptions": {
    // ...
    "module": "commonjs",
    "moduleResolution": "node",
    // ...
  }
}

// クライアント側
{
  "compilerOptions": {
    // ...
    "module": "esnext",
    "moduleResolution": "node",
    // ...
  }
}

こんな感じでサーバー側はそのままに、クライアントコード側(Code Splittingしたい方)に"module": "exnext""moduleResolution": "node"を書き加えてあげます。 そうするとクライアントコード側はmoduleのターゲットがesnext(未来仕様)にしているため、Dynamic Importsは変換されずそのままの状態として残ります。ただそれだけだと今度はESModulesが変換されないため"moduleResolution": "node"と設定することで、import/export文はcommonjs(nodejs)方式でoutputされます。

もちろん、上記設定をサーバーとクライアントで分けたのはNode.js側ではimportは動かないので変換をかけています。

こうしてサーバーサイドとクライアントサイドのコードを共有することが出来ました。ちなみにrequire.ensure時代はわりと力技になっていたのでDynamic Importsが導入できて僕らはまたひとつ幸せになることができました。

多種多様な人が働く環境下でTypeScriptの静的型付けはかなり(安心安全的な意味で)パフォーマンスを出してくれるのかなと個人的に思っているので、引き続きTSさんをよろしくお願いします。