AngularプロジェクトにHuskyを導入する

今回、Huskyを導入しますが、これにはトレードオフ(メリットとデメリット)があります。

メリット:

  1. コードの品質を保つ: Huskyはコードがリポジトリにコミットされる前に自動的にリントやテストを行うことができます。これにより、間違ったコードやコーディング規約に適合しないコードがマージされるのを防ぐことができます。
  2. 自動化: Huskyを設定することで、手動でリントやテストを行う手間を省くことができます。
  3. チームの一貫性: Huskyをプロジェクトに組み込むことで、全ての開発者が同じリントやテストを実行することを保証することができます。

デメリット:

  1. 初期設定の複雑さ: Huskyの設定は少々複雑であり、最初に設定する際には時間と労力が必要となります。
  2. 間違った設定による問題: Huskyが間違って設定された場合、全く関係のないファイルがコミットの対象になってしまったり、正常なコードでもコミットできなくなる可能性があります。
  3. コミットの遅延: もし大きなプロジェクトでテストが時間を要する場合、Huskyによりコミットが遅延する可能性があります。ただし、これは通常はリントやテストの速度問題であり、Husky自体の問題ではありません。

最も注意しなければならないのは、修正した箇所とは関係ない箇所でリントエラーが発生してコミットができないという自体が起こる可能性があるか、という点です。特に途中から導入する場合はリントエラーだらけでその対応に追われるということがあります。

リントエラーが解消したコードがコミットされることが理想的ではありますが、時と場合によってはそれよりもコミットすることが優先事項であることもありますし、プロジェクトの事情で修正箇所以外のエラーは一旦見なかったことにしたいということもあると思います。

また、コードベースが非常に大きく、リントに時間がかかってコミットがストレスになるというリスクもあります。こちらについては対策がありますので、併せてご紹介します。

Huskyとは

Huskyは、JavaScriptプロジェクトにおいてGitのフック(特定のGitイベントが発生した時に実行されるスクリプト)を簡単に管理できるようにするツールです。

Gitフックは、コミット、プッシュなどのGit操作の前後に自動的に実行されるスクリプトです。これを活用することで、コードの品質を一定に保つためのチェックやテストを自動化したり、特定の操作を制限したりすることが可能になります。

しかし、GitフックはGitリポジトリごとに設定され、通常はGitリポジトリ自体と一緒にバージョン管理されません。これは、他の開発者が同じフックを設定するのを難しくするだけでなく、フックの変更を追跡するのも困難にします。

Huskyはこれらの問題を解決します。Huskyを使用すると、フックはプロジェクトの一部としてバージョン管理でき、設定はすべての開発者間で共有できます。また、Huskyの設定は通常、プロジェクトのpackage.jsonファイルに記述され、設定の変更もGitを通じて追跡することができます。

このように、HuskyはJavaScriptプロジェクトにおけるGitフックの管理を効率化し、プロジェクト全体のコード品質を向上させる強力なツールとなることができます。

Huskyのインストール

まずはじめにHuskyをインストールします。

$ npm install husky --save-dev

added 1 package, and audited 1098 packages in 1s

165 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

インストール自体に難しいところはありません。

pre-commitフックを設定する

では早速pre-commitフックを設定しましょう。

$ npx husky install
husky - Git hooks installed

上記のコマンドを実行すると.huskyディレクトリが作成されます。

次にpre-commitフックを追加します。実行するコマンドは前回設定したnpm run lintにします。

$ npx husky add .husky/pre-commit "npm run lint"
husky - created .husky/pre-commit

では実際に実行してみましょう。Huskyをインストールしたときの変更のコミットがまだなので、これをコミットします。

$ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   package-lock.json
        modified:   package.json

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        .husky/

no changes added to commit (use "git add" and/or "git commit -a")
$ git add .  
$ git commit -m 'install husky'

> angular-tutorial@0.0.0 lint
> ng lint


Linting "angular-tutorial"...

All files pass linting.

[main e0c8f78] install husky
 3 files changed, 21 insertions(+)
 create mode 100755 .husky/pre-commit

コミット時にリントが実行されていることが確認できます。

pre-commitフックでフォーマットを行う

次にpre-commitフックでPrettierを実行するようにしてみましょう。ついでに動作を確認もしてみます。

まず、Huskyのコマンドにはpre-commitフックを削除するコマンドがありません。では変更したい場合はどのようにするかを見ていきます。

現在の状態を確認する

まず、現在の状態はnpm run lintを設定してある状態です。

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run lint

ここに新たなコマンドを追加する場合は変わらずnpx husky addコマンドを使用します。

