$ ryokkkke.com/TypeScript/tsconfig.json

isolatedModules

概要

https://www.typescriptlang.org/tsconfig#isolatedModules

"isolatedModules": true

コンパイル対象のファイル間の関係性を一切無視して、全てのファイルを単一のモジュールとしてコンパイルします。
その場合の安全でない記法についてコンパイル時にエラーを出すようにします。

これをtrueにした場合、コンパイル対象の全てのファイルがexport構文を含む必要があり、コード中の

  • declare const enum
  • 型の re-export(再エクスポート)

はエラーとなりコンパイル出来ません。

どういう意味があるの?

TypeScript: TSConfig Reference - Docs on every TSConfig option

While you can use TypeScript to produce JavaScript code from TypeScript code, it’s also common to use other transpilers such as Babel to do this. However, other transpilers only operate on a single file at a time, which means they can’t apply code transforms that depend on understanding the full type system.

「TypeScript を使用して TypeScript コードから JavaScript コードを生成できますが、これを行うために Babel などの他のトランスパイラーを使用することも一般的です。 ただし、他のトランスパイラーは一度に 1 つのファイルのみを操作するため、完全な型システムの理解に依存するコード変換を適用することはできません。」
by Google 翻訳

また、このオプションの提案の issue: https://github.com/microsoft/TypeScript/issues/2499 も見てみると、
(このオプションが追加された PR: https://github.com/microsoft/TypeScript/pull/2550/)

The compiler does not do emit based on semantic information in most cases. this allows us to provide an api for transpiling a module that does not require typecheck or full program information. just single file syntactic transformation.
This can be helpful in multiple scenarios, including: typescript-simple (https://github.com/teppeis/typescript-simple), JSPM (#2233), es6-module-loader and as atom extensions.

Google 翻訳を一部意訳すると、

「TypScript のコンパイラは、多くのケースでコンパイルのために(ファイルをまたぐような)コードの文脈の情報を必要としません。
(後にisolatedModulesと名付けられるこのオプションを実装することで)単一ファイルの構文変換といった、型チェックや完全なプログラム情報を必要としないモジュールをコンパイルする際の API を提供できます。
これは、typescript-simple( https://github.com/teppeis/typescript-simple )、JSPM(#2233)、es6-module-loader、およびアトム拡張としてなど、複数のシナリオで役立ちます。」

つまり、デフォルトで TypeScript のコンパイラはファイルをまたいで(つまりimportexportによって関連づけられた複数のファイルをまたいで)コード全体の文脈を判断してコンパイルを実行しますが、TypeScript のコンパイルという意味ではそれを必要としない(もしくはそもそも複数ファイルを確認できない)ユースケースがよくあると。
で、その際に何が問題になるかというと、ファイル間の意味を無視してコンパイルする(せざるを得ない)ため、型情報を使用してコンパイルするような記法がコンパイルできないのです。
そうしたユースケースで問題なくコンパイルできるようにするために、その状況下で問題のあるコードをコンパイルエラーとするのがこのオプションの役割です。

逆に言うと、そうしたユースケースでのコンパイルを使用する場合は、TypeScript の記法が一部使えないということです。

trueだと何が起きるの?

isolatedModulestrueにすると、何もimportexportしていないファイルは、1 行目で以下のようなエラーが出ます。

const hoge = "hoge";
error TS1208: All files must be modules when the '--isolatedModules' flag is provided.

また、上述の通りdeclare const enumと型の re-export(再エクスポート)もエラーになります。

declare const enum FooBar {
  Foo = "foo",
  Bar = "bar",
}

export const str = `hoge: ${FooBar.Foo}`;

これは(↑)エラー。

// 型の export
export type Hoge = string;

こういう(↑)Hogeという型をexportしているファイルがあるとして

// 型の re-export
export { Hoge } from "./hoge";

このように(↑)型を再度別のファイルでexportすると型の re-export となり、これもエラーになります。

error TS2748: Cannot access ambient const enums when the '--isolatedModules' flag is provided.

error TS1205: Cannot re-export a type when the '--isolatedModules' flag is provided.

なぜこの辺りの記法がダメなのかは、公式のドキュメントをご覧ください。

型の re-export のコンパイルエラー回避

re-export について言及すると、下記 2 点の回避策(参考:create-react-app#6054)があります。

1. re-export 時のexportexport *に変える。

// NG
export { Hoge } from "./src";

// OK
export * from "./src";

2. re-export 時に再定義する。

// NG
export { Hoge } from "./src";

// OK
import { Hoge } from "./src";
export type ReExportedHoge = Hoge;

Next.js 9 とisolatedModules

Next.js では、9 からデフォルトで TypeScript をサポートするようになりました。
しかし、その TS のコンパイルの方法は tsc を使う方法ではなく Babel を用いたコンパイルであり、それに伴い"isolatedModules": trueが必須になりました。

どうしてisolatedModulestrue必須なのか

Next の TypeScript コンパイルは、tscではなく@babel/plugin-transform-typescriptが使われています。

Basic Features: TypeScript | Next.js

Next.js uses Babel to handle TypeScript, which has some caveats, and some compiler options are handled differently.

@babel/plugin-transform-typescript · Babel

Babel は JS のバージョン差分を吸収するツールなので、TypeScript の型チェックなどは行わず、あくまでも「TypeScript の記法を JavaScript に準拠したコードに書き換える」という処理を行います。
そのコードが TypeScript の型的に正しいかどうかを判断するのは Babel の責務外であり、コンパイル時にコードの文脈を知る必要がある TypeScript の記法は Babel にはコンパイルできません。
つまり、まさしく上述の isolatedModulesを使用するユースケースということです。

Babel で使えない記法をどうするか

Next の公式ドキュメントでもリンクされている Babel の注意事項の項目をご覧ください。

@babel/plugin-transform-typescript · Babel

今まで独自に tscを使って TypeScript で書いていた場合

今まで独自に tsc で TypeScript をコンパイルして Next を使っていた(かつ"isolatedModules": falseである)場合、使用しているdeclare const enumや型の re-export が全てエラーになり、コンパイルが通らなくなります。
とにかくこれはもう全て書き直すしかない…