これは、なにをしたくて書いたもの?
前に、tsconfig.jsonはextendsで拡張(オーバーライド)できるらしいというエントリーを書きました。
tsconfig.jsonをextendsして、設定内容をオーバーライドする - CLOVER🍀
あるきっかけで、extendsに指定する対象を複数にできないのかな?と思ったのですが、どうやらTypeScript 5.0でできるようになっていた
みたいです。
tsconifg.jsonに複数のファイルをextends元として指定する
tsconfig.jsonのリファレンスのextendsの部分を見てみます。
Intro to the TSConfig Reference / Root Fields / Extends - extends
ここを見ると、extendsは文字列を指定するオプションに見えるのですが。
TypeScript 5.0のリリースブログを見ると、extendsに複数ファイルを指定できるようになったことが書かれています。
Announcing TypeScript 5.0 - TypeScript
こちらですね。
Announcing TypeScript 5.0 / Supporting Multiple Configuration Files in extends
複数ファイルのextendsができるようになったと書かれています。
To give some more flexibility here, Typescript 5.0 now allows the extends field to take multiple entries.
このように、配列で複数指定できるみたいですね。
{ "extends": ["a", "b", "c"], "compilerOptions": { // ... } }
こういう機能があると、「同じフィールドが競合した場合はどうなるのか?」というのが気になるところですが、その場合は後ろに指定したものが
優先されるようです。
Writing this is kind of like extending c directly, where c extends b, and b extends a. If any fields "conflict", the latter entry wins.
それはそうでしょうね、という感じではあります。
tsconfig.jsonのリファレンスには書かれていないのですが、1度試しておこうと思います。
環境
今回の環境はこちら。
$ node --version v20.16.0 $ npm --version 10.8.1
準備
ひとまず、プロジェクトの作成とTypeScriptのインストールを行います。
$ npm init -y $ npm i -D typescript $ npm i -D @types/node@v20
TypeScript 5.5.4です。
"devDependencies": { "@types/node": "^20.14.8", "typescript": "^5.5.4" }
複数ファイルのextendsを試してみる
それでは、複数ファイルのextendsを試していってみましょう。
まずは単一のファイルをextendsから。
ベースのファイルを用意。
tsconfig.a.json
{ "compilerOptions": { "target": "esnext", "module": "nodenext", "moduleResolution": "nodenext" } }
これをextendsしたファイルを用意。
tsconfig.last.json
{ "extends": "./tsconfig.a" "compilerOptions": { "baseUrl": "./src", "outDir": "dist", "skipLibCheck": true, "esModuleInterop": true }, "include": [ "src" ] }
tscは入力ファイルがなにもないとエラーになってノイズになるので、適当にファイルを用意しておきます。
src/message.ts
export function message(word: string): string { return `Hello ${word}!!`; }
--showConfigオプションで結果を確認。
$ npx tsc --showConfig --project ./tsconfig.last.json
こうなりました。
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"moduleResolution": "nodenext",
"baseUrl": "./src",
"outDir": "./dist",
"skipLibCheck": true,
"esModuleInterop": true,
"moduleDetection": "force",
"allowSyntheticDefaultImports": true,
"resolvePackageJsonExports": true,
"resolvePackageJsonImports": true,
"useDefineForClassFields": true
},
"files": [
"./src/message.ts"
],
"include": [
"src"
],
"exclude": [
"/path/to/dist"
]
}
resolvePackageJsonExportsやuseDefineForClassFieldsなど、指定していないものも入っているようですが、これはmoduleResolutionや
targetに指定した値で暗黙的に変わるものらしいです。
つまり、このような変更を含めた最終結果を-showConfigフラグでは確認できるということですね。
--showConfig
Print the final configuration instead of building.
TypeScript: Documentation - tsc CLI Options
さて、今回のお題は複数のextendsでした。というわけでファイルを追加します。
tsconfig.b.json
{ "compilerOptions": { "strict": true } }
tsconfig.c.json
{ "compilerOptions": { "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "noImplicitOverride": true, "noImplicitReturns": true } }
複数のファイルをextends。
tsconfig.last.json
{ "extends": [ "./tsconfig.a", "./tsconfig.b", "./tsconfig.c" ], "compilerOptions": { "baseUrl": "./src", "outDir": "dist", "skipLibCheck": true, "esModuleInterop": true }, "include": [ "src" ] }
結果。
$ npx tsc --showConfig --project ./tsconfig.last.json
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"moduleResolution": "nodenext",
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"baseUrl": "./src",
"outDir": "./dist",
"skipLibCheck": true,
"esModuleInterop": true,
"moduleDetection": "force",
"allowSyntheticDefaultImports": true,
"resolvePackageJsonExports": true,
"resolvePackageJsonImports": true,
"useDefineForClassFields": true,
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"alwaysStrict": true,
"useUnknownInCatchVariables": true
},
"files": [
"./src/message.ts"
],
"include": [
"src"
],
"exclude": [
"/path/to/dist"
]
}
なんかものすごく増えましたが…strictをtrueにしたことで有効なものになったものなども展開されているからですね。
では、ここで競合するルールをひとつ追加しましょう。
tsconfig.d.json
{ "compilerOptions": { "forceConsistentCasingInFileNames": false } }
tsconfig.c.jsonと競合する設定です。こちらをextendsに追加。
tsconfig.last.json
{ "extends": [ "./tsconfig.a", "./tsconfig.b", "./tsconfig.c", "./tsconfig.d" ], "compilerOptions": { "baseUrl": "./src", "outDir": "dist", "skipLibCheck": true, "esModuleInterop": true }, "include": [ "src" ] }
forceConsistentCasingInFileNamesが上書きされました。
$ npx tsc --showConfig --project ./tsconfig.last.json
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"moduleResolution": "nodenext",
"strict": true,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"baseUrl": "./src",
"outDir": "./dist",
"skipLibCheck": true,
"esModuleInterop": true,
"moduleDetection": "force",
"allowSyntheticDefaultImports": true,
"resolvePackageJsonExports": true,
"resolvePackageJsonImports": true,
"useDefineForClassFields": true,
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"alwaysStrict": true,
"useUnknownInCatchVariables": true
},
"files": [
"./src/message.ts"
],
"include": [
"src"
],
"exclude": [
"/path/to/dist"
]
}
ここですね。
"forceConsistentCasingInFileNames": false,
extendsの順番を入れ替えてみます。tsconfig.d.jsonをtsconfig.c.jsonの前にしました。
tsconfig.last.json
{ "extends": [ "./tsconfig.a", "./tsconfig.b", "./tsconfig.d", "./tsconfig.c" ], "compilerOptions": { "baseUrl": "./src", "outDir": "dist", "skipLibCheck": true, "esModuleInterop": true }, "include": [ "src" ] }
$ npx tsc --showConfig --project ./tsconfig.last.json
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"moduleResolution": "nodenext",
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"baseUrl": "./src",
"outDir": "./dist",
"skipLibCheck": true,
"esModuleInterop": true,
"moduleDetection": "force",
"allowSyntheticDefaultImports": true,
"resolvePackageJsonExports": true,
"resolvePackageJsonImports": true,
"useDefineForClassFields": true,
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"alwaysStrict": true,
"useUnknownInCatchVariables": true
},
"files": [
"./src/message.ts"
],
"include": [
"src"
],
"exclude": [
"/path/to/dist"
]
}
forceConsistentCasingInFileNamesはtsconfig.c.jsonの値になりました。
"forceConsistentCasingInFileNames": true,
というわけで、複数ファイルをextendsをした場合の結果と--showConfigフラグについて見てみました。
ちなみに、ここまでずっと--projectフラグでtsconfig.jsonを明示的に指定していましたが、ファイル名がtsconfig.jsonであれば
--projectフラグ自体要りません。
tsconfig.json
{ "extends": [ "./tsconfig.a", "./tsconfig.b", "./tsconfig.c" ], "compilerOptions": { "baseUrl": "./src", "outDir": "dist", "skipLibCheck": true, "esModuleInterop": true }, "include": [ "src" ] }
結果。
$ npx tsc --showConfig
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"moduleResolution": "nodenext",
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"baseUrl": "./src",
"outDir": "./dist",
"skipLibCheck": true,
"esModuleInterop": true,
"moduleDetection": "force",
"allowSyntheticDefaultImports": true,
"resolvePackageJsonExports": true,
"resolvePackageJsonImports": true,
"useDefineForClassFields": true,
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"alwaysStrict": true,
"useUnknownInCatchVariables": true
},
"files": [
"./src/message.ts"
],
"include": [
"src"
],
"exclude": [
"/path/to/dist"
]
}
まあ、コマンド例、ということで。
おわりに
TypeScript 5.0からtsconfig.jsonのextends元を複数指定できるようになっていたようなので、ちょっと試してみました。
挙動自体はわかりやすかったですが、リファレンスに載っていないのでちょっと???という感じですね。あと、そんなに多く使う機能でも
ないような気はします。
せっかくなので覚えておきましょう、ということで。
--showConfigフラグの方が重要かもしれません。