create-vue で学ぶ tsconfig.json
概要
本記事は、 Vue.js の公式ツールである create-vue によって生成された tsconfig.json を元に、 Vue 3 + TypeScript における、 tsconfig
のベストプラクティスを理解しようという内容です。
注意事項
筆者自身はまだまだ Vue
及び TypeScript
の理解が不十分であり、誤った解釈を含んでいる可能性があるのでご注意ください。
なにか間違いなどにお気付きの場合は、コメントまたは GitHub
での編集提案を頂けると幸いです。
create-vue について
create-vue
は、Vite ベースの Vue
プロジェクトを CLI から構築できるツールです。
npm init vue@3
上記のようなコマンドから、対話形式の質問に解答することで、ビルド設定から周辺ライブラリの追加、テストやリンター、フォーマッターのセットアップまで行ってくれます。
本記事では create-vue
によって生成されたプロジェクト構成が、現在の Vue
におけるベストプラクティスであるという前提の元、生成された tsconfig
を深堀ります。
なお、本記事で使用する create-vue
のバージョンは v3.3.4 です。
プロジェクト作成
本記事では以下のようにプロジェクトをセットアップします。
$ npm init vue@3
Vue.js - The Progressive JavaScript Framework
✔ Project name: … vue-project
✔ Add TypeScript? … Yes
✔ Add JSX Support? … No
✔ Add Vue Router for Single Page Application development? … No
✔ Add Pinia for state management? … No
✔ Add Vitest for Unit Testing? … Yes
✔ Add Cypress for End-to-End testing? … No
✔ Add ESLint for code quality? … No
プロジェクト名はデフォルトの vue-project
とし、TypeScript
と Vitest を有効化しています。
VueRouter, Pinia, Cypress, ESLint, Prettier のセットアップも可能ですが、本記事で扱う内容に影響しないため、これらは無効化しています。
package.json
create-vue
によって生成されたプロジェクトの package.json
は以下になります。
{
"name": "vue-project",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "run-p type-check build-only",
"preview": "vite preview --port 4173",
"test:unit": "vitest --environment jsdom",
"build-only": "vite build",
"type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false"
},
"dependencies": {
"vue": "^3.2.38"
},
"devDependencies": {
"@types/jsdom": "^20.0.0",
"@types/node": "^16.11.56",
"@vitejs/plugin-vue": "^3.0.3",
"@vue/test-utils": "^2.0.2",
"@vue/tsconfig": "^0.1.3",
"jsdom": "^20.0.0",
"npm-run-all": "^4.1.5",
"typescript": "~4.7.4",
"vite": "^3.0.9",
"vitest": "^0.23.0",
"vue-tsc": "^0.40.7"
}
}
TypeScript 関連として、以下パッケージが追加されています。
-
typescript
: いわずもがな -
vue-tsc
:tsc
をラップし、.vue
ファイルに対して型チェックを行うツール -
@types/jsdom
: jsdom の型パッケージ -
@types/node
: Node.js のビルトインモジュールの型パッケージ -
@vue/tsconfig
:Vue
プロジェクト向けのtsconfig
のベース
tsconfig
の全体像
詳細については順に述べますが、 create-vue
で生成される tsconfig
は、以下のグラフのように複数の設定ファイルから構成されています。
上記グラフにて、破線は参照(references
)、矢印は継承(extends
) を表します。
また、 @vue/tsconfig
プレフィックスの設定は、プロジェクト内に直接存在するわけではなく、@vue/tsconfig
パッケージでさらに切り出された別の設定ファイルになります。
以上をふまえて、各設定ファイルの中身を覗いてみましょう。
tsconfig.json
まず、プロジェクトの TypeScript
設定のベースとなる、tsconfig.json
です。
{
"files": [],
"references": [
{
"path": "./tsconfig.config.json"
},
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.vitest.json"
}
]
}
references
という、あまり見慣れないフィールドが定義されていますが、これは Project References
という TypeScript の機能です。
Project References
は複雑な仕組みではありますが、ここでは TypeScript
プロジェクトを tsconfig.config.json
tsconfig.app.json
tsconfig.vitest.json
の3種類の設定ファイルに基づいて論理分割していると考えて頂ければ大丈夫です。
ルートプロジェクト (tsconfig.json
) 自体は、分割された3プロジェクトに依存しているだけで自身は何もしないため、 files
フィールドが空になっています。
プロジェクトは以下の役割によって論理分割されています。
設定ファイル名 | 役割 |
---|---|
tsconfig.config.json |
vite.config.ts などの設定ファイルを TypeScript で書けるようにする |
tsconfig.app.json |
アプリケーションコードを TypeScript で書けるようにする |
tsconfig.vitest.json |
vitest によるテストコードを TypeScript で書けるようにする |
上記のようにプロジェクトを論理分割することで、単一のプロジェクトでまとめる場合に比べ、以下のようなメリットが得られます。
- アプリケーションコードからテストコードを
import
するような歪な依存を防げる - アプリケーションコードはブラウザで動作するが、テストコードは Node 上で動作するといった、ランタイムに応じた設定を分けることができる
- ビルドの対象を制限することでパフォーマンスが向上する
参照する各設定ファイルについても覗いてみましょう。
tsconfig.config.json
tsconfig.config.json
は、 Vite
などの各種ツールに関する設定ファイルを TypeScript で書くための設定です。
{
"extends": "@vue/tsconfig/tsconfig.node.json",
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*"],
"compilerOptions": {
"composite": true,
"types": ["node"]
}
}
extends
"extends": "@vue/tsconfig/tsconfig.node.json",
extends
している継承元の設定は @vue/tsconfig
パッケージに含まれる Node.js
向けの設定で、以下のようになっています。
上記設定ではさらに ./tsconfig.json
を extends
していますが、これは他の設定ファイル (tsconfig.app.json
, tsconfig.vitest.json
) でも共通なので後述します。
compilerOptions
についても、同様の設定を継承先で行っているので割愛します。
includes
ここでは以下のように、 vite
vitest
cypress
それぞれの設定ファイルのみをビルドの対象としています。
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*"],
このように、 Project References
を用いることで、ディレクトリでなく、ファイル単位でのプロジェクトの論理分割が出来ていることがわかります。
compilerOptions
"compilerOptions": {
"composite": true,
"types": ["node"]
}
vite
vitest
cypress
はいずれも Node.js
上で動作するため、 Node.js
のビルトインモジュールを参照することができます。そのため、 "types": ["node"]
を指定することで TypeScript
でそれに型レベルでもアクセスできるようにします。
また、 "composite": true
は、 Project References
において、このプロジェクトが他のプロジェクトから参照されることがあるという宣言になります。この設定がある場合、プロジェクトを正しく参照できるようにするためのいくつかの制約が発生しますが、詳細は以下ドキュメントを御覧ください。
tsconfig.app.json
tsconfig.app.json
は、 .vue
ファイルを含むアプリケーションコードを TypeScript
で書くための設定です。
{
"extends": "@vue/tsconfig/tsconfig.web.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
extends
"extends": "@vue/tsconfig/tsconfig.web.json",
extends
している継承元の設定は @vue/tsconfig
パッケージに含まれる Web 向けの設定で、以下のようになっています。
Vue 3
は Proxy に依存していることもあり、 ECMAScript
の最低バージョンは ES2016
になります。
また、ランタイムがウェブブラウザである想定なので、 DOM
及び DOM.Iterable
の参照も可能です。
include
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"]
アプリケーションコードは src
ディレクトリにあるものとし、 .ts
のほかに .vue
ファイルも対象としています。
exclude
"exclude": ["src/**/__tests__/*"]
include
で指定されていた src
ディレクトリであっても、テストコードを配置する __tests__
ディレクトリの場合は除外します。
これによって、アプリケーションコードからテストコードをインポートすることを不可能に出来たり、アプリケーションコードのビルド・型チェック時にテストコードを除外できるなどを実現できます。
ただし、この設定の場合はテストコードを __tests__
ディレクトリ以下に配置することが前提となるため注意が必要です。
compilerOptions
"compilerOptions": {
"composite": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
paths
で指定しているインポートパスの alias は、 Vite
側の設定(resolve.alias['@']
)との整合性のための設定で、絶対パスを用いたモジュールのインポートを簡略化させてくれます。
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
tsconfig.vitest.json
tsconfig.vitest.json
は、vitest
によるテストコードを TypeScript
で書くための設定です。
{
"extends": "./tsconfig.app.json",
"exclude": [],
"compilerOptions": {
"composite": true,
"lib": [],
"types": ["node", "jsdom"]
}
}
extends
"extends": "./tsconfig.app.json"
tsconfig.vitest.json
では、これまでとは異なり、先程の tsconfig.app.json
を継承しています。
これはテストコードからはアプリケーションコードをインポートすることになり、テストコード単体での実行はありえないためです。
exclude
"exclude": [],
tsconfig.app.json
を継承しただけだと、 exclude
でテストコード (__tests__
) が除外されてしまう、ここでその設定を上書きしています。
compilerOptions
"compilerOptions": {
"composite": true,
"lib": [],
"types": ["node", "jsdom"]
}
テストコードは Node.js
及び jsdom
上で実行されるため、そのモジュールを参照できるようにします。
@vue/tsconfig/tsconfig.json
最後に、 @vue/tsconfig/tsconfig.web.json
及び @vue/tsconfig/tsconfig.node.json
それぞれが継承している、大元の tsconfig.json
です。
ここでは compilerOptions
のみ定義し、主に .vue
ファイル内で TypeScript
を使用するための基本的なコンパイルオプションが設定されています。
なお、 .vue
ファイルについては tsc
では直接型チェックをすることが出来ないので、 tsc
をラップした vue-tsc
が使用されます。
"jsx": "preserve"
コード中の JSX
を、 h()
などのレンダリング関数に置き換えずに、そのまま保持します。
やや違和感があるかもしれませんが、 Volar における <template>
は、 TypeScript
では JSX
として扱うようにしているようです。
"noImplicitThis": true
Option API
においては this
を通じた Vue インスタンスの操作は頻出であるためです。
("strict": true
を設定している場合は、それに含まれているため不要です)
"isolatedModules": true
Vite
(esbuild) で TypeScript
を解決する場合に、ファイル単体でもビルド可能であることを担保するための対応です。
"preserveValueImports": true
未使用の値インポートをあえて削除しないようにします。
Vue 3
で追加された <script setup>
においては、<script>
内で import
した値をブロック内で参照していなくても、<template>
に自動でバインドされて参照可能になるため、それを勝手に削除されることを避けるための設定です。
"target": "ESNext"
Vite
においては TypeScript
でなく Vite
側でコード変換を行うので、ここでは Vite
自体が使用する動的インポートを使えるバージョンにしておきます。
まとめ + 所感
以上、本記事では、 create-vue によって生成された tsconfig
の構成を元に、Vue 3
+ TypeScript
におけるプロジェクト設定のベストプラクティスを模索しました。
特に Project References
のような、存在は知っていたけど詳しい挙動や活用事例を知らなかった仕組みを深堀りできた良い機会になりました。
また、Vue
は TypeScript
で扱うことが難しいフレームワークであるため、裏側で様々な工夫を積み重ねて実現していることを僅かながら体感することができました。
こういったベストプラクティスを認知しておくことで、いざというときのトラブルシューティングに役立つと思うので、今後もさらなる深堀りをしていきたいと思います。
Discussion