$ npx husky add .husky/pre-commit "npm run format"
husky - updated .husky/pre-commit

npx husky addコマンドを実行することで現在設定されているコマンドに加えて新たなコマンドがpre-commitフックに追加されます。

次にnpm run formatを削除してnpm run lintだけの状態にしてみます。これを実現するには、npx husky setコマンドを使用してnpm run lintを設定することでnpm run lintだけの状態になります。

$ npx husky set .husky/pre-commit "npm run lint"
husky - created .husky/pre-commit

このコマンドの実行するとnpm run lintだけになっていることが確認できます。

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run lint

最後にすべて削除する方法を確認します。先ほど使用したnpx husky setコマンドを使って削除ができます。

$ npx husky set .husky/pre-commit ""
husky - created .husky/pre-commit

上記コマンドを実行するとコマンドが削除されます。

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"


pre-commitフックでlintとフォーマットの設定を行う

話を戻してpre-commitフックにnpm run lintコマンドとnpm run formatを設定します。npx husky addコマンドを2回呼び出してコマンドを追加します。

$ npx husky add .husky/pre-commit "npm run lint"
husky - updated .husky/pre-commit
$ npx husky add .husky/pre-commit "npm run format"
husky - updated .husky/pre-commit

これにより期待したとおりコマンドが登録されます。

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"


npm run lint
npm run format

では、動作確認をしていきます。.husky/pre-commitが変更されているのでこれをコミットすることで動作確認とします。

$ git add .
$ git commit -m 'update pre-commit hook'

> angular-tutorial@0.0.0 lint
> ng lint


Linting "angular-tutorial"...

All files pass linting.


> angular-tutorial@0.0.0 format
> prettier "src/**/*.{js,jsx,ts,tsx,html,css,scss}" --write

src/app/app.component.css 9ms
src/app/app.component.html 85ms
src/app/app.component.spec.ts 71ms
src/app/app.component.ts 3ms
src/app/app.module.ts 2ms
src/index.html 1ms
src/main.ts 2ms
src/styles.css 0ms
[main aeba2e0] update pre-commit hook
 1 file changed, 2 insertions(+)

git commitをするとリントに続いてフォーマットが行われていることが確認できます。

リントおよびフォーマットの対象を限定する

現在は非常にコードが少ないためリントやフォーマットの時間がそれほどかかりません。ただ、実際のプロジェクトでは非常にたくさんのコードがある場合もあります。そのような場合、リントやフォーマットに時間がかかるだけでなく、対応した範囲とは関係ない箇所でエラーになってしまい、コミットがいつまでもできない、ということが起きることは容易に想像できます。

こういった問題を解決する一つの方法として、lint-stagedを導入します。

lint-stagedをインストール

普通のパッケージと同じようにインストールしていきます。

$ npm install --save-dev lint-staged

added 37 packages, and audited 1135 packages in 3s

180 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

lint-stagedを設定する

lint-stagedを設定していきます。まずはHuskyがpre-commitフックで実行するコマンドをnpx lint-stagedに変更します。

$ npx husky set .husky/pre-commit "npx lint-staged"
husky - created .husky/pre-commit

次にlint-stagedでnpm run lintコマンドとnpm run formatを実行するようにします。

{
  "name": "angular-tutorial",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "watch": "ng build --watch --configuration development",
    "test": "ng test",
    "lint": "ng lint",
    "format": "prettier \"src/**/*.{js,jsx,ts,tsx,html,css,scss}\" --write"
  },
+ "lint-staged": {
+   "*.{js,jsx,ts,tsx}": [
+     "npm run lint"
+   ],
+   "*.{js,jsx,ts,tsx,html,css,scss}": [
+     "npm run format"
+   ]
+ },
  "private": true,
  "dependencies": {
    "@angular/animations": "^16.1.0",
    "@angular/common": "^16.1.0",
    "@angular/compiler": "^16.1.0",
    "@angular/core": "^16.1.0",
    "@angular/forms": "^16.1.0",
    "@angular/platform-browser": "^16.1.0",
    "@angular/platform-browser-dynamic": "^16.1.0",
    "@angular/router": "^16.1.0",
    "rxjs": "~7.8.0",
    "tslib": "^2.3.0",
    "zone.js": "~0.13.0"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^16.1.6",
    "@angular-eslint/builder": "16.1.0",
    "@angular-eslint/eslint-plugin": "16.1.0",
    "@angular-eslint/eslint-plugin-template": "16.1.0",
    "@angular-eslint/schematics": "16.1.0",
    "@angular-eslint/template-parser": "16.1.0",
    "@angular/cli": "~16.1.6",
    "@angular/compiler-cli": "^16.1.0",
    "@types/jasmine": "~4.3.0",
    "@typescript-eslint/eslint-plugin": "5.62.0",
    "@typescript-eslint/parser": "5.62.0",
    "eslint": "^8.44.0",
    "eslint-config-prettier": "^8.9.0",
    "husky": "^8.0.3",
    "jasmine-core": "~4.6.0",
    "karma": "~6.4.0",
    "karma-chrome-launcher": "~3.2.0",
    "karma-coverage": "~2.2.0",
    "karma-jasmine": "~5.1.0",
    "karma-jasmine-html-reporter": "~2.1.0",
    "lint-staged": "^13.2.3",
    "prettier": "^3.0.0",
    "typescript": "~5.1.3"
  }
}

