Prettier import 자동 정렬
프로젝트가 커지면 파일 하나에 import가 20~30줄씩 쌓인다. 처음에는 나름 순서를 맞춰서 쓰지만, 여러 명이 작업하다 보면 금방 뒤죽박죽이 된다. React import 아래에 로컬 유틸이 있고, 그 아래 다시 서드파티 라이브러리가 오고, 상대경로 import가 여기저기 흩어진다.
import { useState } from "react";
import { format } from "date-fns";
import { Button } from "@/components/ui/button";
import axios from "axios";
import { cn } from "@/lib/utils";
import { useRouter } from "next/navigation";
import { UserProfile } from "@/domain/auth/types";
import "./styles.css";
이걸 매번 수동으로 정리하는 건 비효율적이고, 팀 내에서 "import 순서 규칙"을 정해봤자 코드 리뷰 때마다 지적하는 것도 한계가 있다. Prettier가 코드 포매팅을 자동화하듯이, import 순서도 자동화할 수 있으면 좋겠다는 발상에서 나온 게 @trivago/prettier-plugin-sort-imports다.
왜 Prettier 플러그인인가
import 정렬 도구는 여러 가지가 있다. ESLint의 eslint-plugin-import에도 order 규칙이 있고, simple-import-sort 같은 ESLint 플러그인도 있다.
| 도구 | 방식 | 자동 수정 | 설정 복잡도 |
|---|---|---|---|
eslint-plugin-import/order | ESLint 규칙 | --fix로 가능 | 높음 (그룹, 알파벳 등 세부 설정 多) |
simple-import-sort | ESLint 플러그인 | --fix로 가능 | 낮음 (거의 설정 없이 동작) |
@trivago/prettier-plugin-sort-imports | Prettier 플러그인 | 저장 시 자동 | 중간 (정규식 패턴으로 그룹 정의) |
@ianvs/prettier-plugin-sort-imports | Prettier 플러그인 (fork) | 저장 시 자동 | 중간 (trivago fork, 추가 기능) |
ESLint 기반 도구는 린팅 파이프라인에서 동작한다. --fix로 자동 수정할 수 있지만, 에디터에서 저장할 때 Prettier만 돌리는 환경이라면 별도로 ESLint fix를 연결해야 한다. Prettier 플러그인 방식은 이미 Prettier를 쓰고 있다면 .prettierrc에 설정 몇 줄만 추가하면 되고, 저장할 때 코드 포매팅과 동시에 import도 정렬된다. 별도의 ESLint 설정이나 추가 파이프라인 없이 기존 포매팅 흐름에 자연스럽게 끼워넣을 수 있다는 게 장점이다.
설치
npm install -D @trivago/prettier-plugin-sort-imports
# 또는
pnpm add -D @trivago/prettier-plugin-sort-imports
Prettier 3.x 이상을 사용한다면 플러그인 버전도 4.x 이상이어야 한다. Prettier 3에서 플러그인 API가 바뀌었기 때문에, 구버전 플러그인은 SyntaxError: Unexpected token이나 Cannot find module 같은 에러가 발생할 수 있다.
기본 설정
.prettierrc에 플러그인을 등록하고 정렬 규칙을 정의한다.
{
"plugins": ["@trivago/prettier-plugin-sort-imports"],
"importOrder": [
"^react$",
"^next",
"<THIRD_PARTY_MODULES>",
"^@/(.*)$",
"^[./]"
],
"importOrderSeparation": true,
"importOrderSortSpecifiers": true
}
이 설정을 적용하면 아까 뒤죽박죽이었던 import가 이렇게 정렬된다:
import { useState } from "react";
import { useRouter } from "next/navigation";
import axios from "axios";
import { format } from "date-fns";
import { UserProfile } from "@/domain/auth/types";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import "./styles.css";
react → next → 서드파티 → 내부 alias → 상대경로 순서로 깔끔하게 정리된다.
importOrder 패턴 상세
importOrder는 정규식 패턴의 배열이다. 배열에서 위에 있을수록 먼저 배치된다. 각 패턴에 매칭되는 import끼리 하나의 그룹을 이루고, 패턴에 매칭되지 않는 import는 <THIRD_PARTY_MODULES> 위치에 들어간다.
패턴 매칭 규칙
"importOrder": [
"^react$", // 정확히 "react"만 매칭
"^next", // "next"로 시작하는 모든 것 (next/navigation, next/image 등)
"<THIRD_PARTY_MODULES>", // node_modules에서 오는 나머지 전부
"^@/domain/(.*)$", // @/domain/ 하위 경로
"^@/components/(.*)$", // @/components/ 하위 경로
"^@/(.*)$", // 나머지 @ alias 경로
"^[./]" // 상대경로 (./ 또는 ../)
]
패턴은 import 경로(from 뒤의 문자열)에 대해 매칭된다. import { Button } from "@/components/ui/button"이라면 @/components/ui/button이 매칭 대상이다.
주의할 점은 패턴의 순서가 우선순위라는 것이다. ^@/domain/(.*)$와 ^@/(.*)$가 둘 다 있으면, @/domain/auth/types는 더 먼저 나오는 ^@/domain/(.*)$에 매칭된다. 만약 순서가 뒤바뀌면 ^@/(.*)$가 먼저 잡아버려서 domain 그룹이 의미 없어진다.
<THIRD_PARTY_MODULES> 특수 토큰
이 토큰은 node_modules에서 오는 패키지를 의미한다. importOrder 배열에 명시적으로 넣어야 한다. 생략하면 서드파티 모듈이 최상단에 배치된다.
// 서드파티를 react/next 뒤에 배치하고 싶다면
"importOrder": ["^react$", "^next", "<THIRD_PARTY_MODULES>", "^@/(.*)$", "^[./]"]
// 서드파티를 맨 위에 두고 싶다면
"importOrder": ["<THIRD_PARTY_MODULES>", "^@/(.*)$", "^[./]"]
side-effect import
import "./styles.css" 같은 side-effect import(from 없이 경로만 있는 import)는 기본적으로 정렬 대상에서 제외되고 원래 위치에 남는다. 이를 정렬에 포함하려면 importOrderSideEffects 옵션을 사용한다(v4.x 이상).
주요 옵션
importOrderSeparation
"importOrderSeparation": true
true로 설정하면 각 그룹 사이에 빈 줄이 삽입된다. false면 빈 줄 없이 모든 import가 연속으로 나열된다.
빈 줄이 있으면 그룹 구분이 시각적으로 명확해진다. 하지만 import가 많지 않은 파일에서는 오히려 공간을 차지하기만 할 수 있어서 팀 취향에 따라 선택하면 된다.
// importOrderSeparation: true
import { useState } from "react";
import { useRouter } from "next/navigation";
import axios from "axios";
import { Button } from "@/components/ui/button";
// importOrderSeparation: false
import { useState } from "react";
import { useRouter } from "next/navigation";
import axios from "axios";
import { Button } from "@/components/ui/button";
importOrderSortSpecifiers
"importOrderSortSpecifiers": true
true로 설정하면 하나의 import 문 안에서 named import들이 알파벳 순으로 정렬된다.
// 정렬 전
import { useState, useEffect, useCallback, useMemo } from "react";
// importOrderSortSpecifiers: true 적용 후
import { useCallback, useEffect, useMemo, useState } from "react";
이 옵션이 없으면 import 문의 순서는 바뀌지만 중괄호 안의 순서는 그대로다. 코드 리뷰에서 "useEffect를 useState 앞에 쓰지 마세요" 같은 불필요한 지적을 없애려면 켜두는 게 좋다.
importOrderCaseInsensitive
"importOrderCaseInsensitive": true
정렬 시 대소문자를 무시한다. 기본적으로 대문자가 소문자보다 앞에 오는데(Button < cn), 이 옵션을 켜면 순수 알파벳 순으로 정렬된다.
importOrderParserPlugins
"importOrderParserPlugins": ["typescript", "jsx", "decorators-legacy"]
내부적으로 Babel 파서를 사용해서 AST를 파싱하는데, TypeScript나 JSX를 사용한다면 해당 파서 플러그인을 명시해야 한다. 보통은 파일 확장자를 보고 자동 감지하지만, 데코레이터(@) 문법을 쓰는 경우 decorators-legacy를 추가해야 파싱 에러가 안 난다.
prettier-plugin-tailwindcss와 함께 사용
Tailwind CSS를 쓰고 있다면 prettier-plugin-tailwindcss로 클래스를 자동 정렬하는 경우가 많다. 이 두 플러그인을 동시에 쓸 때는 plugins 배열에서 tailwindcss 플러그인이 반드시 마지막에 와야 한다.
{
"plugins": [
"@trivago/prettier-plugin-sort-imports",
"prettier-plugin-tailwindcss"
]
}
이 순서가 중요한 이유는 Prettier 플러그인 체인의 동작 방식 때문이다. Prettier는 plugins 배열의 마지막 플러그인부터 역순으로 처리하는데, prettier-plugin-tailwindcss는 다른 플러그인들이 코드를 먼저 변환한 뒤 마지막에 Tailwind 클래스를 정렬하도록 설계되어 있다. 순서가 바뀌면 import 정렬이 Tailwind 플러그인의 출력을 다시 건드려서 충돌이 발생할 수 있다.
Tailwind 공식 문서에서도 이 플러그인과의 호환성을 명시하고 있고, prettier-plugin-tailwindcss를 항상 마지막에 두라고 안내하고 있다.
@ianvs/prettier-plugin-sort-imports (fork)
@trivago/prettier-plugin-sort-imports의 포크 버전으로, 몇 가지 추가 기능이 있다.
<BUILTIN_MODULES>: Node.js 내장 모듈(fs, path 등)을 별도 그룹으로 분리할 수 있다.- Type import 분리:
import type { ... }을 일반 import와 별도 그룹으로 묶을 수 있다. - 빈 문자열로 그룹 분리:
importOrder배열에""를 넣으면 해당 위치에 빈 줄이 삽입된다.importOrderSeparation보다 세밀한 제어가 가능하다.
{
"plugins": ["@ianvs/prettier-plugin-sort-imports"],
"importOrder": [
"^react$",
"^next",
"",
"<THIRD_PARTY_MODULES>",
"",
"<TYPES>",
"",
"^@/(.*)$",
"^[./]"
]
}
<TYPES> 토큰으로 import type 구문만 별도로 모을 수 있다. TypeScript 프로젝트에서 일반 import와 타입 import를 시각적으로 분리하고 싶다면 유용하다.
내부 동작 원리
이 플러그인이 import를 정렬하는 과정은 다음과 같다:
- AST 파싱: Prettier가 코드를 받으면 플러그인이 Babel 파서로 소스 코드를 AST(Abstract Syntax Tree)로 변환한다.
- ImportDeclaration 노드 추출: AST에서
ImportDeclaration타입의 노드들만 추출한다. 이 노드에는 import 경로(source.value)와 imported specifier 정보가 들어 있다. - 패턴 매칭: 각 import의 경로를
importOrder배열의 정규식과 순서대로 매칭한다. 처음 매칭되는 패턴의 인덱스가 해당 import의 그룹 번호가 된다. - 그룹 내 정렬: 같은 그룹에 속하는 import들을 경로 문자열 기준으로 알파벳 순 정렬한다.
- specifier 정렬:
importOrderSortSpecifiers가 true면 각 import 문의{ }안에 있는 specifier들도 알파벳 순으로 정렬한다. - 코드 재생성: 정렬된 순서대로 import 문을 재생성하고,
importOrderSeparation에 따라 그룹 사이에 빈 줄을 삽입한다.
이 과정이 Prettier의 포매팅 파이프라인 안에서 일어나기 때문에 별도의 CLI나 추가 도구 없이 prettier --write만으로 import 정렬까지 완료된다.
실전 설정 예시
Next.js + TypeScript 프로젝트에서 자주 쓰는 설정 패턴이다.
{
"plugins": [
"@trivago/prettier-plugin-sort-imports",
"prettier-plugin-tailwindcss"
],
"importOrder": [
"^react$",
"^next",
"<THIRD_PARTY_MODULES>",
"^@/domain/(.*)$",
"^@/components/(.*)$",
"^@/(.*)$",
"^[./]"
],
"importOrderSeparation": true,
"importOrderSortSpecifiers": true,
"singleQuote": true,
"semi": true,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 120
}
이 설정의 의도를 풀어보면:
- react가 항상 최상단: React의 훅이나 타입을 가장 먼저 선언한다.
- next 관련이 그 다음: Next.js의 라우터, 이미지, 링크 등 프레임워크 레벨 import.
- 서드파티 라이브러리: axios, date-fns, zod 등 외부 패키지.
- domain 레이어: 비즈니스 로직, 타입 정의, API 호출 함수 등.
- components: UI 컴포넌트들.
- 나머지 alias: 유틸리티, 상수, 설정 등.
- 상대경로: 같은 폴더나 부모 폴더의 파일.
이렇게 하면 "이 파일이 외부에 어떤 의존성을 갖고 있는지"를 import 블록만 보고 한눈에 파악할 수 있다.
기존 프로젝트에 도입할 때
이미 코드가 많은 프로젝트에 도입하면 첫 실행 시 대량의 파일이 변경된다. 한 번에 prettier --write 해서 전체를 포매팅하고, 그 변경을 단독 커밋으로 남기는 게 좋다.
npx prettier --write "**/*.{ts,tsx,js,jsx}" --ignore-path .gitignore
이렇게 하면 git blame이 깨지는 문제가 있는데, .git-blame-ignore-revs 파일에 해당 커밋 해시를 추가하면 된다.
정리
- Prettier 파이프라인에 끼워넣어서 저장 시 import가 자동 정렬되므로, ESLint fix를 별도로 연결할 필요가 없다.
importOrder배열의 정규식 순서가 곧 우선순위이고,<THIRD_PARTY_MODULES>위치를 명시적으로 지정해야 의도한 그룹 배치가 된다.- @ianvs fork는
<TYPES>토큰과 빈 문자열 구분자를 지원하므로, TypeScript 프로젝트에서 타입 import를 분리하고 싶다면 검토할 만하다.
# .git-blame-ignore-revs
# Prettier import 정렬 일괄 적용
abc123def456...
# .git-blame-ignore-revs
# Prettier import 정렬 일괄 적용
abc123def456...
GitHub은 이 파일을 자동 인식해서 웹 UI의 blame에서 해당 커밋을 건너뛴다.
관련 문서
트러블슈팅
파싱 에러
SyntaxError: This experimental syntax requires enabling one of the following parser plugin(s): "decorators"
데코레이터를 사용하는 프로젝트에서 자주 발생한다. importOrderParserPlugins에 decorators-legacy를 추가하면 해결된다.
플러그인 로딩 실패
Cannot find module '@trivago/prettier-plugin-sort-imports'
pnpm을 사용하는 경우 hoisting 설정 때문에 Prettier가 플러그인을 찾지 못할 수 있다. .npmrc에 public-hoist-pattern[]=*prettier*를 추가하거나, .prettierrc에서 플러그인 경로를 명시적으로 지정한다.
정렬이 적용 안 될 때
Prettier 캐시가 원인일 수 있다. prettier --write --no-cache로 캐시를 무시하고 실행해보면 된다.