Front-end

나의 전용 템플릿 엔진(?) 제작기 - ESLint

rendar02 2025. 3. 14. 23:17
반응형

개요

앞선 템플릿엔진용 모노레포를 구성하였다.
이번 내용에서는 템플릿 엔진용 ESLinst 구성을 해볼까한다. 사실 너무나 간단한 구성이라서 저번 내용에 넣을까 고민했지만, 분리시키는게 가독성이나 내용 측면에서 좋을 것 같아서 따로 분리시켰다.

환경구성

my-monorepo-template/      
├── packages/
│   ├── **eslint-config/**
│   ├── prettier-config/
│   ├── tsconfig/
│   ├── react-template/
│   └── nextjs-template/
├── package.json
└── pnpm-workspace.yaml

기존 구조에서 packages/eslint-confing 부분에 넣을예정이다

초기설정

  1. 패키지내부에 폴더랑 파일을 추가해 주도록한다

    🚨Typescript 및 Eslint 등의 설치는 되어있는 상태로 설명

     mkdir -p packages/eslint-config
     cd packages/eslint-config
    
     # 패키지 초기화
     pnpm init
  2. 내부 파일을 만들어서 넣는다

     # 다음 파일들 생성
     touch base.js index.js react.js next.js typeCheck.js prettier.js README.md

설치

패키지 내부에 의존성을 설치해준다 (현재 필자는 아래 패키지들만 설치)

패키지 이름부분은 패키지내부 package.json - name 부분을 따라간다 참고

pnpm --filter **${본인 패키지 이름}**/eslint-config add @next/eslint-plugin-next @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-config-airbnb eslint-config-next eslint-config-prettier eslint-config-standard eslint-config-standard-jsx eslint-import-resolver-typescript eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-n eslint-plugin-prettier eslint-plugin-promise eslint-plugin-react eslint-plugin-react-hooks

