概要
TypeScriptの開発環境を整備していくうえで、ある程度必要そうなチェックが楽にできる環境が出来上がったので記事にします。
環境
- VSCode
- TypeScript
- Node(npm利用のため)
- npm(yarnでも可)
利用する拡張機能・パッケージの紹介
名称 | 説明 |
---|---|
ESLint | 静的コード分析ツール |
Prettier | 自動フォーマットツール |
CSpell | 打ち間違い検出ツール |
npm-run-all | 複数のnpm scriptをまとめて実行 |
ESLint
javasciptの開発を行う際に、ソースコードが決められた構文に沿っているかをチェックするためのツールとして提供されています。
(このようなプログラムを静的に解析して問題点を発見するツールをリンター Linterと呼びます)
ECMAScriptというJavaScriptの標準仕様は年1のペースでバージョンが上がっているため最新の仕様に対応できているかを人の目だけで監視するのは困難です。
また、チーム内で定めたコーディング規約についても命名規則が合っているかをコードレビュー内で確認するのは手間がかかります。
ESLintはこれらの悩みを解決します。
Prettier
綺麗にフォーマットされたコードは可読性を高め、扱いやすいソースコードとなります。
また、複数人で開発する際のフォーマットの仕方を統一することでフォーマットずれによる差分の抑制となります。
CSpell
キーボードでの英単語の打ち間違いの事をタイポ(typo)とも呼びます。
ソースコード上の変数名や関数名のタイポはエンドユーザーに見える部分ではありませんが、それでもシステムの保守性を高めるならタイポ無しを目指すべきです。
また、APIの項目名やテーブルの列名などはタイポが原因でエラーが発生する事もあります。
それに対しエラーが発生した際に気づくよりは打ち間違いをしたタイミングで気づけるのが好ましいです。
npm-run-all
npmにはnpmスクリプトという、CLIで実行するようなコマンドを定義できる機能があります。
例えばビルドコマンドやテストの実行などをコマンドで行う場合に、npmスクリプト機能を用いれば毎回同じコマンドを打ち直す必要は無くなります。
npm run [scriptname]
の形式で実行することが可能です。
但しデフォルトのままでは1個のnpm run [scriptname]
で実行できるコマンドは1個なのでシェルスクリプトなどを作成しそれを呼び出すといった手間が発生します。
npm-run-allはnpmスクリプトの仕組みの中で複数のコマンドを実行するための仕組みが備わっています。
ゴール
コマンド1つで
- JavaScriptやTypeScriptの規約チェック
- プロジェクト内のコーディング規約チェック
- スペルチェック
- TypeScriptファイルのコンパイルによるエラーチェック
- デバック開始
までが行えて、かつ開発時に随時フォーマットと規約チェックが行われる状態を目指します。
構築手順
- プロジェクト用のフォルダ作成
- package.jsonの生成
- npmパッケージのインストール
- ESLint設定ファイルの作成
- Prettier設定ファイルの作成
- CSpell用ファイルの作成
- VSCodeの設定ファイルの作成
- package.jsonにnpmスクリプトを定義
- 動作確認
プロジェクト用のフォルダ作成
まず.vscode
フォルダを直下に作成します。
この中には現在開いているプロジェクト内にのみ影響のある設定を残すことが出来ます。
VSCodeの設定はユーザ単位での設定とプロジェクトごとの設定の2種類があり、同じ設定項目に対しユーザごとの設定とプロジェクトに対する設定の2種類が存在する場合は、プロジェクトの設定が優先されたはずです。
そのためチームで開発する際にVSCode関連の設定の統制を取りたい場合はこのフォルダごとソース管理をする事で可能となります。
次にsrc
フォルダを作成します。
ここは名前の通りソースコードを格納するフォルダです。また、__test__
というフォルダを作成していますがこちらはJestなどでテストをする際のテストコードを格納するフォルダとなっています。
VSCode拡張機能のインストール
設定は後ほど行いますので、まずは以下3つの拡張機能をインストールまででお願いいたします。
ESLint
Prettier
Code Spell Checker
package.jsonの作成
npmは導入完了しているものとして進めます。
npm init
コマンドを実行したら後はひたすらエンターを押して、最後の確認でyes
と打てばpackage.jsonが出来上がります。
$ npm init
...
package name: (typescript_project)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to C:...\typescript_project\package.json:
{
"name": "typescript_project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
Is this OK? (yes) yes
mainの項目がindex.jsになっているなど直すところはありますが、一旦先に進みます。
npmパッケージのインストール
コマンド一覧
TypeScript本体
npm install typescript
ESLint本体+TypeScript検証用パッケージ
npm install -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
Prettier本体+ESLintとPrettierを連携させるためのパッケージ
npm install -D prettier eslint-config-prettier
CSpell本体
npm install -D cspell
npm-run-all本体
npm install -D npm-run-all
加えてお好みでJestを入れたりTypeScriptで利用するフレームワークを入れたりしてください。
ESLint設定ファイルの作成
直下に.eslintrc.json
ファイルを作成します。
今回は
- ES2022の規約に基づいたチェック
- 変数名などの命名規則のチェック
をESLintで行います。
全ての設定の説明は省略します。
{
"root": true,
"env": {
"browser": true,
"es2022": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2022
},
"plugins": ["@typescript-eslint"],
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"rules": {
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "variable",
"modifiers": ["const"],
"format": ["strictCamelCase", "UPPER_CASE"]
},
{
"selector": "variable",
"format": ["strictCamelCase"]
},
{
"selector": "function",
"format": ["strictCamelCase"],
"leadingUnderscore": "allow"
},
{
"selector": "parameter",
"format": ["camelCase"]
},
{
"selector": "class",
"format": ["StrictPascalCase"]
},
{
"selector": "accessor",
"format": ["strictCamelCase"]
},
{
"selector": "typeAlias",
"format": ["StrictPascalCase", "UPPER_CASE"]
},
{
"selector": "method",
"format": ["strictCamelCase"]
},
{
"selector": "interface",
"format": ["StrictPascalCase"]
}
]
}
}
extendsに設定している"eslint:recommended", "plugin:@typescript-eslint/recommended"
が
ESLintが推奨している規約チェックを行うといった設定内容になっています。
また、rulesには個別のルールが設定できるので、その中の一つである命名規則関連のルールを細かく定義しています。
詳しくは公式ドキュメントなどをご参照ください。
Prettier設定ファイルの作成
直下に.prettierrc.json
ファイルを作成します。
作成が出来たら以下公式ドキュメントなどを参考に適用したいフォーマットの設定を記述します。
色々調べて考えた結果
- printWidthは120程度、但しPrettierの仕様的に改行がいまいちと感じる場合はもっと長めに設定する
- インデントはデフォルトと同じスペース2文字
- デフォルトはダブルクォーテーションだがシングルクォーテーションの方が派閥が多い
- 改行コードはLinuxかWIndowsかで違うところで、かつデフォルトはLFなのでWindowsユーザはCRLFにする
- それ以外はデフォルトでも良さそう
という結論に至りました。
{
"printWidth": 120,
"singleQuote": true,
"endOfLine": "crlf",
"overrides": [
{
"files": "*.vue",
"options": {
"printWidth": 240
}
}
]
}
overridesを用いるとファイルの形式ごとに設定を分けることが出来ます。
この書き方の場合vueコンポーネントではprintWidthが240となります。
Vueの場合template部分にHTMLとかを書く関係で120でも短いと感じたため、値を倍にしています。
CSpell用ファイルの作成
.vscodeフォルダ配下にcspell.json
ファイルを作成します。
例えばEnshu(遠州)
のような固有名詞が出た場合、もちろんこれは英単語ではないのでスペルチェックに引っかかります。
しかしこれはスペルミスではない為辞書に登録することでエラーを回避します。
cspell.json
ファイルはこの辞書を管理する役目を果たします。
例えばこのような変数があったとして、青線の部分がタイポではないかと指摘されています。
青線にカーソルを当てるとクイックフィックスが選択可能なので、その中から「Enshuをcspell.jsonに追加」を選びます。
ユーザー側の辞書ではなくプロジェクトごとのフォルダにワードを登録するメリットとしては、これもまたチーム内で同じ辞書を使うことによる統一ができることとなります。
反面、複数のプロジェクトがある場合に何度も同じワードを辞書に登録する必要がありますが、cspell.jsonの中身をコピーすればその手間は省けます。
TypeScriptコンパイル用のファイル作成
npx tsc --init
コマンドでtsconfig.json
ファイルを作成します。
$ npx tsc --init
Created a new tsconfig.json with:
TS
target: es2016
module: commonjs
strict: true
esModuleInterop: true
skipLibCheck: true
forceConsistentCasingInFileNames: true
You can learn more at https://aka.ms/tsconfig
完了するとやけにコメントアウトがたくさんな設定ファイルが直下に出来上がります。
本来であれば作成するプロジェクトごとに必要な項目を精査して設定ファイルを作りますが今回は割愛します。
strictを指定して厳格なチェックを行うのが一般的で、targetやmoduleの指定をどうするかをちゃんと考えるのが大事です。
※targetはコンパイル成功時にどのバージョンのjavascriptで出力するかを示す設定値となります。
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true ,
"skipLibCheck": true ,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src/*.ts", "src/**/*.ts", "src/**/*.d.ts"]
}
VSCodeの設定ファイルの作成
.vscodeフォルダ配下にsetting.json
ファイルを作成します。
ここには主にESLintやPrettierをファイル保存時に実行できるような設定を記述します。
下の方にあるインデントをスペース2個に強制する設定は恐らく書かなくてもPrettierで自動フォーマットしてくれるので問題ないです。
editor.formatOnSave
がセーブ時にフォーマットを自動実行
editor.formatOnPaste
がテキストの貼り付け時にフォーマットを自動実行
editor.formatOnType
が文字入力を行った行のフォーマットを自動実行
の設定項目なので全てTrueにし、都度Prettierによるフォーマットが行われるようにしています。
また、editor.codeActionsOnSave
ではESLintによるチェックをセーブのたびに行えるように設定しています。
{
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.formatOnType": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"editor.insertSpaces": true,
"editor.indentSize": 2
}
package.jsonにnpmスクリプトを定義
現在はpackage.jsonにscriptsという項目に
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
}
このような内容が入っていると思います。
これを
"scripts": {
"pipeline:debug": "run-s cspell lint:report compile debug",
"pipeline:build": "run-s cspell lint:report compile build",
"compile": "tsc",
"cspell": "cspell src/**/*",
"lint": "eslint src",
"lint:report": "eslint src --format=html > eslint_result.html",
"debug": "echo debug start!!",
"build": "echo build success!!"
}
に書き換えてください。
pipeline:debug
はデバッグ実行する際に実行するコマンド
pipeline:build
はビルドする際に実行するコマンドを想定しています。
デバッグ実行やビルドを行う前に
- スペルチェック
- ESLintによるチェック
- コンパイルによるエラーチェック
を行い、途中でエラーが発生したら終了する仕組みにしています。
pipeline:○○
のコマンドの中にrun-s
とありますが、これがnpm-run-allの機能の1つです。
run-s
の後ろに指定したnpmスクリプトを順番に実行してくれます。
こうすることで開発者はnpm run pipeline:debug
やnpm run pipeline:build
を使うことさえ意識すれば開発時にチェックしてもらいたい諸々をツールたちが自動で確認して問題ないと判断したうえでデバッグ実行やビルドを行うフローが出来上がります。
なお、ESLintはWARNINGレベル以下のみの場合はエラーとして途中終了しない為、WARNINGレベルで必ず対応してほしいチェック項目が存在する場合はエラーレベルまで引き上げるように.eslintrc.jsonのrulesに記載する必要があります。
設定完了後の再起動
ここまで終わったらVSCodeを再起動してください。
動作確認
①打ち間違いが存在する場合
拡張機能によりPROBLEMSにお知らせしてくれます。
対応せずにデバッグ実行を実施した場合はエラーとして弾かれて途中で終了します。
$ npm run pipeline:debug
> typescript_project@1.0.0 pipeline:debug
> run-s cspell lint:report test compile debug
> typescript_project@1.0.0 cspell
> cspell src/**/*
1/2 .\src\__test__\hogehoge.spec.ts 655.99ms
2/2 .\src\hogehoge.ts 33.58ms X
C:...\typescript_project\typescript_project\src\hogehoge.ts:1:11 - Unknown word (Hoge)
CSpell: Files checked: 2, Issues found: 1 in 1 files
ERROR: "cspell" exited with 1.
②ES2022の規約に沿っていなかったり、命名規則が間違っている場合
試しにvarを使用したり、本来キャメルケースまたはアッパーケースの変数名をパスカルケースで記述すると問題の部分にエラーとして表示されます。
警告の方は、用意した変数が他の処理で全く使われていないので警告として出ています。
ESLintの機能だと思われますが、保存するときにvarはconstに変更されました。
エラーをすべて対応せずにデバッグ実行を実施した場合はエラーとして弾かれて途中で終了します。
$ npm run pipeline:debug
> typescript_project@1.0.0 pipeline:debug
> run-s cspell lint:report test compile debug
> typescript_project@1.0.0 cspell
> cspell src/**/*
1/2 .\src\__test__\hogehoge.spec.ts 571.36ms
2/2 .\src\hogehoge.ts 18.22ms
CSpell: Files checked: 2, Issues found: 0 in 0 files
> typescript_project@1.0.0 lint:report
> eslint src --format=html > eslint_result.html
ERROR: "lint:report" exited with 1.
ESLintの結果はコンソールに大量に出ると見づらいためHTMLに出力しています。
③コンパイルエラーが発生する場合
存在しない変数の値を代入しようとしてみました。
$ npm run pipeline:debug
> typescript_project@1.0.0 pipeline:debug
> run-s cspell lint:report compile debug
> typescript_project@1.0.0 cspell
> cspell src/**/*
1/1 .\src\hogehoge.ts 622.14ms
CSpell: Files checked: 1, Issues found: 0 in 0 files
> typescript_project@1.0.0 lint:report
> eslint src --format=html > eslint_result.html
> typescript_project@1.0.0 compile
> tsc
src/hogehoge.ts:3:19 - error TS2552: Cannot find name 'fileName3'. Did you mean 'fileName'?
3 const fileName2 = fileName3;
~~~~~~~~~
src/hogehoge.ts:1:7
1 const fileName = 'test.txt';
~~~~~~~~
'fileName' is declared here.
Found 1 error in src/hogehoge.ts:3
ERROR: "compile" exited with 2.
まとめ
これらのパッケージと拡張機能によりコーディング規約の統一、ミスの事前発見が可能となりました。
また、開発環境のインストール以外はリポジトリを落としてもらえれば簡単に導入可能となっています。
今回はVueやJestなどを使う際の考慮は除いていますが、環境作成のサポートになれば幸いです。