"lint-staged"がコマンドの設定を行っている箇所です。リントとフォーマットで対象としたいファイルが異なるため分けて設定しています。

pre-commitフックを確認するまえに、npx lint-stagedコマンドを実行して動作を確認してみましょう。

$ npx lint-staged
→ No staged files found.

ステージされているファイルが何もないため、処理が行われませんでした。

では、現在変更されているコードをステージしてから実行してみましょう。

$ npx lint-staged
→ No staged files match any configured task.

少しメッセージが変わりました。ステージされているファイルはありましたが、条件にマッチするファイルがなかったため、処理が行われませんでした。

では、1つだけ処理対象となるファイルを変更してステージして動作を確認してみましょう。

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  title = 'angular-tutorial';
}

app.component.tsを以下のように変更します。

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
* title = 'angular-tutorial1';
}

titleをちょっとだけ変更しています。

変更をステージして実行してみます。

$ git add .
$ npx lint-staged
✔ Preparing lint-staged...
❯ Running tasks for staged files...
  ❯ package.json — 4 files
    ❯ *.{js,jsx,ts,tsx} — 1 file
      ✖ npm run lint [FAILED]
    ✔ *.{js,jsx,ts,tsx,html,css,scss} — 1 file
↓ Skipped because of errors from tasks. [SKIPPED]
✔ Reverting to original state because of errors...
✔ Cleaning up temporary files...

✖ npm run lint:
Error: Invalid values:
  Argument: project, Given: "/Users/t0k0sh1/Workspace/angular-tutorial/src/app/app.component.ts", Choices: "angular-tutorial"

> angular-tutorial@0.0.0 lint
> ng lint /Users/t0k0sh1/Workspace/angular-tutorial/src/app/app.component.ts 

実行したところエラーになりました。

結論からいうと、lint-stagedに指定したコマンドが原因です。リントを行うためにeslintを使用しているのではなく、ng lintを使用しています。

ng lintはプロジェクト全体に対して実行するコマンドのため、ステージしたファイルに対して限定的に使用することができません。ですので、少しコマンドを変更します。

{
  "name": "angular-tutorial",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "watch": "ng build --watch --configuration development",
    "test": "ng test",
    "lint": "ng lint",
    "format": "prettier \"src/**/*.{js,jsx,ts,tsx,html,css,scss}\" --write"
  },
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
*     "eslint --fix"
    ],
    "*.{js,jsx,ts,tsx,html,css,scss}": [
      "npm run format"
    ]
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^16.1.0",
    "@angular/common": "^16.1.0",
    "@angular/compiler": "^16.1.0",
    "@angular/core": "^16.1.0",
    "@angular/forms": "^16.1.0",
    "@angular/platform-browser": "^16.1.0",
    "@angular/platform-browser-dynamic": "^16.1.0",
    "@angular/router": "^16.1.0",
    "rxjs": "~7.8.0",
    "tslib": "^2.3.0",
    "zone.js": "~0.13.0"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^16.1.6",
    "@angular-eslint/builder": "16.1.0",
    "@angular-eslint/eslint-plugin": "16.1.0",
    "@angular-eslint/eslint-plugin-template": "16.1.0",
    "@angular-eslint/schematics": "16.1.0",
    "@angular-eslint/template-parser": "16.1.0",
    "@angular/cli": "~16.1.6",
    "@angular/compiler-cli": "^16.1.0",
    "@types/jasmine": "~4.3.0",
    "@typescript-eslint/eslint-plugin": "5.62.0",
    "@typescript-eslint/parser": "5.62.0",
    "eslint": "^8.44.0",
    "eslint-config-prettier": "^8.9.0",
    "husky": "^8.0.3",
    "jasmine-core": "~4.6.0",
    "karma": "~6.4.0",
    "karma-chrome-launcher": "~3.2.0",
    "karma-coverage": "~2.2.0",
    "karma-jasmine": "~5.1.0",
    "karma-jasmine-html-reporter": "~2.1.0",
    "lint-staged": "^13.2.3",
    "prettier": "^3.0.0",
    "typescript": "~5.1.3"
  }
}