각 파일별 설정은 아래와 같다

  • base.js: 기본 ESLint 설정 (프론트엔드/백엔드 공통)
  • index.js: 기본 JavaScript/TypeScript 규칙
  • react.js: React 관련 규칙
  • next.js: Next.js 확장 규칙
  • typeCheck.js: 강화된 TypeScript 타입 체크
  • prettier.js: ESLint와 Prettier 통합
  1. package.json

    homepage, repository는 각각 본인들의 깃 링크를 삽입, files또한 배포파일 경로를 넣는다

     {
       "name": "@rendardev/eslint-config",
       "homepage": "https://github.com/RendarCP/rendar-mono-template",
       "repository": {
         "type": "git",
         "url": "git+https://github.com/RendarCP/rendar-mono-template.git"
       },
       "version": "1.0.1",
       "description": "ESLint configurations for Rendar Mono Template",
       "main": "index.js",
       "files": [
         "*.js",
         "README.md"
       ],
       "publishConfig": {
         "access": "public"
       },
       "license": "MIT",
       "dependencies": {
         "@next/eslint-plugin-next": "^15.1.4",
         "@typescript-eslint/eslint-plugin": "^8.20.0",
         "@typescript-eslint/parser": "^8.20.0",
         "eslint-config-airbnb": "^19.0.4",
         "eslint-config-next": "latest",
         "eslint-config-prettier": "^10.0.1",
         "eslint-config-standard": "^17.1.0",
         "eslint-config-standard-jsx": "^11.0.0",
         "eslint-import-resolver-typescript": "^3.8.7",
         "eslint-plugin-import": "^2.31.0",
         "eslint-plugin-jsx-a11y": "^6.10.2",
         "eslint-plugin-n": "^16.6.2",
         "eslint-plugin-prettier": "^5.2.1",
         "eslint-plugin-promise": "^6.6.0",
         "eslint-plugin-react": "^7.37.4",
         "eslint-plugin-react-hooks": "^5.1.0"
       },
       "peerDependencies": {
         "eslint": ">=8.0.0"
       }
     }
  2. 각 파일들

    • base.js

        /**
         * base.js
         *
         * 기본 ESLint 설정 파일
         * 프론트엔드와 백엔드 모두에서 사용할 수 있는 공통 규칙 정의
         *
         * @type {import('eslint').Linter.Config}
         */
        module.exports = {
          // 실행 환경 설정
          env: {
            browser: true, // 브라우저 환경 지원
            es6: true, // ES6 문법 지원
            node: true, // Node.js 환경 지원
          },
      
          // TypeScript 파서 사용
          parser: "@typescript-eslint/parser",
      
          // 확장할 기본 규칙 설정
          extends: [
            "eslint:recommended", // ESLint 기본 권장 규칙
            "plugin:react/recommended", // React 권장 규칙
            "plugin:prettier/recommended", // Prettier 통합 지원
          ],
      
          // React 설정
          settings: {
            react: {
              version: "detect", // React 버전 자동 감지
            },
          },
      
          // 파서 옵션 설정
          parserOptions: {
            ecmaFeatures: {
              jsx: true, // JSX 문법 지원
            },
            ecmaVersion: "latest", // 최신 ECMAScript 버전 사용
            sourceType: "module", // ES 모듈 시스템 사용
          },
      
          // 사용할 플러그인
          plugins: [
            "react", // React 린트 규칙
            "react-hooks", // React Hooks 린트 규칙
            "@typescript-eslint", // TypeScript 규칙
            "prettier", // Prettier 통합
            "import", // import/export 문법 규칙
          ],
      
          // 세부 규칙 설정
          rules: {
            // React 관련 규칙
            "react-hooks/rules-of-hooks": "error", // Hooks 규칙 강제
            "react/jsx-no-useless-fragment": 0, // 불필요한 Fragment 검사 비활성화
            "react/prop-types": "off", // PropTypes 검사 비활성화 (TypeScript 사용 시)
            "react/function-component-definition": [
              2, // error
              {
                namedComponents: ["arrow-function", "function-declaration"], // 화살표 함수와 함수 선언식 모두 허용
              },
            ],
            "react/jsx-props-no-spreading": "off", // props 전개 연산자 사용 허용
            "react/react-in-jsx-scope": 0, // React import 필수 규칙 비활성화 (React 17+)
            "react/prefer-stateless-function": 0, // 상태 없는 컴포넌트 강제 비활성화
            "react/jsx-filename-extension": 0, // JSX 파일 확장자 제한 비활성화
            "react/jsx-one-expression-per-line": 0, // JSX 내 한 줄 한 표현식 규칙 비활성화
      
            // Import 관련 규칙
            "import/order": 0, // import 순서 규칙 비활성화
            "import/prefer-default-export": "off", // 기본 내보내기 선호 규칙 비활성화
            "import/extensions": 0, // 확장자 명시 규칙 비활성화
            "import/no-unresolved": 0, // 미해결 import 경로 검사 비활성화
      
            // JavaScript/TypeScript 관련 규칙
            "prefer-arrow-callback": "off", // 화살표 함수 콜백 선호 규칙 비활성화
            "no-var": "error", // var 대신 let/const 사용 강제
            "no-dupe-keys": "error", // 객체 내 중복 키 금지
            "no-nested-ternary": 0, // 중첩 삼항 연산자 허용
      
            // TypeScript 관련 규칙
            "@typescript-eslint/no-explicit-any": "error", // any 타입 사용 금지
          },
      
          // 전역 변수 설정
          globals: {
            React: "writable", // React를 전역 변수로 설정
          },
        };
    • index.js

        /**
         * index.js
         *
         * 기본 ESLint 설정 - 주로 Node.js 환경에서 사용되는 설정
         * JavaScript/TypeScript 코드를 위한 핵심 규칙 정의
         *
         * @type {import('eslint').Linter.Config}
         */
        module.exports = {
          // Node.js 환경 설정
          env: {
            node: true,
          },
      
          // 확장할 규칙 설정
          extends: [
            "eslint:recommended", // ESLint 기본 권장 규칙
            "plugin:import/recommended", // import 관련 권장 규칙
            "plugin:promise/recommended", // Promise 관련 권장 규칙
            "standard", // JavaScript Standard 스타일
          ],
      
          // 기본 규칙
          rules: {
            // 함수 스타일 규칙
            "func-style": ["error", "declaration", { allowArrowFunctions: true }], // 함수 선언식 강제, 화살표 함수 허용
      
            // 코드 품질 규칙
            "no-console": "error", // console.log() 등 사용 금지
            "no-empty-function": "off", // 빈 함수 허용
            "no-implicit-coercion": ["error", { allow: ["!!"] }], // 암시적 형변환 제한, !! 연산자는 허용
            "no-return-await": "error", // 불필요한 return await 금지
            "no-unused-expressions": [
              // 미사용 표현식 제한
              "error",
              {
                allowShortCircuit: true, // 단축 평가 허용
                allowTernary: true, // 삼항 연산자 허용
                allowTaggedTemplates: true, // 태그된 템플릿 허용
              },
            ],
            "no-unused-vars": [
              // 미사용 변수 제한
              "error",
              { ignoreRestSiblings: true, argsIgnorePattern: "^_+$" }, // rest 연산자 형제 무시, _ 시작 매개변수 무시
            ],
            "no-use-before-define": ["error", { functions: false }], // 정의 전 사용 금지, 함수는 예외
            "no-void": ["error", { allowAsStatement: true }], // void 연산자 제한, 문장으로는 허용
            "object-shorthand": ["error", "properties"], // 객체 속성 단축 문법 사용
            "require-atomic-updates": "off", // 원자적 업데이트 요구 비활성화
      
            // Import 관련 규칙
            "import/order": [
              // import 순서 규칙
              "error",
              {
                "newlines-between": "always", // 그룹 간 항상 새 줄 추가
                pathGroups: [
                  {
                    pattern: "@/**", // @ 경로 패턴
                    group: "parent", // 부모 그룹에 포함
                    position: "before", // 부모 그룹 앞에 배치
                  },
                ],
                pathGroupsExcludedImportTypes: ["builtin"], // 내장 모듈은 제외
              },
            ],
            "import/newline-after-import": "error", // import 문 후 새 줄 강제
      
            // Promise 관련 규칙
            "promise/catch-or-return": ["error", { allowFinally: true }], // Promise는 catch 또는 return 필수, finally 허용
            "promise/prefer-await-to-callbacks": "error", // 콜백보다 await 선호
            "promise/prefer-await-to-then": "error", // then보다 await 선호
          },
      
          // TypeScript 파일을 위한 추가 설정
          overrides: [
            {
              files: ["*.ts", "*.tsx"], // TypeScript 파일 대상
              extends: [
                "plugin:@typescript-eslint/recommended", // TypeScript 권장 규칙
                "plugin:import/typescript", // TypeScript import 지원
              ],
              parser: "@typescript-eslint/parser", // TypeScript 파서 사용
              settings: {
                "import/resolver": {
                  typescript: {
                    alwaysTryTypes: true, // 항상 타입 정의 파일 시도
                  },
                },
              },
              rules: {
                // TypeScript 관련 규칙
                "@typescript-eslint/consistent-type-assertions": "error", // 일관된 타입 단언 형식 사용
                "@typescript-eslint/consistent-type-definitions": "error", // 일관된 타입 정의 형식 사용
                "@typescript-eslint/explicit-member-accessibility": "error", // 명시적 접근 제한자 요구
                "@typescript-eslint/no-var-requires": "off", // require() 사용 허용
                "@typescript-eslint/no-empty-function": "off", // 빈 함수 허용
                "@typescript-eslint/no-empty-object-type": [
                  // 빈 객체 타입 제한
                  "error",
                  { allowInterfaces: "always" }, // 인터페이스는 항상 허용
                ],
      
                // TypeScript로 재정의된 규칙
                "no-use-before-define": "off", // JS 버전 비활성화
                "@typescript-eslint/no-use-before-define": [
                  // TS 버전 활성화
                  "error",
                  { functions: false }, // 함수는 정의 전 사용 허용
                ],
                "no-unused-expressions": "off", // JS 버전 비활성화
                "@typescript-eslint/no-unused-expressions": [
                  // TS 버전 활성화
                  "error",
                  {
                    allowShortCircuit: true, // 단축 평가 허용
                    allowTernary: true, // 삼항 연산자 허용
                    allowTaggedTemplates: true, // 태그된 템플릿 허용
                  },
                ],
                "no-unused-vars": "off", // JS 버전 비활성화
                "@typescript-eslint/no-unused-vars": [
                  // TS 버전 활성화
                  "error",
                  { ignoreRestSiblings: true, argsIgnorePattern: "^_+$" }, // rest 연산자 형제 무시, _ 시작 매개변수 무시
                ],
                // '@typescript-eslint/naming-convention': createNamingConventionConfig(), // 명명 규칙 (주석 처리됨)
      
                // TypeScript에서 불필요한 import 규칙 비활성화
                "import/named": "off", // TS가 이미 확인함
                "import/namespace": "off", // TS가 이미 확인함
                "import/default": "off", // TS가 이미 확인함
                "import/no-named-as-default-member": "off", // TS가 이미 확인함
              },
            },
          ],
        };
    • react.js

        /**
         * react.js
         *
         * React 애플리케이션을 위한 ESLint 설정
         * React, JSX, 접근성(a11y) 관련 규칙을 정의
         *
         * @type {import('eslint').Linter.Config}
         */
        module.exports = {
          // 브라우저 환경 설정
          env: {
            browser: true, // 브라우저 환경에서 실행
          },
      
          // 확장할 규칙 설정
          extends: [
            "plugin:react/recommended", // React 권장 규칙
            "plugin:react/jsx-runtime", // React 17+ JSX 변환 지원 (import React 불필요)
            "plugin:react-hooks/recommended", // React Hooks 권장 규칙
            "plugin:jsx-a11y/recommended", // JSX 접근성(a11y) 권장 규칙
            "standard-jsx", // JSX 표준 스타일
          ],
      
          // React 설정
          settings: {
            react: {
              version: "detect", // React 버전 자동 감지
            },
          },
      
          // React 관련 규칙
          rules: {
            // JSX 문법 관련 규칙
            "react/jsx-no-useless-fragment": ["error", { allowExpressions: true }], // 불필요한 Fragment 금지, 표현식은 허용
            "react/jsx-pascal-case": "error", // 컴포넌트 이름 파스칼 케이스 강제
      
            // 속성 관련 규칙
            "react/no-unknown-property": ["error", { ignore: ["css"] }], // 알 수 없는 속성 금지, css 속성은 허용 (CSS-in-JS)
            "react/prop-types": "off", // PropTypes 검사 비활성화 (TypeScript 사용 시)
      
            // 컴포넌트 스타일 규칙
            "react/self-closing-comp": [
              "error",
              {
                component: true, // 내용 없는 컴포넌트는 자체 닫는 태그 사용
                html: true, // HTML 요소도 적용
              },
            ],
      
            // 일반 코드 스타일 규칙
            curly: ["error", "all"], // 모든 조건문에 중괄호 사용 강제
          },
        };
    • next.js

        /**
         * next.js
         *
         * Next.js 프레임워크 프로젝트를 위한 ESLint 설정
         * React 설정을 확장하고 Next.js 특화 규칙 추가
         */
        module.exports = {
          // 확장할 설정
          extends: [
            "./react.js", // React 기본 설정 확장
            "next/core-web-vitals", // Next.js 코어 웹 바이탈 규칙 (성능 측정 지표)
          ],
      
          // Next.js 설정
          settings: {
            next: {
              rootDir: ["apps/*/", "packages/*/"], // 모노레포 구조의 루트 디렉토리 설정
            },
          },
        };
    • prettier.js

        /**
         * prettier.js
         *
         * ESLint와 Prettier 통합을 위한 설정
         * 코드 포맷팅 일관성 유지
         *
         * @type {import('eslint').Linter.Config}
         */
        module.exports = {
          // Prettier 관련 설정 확장
          extends: ["prettier"], // ESLint와 Prettier 충돌 규칙 비활성화
      
          // Prettier와 함께 사용할 ESLint 규칙
          rules: {
            /**
             * prettier에서 기본적으로 비활성화하지만
             * 필요에 의해 다시 활성화하는 규칙
             */
            curly: ["error", "all"], // 모든 조건문에 중괄호 사용 강제
          },
        };

