ETC -

💡 실수 줄이기 위해 도입한 Stylelint 자동화 – 삼성 AI 구독 서비스 프로젝트 경험담

  • -

최근 에이전시에서 삼성 AI 구독 서비스 관련 프로젝트에 참여하고 있습니다.
규모가 크고 협업이 많은 만큼, 팀 내부에서 정한 CSS 컨벤션이 굉장히 엄격하게 구성되어 있었죠.

예를 들어,

  • 클래스명은 반드시 kebab-case로 작성
  • 선택자 순서, 중첩 깊이, 단일 선언 규칙 등
  • font-family에 generic family 필수 등...

이 컨벤션을 사람이 수동으로 일일이 맞추기란… 쉽지 않았습니다 😓
작업을 하다 보면 자꾸만 작고 반복적인 실수가 발생했고,
이로 인해 코드 리뷰에서 지적도 자주 받게 되더라고요.

❗ 그래서 결정했습니다 – 자동화 도입!

이러한 문제를 해결하기 위해 도입한 것이 바로 Stylelint 자동화 설정입니다.
.stylelintrc.json 파일을 통해 규칙을 명시하고, VS Code 확장 프로그램과 함께 사용했어요.

stylelint, stylelint-config-standard, stylelint-order 3가지 의존성를 설치해야 합니다.

// package.json
{
  "name": "name",
  "version": "1.0.0",
  "main": "index.js",
  "repository": "",
  "author": "",
  "license": "MIT",
  "type": "module",
  "scripts": {},
  "devDependencies": {
    "stylelint": "^16.21.0",
    "stylelint-config-standard": "^38.0.0",
    "stylelint-order": "^7.0.0"
  }
}
// .stylelintrc.json
{
  "extends": ["stylelint-config-standard"],
  "plugins": ["stylelint-order"],
  "rules": {
    "order/properties-order": [["display", "position", "top", "left"]],
    "selector-class-pattern": "^[a-z0-9\\-]+$",
    "font-family-no-missing-generic-family-keyword": true
  }
}

설정 후에는 저장할 때마다 자동으로 포맷되고, 규칙에 어긋나는 코드엔 경고도 뜨기 때문에
실수를 미리 방지할 수 있었고, 리뷰도 훨씬 매끄러워졌습니다.

✅ 정리

  • stylelint는 개발자에게 코드 컨벤션 규칙에 안맞다고 알려주는 용도이다.
  • 코드 컨벤션이 까다로운 프로젝트일수록 자동화 도구는 필수!
  • Stylelint 설정 한 번으로 반복 실수를 줄이고 팀 기준에 맞춘 코드 작성 가능
  • 특히 에이전시 환경에서는 빠르고 정확한 작업을 위해 이런 도구의 선제적 도입이 중요

❗자동으로  파일들 규칙에 맞게 수정

프로젝트를 진행하다 보면, 이미 작성된 CSS 또는 SCSS 파일들이 스타일 가이드에 맞지 않거나, 일관되지 않게 작성되어 있는 경우가 있습니다.
이럴 땐 수동으로 하나씩 고치는 대신, PostCSS를 활용하여 자동화된 방식으로 규칙을 적용하면 훨씬 효율적입니다.

왜 PostCSS인가?

PostCSS는 CSS를 JavaScript처럼 파싱하고 변환할 수 있는 강력한 도구입니다.
다양한 플러그인을 통해 스타일을 자동 정리하거나, 브라우저 호환성 처리, 변수/중첩 처리 등도 가능합니다.

사용 목적

이번 작업의 목적은 다음과 같습니다:

  • 기존 CSS 파일을 스타일 가이드 규칙에 맞게 일괄 정리
  • 나아가 Stylelint 규칙과도 호환 가능하도록 설정
yarn add postcss postcss-cli postcss-sorting -D

최종적인 package.json 파일 입니다.