実行するコマンドをnpm run lintからeslint --fixに変更しました。変更したついでに--fixオプションをつけて修正もできるようにしてあります。

再度実行してみましょう。

$ npx lint-staged
✔ Preparing lint-staged...
✔ Hiding unstaged changes to partially staged files...
✔ Running tasks for staged files...
✔ Applying modifications from tasks...
✔ Restoring unstaged changes to partially staged files...
✔ Cleaning up temporary files...

今度はすべて成功しています。

最後にコードを整理してコミット&プッシュ

app.component.tsに加えた変更は不要なので、戻しておきます。

$ git restore --staged src/app/app.component.ts
$ git checkout -- src/app/app.component.ts

それ以外の変更は正式に採用するので、ステージしてコミットし、これまでの変更も含めてプッシュしておきます。

$ git add .
$ git commit -m 'set up husky and lint-staged'
→ No staged files match any configured task.
[main 58ad1f8] set up husky and lint-staged
 3 files changed, 504 insertions(+), 3 deletions(-)
$ git push
Enumerating objects: 19, done.
Counting objects: 100% (19/19), done.
Delta compression using up to 10 threads
Compressing objects: 100% (13/13), done.
Writing objects: 100% (16/16), 5.23 KiB | 892.00 KiB/s, done.
Total 16 (delta 7), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (7/7), completed with 3 local objects.
To github.com:t0k0sh1/angular-tutorial.git
   f20d6df..58ad1f8  main -> main

まとめ

少し長くなりましたが、Huskyとlint-stagedを導入してリントとフォーマットをpre-commitフックで実行できるようになりました。

もう少し設定が必要ですが、それは必要になったときに設定することとします。

開発を行う前に結構時間がかかりましたが、こういった設定は最初の段階で整備しておいた方がよいです。開発途中で導入しようとすると、これまでの分の修正に追われ、本来やるべきことに集中できなくなりますので、最初に説明しました。

次回はTailwind CSSを導入してコンポーネントを作成してみたいと思います。

AngularプロジェクトにPrettierとESLintを導入する

AngularプロジェクトではPrettierとESLintは最初から導入されてはいません。プロジェクト作成後にセットアップすることで利用可能となります。

実際のプロジェクトではPrettierとESLintは導入した方がよいので、実際にセットアップしていきます。

ESLintを導入する

Angular CLIにはng lintというコマンドが用意されています。プロジェクト作成直後はESLintが導入されていないため、lintの実行の代わりにELintのインストールを行うことができます。実際にやってみましょう。

$ ng lint
? Would you like to share pseudonymous usage data about this project with the Angular Team
at Google under Google's Privacy Policy at https://policies.google.com/privacy. For more
details and how to change this setting, see https://angular.io/analytics. No
Global setting: enabled
Local setting: disabled
Effective status: disabled
Cannot find "lint" target for the specified project.
You can add a package that implements these capabilities.

For example:
  ESLint: ng add @angular-eslint/schematics

Would you like to add ESLint now? Yes
ℹ Using package manager: npm
✔ Found compatible package version: @angular-eslint/schematics@16.1.0.
✔ Package information loaded.

The package @angular-eslint/schematics@16.1.0 will be installed and executed.
Would you like to proceed? Yes
✔ Packages successfully installed.
    
    All @angular-eslint dependencies have been successfully installed 🎉
    
    Please see https://github.com/angular-eslint/angular-eslint for how to add ESLint configuration to your project.
    
    We detected that you have a single project in your workspace and no existing linter wired up, so we are configuring ESLint for you automatically.
    
    Please see https://github.com/angular-eslint/angular-eslint for more information.
    
CREATE .eslintrc.json (991 bytes)
UPDATE package.json (1424 bytes)
UPDATE angular.json (3085 bytes)
✔ Packages installed successfully.

一番最初の質問はAngular CLIの使用状況データを提供するかどうかを聞いています。その後に@angular-eslint/schematicsのインストールを行うかの確認があります。基本的にはデフォルトのまま進めていただいて構いません。

インストールが完了すると、以下が行われます。

  • AngularプロジェクトでESLintを使用する基本的なパッケージがインストールされる
  • lintを実行するためのスクリプトがpackage.jsonに追加される
  • ESLintの設定ファイル(.eslintrc.json)が追加される
  • angular.jsonにESLintに関する設定が追加される

