Babel 플러그인 직접 만들기 - JSX에 소스 코드 위치 주입하여 개발 생산성 높이기

2026-03-22

새로운 팀에 합류한 뒤 마주한 프로젝트에는 독특한 스타일 컨벤션이 있었습니다. 기존 팀원들은 브라우저 개발자 도구에서 요소를 찍어보고, 해당 위치를 소스 코드에서 유추하기 위해 고군분투하고 있었습니다.

  • SCSS 영역: 위치 파악을 용이하게 하려고 .checkin-wrapper, .order-item-list처럼 불필요하게 긴 접두사를 모든 클래스에 강제하고 있었습니다. 이는 코드 가독성을 해칠 뿐만 아니라, 매번 중복된 이름을 생각해야 하는 작명 스트레스를 유발했습니다.
  • Styled-components 영역: CSS-in-JS 특성상 클래스명이 변조되거나 컴포넌트 이름만 남기 때문에, SCSS처럼 접두사를 붙이는 방식조차 통하지 않았습니다. 결국 Inspector로 요소를 찍어도 정확히 어떤 파일의 몇 번째 줄인지 한 번에 알 수 없었습니다.

이러한 불편함을 지켜보던 중, 문득 과거에 인상 깊게 읽었던 토스 기술 블로그의 Transpiler, “사용”말고 “활용”하기 글이 떠올랐습니다. 그리고 자연스럽게 '컴파일 단계에서 위치 정보를 주입하면, 개발자 도구에서 바로 소스 위치를 확인할 수 있지 않을까?'라는 아이디어로 이어지게 되었습니다.

JSX에 위치 정보를 주입하는 Babel 플러그인

개발 환경에서 모든 JSX 요소에 해당 요소가 정의된 파일명과 라인 번호를 data-loc 속성을 맨 앞으로 자동 주입해주는 Babel 플러그인을 제작했습니다.

babel-plugin-jsx-data-loc.cjs

/**
 * 개발 환경에서 JSX 요소에 data-loc 속성을 추가하는 Babel 플러그인
 * 해당 태그가 위치한 파일명과 라인 번호를 표시합니다.
 */
module.exports = function babelPluginJsxDataLoc({ types: t }) {
  return {
    name: 'babel-plugin-jsx-data-loc',
    visitor: {
      JSXOpeningElement(path, state) {
        const { filename } = state;
        if (!filename) return;

        // node_modules 내의 파일은 제외
        if (filename.includes('node_modules')) return;

        // Fragment 제외 (<> 또는 <React.Fragment>)
        const elementName = path.node.name;
        if (t.isJSXIdentifier(elementName) && elementName.name === 'Fragment')
          return;
        if (
          t.isJSXMemberExpression(elementName) &&
          elementName.property.name === 'Fragment'
        )
          return;

        // 이미 data-loc 속성이 존재한다면 중복 추가 방지
        const hasDataLoc = path.node.attributes.some(
          (attr) =>
            t.isJSXAttribute(attr) && attr.name && attr.name.name === 'data-loc'
        );
        if (hasDataLoc) return;

        // 파일명만 추출 및 라인 번호 획득
        const fileName = filename.split('/').pop();
        const line = path.node.loc?.start?.line || 0;
        const locValue = `${fileName}:${line}`;

        // data-loc="파일명:라인번호" 속성 생성 및 추가
        const dataLocAttr = t.jsxAttribute(
          t.jsxIdentifier('data-loc'),
          t.stringLiteral(locValue)
        );

        path.node.attributes.unshift(dataLocAttr);
      },
    },
  };
};

Vite 환경에서의 적용

Vite 환경에서는 @vitejs/plugin-reactbabel 옵션을 활용해 이 플러그인을 매우 간단하게 적용할 수 있습니다. 운영 환경(Production)에는 불필요한 속성이 남지 않도록 개발 환경에서만 활성화되도록 구성했습니다.

// vite.config.js 예시
export default defineConfig(({ mode, command }) => {
  const isLocalDev = mode === 'development' && command === 'serve';

  return {
    plugins: [
      react({
        babel: {
          plugins: isLocalDev
            ? ['./plugins/babel-plugin-jsx-data-loc.cjs']
            : [],
        },
      }),
      // ...
    ],
  };
});

플러그인 하나로 바뀐 DX

플러그인 도입 후, 팀의 개발 방식에는 다음과 같은 긍정적인 변화가 생겼습니다.

  1. 작명 스트레스 해소: 더 이상 위치 파악을 위해 클래스명에 .checkin- 같은 불필요한 접두사를 붙일 필요가 없어졌습니다. 의미에만 집중한 간결한 이름을 사용할 수 있게 되었습니다.
  2. Styled-components 디버깅 강화: CSS-in-JS 요소도 개발자 도구에서 data-loc="CheckInForm.tsx:24"와 같이 소스 위치를 명확히 표시해주므로 검색 과정이 대폭 단축되었습니다.
  3. 커뮤니케이션 비용 감소: 동료 개발자나 기획자와 화면을 보며 논의할 때, 특정 요소의 위치를 묻고 답하는 과정 없이 개발자 도구만으로 즉시 소스 코드를 확인할 수 있게 되었습니다.

팀의 불편함을 기술로 해결한다는 것

단순히 라이브러리를 가져다 쓰는 것에 그치지 않고, 팀이 겪고 있는 불편함을 기술적으로 해결했다는 기분이 들어 뿌듯했던 경험이었습니다.

기술 블로그의 아이디어를 프로젝트 상황에 맞춰 직접 구현해 보면서, 간단한 개발이지만 이것이 전체 팀의 생산성에 얼마나 큰 기여를 하는지 다시 한번 느꼈습니다. 만약 팀 내에 반복되는 불편함이 있다면, 컴파일러 레벨에서의 해결책을 한 번쯤 고민해 보시길 추천합니다. 이 글 뿐만 아니라 Transpiler, “사용”말고 “활용”하기도 읽어보기실 추천드리고요 :)

이런 포스팅은 어떤가요?