글 작성 시 사용한 Nextjs 버전 → 14.2.4
⚽️ 목표
Next.js에서 제공하는 next lint와 eslint 명령어의 차이를 확인하여 최적의 설정을 만들어본다.
TL;DR
next lint는 결과적으로 eslint를 이용하여 lint를 수행하지만 그 외에 많은 작업을 한다.
0. next lint의 코드
•
해당 글을 읽기 전에 next lint의 코드를 한 번 읽어보면 이 글을 이해하기 한층 더 쉬워진다. 위의 북마크에서 글을 한 번 읽어보자.
•
TL;DR에도 언급했듯이, next lint는 결과적으로 eslint를 이용하여 코드를 lint한다.(당연히 eslint를 돌려야 코드를 lint할 수 있겠지?) 이 글은 “next lint가 eslint를 수행하기 전” 그 과정을 next lint 코드와 함께 찾아본다.
•
추가로 글의 마지막에는 Next.js에서 최적의 lint 설정에 대해 이야기해본다.
1. 빌드 시 린트
build 시 실행하는 next lint
•
eslint와는 다른점 중 첫번째로 Next.js는 기본 설정으로 build 시 next lint 명령어를 실행하여 lint 검사를 수행한다.
•
lint 검사와 함께 type 유효성 검사도 함께 수행한다.
ignoreDuringBuild
•
이미 개발 환경에서 pre-commit, pre-push 등을 이용하여 린트 검사를 수행하고 CI 환경에서도 lint 검사를 할 경우 중복으로 build 시 lint 검사를 할 경우 리소스 낭비가 될 수 있으므로 “충분한 lint 검사”가 존재한다는 전제하에 위와 같이 옵션을 비활성화하는 게 빌드 시간을 줄일 수 있는 방법 중 하나다.
2. next lint의 scope
•
next lint는 기본적으로 pages, app, components, lib, src 디렉터리를 lint 한다.
// next.config.js
module.exports = {
eslint: {
dirs: ['pages', 'utils'], // Only run ESLint on the 'pages' and 'utils' directories during production builds (next build)
},
}
TypeScript
복사
next lint --dir pages --dir utils --file bar.js
Bash
복사
•
위와 같은 설정 또는 command로 scope를 설정할 수 있다.
3. package.json
•
typescript, @types/react, @types/node와 같이 각종 설정파일이 잘 설정됐는가?도 확인한다.
@types/node가 없을경우 출력되는 에러
•
next lint 실행 시 @types/node가 없을 경우 위와같이 해당 패키지를 설치한다.
4. tsconfig.json
tsconfig.json에 skipLibCheck 설정이 없을 경우 출력되는 에러
•
next lint 실행 시 tsconfig에 suggested된 설정들이 존재하는가에 대한 유무를 확인한다.
•
해당 옵션들이 desired value가 아닐때 설정하는게 아니라 desired property 자체가 없을때 해당 prporty를 추가한다.
5. pages(app) 디렉터리 존재 유무 확인
pages 디렉터리가 없을 경우 출력되는 에러
•
next lint는 pages, app 디렉터리의 유무 또한 확인한다. 해당 디렉터리가 존재하지 않을 경우 위와같은 에러를 출력한다.
lint-staged
// ...생략
"lint-staged": {
"**/*.{js,jsx,ts,tsx}": [
"next lint --fix",
"bash -c tsc --noEmit --pretty"
]
},
// ...생략
JSON
복사
•
Next.js에서 lint-staged를 사용할 때 위와같이 package.json에 lint-staged 옵션을 넣을 경우 아래와 같은 에러가 발상한다
•
staged된 파일들만 린트하기 때문에 next lint가 “pages” 디렉터리의 유무를 확인 할 때 실패하기 때문이다. 이에대한 해결법으로 Next.js 공식문서에 해결법이 존재한다.
•
위와 같은 방법으로 lint-staged를 수행할 경우에 next lint를 사용할 수 있게된다.
•
하지만 package.json, typescript.json과 같은 next lint의 장점인 추가 검사를 수행하지 않게된다.
6. 캐시 결과물 저장 위치
•
eslint와 next lint는 lint 결과를 cache 할 수 있다. eslint는 —cache 옵션으로 활성화 시켜줘야하며, next lint는 기본값으로 활성화 되어 있다.
next lint
// .next/cache/eslint/.cache_random-numbers
const distDir = join(baseDir, nextConfig.distDir)
const defaultCacheLocation = join(distDir, 'cache', 'eslint/')
const { pagesDir, appDir } = findPagesDir(baseDir)
JavaScript
복사
•
next lint의 cache 파일의 위치는 next.config.js에서 설정한 위치에 디렉터리를 생성한다.
•
물론 다른 옵션들을 활용해 캐시파일의 위치를 변경할 수 있다.
cache 파일
•
eslint의 경우 기본 설정으로 .eslintcache 파일을 사용한다.
•
next lint의 cache 파일과 eslint의 cache 파일을 비교해본 결과 둘이 같은 파일임을 확인할 수 있다. next lint가 자랑하는 caching은 실제로 eslint의 caching을 사용하는것.
속도 차이
cache를 사용하지 않을 경우
cache를 사용할 경우 - 변동사항 존재 시
cache를 사용할 경우 - 변동사항이 없을 경우
•
cache를 사용하지 않은 케이스와 사용한 케이스(변동사항이 존재하는 경우, 존재하지 않는 경우 모두)를 비교해봤을 때 유의미한 차이가 존재하며, 이는 코드 스페이스가 커지면 커질수록(검사해야 하는 코드가 많아질수록) 차이가 더 커질것이다.
.eslintacache의 git 추적 여부
•
.eslintcache 파일을 git으로 추적하는 것을 고민해볼 수 있지만 크게 두 가지 이유로 추적하지 않는다.
1.
.eslintcache 파일은 상대 경로가 아닌 절대경로로 파일의 변화 여부를 확인하기 때문에, 다른 환경에서(구성원의 개발 환경)에서는 해당 cache 파일을 사용할 수 없다.
절대 경로를 가진 .eslintcache 파일
2.
혼자 프로젝트를 관리하더라도 커밋마다 많은 변경내역으로 코드스페이스의 중요한, 의미있는 변경사항들을 추적하기 어렵게 만든다.
lint-staged vs eslint —cache
lint-staged with —cache | eslint with —cache | |
첫실행(cache 파일 없을 경우) | 3.58s - 3.70s | 12.31s - 12.34s |
그 이후 실행 | 3.49s - 3.54s | 3.33s -3.35s |
•
cache 파일이 존재하지 않을 경우 eslint의 경우 cache 파일이 존재할 때에 비해 4배에 가까운 시간이 더 소요됐다. 그 이후 실행은 eslint가 근소한 차로 앞섰다.
•
안정적이고 기대가능한 린트 퍼포먼스를 위해 lint-staged를 사용하는게 좋은선택이라 생각된다.
7. 최적의 설정
•
husky를 이용하여 pre-commit, pre-push에 린트와 타입 검사를 수행할 계획이므로, 1.빌드 시 린트에서 나온 ignoreDuringBuild 옵션을 활성화해 빌드 시간을 줄인다.
•
3. package.json과 4. tsconfig.json에서 확인할 수 있듯이 next lint 명령어는 몇몇 항목들은 에러를 출력하고 종료하는 것이 아니라 수정한 후 프로세스를 정상 종료한다. 즉, pre-commit이나 pre-push에서 사용할 경우 추가적인 스크립트를 작성하여 변경 사항이 있는지, 없는지를 확인해야 한다는 뜻이다. → “pre-commit, pre-push에서 사용 시 변경 사항을 추적하는 스크립트를 작성한다.”
•
5. pages(app) 디렉터리 존재 유무 확인에서 확인할 수 있듯이 lint-staged에서 next lint를 사용할 수 있지만 이럴 경우 next lint의 장점이 많이 퇴색된다. → “lint-staged에서는 next lint를 사용하지 않는다.”
•
6. 캐시 결과물 저장 위치에서 확인할 수 있듯이 cache를 최대한 활용하지만, git으로 cache 파일은 추적하지 않는다.
•
6. 캐시 결과물 저장 위치에서 확인할 수 있듯이 eslint —cache의 경우 초기 속도 차이가 너무 크게(4배이상) 나므로 lint-staged를 사용한다.
특이사항 - pre-commit
// ...생략...
"lint-staged": {
"**/*.{js,jsx,ts,tsx}": [
"eslint --fix",
"bash -c tsc --noEmit --pretty"
]
}
// ...생략...
JSON
복사
•
lint-staged를 이용하여 pre-commit을 수행하는데 이때 next lint를 굳이 사용할 필요가 없으므로 코드의 간결함 유지를 위해 eslint를 사용하여 commit을 검사한다.
•
이때, eslint에 —cache 옵션을 사용하지 않았다. 이미 staged된 파일들은 모두 변경된 파일들이므로, 추가적으로 cache를 확인할 필요가 없기 때문이다.
•
“bash -c tsc —noEmit —pretty” 커맨드를 사용하여 타입 검사를 하는데, 이때 bash를 사용하는 이유는 bash 사용 시 프로젝트의 tsconfig.json을 respect하여 타입 검사를 수행하기 때문이다.
특이사항 - pre-push
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
output=$(script -q /dev/null pnpm lint)
line_count=$(echo "$output" | wc -l)
echo "$output"
echo
if [ "$line_count" -gt 6 ]; then
echo "\033[31mLinting failed or there are warnings. Please add and commit your code, then push again.\033[0m"
exit 1
else
echo "\033[32mLinting passed with no warnings or errors.\033[0m"
fi
Bash
복사
•
next lint는 husky의 pre-push에서 수행한다. next lint는 일부 변동사항을 에러 출력 없이 수행해버리므로 결과값을 유추하여 성공했는지, 추가로 설치했는지 여부를 확인하여 사용자에게 add와 commit을 다시 유도한다. 필자는 "결과물이 6줄 이상이면 실패"라는 조건을 이용하여 추가 add, commit 필요 여부를 확인했다.
특이사항 - gitIgnore에 .eslintcache추가
git rm -rf --cached .eslintcache
Bash
복사
•
이미 .eslintcache가 git에 추적되고 있을 경우 먼저 .gitignore에 .eslintcache를 추가 후 위의 명령어를 사용하여 .eslintcache를 git에서 제거한다.