서론
최근 새로운 프로젝트를 시작하였습니다. 프로젝트를 시작하면서 이것저것 초기세팅을 진행하였는데, 그 중 eslint는 이전에 사용한 eslint를 이번 프로젝트에도 적용했는데, 똑같이 설정했는데도 적용이 안 되는 상황이 발생했습니다...;;
지난번 프로젝트에서는 정상적으로 작동했던 eslint인데 적용이 안되니까 상당히 당황스러웠는데, 여기저기 찾아보니 eslint가 업데이트하면서 v9부터 flat config가 디폴트 config로 등록되면서 이전에 작성했던 module.exports로 작성했던 config는 작동하지 않았던 것입니다. 이걸 몰라서 한참 헤맸다;; 내 코드가 잘못된 줄 알고 애꿎은 코드만 계속 들여다보고 있었다... 아무튼 원인을 알게 되어 flat config로 리팩토링을 진행하였습니다.
과정
기존 코드를 보면 이런식으로 module.exports를 사용하여 eslint를 설정해 주었습니다. 이걸 이제 flat config로 변환해 주었는데 공식 홈페이지를 참고하면서 변환해주었습니다.
module.exports = {
// ESLint 설정
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
'plugin:storybook/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'], // 무시할 파일 패턴
parser: '@typescript-eslint/parser', // TypeScript 코드 파싱기
plugins: ['react-refresh'], // 사용할 플러그인
rules: {
// 사용자 정의 규칙
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true }, // React 컴포넌트만 내보내도록 하는 규칙
],
'@typescript-eslint/explicit-module-boundary-types': 'off', // 함수 반환 타입 명시 규칙 해제
'@typescript-eslint/no-unused-vars': 'error', // 사용하지 않는 변수 사용시 에러 규칙
'@typescript-eslint/no-empty-interface': 'error', // 빈 인터페이스 사용 시 에러 규칙
'@typescript-eslint/no-var-requires': 'off', // require 사용 규칙 해제
'@typescript-eslint/ban-types': 'off', // 특정 타입 사용 제한 규칙 해제
'@typescript-eslint/no-non-null-assertion': 'off', // 비-널 단언 연산자 사용 규칙 해제
'@typescript-eslint/no-empty-function': 'off', // 빈 함수 규칙 해제
'@typescript-eslint/no-namespace': 'off', // 네임스페이스 사용 규칙 해제
'@typescript-eslint/ban-ts-comment': 'off', // @ts-ignore 사용 규칙 해제
},
};
바뀐 코드는 다음과 같습니다. 기본적인 작성법은 비슷하지만 기존 module.exports를 export default 방식으로 바꿔 선언해 주었고, flat config의 경우 tsconfig에 파일을 명시해주어야 하기에 tsconfig에 등록도 해주었습니다.
그 외 다른 점은 플러그인 사용방식이 다르다는 점..? flat config 방식이 좀 더 사용하기 편하게 바뀐 거 같다.
import prettier from 'eslint-plugin-prettier/recommended';
export default tseslint.config(
{
ignores: ['**/build/**', '**/dist/**', '**/.yarn/**', '**/public/**', '**/.pnp.cjs', '**/.pnp.loader.mjs'],
},
eslint.configs.recommended,
prettier,
{
plugins: {
'@typescript-eslint': tseslint.plugin,
'react-refresh': reactRefreshPlugin,
},
languageOptions: {
parser: tseslint.parser,
parserOptions: {
projectService: true,
},
},
rules: {
// 사용자 정의 규칙
'react-refresh/only-export-components': [
'error',
{ allowConstantExport: true }, // React 컴포넌트만 내보내도록 하는 규칙
],
'@typescript-eslint/explicit-module-boundary-types': 'off', // 함수 반환 타입 명시 규칙 해제
'@typescript-eslint/no-unused-vars': 'warn', // 사용하지 않는 변수 사용시 에러 규칙
'@typescript-eslint/no-empty-interface': 'error', // 빈 인터페이스 사용 시 에러 규칙
'@typescript-eslint/no-var-requires': 'off', // require 사용 규칙 해제
'@typescript-eslint/ban-types': 'off', // 특정 타입 사용 제한 규칙 해제
'@typescript-eslint/no-non-null-assertion': 'off', // 비-널 단언 연산자 사용 규칙 해제
'@typescript-eslint/no-empty-function': 'off', // 빈 함수 규칙 해제
'@typescript-eslint/no-namespace': 'off', // 네임스페이스 사용 규칙 해제
'@typescript-eslint/ban-ts-comment': 'off', // @ts-ignore 사용 규칙 해제
'explicit-module-boundary-types': 'off', // 함수 반환 타입 명시 규칙 해제
'no-unused-vars': 'warn', // 사용하지 않는 변수 사용시 에러 규칙 해제
'no-var-requires': 'off', // require 사용 규칙 해제
'ban-types': 'off', // 특정 타입 사용 제한 규칙 해제
'no-non-null-assertion': 'off', // 비-널 단언 연산자 사용 규칙 해제
'no-empty-function': 'off', // 빈 함수 규칙 해제
'no-namespace': 'off', // 네임스페이스 사용 규칙 해제
'ban-ts-comment': 'off', // @
'no-undef': 'off', // 정의되지 않은 변수 사용 규칙 해제
'prettier/prettier': 'warn', // prettier 규칙
},
},
{
files: ['src/**/*.{js,jsx,ts,tsx}'],
extends: [tseslint.configs.disableTypeChecked],
},
);
추가
아무튼 이렇게 flat config로 리팩토링을 진행해 주었고, 이왕 리팩토링을 진행하면서 전체적으로 규칙을 다듬기로 하였다. 팀원분과 의논하에 기존에 사용하던 규칙에서 사용할 필요가 없다 싶은 규칙들을 정리해 주었고, 접근성 관련 규칙들과 테스트 관련 규칙들을 추가해 주었습니다. 여기서 한 번 더 헤맸다;;;; 접근성 관련 규칙을 사용하기 위해 jsx-a11y 플러그인을 사용하였는데 공식 문서에서 하라는 대로 똑같이 했는데도 적용이 안 되는 것이다ㅜㅜㅜㅜ 진짜 많이 고민을 했는데 원인은 간단했습니다. 이번 프로젝트에서 styled-components를 사용하여 개발 중이었는데 알고 보니 styled-components용 접근성 플러그인이 따로 있었습니다..ㅎ (styled-components-a11y 플러그인) 아무튼 이렇게 새로운 eslint를 적용해 주었습니다. 바뀐 규칙이 맘에 들어 다음 프로젝트에서도 계속 사용하게 될 거 같은 느낌입니다...ㅎㅎ
import globals from 'globals';
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import reactRefreshPlugin from 'eslint-plugin-react-refresh';
import eslintPluginJsxA11y from 'eslint-plugin-jsx-a11y';
import eslintPluginPrettier from 'eslint-plugin-prettier';
import styledComponentsA11y from 'eslint-plugin-styled-components-a11y';
import eslintPluginVitest from '@vitest/eslint-plugin';
export default tseslint.config(
{
ignores: [
'**/build/**',
'**/dist/**',
'**/.yarn/**',
'**/public/**',
'**/.pnp.cjs',
'**/.pnp.loader.mjs',
'.storybook/**',
'**/*.stories.tsx',
'**/*.stories.ts',
],
},
{
files: ['**/*.ts', '**/*.tsx'],
plugins: {
'@typescript-eslint': tseslint.plugin,
'react-refresh': reactRefreshPlugin,
'jsx-a11y': eslintPluginJsxA11y,
prettier: eslintPluginPrettier,
'styled-components-a11y': styledComponentsA11y,
vitest: eslintPluginVitest,
},
languageOptions: {
parser: tseslint.parser,
parserOptions: {
projectService: true,
},
globals: {
...globals.browser,
...globals.node,
},
},
rules: {
// 사용자 정의 규칙
'react-refresh/only-export-components': [
'error',
{ allowConstantExport: true }, // React 컴포넌트만 내보내도록 하는 규칙
],
'prettier/prettier': 'warn', // prettier 규칙
'jsx-a11y/alt-text': 'warn', // 이미지에 alt 속성 요구
'jsx-a11y/anchor-has-content': 'warn', // 앵커 태그가 콘텐츠를 가져야 함
'jsx-a11y/aria-role': 'warn', // 올바른 ARIA 역할 사용
'jsx-a11y/no-autofocus': 'warn', // 자동 포커스 사용 제한
'jsx-a11y/no-noninteractive-element-to-interactive-role': 'warn', // 비인터랙티브 요소에 인터랙티브 역할 제한
'jsx-a11y/label-has-associated-control': [
'warn',
{ labelComponents: ['label'], labelAttributes: ['htmlFor'], controlComponents: ['input'] }, // 레이블과 연결된 컨트롤 요구
],
'jsx-a11y/label-has-for': ['warn', { required: { every: ['id'] } }], // 레이블에 id 연결 요구
'styled-components-a11y/alt-text': 'error', // styled-components에 적용되는 규칙
'styled-components-a11y/anchor-has-content': 'warn',
'styled-components-a11y/aria-role': 'warn',
'styled-components-a11y/label-has-associated-control': 'warn',
'styled-components-a11y/no-autofocus': 'warn',
'styled-components-a11y/no-noninteractive-element-to-interactive-role': 'warn',
'vitest/expect-expect': 'warn',
'vitest/no-disabled-tests': 'warn',
'vitest/consistent-test-it': ['warn', { fn: 'it', withinDescribe: 'it' }],
},
},
{
extends: [tseslint.configs.disableTypeChecked],
},
eslint.configs.recommended,
);
'React' 카테고리의 다른 글
[React] 재사용 가능한 AppBar 제작하기 (0) | 2025.01.13 |
---|---|
[React] 라이브러리 없이 캘린더 컴포넌트 구현하기 (0) | 2025.01.11 |
[React] Kakao Map Api 사용하기 (5) - debounce를 활용한 Api 호출 최적화하기 (1) | 2024.09.15 |
[React] Kakao Map API 사용하기 (4) - customoverlay 클릭 이벤트 등록하기 (1) | 2024.08.30 |
[React] Kakao Map API 사용하기 (3) - customoverlay를 활용하여 마커 커스텀 하기 (1) | 2024.08.29 |