TypeScriptのtsconfig.json実践入門:strictと追加の型チェックを設定する
Node.js用のtsconfig.jsonを実践解説。strictと追加の型チェック、任意項目、NodeNext、型チェックとビルドの違いを実行できる例で確認します。
tsconfig.json は、型チェックの条件とJavaScriptの生成方法をまとめる設定ファイルです。この記事では小さなNode.js用プロジェクトを作り、strict、noUncheckedIndexedAccess、exactOptionalPropertyTypes がそれぞれ何を検査するのか確認します。
対象は関数・配列・オブジェクト型の基礎が分かる方です。掲載コードはTypeScript 4.9.4で検証しています。実際の開発では、利用するNode.jsに対応したTypeScriptと型定義を使ってください。既存のNext.jsなどの設定を、この実習の設定で上書きする必要はありません。
新しいフォルダで準備する
Node.jsとnpm が利用できる環境で、空のフォルダを作成します。@types/node はNode.jsのAPIをTypeScriptに知らせる型定義です。実行するNode.jsのメジャーバージョンと型定義の対応も確認してください。
mkdir tsconfig-practice
cd tsconfig-practice
npm init -y
npm install --save-dev typescript @types/node
npm pkg set type=module
mkdir src
この実習ではpackage.json に "type": "module" を設定し、生成したJavaScriptをES Modulesとして実行します。package-lock.json も保存すると、依存関係を再現しやすくなります。
tsconfig.json を配置する
ファイル: tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noEmitOnError": true,
"rootDir": "src",
"outDir": "dist",
"lib": ["ES2022"],
"types": ["node"]
},
"include": ["src/**/*.ts"]
}
include は最初に読み込むファイルの範囲、rootDir は出力先に入力のディレクトリ構造を反映するときの基準、outDir は出力先です。rootDir 自体には、型チェック対象を選別する働きはありません。また、対象ファイルが参照するファイルは読み込まれるため、include をセキュリティ上の境界として扱わないでください。
lib: ["ES2022"] は標準JavaScriptのAPI、types: ["node"] はNode.jsのグローバルAPIの型を読み込みます。Node.js向けのこの例では、ブラウザの window や document を型に含める DOM は不要です。型定義を追加しても、そのAPIが実行環境に実装されるわけではありません。
noEmitOnError は、型エラーがあるときに新たな出力を生成しない設定です。前回のビルドでできた dist 内のファイルは自動削除しません。noEmitOnErrorの公式説明
strict と追加設定の違いを理解する
strict: true は、暗黙的な any や null・undefined の扱いなど、複数の厳密な検査をまとめて有効にします。個別の検査は明示的に上書きでき、TypeScriptの更新で検査が強化される場合もあります。strictの公式説明
一方、noUncheckedIndexedAccess と exactOptionalPropertyTypes は、strict だけでは有効になりません。この例では配列と任意項目の扱いを確認するため、両方を指定しています。
noUncheckedIndexedAccessは、配列の添字アクセスなど、値の存在が保証できない読み取りにundefinedの可能性を加えます。exactOptionalPropertyTypesは、任意項目を省略することと、その項目にundefinedを代入することを区別します。
空の配列と任意項目を扱う
ファイル: src/index.ts
type Preferences = { nickname?: string };
function firstLabel(labels: string[]): string {
const first = labels[0];
if (first === undefined) {
return "未設定";
}
return first.toUpperCase();
}
const preferences: Preferences = {};
console.log(firstLabel([])); // 未設定
console.log(firstLabel(["typescript"])); // TYPESCRIPT
console.log(preferences.nickname ?? "ゲスト"); // ゲスト
export {};
空の配列では labels[0] は undefined になります。noUncheckedIndexedAccess を有効にしたこの設定では、読み取った値を確認してから toUpperCase() を呼びます。タプルの既知の位置など、存在が型で保証されるアクセスまで一律に undefined になるわけではありません。noUncheckedIndexedAccessの公式説明
nickname?: string は項目を省略できますが、今回の設定では { nickname: undefined } を代入できません。値としても undefined を許すなら nickname?: string | undefined と記述します。任意項目を読み取るときには、項目が存在しない可能性があるので undefined の考慮が必要です。exactOptionalPropertyTypesの公式説明
型チェック・ビルド・実行を分ける
npx tsc -p tsconfig.json --noEmit
npx tsc -p tsconfig.json
node dist/index.js
最初のコマンドは出力を作らず型を確認し、次のコマンドはJavaScriptを生成します。最後のコマンドで「未設定」「TYPESCRIPT」「ゲスト」の順に表示されます。
tsc src/index.ts のように入力ファイルをコマンドへ直接渡すと、tsconfig.json は読み込まれません。設定を使う実習では -p tsconfig.json を指定してください。tsconfig.jsonの公式説明
モジュール設定は実行環境に合わせる
NodeNext はNode.jsのモジュール規則を扱う設定です。この実習のようなES Modulesで自作ファイルを相対パスから読み込む場合、生成後のJavaScriptに合わせて import { helper } from "./helper.js" のように拡張子を指定します。TypeScriptの入力ファイルが helper.ts でも、ここに書くのは出力時の .js です。
バンドラーがコードをまとめるブラウザ向けアプリやフレームワークでは、推奨される設定が異なります。環境が生成した設定を出発点にし、理由を確認して変更してください。モジュール設定の選び方
target は主にJavaScriptの構文水準を決めます。古い環境に不足するAPIを追加する設定ではないため、実際の実行環境でも確認します。
練習問題
firstLabel()のundefined判定を外し、型エラーが出ることを確認します。preferencesを{ nickname: undefined }に変更し、省略との違いを確認します。- 元に戻してから型チェックとビルドを行い、正しい出力を再確認します。
型エラーを理由のない as や非nullアサーションで消すと、値が存在しないケースを見逃します。まず欠けた値をどう扱うかを決め、その処理をコードに表してください。
よくある質問
strict を有効にすれば実行時エラーはなくなりますか?
なくなりません。外部データの形式、通信障害、業務ルールなどは、実行時の検証やテストでも確かめます。型チェックは、それらと組み合わせて使う確認手段です。
既存の大きなプロジェクトにはどう導入しますか?
まず出力しない型チェックを行い、影響を把握します。欠けたデータを扱う分岐を追加し、依存するライブラリとの互換性を確認しながら設定を導入します。フレームワークの extends やビルド設定も確認してください。
設定が反映されないときの確認点
読み込んだ設定を確認する
エディターの表示とコマンドの結果が違うときは、実行した場所と対象の設定を確認します。npx tsc -p tsconfig.json --showConfigで最終的な設定を表示し、extendsで継承した項目やincludeも調べます。
型検査と実行環境を別々に確認する
libに型を追加してエラーが消えても、実行環境へ機能が追加されるわけではありません。設定変更後は型チェックに加え、実際のNode.jsやブラウザーで必要な処理を実行してください。