劇的改善!Prettier + ESLintでimport周りを自動で整える

投稿日
劇的改善!Prettier + ESLintでimport周りを自動で整える

Prettier + ESLint、それぞれのプラグインを組み合わせ、tsのimport周りを改善する方法をご紹介します!




以前にPrettierを使ってimport周り整えるプラグインについて記載しました



しかし、どちらもモヤモヤが残る結果になりました

prettier-plugin-sort-importsprettier-plugin-organize-imports
導入のしやすさ
importの並び順ルールの定義
importのソート
冗長なimportをまとめる
未使用のimportの削除

prettier-plugin-organize-imports はほぼ完璧なものの

自分で並び順のルールを定義することが出来ません。



加えて、

改行がある場合・ない場合でソートの結果が変わります

// before
import React, { useState } from 'react'
 
import clsx from 'clsx'
 
 
// after
import React, { useState } from 'react'
 
import clsx from 'clsx'
// before
import React, { useState } from 'react'
import clsx from 'clsx'
 
 
// after
import clsx from 'clsx'
import React, { useState } from 'react'

しかし、今回記載するESLintのプラグインと組み合わせる

それらのモヤモヤも解消出来る事が分かったため備考欄も兼ねて記載していきます!!

検証した環境

1 prettier 3.2.5
2 prettier-plugin-organize-imports 3.2.4
3 eslint 8.57.0
4 eslint-plugin-import 2.29.1

前段(IDEの設定)

今回記載する方法を実施するにあたり prettier --write そして eslint --fix がファイルを保存した際に実行されるようにします。


これを設定するだけでDXが爆上がりするのでおすすめです!

VS Code

動作する事は確認したものの、

普段IntelliJ IDEAを使っているので自信が持てない部分ありです 🙏



VS Code用の2つのプラグインを追加


Prettier - Code formatter

VS CodeのPrettier用プラグイン

ESLint

VS CodeのESLint用プラグイン


.setting.jsonの設定を更新

.setting.json
{
    "editor.codeActionsOnSave": {
        "source.fixAll.eslint": true
    },
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "editor.formatOnSave": true,
    "[typescript]": {
        "editor.formatOnSave": false
    }
}

参考にさせていただいたページ曰く

“editor.formatOnSave”をfalseに設定するとESLintのfixが対象でないファイルのフォーマットが行われないため、ESLintでfixを実行するファイルのみ”editor.formatOnSave”をfalseにしましょう。


【VSCode】ESLint(fixあり/fixなし)とPrettierを保存時に走らせる #VSCode - Qiitaより転記

とのことですmm



Jet Brains系

Web Storm と Intellij IDEA のみになりそうですがそれぞれ設定ページから設定します

Prettier

言語 & フレームワーク > JavaScript > Prettier

JetBrain系のPrettierの設定
  • 自動 Prettier 構成を選択
  • 次のファイルに実行: 必要なファイルを指定
    • ex: **/*.{js,ts,jsx,tsx,vue}
  • 保存時に実行に✓

ESLint

言語 & フレームワーク > JavaScript > コード品質ツール > ESLint

JetBrain系のESLintの設定
  • 自動 ESLint 構成を選択
  • 次のファイルに実行: 必要なファイルを指定
    • ex: **/*.{js,ts,jsx,tsx,vue}
  • 保存時に eslint —fix を実行に✓

import周りの改善

本題です!



手順としては、Prettier・ESLint合わせて、2つのプラグインを導入

  1. prettier-plugin-organize-imports
  2. eslint-plugin-import

最後に eslint-plugin-import の調整をします

prettier-plugin-organize-imports

まず prettier-plugin-organize-imports を導入します

npm install -D prettier-plugin-organize-imports
prettier.config.js
module.exports = {
  plugins: ['prettier-plugin-organize-imports'],
}


ここまで設定しPrettierを実行すると



// before
import { initializeApp } from '@core/app'
import { updateSettings, createConnection } from '@server/database'
import { useState } from 'react'
import clsx from 'clsx'
import { Button } from '@ui/form/Button'
import { SampleComponent } from '@ui/sample/SampleComponent'
import React from 'react'
// after
import { createConnection, updateSettings } from '@server/database'
import { Button } from '@ui/form/Button'
import { SampleComponent } from '@ui/sample/SampleComponent'
import clsx from 'clsx'
import React, { useState } from 'react'

  • 不要なimportを削除
  • import順のソート
  • import内のソート
  • 冗長だったimportがひとまとめ

になります!




課題としては以下が残ります

  • importの順番を自分で決められない
  • 改行が入った場合の挙動が違う

そこでこの2点を解決するために、ESLintのプラグインを使います!

eslint-plugin-import

今回の最も重要となるプラグイン eslint-plugin-import を導入します

npm install -D eslint-plugin-import
.eslintrc.js
module.exports = {
  plugins: ['import'],
  rules: {
    'import/order': [
      'error',
      {
        'newlines-between': 'always',
        groups: ['builtin', 'external', 'parent', 'sibling', 'index'],
      },
    ],
  },
}

ポイントが2つあります

  • rulesに warn or error を設定する
    • これにより eslint --fix を実行すると import/order の修正を行ってくれる
  • 'newlines-between': 'always' を設定する
    • eslint-plugin-import独自のグルーピング毎に改行が入ります
    • なぜ必要かについての詳細は後述します

この状態でPrettierの実行・eslint —fix を実行すると



// after
import clsx from 'clsx'
import React, { useState } from 'react'
 
