본문 바로가기

Programming/TypeScript

[ESLint] imports/exports 구문의 순서 자동 정렬하기

이 글은 ESLint를 통해 typescript(혹은 javascript)의 imports/exports 구문을 정렬하는 방법에 대해 공부하며 작성한 글이다. ESLint에 관심이 있거나, imports를 자동으로 정렬하는 방법을 찾고 고민하고 계신 분들이 읽으면 좋을 글이다.

사건의 발단

여러 명이서 작업하고 있는 Angular 프로젝트가 있었다. 요즘에는 개발을 시작할 때, 초기 세팅(tsconfig, eslint, babel, webpack 등등)을 다 한 상태에서 작업을 하지만(심지어 boilerplate도 너무 잘 나와있다.) 사건의 발단이 된 프로젝트는 그렇지 않았다. Linter 설정이 되어있지 않아서 사람마다 코드 스타일도 달랐고, 무엇보다 코드 리뷰할 때 PR 작업과 전혀 관련없는 오로지 linting과 관련된 코드 수정을 봐야하는 경우도 있었다. 그래서 다른 걸 더 제쳐두고, ESLint부터 도입하기로 했다.

 

팀원들끼리 기본적인 규칙은 서로 맞추고, autofix를 기대하고 --fix 옵션을 실행해본 결과, 다음과 같은 에러 문구를 받았다. 다른 이슈들은 수정하면 됐지만,  import 오류는 조금 다르다고 판단했다. 

error  Expected 'single' syntax before 'multiple' syntax  sort-imports

에러 문구를 보면, 'single' syntax로 선언된 import 구문이 'multiple' syntax보다 먼저 (위에) 선언 되어야한다. 라고 되어있다. 그렇다면 왜 ESLint가 자동으로 정렬해주지는 않는걸까? 

'single', 'multiple' member syntax: ESLint 도큐먼트에 정의되어있다.

sort-imports

ESLint는 import sorting 규칙을 제공한다. ESLint에서 설명하는 규칙에 대해 읽어보자

This rule checks all import declarations and verifies that all imports are first sorted by the used member syntax and then alphabetically by the first member or alias name.

모든 import 선언문을 체크하여, 사용자가 선언한 member syntax에 맞게 정렬되었는지 확인한 후, member나 alias 이름이 알파벳 순서대로 정렬되었는지 확인해준다.

 

The --fix option on the command line automatically fixes some problems reported by this rule: multiple members on a single line are automatically sorted (e.g. import { b, a } from 'foo.js' is corrected to import { a, b } from 'foo.js'), but multiple lines are not reordered.

--fix 옵션은 위 규칙에 맞춰 일부 문제들을 자동으로 수정해준다: 한 줄의 multiple members는 자동으로 정렬된다. 그러나, 여러 줄은 재정렬되지 않는다.

// before
import { b, a } from 'foo.js'

// after
import { a, b } from 'foo.js'

수동으로 imports 구문들의 순서를 정렬해주기엔 너무나도 많은 파일들이 있었고(대략 100개가 넘는 파일이라고 생각해보자. 정말 까마득하다.) 결국 자동 정렬해주는 방법을 찾아야겠다고 결심했다. 그렇게 알게된 플러그인은 eslint-plugin-import이다.

eslint-plugin-import를 알아보자

eslint-plugin-import는 ES2015+ import/export 구문의 linting을 지원하고 import명이나 잘못 작성한 파일 경로에 대한 이슈를 방지해주는 플러그인이라고 소개하고있다. 

 

import syntax에도 여러가지 규칙을 정해줄 수 있는데, 그 중에서 'import/order'를 확인해보자.

import/order: Enforce a convention in module import order

이 규칙을 제대로 정의하면, autofix를 사용할 수 있다! 나는 가장 최상단에 Angular와 관련된 패키지를 나열하고 싶었다. 그 후, import 구문들을 그룹별로 나누고 그룹마다 한 줄 띄움을 통해 가독성을 높이고 싶었다.

 

 

내가 설정한 .eslintrc.js의 rules에 선언한 규칙은 다음과 같다. eslint-plugin-import와 관련된 규칙만 명시해두었다.

module.exports = {
  ...,
  settings: {
    'import/parsers': { '@typescript-eslint/parser': ['.ts'] },
  },
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:import/typescript',
    'plugin:import/recommended',
  ],
  rules: {
    ...,
    'import/order': [
      'error',
      {
        groups: ['builtin', 'external', ['parent', 'sibling'], 'index'],
        pathGroups: [
          {
            pattern: 'angular',
            group: 'external',
            position: 'before'
          }
        ],
        alphabetize: {
        order: 'asc',
        caseInsensitive: true,
      },
      'newlines-between': 'always',
      },
    ],
  }
}

error: 위 규칙을 위반할 경우, ESLint가 경고를 표시해 줌

groups: 'builtin', 'external', 'internal' 세 그룹으로 분리한다. (각 그룹에 대한 정의는 아래와 같다.)

// 1. node "builtin" modules
import fs from 'fs';
// 2. "external" modules
import _ from 'lodash';
// 3. "internal" modules
import foo from 'src/foo';

pathGroups: 패턴을 통해서 그룹을 만들 수 있다. path에 'angular'가 들어있다면, external 그룹으로 지정하고, 제일 위에(before) 위치하도록 했다.

// 아래와 같이 path에 angular가 있으면, pathGroups에 속하게 된다.
import { HashLocationStrategy, LocationStrategy } from '@angular/common';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

alphabetize: 그룹에 속한 import 구문들을 알바벳 오름차순 정렬한다.

'newlines-between': 그룹과 그룹 사이에 new line을 추가한다.

정렬 결과는?

처음 코드 (예시)

import { BrowserModule } from '@angular/platform-browser';
import { Observable, BehaviorSubject } from 'rxjs';
import { NgModule } from '@angular/core';
import * as _ from 'lodash';
import { routing } from './app.routing';
import { combineLatest } from 'rxjs';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { HttpInterceptorService } from './httpInterceptor.service';
import { LocationStrategy, HashLocationStrategy } from '@angular/common';

적용 후

import { HashLocationStrategy, LocationStrategy } from '@angular/common';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import * as _ from 'lodash';
import { BehaviorSubject, Observable , combineLatest } from 'rxjs';

import { routing } from './app.routing';
import { HttpInterceptorService } from './httpInterceptor.service';

깔끔하게 자동 정렬해주는 것을 확인할 수 있다.

Referrence

https://github.com/eslint/eslint/issues/11542

https://eslint.org/docs/rules/sort-imports

https://github.com/import-js/eslint-plugin-import

https://dev.to/otamnitram/sorting-your-imports-correctly-in-react-213m

반응형