배포 및 테스트

전 글에서 root - package.json은 변동될 예정이다했는데 아래와같아진다

아직은 eslint 설정 부분이지만 아래 과정으로 넣을 예정이다 (모두 배포예정)

  • prettier
  • nextjs-template
  • react-template
{
  "name": "root",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "private": true,
  "scripts": {
    "build": "pnpm -r build",
    "test": "pnpm -r test",
    "lint": "pnpm -r lint",
    "publish-all": "pnpm -r publish",
    "publish-eslint": "pnpm --filter @rendardev/eslint-config publish",
    "publish-prettier": "pnpm --filter @rendardev/prettier-config publish",
    "publish-react": "pnpm --filter @rendardev/react-template publish",
    "publish-nextjs": "pnpm --filter @rendardev/nextjs-template publish"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "eslint": "^9.0.0",
    "typescript": "^5.8.2"
  },
  "workspaces": [
    "packages/*"
  ],
  "packageManager": "pnpm@8.9.0"
}
  • 배포

      pnpm publish-eslint

테스트는 어렵지 않다
아래 링크에서 README.md를 참고하면된다

https://www.npmjs.com/package/@rendardev/eslint-config?activeTab=readme

물론 모두 사용할리는 없겠지만 만약 모두 사용한다면 순서가 중요하다

위 순서대로 오버라이드 시키는 편이 좋다.

module.exports = {
  extends: [
    "@rendar-mono-template/eslint-config",
    "@rendar-mono-template/eslint-config/next",
    "@rendar-mono-template/eslint-config/typeCheck",
    "@rendar-mono-template/eslint-config/prettier",
  ],
};

마무리

전편에 이어 전용 템플릿 시리즈 - ESLint 편을 보았다
사실 Lint 설정이랄게 크게 없어서 어려움은 없었지만 관련된 패키지 부분에서 많은 부분 시간을 쏟은 것 같다.

GPT랑 클로드 활용해서 코드 분석 및 주석을 통해서 어떤 패키지인지 파악하는게 굉장히 쉬워져서
패키지 만드는데 큰 어려움이 없었던 것 같다

다음은 가장 쉬운 prettier 부분으로 넘어갈려고한다

반응형