// package.json
{
  "name": "",
  "version": "1.0.0",
  "main": "index.js",
  "repository": "",
  "author": "",
  "license": "MIT",
  "type": "module",
  "scripts": {
    "css:minify": "node lib/clean.js compressed",
    "lint:css": "stylelint 'css/**/*.css' --fix",
    "sort:css": "postcss 'css/**/*.css' --config postcss.config.cjs -r",
    "css:all": "yarn css:minify && yarn lint:css && yarn sort:css"
  },
  "devDependencies": {
    "@trivago/prettier-plugin-sort-imports": "^4.3.0",
    "clean-css": "^5.3.3",
    "eslint": "^8",
    "eslint-config-prettier": "^10.1.1",
    "eslint-plugin-prettier": "^5.1.0",
    "postcss": "^8.5.6",
    "postcss-cli": "^11.0.1",
    "postcss-sorting": "^9.1.0",
    "prettier": "^3.1.1",
    "stylelint": "^16.21.0",
    "stylelint-config-standard": "^38.0.0",
    "stylelint-order": "^7.0.0"
  },
  "lint-staged": {
    "**/*.{js,ts,jsx,tsx}": "eslint --fix",
    "**/*.{js,ts,jsx,tsx,css,scss,md}": "prettier --write"
  }
}
// lib/clean.js
import fs from 'fs';
import glob from 'glob';
import CleanCSS from 'clean-css';

const inputFiles = glob.sync('./css/**/*.css'); // 기존 경로를 정확히 설정하세요

inputFiles.forEach(file => {
  const inputCSS = fs.readFileSync(file, 'utf8');

  const output = new CleanCSS({ format: 'keep-breaks' }).minify(inputCSS);

  // ✅ 기존 파일에 덮어쓰기
  fs.writeFileSync(file, output.styles);
  console.log('✅ 덮어쓰기 완료:', file);
});
// .stylelintrc.json
{
  "extends": ["stylelint-config-standard"],
  "plugins": ["stylelint-order"],
  "rules": {
    "selector-id-pattern": null,
    "selector-class-pattern": null,
    "declaration-block-single-line-max-declarations": null,
    "no-descending-specificity": null,
    "no-duplicate-selectors": null,
    "function-url-quotes": null,
    "color-function-notation": null,
    "property-no-vendor-prefix": null,
    "comment-empty-line-before": null,
    "shorthand-property-no-redundant-values": null,
    "font-family-no-missing-generic-family-keyword": null,
    "rule-empty-line-before": ["always", {
      "except": ["first-nested"],
      "ignore": ["after-comment"]
    }],
    "order/properties-order": [
      {
        "emptyLineBefore": "never",
        "properties": ["display", "position", "top", "right", "bottom", "left", "z-index"]
      },
      {
        "emptyLineBefore": "never",
        "properties": ["width", "height", "margin", "padding"]
      },
      {
        "emptyLineBefore": "never",
        "properties": ["font-size", "line-height", "text-transform", "color"]
      },
      {
        "emptyLineBefore": "never",
        "properties": ["background", "border", "box-shadow"]
      }
    ]
  }
}
//.postcss-sorting.json
{
  "order": [
    "custom-properties",
    "dollar-variables",
    "declarations",
    "rules",
    "at-rules"
  ],
  "properties-order": [
    "display",
    "position",
    "top",
    "right",
    "bottom",
    "left",
    "z-index",

    "width",
    "height",
    "margin",
    "padding",

    "font-size",
    "line-height",
    "text-transform",
    "color",

    "background",
    "border",
    "box-shadow"
  ],
  "unspecified-properties-position": "bottom"
}
//postcss.config.cjs
module.exports = {
  plugins: [
    require('postcss-sorting')(require('./.postcss-sorting.json'))
  ]
};

이렇게 설정하고 터미널에서 아래 명령어를 하나씩 실행해보고, 오류발견하면 찾아가서 수정하는 방향으로 하는것으로 진행 햇다.
lint:css에서 많은 오류들을 찾아줄것이다.!

"css:minify": "node lib/clean.js compressed",
"lint:css": "stylelint 'css/**/*.css' --fix",
"sort:css": "postcss 'css/**/*.css' --config postcss.config.cjs -r",

💡 본 문서의 모든 코드는 실무와 유사한 방식으로 작성되었으나, 실제 운영 중인 코드는 아닙니다.

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.