import { createConnection, updateSettings } from '@server/database'
import { Button } from '@ui/form/Button'
import { SampleComponent } from '@ui/sample/SampleComponent'

reactclsx のみでグルーピングされ、それ以外のimportとで改行が入っているのが分かります!





これは先ほど記載した groups オプションに準拠した並び順になったためです

groups: ['builtin', 'external', 'parent', 'sibling', 'index'],


公式の情報を日本語化して記載するとgroupsはそれぞれ以下の意味になっています

// 1. node "builtin" モジュール
import fs from 'fs';
import path from 'path';
 
// 2. "external" モジュール
import _ from 'lodash';
import chalk from 'chalk';
 
// 3. "internal" モジュール
// (パスやwebpackを設定して内部パスを異なる方法で扱っている場合)
import foo from 'src/foo';
 
// 4. "parent" ディレクトリからのモジュール
import foo from '../foo';
import qux from '../../foo/qux';
 
// 5. 同じディレクトリまたは兄弟ディレクトリからの "sibling" モジュール
import bar from './bar';
import baz from './bar/baz';
 
// 6. 現在のディレクトリの "index"
import main from './';
 
// 7. "object" インポート (TypeScriptでのみ利用可能)
import log = console.log;
 
// 8. "type" インポート (FlowとTypeScriptでのみ利用可能)
import type { Foo } from 'foo';

‘newlines-between’: ‘always’ が必要な理由

prettier-plugin-organize-imports は改行が入った箇所にはソートを行いません。

逆に言うと改行が入っていない場合は常にorganize-importのルールでソートを行います



もし 'newlines-between': 'always' を設定しなかった場合

  • eslint-plugin-import はgroupsで決まった順でソート
  • prettier-plugin-organize-imports はorganize-importのルールでソート

をそれぞれ行い、並び順がバッティングしてしまいます。



そこで常にグルーピング毎に改行を入れ

グループ毎のソートは prettier-plugin-organize-imports に任せる、としています



eslint-plugin-import の調整

eslint-plugin-import はかなり柔軟なプラグインで、

オプションを使用する事でかなり細かく自分好みな設定をする事が出来ます。



例えば以下のようなimportがあります

import clsx from 'clsx'
import Link from 'next/link'
import React, { useState } from 'react'
 
import { TestConstants } from '@/constants'
import { createConnection, updateSettings } from '@server/database'
import { Button } from '@ui/form/Button'
import { SampleComponent } from '@ui/sample/SampleComponent'
  • react は1番上に書きたい
  • @ui/で始まるコンポーネントは別途グループ化したい

といった要望があった場合に eslint-plugin-import は実現可能です!




先に結論を記載すると以下のようになります

.eslintrc.js
'import/order': [
  'error',
  {
    'newlines-between': 'always',
    groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
    pathGroups: [
      {
        pattern: 'react',
        group: 'builtin',
        position: 'before',
      },
      {
        pattern: '@ui/**',
        group: 'internal',
        position: 'before',
      },
    ],
    pathGroupsExcludedImportTypes: ['builtin'],
  },
],

この状態でprettier,eslint —fixを実行すると以下のようになります、見通し良くなった気がします ✨

import React, { useState } from 'react'
 
import clsx from 'clsx'
import Link from 'next/link'
 
import { Button } from '@ui/form/Button'
import { SampleComponent } from '@ui/sample/SampleComponent'
 
import { TestConstants } from '@/constants'
import { createConnection, updateSettings } from '@server/database'

pathGroups

pathGroups は自分でグループを定義出来る機能です。


以下は「reactbuiltin の前に記述するグループとする」という意味合いになります

{
  pattern: 'react',
  group: 'builtin',
  position: 'before',
},
  • pattern・・グルーピングしたいimport群を指定
    • minimatchの記述を使用可能
    • ex: pattern: '{react,next/**}',
  • group・・基準となる規定グループを指定
  • position・・groupで指定したグループ前後どちらに持ってくるか
    • 指定をしなかった場合は group で記述したグループに含まれるようになります

pathGroupsExcludedImportTypes

ここに記載したグループのimportは pathGroups が適用されません



例えば reactnext/linkexternal グループに元々含まれます。

つまり pathGroupsExcludedImportTypesexternal を指定するとpathGroupsに記述しても反応しなくなります

.eslintrc.js
pathGroupsExcludedImportTypes: ['builtin', 'external'],
pathGroups: [
  {
    // react や next/** はexternalグループに属す
    // pathGroupsExcludedImportTypes にて external を指定しているため、
    // 以下の記述は効果がない
    pattern: '{react,next/**}',
    group: 'builtin',
  },
 
  // @ui/** はinternalグループに属す
  // pathGroupsExcludedImportTypes に internal は指定していないため、
  // 新たなグループとなる
  {
    pattern: '@ui/**',
    group: 'internal',
    position: 'before',
  },
],
import clsx from 'clsx'
import Link from 'next/link' //新たなグループが作られない
import React, { useState } from 'react' //新たなグループが作られない
 
// @ui/**は新たなグループとなる
import { Button } from '@ui/form/Button'
import { SampleComponent } from '@ui/sample/SampleComponent'
 
import { TestConstants } from '@/constants'
import { createConnection, updateSettings } from '@server/database'


注意点としてはデフォルト値が以下のように定義されていること

pathGroupsExcludedImportTypes: ['builtin', 'external', 'object']

pathGroups が思ったように反応しない場合は pathGroupsExcludedImportTypes を調査してみて下さい

プロフィール画像
Yuki Takara
都内でフリーランスのエンジニアをやってます。フロントとアプリ開発メインに幅広くやってます。