TL;DR
"include": ["src/index.ts"]
はやめようsrc
配下にあるのに型チェックされない & auto-import できないファイルが生まれてしまう
"include": ["src/**/*"]
や"include": ["**/*"]
がオススメ- どっちが良いかはプロジェクトによる
"include": ["src/**/*"]
は"include": ["src"]
と、"include": ["**/*"]
は include 指定無しと同じなので、それでも OK
- すっごい凝りたいなら Solution Style tsconfig.json を使おう
はじめに
tsconfig.json
の include
オプションは、プロジェクトを構成するファイルを指定するオプションです。
例えば src/**/*
を指定すると、src
ディレクトリ以下の全てのファイル *1 がプロジェクトに所属するようになります。プロジェクトに所属するファイルは、型チェックやコンパイルの対象になります。
{ "include": ["src/**/*"] }
├── scripts │ └── lint.ts ⨯ ├── src │ ├── util │ │ └── log.ts ✓ │ ├── index.ts ✓ │ └── math.ts ✓ ├── package.json ├── tsconfig.json └── vitest.config.ts ⨯
人によっては "include": ["src/**/*"]
以外にも、"include": ["src"]
, "include": ["src/index.ts"]
, "include": ["**/*"]
などと指定することもあります。一見するとどれも同じように見えますが、実は挙動が異なります。この記事では、include
オプションの指定方法によってどのような挙動になるのか、どう指定するのが良いのかについて考えてみます。
include
オプションとそれに関連する仕様について
本題に入る前に、まずは include
オプションやそれに関連する仕様について説明します。
プロジェクトに所属しないファイルは tsc -p tsconfig.json
による型チェックの対象にならない
プロジェクトに所属しないファイルは tsc -p tsconfig.json
による型チェックの対象になりません。先ほどの例で言うと、scripts/lint.ts
と vitest.config.ts
は tsc
コマンドによる型チェックの対象にならず、tsc -p tsconfig.json
で型エラーが報告されません。
プロジェクトに所属しないファイルでも、エディタ上で型チェックされる
ややこしいことにプロジェクトに所属しないファイルも、エディタ上では型チェックの対象になります (厳密にはエディタではなく、「tsserver」と呼ばれる TypeScript の Language Server 実装に由来する振る舞いです。)。そのため、エディタ上では型エラーが報告されるものの、tsc -p tsconfig.json
では報告されません。
また、型チェックに使われる設定は tsconfig.json
で定義されているものではなく、tsserver 組み込みのデフォルトの設定が使われます *2。そのため、tsconfig.json
に "noImplicitAny": true
を設定していても、デフォルトの設定である "noImplicitAny": false
相当で型チェックが行われてしまいます。
その結果、「エディタでは型エラーが出ているけど、CI を pass してしまう」「普段と異なる設定で型チェックが行われてしまう」といった問題に繋がりがちです。
プロジェクトに所属しないファイルは auto-imports の対象にならない
TypeScript には auto-imports という機能があります。auto-imports は、他のファイルに定義されている変数や関数を使おうとしたときに、補完の候補に出して、自動的に import 文を挿入してくれる機能です。
しかし、プロジェクトに所属しないファイルは auto-imports の対象になりません。もし仮に src/math.ts
がプロジェクトに所属していない場合、src/math.ts
に定義されている関数を使おうとしても、補完候補に出てきません。
include
にマッチしなくても、include
にマッチするファイルから import されていればプロジェクトに所属する
ややこしいことに、include
にマッチしないファイルでも、include
にマッチするファイルから import されていればプロジェクトに所属すると見なされます。
例えば、以下のようなリポジトリがあるとします。
{ "include": ["src/index.ts"] }
├── src │ ├── util │ │ └── log.ts │ ├── index.ts │ └── math.ts └── tsconfig.json
// src/index.ts import { add } from "./math.js";
src/index.ts
から math.ts
が import されていますから、math.ts
はプロジェクトに所属すると見なされます。そのため、math.ts
は tsc -p tsconfig.json
による型チェックの対象になりますし、auto-imports の対象にもなります。一方、util/log.ts
はプロジェクトに所属しないと見なされます。
つまり include
オプションにマッチしないからといって、プロジェクトに所属しないファイルとは限らないということです。
"include": ["src/**/*"]
と "include": ["src"]
は同じ
TypeScript のソースコードを読んでみると、"include": ["src"]
は "include": ["src/**/*"]
と同義のようでした。
- https://github.com/microsoft/TypeScript/blob/v5.5.4/src/compiler/commandLineParser.ts#L3966
- https://github.com/microsoft/TypeScript/blob/v5.5.4/src/compiler/utilities.ts#L9392-L9400
"include"
が指定されていない場合は "include": ["**/*"]
と同じ
TypeScript のソースコードを読んでみると、"include"
が指定されていない場合は "include": ["**/*"]
と同じ挙動になるようでした。
結局 include
オプションには何を指定したら良いの?
以下のようなディレクトリ構成をもとに話を進めます。
├── scripts │ └── lint.ts ├── src │ ├── util │ │ └── log.ts │ ├── index.ts │ └── math.ts ├── package.json ├── tsconfig.json └── vitest.config.ts
// src/index.ts console.log('Hello, world!');
悪い例: "include": ["src/index.ts"]
src/math.ts
や src/util/log.ts
がプロジェクトに所属しないとファイルと見なされてしまい、tsc -p tsconfig.json
で型チェックされなかったり、auto-imports の対象にならなかったりといった問題が発生します。この設定は絶対に避けるべきです。
プロジェクトに含まれるファイル:
├── scripts │ └── lint.ts ⨯ ├── src │ ├── util │ │ └── log.ts ⨯ │ ├── index.ts ✓ │ └── math.ts ⨯ ├── package.json ├── tsconfig.json └── vitest.config.ts ⨯
悪い例: "include": ["src/*"]
先ほどと違い、src/math.ts
はプロジェクトに所属すると見なされますが、src/util/log.ts
は所属しないと見なされます。これも避けるべきです。
プロジェクトに含まれるファイル:
├── scripts │ └── lint.ts ⨯ ├── src │ ├── util │ │ └── log.ts ⨯ │ ├── index.ts ✓ │ └── math.ts ✓ ├── package.json ├── tsconfig.json └── vitest.config.ts ⨯
良い例: "include": ["src/**/*"]
or "include": ["src"]
src
配下の全てのファイルがプロジェクトに所属すると見なされ、tsc
による型チェックや auto-imports の対象になります。
プロジェクトに含まれるファイル:
├── scripts │ └── lint.ts ⨯ ├── src │ ├── util │ │ └── log.ts ✓ │ ├── index.ts ✓ │ └── math.ts ✓ ├── package.json ├── tsconfig.json └── vitest.config.ts ⨯
scripts/lint.ts
や vitest.config.ts
は型チェックや auto-imports の対象になりませんが...そもそもこうしたファイルを src
と同じ設定で型チェックしたいかというと、場合によると思います。例えば、src
配下がブラウザで実行されるファイルが置かれていたら、src
配下は "lib": ["ESNext", "DOM"]
で、それ以外の Node.js で実行されるファイルは "lib": ["ESNext"]
で型チェックしたいかもしれません。そうしたケースでは、むしろ scripts
配下や vitest.config.ts
が含まれないこのパターンが適しているでしょう。
余談: scripts/lint.ts
や vitest.config.ts
を別の設定で型チェックしたい場合
tsconfig.json
を Node.js 向けの設定ファイルとして使い、src
配下のファイルだけを型チェックする設定ファイル (src/tsconfig.json
) を別途用意すると良いです。
├── scripts │ └── lint.ts ✓ included by tsconfig.json ├── src │ ├── util │ │ └── log.ts ✓ included by src/tsconfig.json │ ├── index.ts ✓ included by src/tsconfig.json │ ├── math.ts ✓ included by src/tsconfig.json │ └── tsconfig.json ✓ included by src/tsconfig.json ├── package.json ├── tsconfig.json └── vitest.config.ts ✓ included by tsconfig.json
// tsconfig.json { "exclude": ["src"], "compilerOptions": { "lib": ["ESNext"] } }
// src/tsconfig.json { "compilerOptions": { "lib": ["ESNext", "DOM"] } }
src/tsconfig.json
のように src
ディレクトリの中にあると取り回しづらいから、tsconfig.browser.json
に rename してプロジェクトルートに置きたい...と思うかもしれませんが、素朴にやってしまうと tsserver がから検出できなくなってしまいます (詳しくは以下の記事を参照)。
もしプロジェクトルートに設定ファイルを集約したければ、Solution Style tsconfig.json というテクニックを使うと良いです。
├── scripts │ └── lint.ts ✓ included by tsconfig.node.json ├── src │ ├── util │ │ └── log.ts ✓ included by tsconfig.browser.json │ ├── index.ts ✓ included by tsconfig.browser.json │ └── math.ts ✓ included by tsconfig.browser.json ├── package.json ├── tsconfig.json ├── tsconfig.browser.json ├── tsconfig.node.json └── vitest.config.ts ✓ included by tsconfig.node.json
// tsconfig.json { "files": [], "references": [ { "path": "./tsconfig.browser.json" }, { "path": "./tsconfig.node.json" } ] }
// tsconfig.browser.json { "include": ["src"], "compilerOptions": { "lib": ["ESNext", "DOM"] } }
// tsconfig.node.json { "exclude": ["src"], "compilerOptions": { "lib": ["ESNext"] } }
良い例: "include": ["**/*"]
or include
指定なし
全てのファイルがプロジェクトに所属すると見なされ、tsc
による型チェックや auto-imports の対象になります。src
配下が Node.js で実行するファイルであり、scripts
配下や vitest.config.ts
とまとめて型チェックしてしまって問題ない場合は、これで良いでしょう。
プロジェクトに含まれるファイル:
├── scripts │ └── lint.ts ✓ ├── src │ ├── util │ │ └── log.ts ✓ │ ├── index.ts ✓ │ └── math.ts ✓ ├── package.json ├── tsconfig.json └── vitest.config.ts ✓
参考
検証に使ったリポジトリを置いておきます。