これは、なにをしたくて書いたもの?
tsconfig.json
は、extends
で拡張(というかオーバーライド)できるらしく。
ちょっとやりたいことがあったので、試してみました。
tsconfig.jsonのextends
tsconfig.json
は、extends
で拡張することができます。
What is a tsconfig.json / TSConfig Bases
extends
で指定したファイルをベースにして、オーバーライドすることができます。ファイル内に記載されているパスは、元のファイルを
基準にして解決するようです。ファイルの循環参照はできません。
The configuration from the base file are loaded first, then overridden by those in the inheriting config file. All relative paths found in the configuration file will be resolved relative to the configuration file they originated in.
It’s worth noting that files, include and exclude from the inheriting config file overwrite those from the base config file, and that circularity between configuration files is not allowed.
Intro to the TSConfig ReferenceCompiler Options / Extends - extends
とりあえず、やってみます。
環境
今回の環境は、こちらです。
$ node --version v16.13.1 $ npm --version 8.1.2
サンプルプロジェクト作成
確認用の、npmプロジェクトを作成します。
Jestも使うことにします。
$ npm init -y $ npm i -D typescript $ npm i -D -E prettier $ npm i -D @types/node@v16 $ npm i -D jest @types/jest $ npm i -D esbuild-jest esbuild
依存関係。
"devDependencies": { "@types/jest": "^27.4.0", "@types/node": "^16.11.19", "esbuild": "^0.14.11", "esbuild-jest": "^0.5.0", "jest": "^27.4.7", "prettier": "2.5.1", "typescript": "^4.5.4" }
Jestの設定。
jest.config.js
module.exports = { testEnvironment: 'node', transform: { "^.+\\.tsx?$": "esbuild-jest" } };
.prettierrc.json
{ "singleQuote": true }
tsconfig.json
は、あとで載せます。
$ mkdir src test
src/message.ts
export function message(word: string): string { return `Hello ${word}!!`; }
テストコード。
test/message.test.ts
import { message } from '../src/message'; test('message test', () => { expect(message('World')).toBe('Hello World!!'); });
ソースコード、テストコードはこういう
$ tree src test src └── message.ts test └── message.test.ts 0 directories, 2 files
確認。
$ npx jest PASS test/message.test.ts ✓ message test (2 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 0.249 s, estimated 1 s Ran all test suites.
tsconfig.jsonを拡張する
ここまでtsconfig.json
を載せていませんでしたが、こういう内容にしていました。こちらを「ベース」にします。
tsconfig.json
{ "compilerOptions": { "target": "esnext", "module": "commonjs", "baseUrl": "./src", "outDir": "dist", "strict": true, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "noImplicitOverride": true, "noImplicitReturns": true, "noPropertyAccessFromIndexSignature": true, "esModuleInterop": true }, "include": [ "src" ] }
このベースのファイルを拡張した、もうひとつのファイルを作成。extends
にベースのファイルパスを指定するのですが、拡張子は要らない
みたいですね。
tsconfig-typecheck-only.json
{ "extends": "./tsconfig", "compilerOptions": { "baseUrl": "./", "noEmit": true }, "include": [ "src", "test" ] }
差はnoEmit
をtrue
にしているのと、include
です。
こちらのファイルではベースのtsconfig.json
を元に、対象とするディレクトリをsrc
、test
の2つにして、コンパイル結果は出力しないように
しています。
動作確認
ベースのtsconfig.json
の方では、ビルドするのはsrc`ディレクトリのみを対象にしています。
なので、ビルドすると
$ npx tsc --project .
こうなります。
$ tree dist dist └── message.js 0 directories, 1 file
1度、ディレクトリを削除。
$ rm -rf dist
次は、ファイルを切り替えて実行。
$ npx tsc --project ./tsconfig-typecheck-only.json
ビルドが終わっても、
$ ll dist ls: 'dist' にアクセスできません: そのようなファイルやディレクトリはありません
結果ファイルは生成されません。
ただ、これだとベースを継承しているのかわからないので、もう少し確認してみましょう。
ベースのtsconfig.json
では、strict
がtrue
になっています。
"strict": true,
strict
にはstrictNullChecks
をtrue
にする効果も含まれているので、たとえばテストコードを以下のように変更すると
test('message test', () => { //expect(message('World')).toBe('Hello World!!'); expect(message(null)).toBe('Hello World!!'); });
ビルドが通らなくなります。
$ npx tsc --project ./tsconfig-typecheck-only.json test/message.test.ts:6:18 - error TS2345: Argument of type 'null' is not assignable to parameter of type 'string'. 6 expect(message(null)).toBe('Hello World!!'); ~~~~ Found 1 error.
ここで、ベースのtsconfig.json
のstrict
をfalse
に変更してみます。
"strict": false,
するとビルドが通るようになり、extends
を使用しているファイルがベースのtsconfig.json
の内容を引き継いでいることが確認できます。
$ npx tsc --project ./tsconfig-typecheck-only.json
そもそもやりたかったこと
前にJestをTypeScriptで扱う際に、速度を改善したいというエントリーを書きました。
Jest+TypeScriptを高速に実行したい(ts-jest、esbuild、SWCを比べる) - CLOVER🍀
この時にesbuildやSWCを使って確認したのですが、これらはTypeScriptを扱うのが高速になりますが、代わりに型チェックを行わなく
なります。
よって、noEmit
をtrue
にした状態でテストコードの型チェックを行いたいのですが、ベースのtsconfig.json
はそのままに差分だけ
変えられないかなぁと思ってこの方法に行き着きました。
tsc
のコマンドラインオプションでも指定できるのですが、他にも変えたい部分もありましたし。
TypeScript: Documentation - tsc CLI Options
package.json
のscripts
は、こんな感じにして型チェックのみ行う定義も使おうかなと。
"scripts": { "build": "tsc --project .", "build:watch": "tsc --project . --watch", "typecheck": "tsc --project ./tsconfig-typecheck-only.json", "typecheck:watch": "tsc --project ./tsconfig-typecheck-only.json --watch", "format": "prettier --write src", "test": "jest" },
実際、こんな感じに型が合わない記述をしても
test('message test', () => { //expect(message('World')).toBe('Hello World!!'); expect(message(3)).toBe('Hello World!!'); });
型チェックをすり抜けてしまいますし。
$ npx jest FAIL test/message.test.ts ● message test expect(received).toBe(expected) // Object.is equality Expected: "Hello World!!" Received: "Hello 3!!" 1 | import { message } from '../src/message'; 2 | > 3 | test('message test', () => { | ^ 4 | //expect(message('World')).toBe('Hello World!!'); 5 | expect(message(3)).toBe('Hello World!!'); at Object.<anonymous> (test/message.test.ts:3:42) PASS dist/test/message.test.js Test Suites: 1 failed, 1 passed, 2 total Tests: 1 failed, 1 passed, 2 total Snapshots: 0 total Time: 0.404 s, estimated 1 s Ran all test suites.
もちろん、tsc
でのビルドではこれは通りません。
$ npx tsc --project ./tsconfig-typecheck-only.json test/message.test.ts:5:18 - error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'. 5 expect(message(3)).toBe('Hello World!!'); ~ Found 1 error.
ちなみに、拡張したファイルからnoEmit
を削除すると
tsconfig-typecheck-only.json
{ "extends": "./tsconfig", "compilerOptions": { "baseUrl": "./", }, "include": [ "src", "test" ] }
こういう感じでdist
ディレクトリ内の階層が変わるのですが。
$ npx tsc --project ./tsconfig-typecheck-only.json $ tree dist dist ├── src │ └── message.js └── test └── message.test.js 2 directories, 2 files
まあ、Node.jsアプリケーションとしてビルドしたい場合は、テストコードのビルド結果が含まれていなくてもいいかなぁと思うので、
こんな感じでいきましょう。