これでlintが使用可能になります。実際に使ってみましょう。

$ ng lint

Linting "angular-tutorial"...

All files pass linting.

ESLint拡張機能を導入する

Visual Studio Codeを使用している場合は、ESLint拡張機能を導入するとリアルタイムに違反をチェックできるようになりますので、導入をお勧めします。

拡張機能にはいくつか設定項目がありますが、特に設定変更しなくても動作します。

Prettierを導入する

次にPrettierを導入します。こちらはAngular CLIに専用のコマンドが用意されているわけではないため、インストールおよび設定作業は手動で行います。

Prettierをインストールしますが、先にインストールしたESLintと競合した設定が一部あるため、eslint-config-prettierを併せてインストールします。

$ npm install --save-dev prettier eslint-config-prettier

added 2 packages, and audited 1097 packages in 4s

164 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

これでインストール作業が完了しました。続いて設定作業を行います。

ESLintとPrettierを併用できるようにする設定を追加する

.eslintrc.jsonにPrettierを併用できるようにするための設定を追加します。

{
  "root": true,
  "ignorePatterns": ["projects/**/*"],
  "overrides": [
    {
      "files": ["*.ts"],
      "extends": [
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended",
        "plugin:@angular-eslint/recommended",
        "plugin:@angular-eslint/template/process-inline-templates",
+       "prettier"
      ],
      "rules": {
        "@angular-eslint/directive-selector": [
          "error",
          {
            "type": "attribute",
            "prefix": "app",
            "style": "camelCase"
          }
        ],
        "@angular-eslint/component-selector": [
          "error",
          {
            "type": "element",
            "prefix": "app",
            "style": "kebab-case"
          }
        ]
      }
    },
    {
      "files": ["*.html"],
      "extends": [
        "plugin:@angular-eslint/template/recommended",
        "plugin:@angular-eslint/template/accessibility"
      ],
      "rules": {}
    }
  ]
}

"prettier"を追記します。前の行にカンマをつけるのを忘れないように気をつけてください。

.prettierrc.jsonを作成する

Prettierの設定ファイルである.prettier.jsonを作成します。

$ touch .prettierrc.json

ファイルを作成したら以下のように記述します。

{
  "printWidth": 120,
  "singleQuote": true
}

ここでは1行の文字数を120文字、文字列でシングルクォーテーションを使用するように設定しています。シングルクォーテーションを使用する設定はEditorConfigの設定ファイル.editorconfigに記載されている

[*.ts]
quote_type = single

と一致するように設定してください。ダブルクォーテーションを使用したい場合は、.prettierrc.jsonの設定を"singleQuote": falseにし、.editorconfigの設定をquote_type = doubleに変更します。ただし、Angularプロジェクトを作成したときに自動生成されているコードはシングルクォーテーションを使用しているため、特段理由がなければシングルクォーテーションを使用してください。

コマンドでフォーマットできるようにする

package.jsonを編集してコマンドでフォーマットできるようにしましょう。

{
  "name": "angular-tutorial",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "watch": "ng build --watch --configuration development",
    "test": "ng test",
    "lint": "ng lint",
+   "format": "prettier \"src/**/*.{js,jsx,ts,tsx,html,css,scss}\" --write"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^16.1.0",
    "@angular/common": "^16.1.0",
    "@angular/compiler": "^16.1.0",
    "@angular/core": "^16.1.0",
    "@angular/forms": "^16.1.0",
    "@angular/platform-browser": "^16.1.0",
    "@angular/platform-browser-dynamic": "^16.1.0",
    "@angular/router": "^16.1.0",
    "rxjs": "~7.8.0",
    "tslib": "^2.3.0",
    "zone.js": "~0.13.0"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^16.1.6",
    "@angular-eslint/builder": "16.1.0",
    "@angular-eslint/eslint-plugin": "16.1.0",
    "@angular-eslint/eslint-plugin-template": "16.1.0",
    "@angular-eslint/schematics": "16.1.0",
    "@angular-eslint/template-parser": "16.1.0",
    "@angular/cli": "~16.1.6",
    "@angular/compiler-cli": "^16.1.0",
    "@types/jasmine": "~4.3.0",
    "@typescript-eslint/eslint-plugin": "5.62.0",
    "@typescript-eslint/parser": "5.62.0",
    "eslint": "^8.44.0",
    "eslint-config-prettier": "^8.9.0",
    "jasmine-core": "~4.6.0",
    "karma": "~6.4.0",
    "karma-chrome-launcher": "~3.2.0",
    "karma-coverage": "~2.2.0",
    "karma-jasmine": "~5.1.0",
    "karma-jasmine-html-reporter": "~2.1.0",
    "prettier": "^3.0.0",
    "typescript": "~5.1.3"
  }
}

