[JS] 폴더, 파일 컨벤션 ESLint 적용하기

낙경·2024년 8월 12일

프론트엔드

목록 보기
5/12

주의

❗ ESLint는 js 린터이므로, js(및 모듈js 등)외의 확장자는 기본적으로 오류를 인식할 수 없음.

따라서, 다른 확장자도 인식하게 하기 위해서는 일반적으로 해당 확장자를 지원하게 하는 plugin을 설치해야 함

상황

폴더 및 파일명 컨벤션을 정했지만, 이를 일일이 기억하고 적용하기 어려웠음.

문제

  • 개발에 집중하기 어려움
  • 실수로 컨벤션을 어기는 경우 발생
  • 팀원 간 일관성 유지 어려움(개인프로젝트지만)

⇒ 휴먼 에러

원인

컨벤션을 자동으로 강제하는 도구를 커스텀하여 활용하지 않음

해결

라이브러리 설치

eslint-plugin-project-structure 플러그인을 통해 folder 및 file structure 컨벤션 정의

eslint.config.js에 플러그인 추가

export default [
  //...
  {
    files: [
      "**/*.{js,mjs,cjs,ts,jsx,tsx}",
      // 확장자가 없는 파일
      // [참고] https://eslint.org/docs/latest/use/configure/configuration-files#specifying-files-without-extension
      "**/!(*.*)",
      // gitkeep 파일에 대해서도 검사 (빈 폴더 추적)
      "**.gitkeep",
    ],
    languageOptions: {
      parser: projectStructureParser,
    },
    plugins: {
      "project-structure": projectStructurePlugin,
    },
    rules: {
      "project-structure/folder-structure": ["error", folderStructureConfig],
    },
  },
];

상세 규칙 정의

코드

import {createFolderStructure} from "eslint-plugin-project-structure";

export const folderStructureConfig = createFolderStructure({
  structure: {
    children: [
      {
        name: "*",
      },
      {
        name: "src",
        children: [
          {name: "App.tsx"},
          {name: "main.tsx"},
          {name: "vite-env.d.ts"},
          {
            name: "@commons",
            children: [
              {name: "components", ruleId: "componentRule"},
              {name: "constants", ruleId: "tsRule"},
              {name: "hooks", ruleId: "hooksRule"},
              {name: "layouts", ruleId: "componentRule"},
              {name: "routes", ruleId: "componentRule"},
              {name: "services", ruleId: "tsRule"},
              {name: "stores", ruleId: "tsRule"},
              {name: "types", ruleId: "tsRule"},
              {name: "utilities", ruleId: "tsRule"},
            ],
          },
          {name: "assets", children: [{name: "*"}]},
          {
            name: "domains",
            children: [
              {
                name: "*",
                children: [
                  {name: "apis", ruleId: "tsRule"},
                  {name: "components", ruleId: "componentRule"},
                  {name: "hooks", ruleId: "hooksRule"},
                  {name: "layouts", ruleId: "componentRule"},
                  {name: "pages", ruleId: "componentRule"},
                  {name: "types", ruleId: "tsRule"},
                ],
              },
            ],
          },
        ],
      },
    ],
  },

});

폴더 및 파일 네이밍 rule 정의

  rules: {
    hooksRule: {
      children: [
        {name: ".gitkeep"},
        {name: "use{PascalCase}.ts"},
        {
          name: "use{PascalCase}",
          children: [
            {
              name: "use{PascalCase}",
              children: [
                {name: ".gitkeep"},
                {name: "use{PascalCase}.ts"},
                {name: "use{PascalCase}", ruleId: "hooksRule"},
              ],
            },
          ],
        },
      ],
    },
    componentRule: {
      children: [
        {name: ".gitkeep"},
        {name: "{PascalCase}(.styled)?.tsx"},
        {
          name: "{PascalCase}",
          children: [
            {name: ".gitkeep"},
            {name: "{PascalCase}(.styled)?.tsx"},
            {name: "{PascalCase}", ruleId: "componentRule"},
          ],
        },
      ],
    },
    tsRule: {
      children: [
        {name: ".gitkeep"},
        {name: "{camelCase}(.*)?.ts"},
        {
          name: "{camelCase}",
          children: [{name: ".gitkeep"}, {name: "{camelCase}(.*)?.ts"}, {name: "{camelCase}", ruleId: "tsRule"}],
        },
      ],
    },
  },
  • components: PascalCase.tsx
  • hooks: usePascalCase.ts
  • 각 도메인별 구조 정의
  • 중첩된 구조 허용



트러블 슈팅

문제

  • 분명 tsx 파일만 허용했는데, 확장자가 없는 파일에 대해 린트 검사를 하지 않음
  • 빈 폴더의 폴더 네이밍이 컨벤션을 어겼음에도 오류를 내지 않음

⇒ --debug를 사용하여 lint 내용을 확인했는데 structure linting이 동작하지 않음을 발견

원인

  • ESLint가 기본적으로 다른 확장자는 검사를 지원하지 않는 다는 것을 간과함
  • 또한 파일을 대상으로 분석하는 서비스이기 떄문에, 빈 폴더에 대해서는 검사자체를 진행하지 않는 다는 것을 간과함

해결

1. 확장자가 없는 파일 검사

  • eslint-plugin-project-structure 플러그인에서 다른 file도 검사할 수 있도록 확장자 추가
  • 특히 확장자를 실수로 작성하지 않아도 이를 검사하도록 **/!(*.*) 추가

2. 빈 폴더 추적

  • 빈 폴더를 감지하지 못하는 것은, 기본적으로 해결하기 어려움
  • 하지만, 빈 폴더에는 컨벤션에 따라 .gitkeep을 반드시 추가하도록 함(git에서도 추적하는데 필요함)
  • ESLint에서는 .gitkeep 파일을 추적하면 빈 폴더도 추적 가능

테스트

결론

  • 개발자가 일일이 컨벤션을 기억할 필요 없어짐
  • 프로젝트 구조의 일관성 유지 용이
  • 인적 오류 감소
  • 코드 리뷰 시 구조보다 로직에 집중 가능
  • 빌드 시 에러가 명시적으로 발생하므로 인지하기 쉬움

참고 자료

0개의 댓글