コマンド名は何でも構いませんが、ここではnpm run formatで実行できるようにしています。

これから動作確認のためにコマンドを実行しますが、実行するといくつかのファイルが変更されます。変更したくない場合は記事を読むだけにしてください。

$ npm run format

> angular-tutorial@0.0.0 format
> prettier "src/**/*.{js,jsx,ts,tsx,html,css,scss}" --write

src/app/app.component.css 9ms
src/app/app.component.html 84ms
src/app/app.component.spec.ts 72ms
src/app/app.component.ts 3ms
src/app/app.module.ts 3ms
src/index.html 1ms
src/main.ts 2ms
src/styles.css 1ms

実行するといくつかのファイルについて変更されたことが表示されます。

Prettier – Code formatter拡張機能を導入する

Visual Studio Codeを使用している場合はPrettier – Code formatter拡張機能を導入することで、コードフォーマットを自動化することができます。

こちらは拡張機能導入後にいくつかの設定を行う必要があります。主な設定項目は以下の3つです。

  • Format On Paste
  • Format On Save
  • Default Formatter

他からコピーしたコードをペーストしたときにフォーマットする場合はFormat On Pasteの設定にチェックを入れてください。これは好みかもしれませんが、ペースト後に少しいじってから保存することがほとんどなので私はチェックを入れていません。

次に保存時にフォーマットを行うようにすることでコードフォーマットを自動化できます。基本的にはチェックを入れていただくとよいのですが、自動フォーマットを有効化していると操作性が気になる場合があるかもしれません。実際に試していただいて好みに合うように調整してください。

デフォルトのフォーマッタをPrettierに変更します。これによりPrettierが有効化します。一つだけ注意点があり、PrettierはPythonには対応していません。そのため、Pythonでの開発もVisual Studio Codeで行う場合は、デフォルトのフォーマッタをPrettierに変更した上で、settings.jsonに以下の記述を追加してください。

  "[python]": {
    "editor.defaultFormatter": null,
  },

これにより.pyファイルではPrettierをフォーマッタとして使用しなくなります。少し脱線しますが、blackをフォーマッタとして使用している場合は、

{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "python.formatting.provider": "black",
  "[python]": {
    "editor.defaultFormatter": null,
  },
}

のような記載になります。

変更をコミットする

前回からずっとコミットしていなかったので、ここまでの変更をコミットしておきます。

$ git add .
$ git commit -m 'install eslint and prettier'
[main f20d6df] install eslint and prettier
 11 files changed, 3137 insertions(+), 327 deletions(-)
 create mode 100644 .eslintrc.json
 create mode 100644 .prettierrc.json

本記事で使用しているプロジェクトは以下にありますので、必要に応じて参考にしてみてください。

まとめ

ちょっと長くなったので今回はここまでにします。次回はHuskyを使ってpre-commitフックでESLintとPrettierを自動実行するようにしたいと思います。

ただし、若干のトレードオフがあるため、その点についても解説します。

Node.jsで開発環境をセットアップする(ESLint、Prettier、husky)

Node.jsの開発環境で、以下の機能をセットアップする方法について解説します。

  • ESLintによるlint
  • Prettierによるコードフォーマット
  • huskyによるpre-commit git hooks

開発環境を作成する

最初に構築対象となる開発環境を用意します。

$ npm init -y
Wrote to /Users/t0k0sh1/workspace/node_lint/package.json:

{
  "name": "node_lint",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

今回の説明とは関係ないため、デフォルトのままで進めます。

パッケージをインストールする

次に必要なパッケージをインストールします。

  • eslint – lintツール
  • prettier – formatterツール
  • eslint-config-prettier – eslintのフォーマットがprettierと競合する場合はeslint側をOFFにする
  • husky – pre-commit git hooksツール
$ npm i -D eslint eslint-config-prettier prettier husky

added 85 packages, and audited 86 packages in 4s

15 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

eslint-plugin-prettierは非推奨になっているため、eslint-config-prettierを使用しています。

インストール後のpackage.jsonは以下のようになっています。

// package.json
{
  "name": "node_lint",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "eslint": "^8.13.0",
    "eslint-config-prettier": "^8.5.0",
    "husky": "^7.0.4",
    "prettier": "^2.6.2"
  }
}

lintの設定を行う

まずはESLintの設定を行います。ここでは必要最低限の設定にしています。prettiereslint:recommendの後に書いてください。

// .eslintrc.json
{
  "extends": [
    "eslint:recommended",
    "prettier"
  ]
}

次にeslintでのlint(修正あり)とprettierでのフォーマットを行うスクリプトを追加します。

// package.json
{
  "name": "node_lint",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "lint": "eslint --cache --fix . && prettier --write ."
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "eslint": "^8.13.0",
    "eslint-config-prettier": "^8.5.0",
    "husky": "^7.0.4",
    "prettier": "^2.6.2"
  }
}

コードを作成して動作確認を行う

実際のコードを作成して動作確認をしておきましょう。

// index.js
function hello(name) {
  return 'Hello, ' + name;
}

console.log(hello("John"));

簡単なプログラムを書きました。動作確認ができるようにlintでエラーになるコードになっています。

では動作確認します。

$ node index.js
Hello, John

エラーにならず動作していることを確認できました。

では、このコードに対してlintprettierを実行します。

$ npm run lint

> node_lint@1.0.0 lint
> eslint --cache --fix . && prettier --write .


/Users/t0k0sh1/workspace/node_lint/index.js
  6:1  error  'console' is not defined  no-undef

✖ 1 problem (1 error, 0 warnings)

consoleが未定義であるとエラーになりました。これは後で修正(除去)しますが、今はこのままにしておきます。

ここまでに作成したコードをコミットしておきます。

git initコマンドでgitリポジトリを初期化します。

$ git init
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint: 
hint:   git config --global init.defaultBranch <name>
hint: 
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint: 
hint:   git branch -m <name>
Initialized empty Git repository in /Users/t0k0sh1/workspace/node_lint/.git/

コミットするにあたり、node_modules.eslintcacheは対象から外したいので、.gitignoreを作成しておきます。

// .gitignore
node_modules/
.eslintcache

作成したファイルをステージングエリアに追加します。

$ git add .

ローカルリポジトリにコミットしておきます。

$ git commit -m 'first commit'
[master (root-commit) ec47452] first commit
 5 files changed, 1682 insertions(+)
 create mode 100644 .eslintrc.json
 create mode 100644 .gitignore
 create mode 100644 index.js
 create mode 100644 package-lock.json
 create mode 100644 package.json

pre-commit git hooksを設定する

huskyを使ってpre-commit git hooksを設定していきます。手順は公式の手順(https://github.com/typicode/husky)に従いますが、pre-commitに追加するコマンドがnpm run lintになっている点にだけ注意してください(公式の手順ではnpm test

$ npm set-script prepare "husky install"
$ npm run prepare

> node_lint@1.0.0 prepare
> husky install

husky - Git hooks installed
$ npx husky add .husky/pre-commit "npm run lint"
husky - created .husky/pre-commit
$ git add .husky/pre-commit

手順どおりに進めると、package.jsonがステージングエリアに追加されていないため、これを追加してローカルリポジトリにコミットします。

$ git add package.json
$ git commit -m 'setup husky'

> node_lint@1.0.0 lint
> prettier --write '**/*.js' && eslint .

index.js 25ms

/Users/t0k0sh1/workspace/node_lint/index.js
  6:1  error  'console' is not defined  no-undef

✖ 1 problem (1 error, 0 warnings)

husky - pre-commit hook exited with code 1 (error)

先ほどコマンド実行で確認した時と同じようにconsoleが未定義であるとエラーになりました。

pre-commitが機能していることを確認できましたが、念のためコミットされていないかを確認しておきます。

$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   .husky/pre-commit
        modified:   package.json

コミットが行われておらず、pre-commitが正しく動作していることを確認できました。

コードを修正して再度コミットする

コードを修正して再度コミットしましょう。

ここでは単純にconsole.log関数を削除しておきます。

// index.js
function hello(name) {
  return "Hello, " + name;
}

hello("John");

念のため、コマンドで確認しておきます。

$ npm run lint

> node_lint@1.0.0 lint
> eslint --cache --fix . && prettier --write .

.eslintrc.json 20ms
index.js 7ms
package-lock.json 30ms
package.json 6ms

エラーにならないことを確認できましたので、ステージングエリアに追加します。

$ git add index.js

では、再度コミットしましょう。

$ git commit -m 'setup husky'
> node_lint@1.0.0 lint
> prettier --write '**/*.js' && eslint .

index.js 25ms
[master ed5dcae] setup husky
 3 files changed, 7 insertions(+), 2 deletions(-)
 create mode 100755 .husky/pre-commit

今度はエラーが表示されず、コミットが実行されました。

git statusgit logでもコミットされていることを確認します。

git status
On branch master
nothing to commit, working tree clean
$ git log
commit ed5dcae1e5e2487854e78e0d388a2f503324231d (HEAD -> master)
Author: Takashi Yamashina <takashi.yamashina@gmail.com>
Date:   Sat Apr 9 12:51:04 2022 +0900

    setup husky

commit 15186ea9211ed9d5ee4de9aa7dab7e2e04bac9de
Author: Takashi Yamashina <takashi.yamashina@gmail.com>
Date:   Sat Apr 9 12:37:45 2022 +0900

    first commit

問題なくコミットできていることが確認できました。

Prettierで特定のファイル、フォルダを対象から除外する(.prettierignore)

Prettierで特定のファイルやフォルダを対象から除外する方法について解説します。

.prettierignoreを作成する

.prettierignoreを作成し、そこに対象から除外したいファイルやフォルダを書きます。

config.js
test/

上記の例では、config.jsというファイルと、testフォルダを対象から除外しています。

実際に試してみる

実際にどう動くのか確認しましょう。

package.json

package.jsonは次のとおりです。prettierをインストールし、prettierコマンドを実行するスクリプトを追加しています。

{
  "name": "node_prettier",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "format": "prettier --write '**/*.js'"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "prettier": "^2.6.2"
  }
}

Prettierは設定ファイルがなくても実行可能なため、ここでは設定ファイルは作成せずに進めます。

プロジェクトの構造

プロジェクトの構造は以下のようになっており、用意した4つのJSファイルはすべてフォーマットされる書き方になっています。

$ tree
.
├── file1.js
├── file2.js
├── dir3
│   └── file3.js
├── dir4
│   └── file4.js
├── package-lock.json
└── package.json

各ファイルの中身は若干の違いはありますが、以下のような内容になっています。

function file1() {
console.log('file1');
}

関数内のインデントがない点と、文字列の囲み文字がダブルクォーテーションではなくシングルクォーテーションになっている点がフォーマットの対象となる箇所です。

Prettierを実行する

最初に.prettierignoreを用意せずに実行します。

$ npm run format

> node_prettier@1.0.0 format
> prettier --write '**/*.js'

dir3/file3.js 22ms
dir4/file4.js 2ms
file1.js 2ms
file2.js 1ms

プロジェクト直下の2ファイル、2つのフォルダ内のそれぞれのファイルが対象になっていることがわかります。

function file1() {
  console.log("file1");
}

実際にファイルを見てみると、インデントおよび文字列の囲み文字が修正されていることが確認できました。

次に.prettierignoreを作成し、プロジェクト直下の1ファイルと2つのファイルのうちの1つを除外してから再度実行してみましょう。

file2.js
dir4/

先ほどと同様に実行してみると、除外したファイルとフォルダ内のファイルが表示されなくなりました。

$ npm run format

> node_prettier@1.0.0 format
> prettier --write '**/*.js'

dir3/file3.js 23ms
file1.js 2ms

ファイルも確認してみると、

ファイル名が表示されたファイルは、先ほどと同様にフォーマットされていますが、

function file1() {
  console.log("file1");
}

ファイル名が表示されなかった(除外された)ファイルはフォーマットされていません。

function file2() {
console.log('file2');
}

node_modulesの除外は不要

実行してみるとわかりますが、node_modulesの除外は不要です。

そのため、対象としたいフォルダ内に除外したいファイルやフォルダがない場合は.prettierignoreを作成する必要はありません。

[Python]VSCodeでPythonのフォーマッタを設定する(black等のライブラリをPrettierプラグインと共存させる)

Visual Studio Codeで、Python以外はPrettierプラグインを使用し、PythonはPrettierプラグインを使わずにblackを使用する方法について解説します。

共通の設定

Prettierプラグインを有効化するにはeditor.defaultFormatterを設定します。保存時にフォーマットしたいので、editor.formatOnSaveも設定します。

{

  ・・・
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true,
  ・・・
 }

Pythonの設定

次にPythonの設定です。PythonではPrettierプラグインを使用せずにblackを使用します。ただし、editor.defaultFormatterの設定が有効な場合、editor.defaultFormatterの方が優先されてしまうため、Pythonではこの設定を無効化(null)します。

以下は、blackを使用するように設定している例です。ここではblackを使用していますが、他のライブラリでも構いません。

{
  ・・・
  "[python]": {
    "editor.defaultFormatter": null
  },
  "python.formatting.provider": "black",
  ・・・

}

上記設定により、PythonだけはPrettierプラグインを使用せず、black等のライブラリを使用できます。

モバイルバージョンを終了