컴포넌트 복사에서 반복되는 실수를 스캐폴딩으로 줄이기

X Facebook

Written by

in

버튼 컴포넌트를 새로 만들려고 기존 폴더를 복사했다. Button.tsx, Button.test.tsx, index.ts를 붙여 넣고 이름을 바꿨는데, index.ts의 export 경로에는 예전 이름이 남았다. import가 깨진 뒤에야 빠뜨린 줄을 발견했다.

새 컴포넌트에서 달라지는 것은 로직인데, 파일의 뼈대를 만드는 데는 매번 같은 작업이 필요했다. 이 반복을 맡기는 방법이 스캐폴딩이다. 자동으로 만들 수 있는 범위와 도구의 차이, 생성된 파일과 템플릿을 관리할 때의 주의점을 정리한다.

1. 세 번째 Button 폴더를 만들 때

손으로 만들면 이런 모양이 된다.

components/
  Button/
    Button.tsx      # 매번 같은 import·타입·export 반복 타이핑
    Button.test.tsx
    index.ts

세 파일 모두 내용이 거의 같다. 다른 건 이름 하나뿐이다. 그래서 새로 짜지 않고 앞의 것을 복사하게 되는데, 복사에는 나름의 문제가 딸려 온다.

첫째, 이름을 한 군데 덜 바꾼다. 도입에서 본 index.ts가 그 경우다. 파일 세 개 중 두 개만 고치고 나머지 하나는 그대로 두는 것.

둘째, 부속 파일을 빠뜨린다. 테스트 파일 없이 컴포넌트만 만들어 두면 당장은 아무 일도 안 난다. 몇 주 뒤에 “얘는 왜 테스트가 없지” 하고 발견한다.

셋째, 오래된 걸 복사해 오면 낡은 방식이 같이 따라온다. 반년 전에 만든 컴포넌트에서 복사하면 반년 전 방식이 새 폴더에 다시 심긴다. 그게 또 다음 사람의 복사 원본이 된다.

넷째, 사람마다 조금씩 다르게 만든다. 누구는 index.ts를 두고 누구는 안 둔다. 규칙이 README에는 적혀 있는데, 읽고 지키는 건 사람이 하는 일이라 일정이 밀리면 지켜지지 않는다.

2. 뼈대를 자동으로 깐다는 게 무슨 뜻인가

정해진 패턴에 맞는 파일과 폴더를, 명령 한 번으로 만들어 내는 것이다. 이미 써 봤을 가능성이 높다.

# 프로젝트 스캐폴딩 예
npx create-next-app@latest my-app
# → app/, package.json, tsconfig.json, next.config.js … 한 번에 생성

이 한 줄이 하는 일은 폴더 몇 개와 설정 파일 몇 개를 규칙대로 놓아 주는 것뿐이다. 로직은 한 줄도 안 들어 있다. 앞의 Button 세 파일도 마찬가지다. 도구를 붙여 두면 이렇게 된다.

plop component Button   # 위 3파일이 규칙대로 자동 생성

이름의 유래가 좀 재밌다. 건설 현장에서 건물 옆에 세워 두는 임시 골조를 비계, 영어로 scaffold라고 하는데 거기서 온 말이다. 건물 자체는 아니지만 그게 있어야 사람이 올라가서 짓는다는 뜻이다.

핵심은 하나다. 매번 똑같이 반복되는 구조를 사람이 손으로 안 만든다. 빈 껍데기를 도구가 만들어 주고, 사람은 알맹이만 채운다.

여기까지는 타이핑을 좀 줄이는 이야기로 보인다. 실제로 더 큰 건 다른 쪽이다. 규칙이 문서가 아니라 실행되는 명령이 된다는 것. “컴포넌트는 이렇게 만든다”가 README 문장으로 있으면 안 지켜지는데, 생성기로 있으면 지킬 수밖에 없다. 다르게 만들려면 도구를 안 쓰고 손으로 만들어야 하니까.

3. 도구가 하는 일은 결국 세 단계다

도구마다 다른 물건처럼 보이지만, 몇 개를 열어 보면 하는 일이 같다.

  1. 입력(변수) — 이름·타입 등을 프롬프트나 인자로 받음 (Button, --type=modal)
  2. 템플릿 — 플레이스홀더가 박힌 틀 ({{name}}.tsx, export const {{name}})
  3. 산출 — 변수를 치환해 실제 파일 생성

템플릿은 이렇게 생겼다. 이름이 들어갈 자리만 구멍으로 비워 둔 파일이다.

{{! plop 템플릿: component.tsx.hbs }}
export function
{{pascalCase name}}() { return
<div>{{name}}</div>; }

그리고 무엇을 물어보고 그 답을 어디에 쓸지를 따로 적어 둔다.

// plopfile.js — "무엇을 물어보고 어디에 쓸지" 정의
plop.setGenerator("component", {
  prompts: [{ type: "input", name: "name" }],
  actions: [
    {
      type: "add",
      path: "src/components/{{pascalCase name}}/{{pascalCase name}}.tsx",
      templateFile: "templates/component.tsx.hbs",
    },
  ],
});

읽어 보면 별게 없다. name을 하나 물어보고, 그 값을 파일 경로와 파일 내용에 끼워 넣어 저장한다. 복사·붙여넣기·이름 바꾸기로 하던 일을 그대로 옮겨 적은 것에 가깝다.

4. 무엇까지 자동으로 만들 수 있을까

컴포넌트 파일만 되는 게 아니다. 무엇을 만들어 내느냐로 나누면 대충 이렇게 된다.

종류 대상 대표 도구
프로젝트 스캐폴딩 앱 전체 초기 구조 create-next-app, create-vite, create-t3-app, (구)Create React App
파일·컴포넌트 제너레이터 반복되는 파일 세트 Plop(가벼움), Hygen(빠름), Yeoman(원조·범용), Nx generators(모노레포)
API 스캐폴딩(codegen) 스펙 → 클라이언트/타입 OpenAPI Generator, openapi-typescript, GraphQL Code Generator
백엔드 스캐폴딩 모델·CRUD·라우트 Rails scaffold, Django startapp, NestJS CLI, Spring Initializr
인프라 스캐폴딩 IaC·CI 골조 terraform init, cookiecutter

표 1. 무엇을 만들어 내느냐로 나눈 스캐폴딩 다섯 갈래

이 표에서 세 번째 줄이 나중에 문제가 된다. 앞의 둘과 성격이 다른데 같은 칸에 있어서다. 그 얘기는 6절에서 한다.

모노레포 도구를 고를 때 Nx와 Turborepo를 비교하는 글이 많은데, Nx가 “풀기능”이라고 불리는 이유 중 하나가 바로 이 생성 기능이다. Turborepo에는 없다.

5. 그럼 뭘 쓰면 되나

시작하는 입장에서 순서를 정하면 이렇다.

프로젝트를 처음 만들 때는 고민할 게 없다. create-next-app, create-vite처럼 프레임워크가 주는 것을 쓰면 된다. 어차피 한 프로젝트에 한 번 쓰고 끝이다.

문제는 그다음, 같은 파일 묶음을 계속 만들게 될 때다. 여기서 도구를 붙인다. Plop 은 설정이 plopfile.js 하나라 읽기 쉽고, Hygen 은 템플릿을 폴더 구조로 두는 방식이라 호불호가 있다. Yeoman 은 이 분야에서 제일 오래된 도구인데 그만큼 무겁다. 모노레포를 쓰고 있으면 Nx generators 가 이미 안에 들어 있다.

처음이라면 Plop부터 열어 보는 편이 낫다. 도구를 배우는 시간보다 템플릿을 뭘로 잡을지 정하는 시간이 더 걸린다.

그리고 도구를 안 써도 된다. 셸 스크립트 열 줄이면 폴더 만들고 파일 세 개 찍어 내는 건 된다.

#!/bin/bash
NAME=$1
mkdir -p "src/components/$NAME"
echo "export function $NAME() { return <div>$NAME</div>; }" > "src/components/$NAME/$NAME.tsx"
echo "export * from './$NAME';" > "src/components/$NAME/index.ts"

거칠지만 돈다. 이름 규칙(PascalCase 변환 같은 것)이 복잡해지거나 물어볼 게 늘어나면 그때 도구로 옮기면 된다. 설치 절차를 셸 스크립트로 옮기는 것과는 성격이 다르다. 그쪽은 절차를 옮기는 일이고 이쪽은 파일을 찍어 내는 일이다. 스크립트를 쓴다는 점만 같다.

6. 생성된 파일을 고쳐도 되나

“자동 생성”이면 다 같은 것으로 묶기 쉬운데, 두 종류가 정반대다.

create-next-app이 만들어 준 next.config.js는 당연히 고쳐도 된다. 만들어 준 순간부터 그건 내 파일이다. 도구는 첫 줄을 대신 써 줬을 뿐이고 다시는 그 파일을 안 본다.

반면 OpenAPI 스펙에서 타입과 API 호출 코드를 뽑아내는 codegen은 다르다. 이건 한 번이 아니라 계속 다시 만들어진다. 백엔드가 스펙을 고치면 다시 돌리고, 그때 그 파일들은 통째로 새로 쓰인다. 여기서 파일을 직접 고쳐 두면 다음 생성 때 없어진다.

같은 “생성” 인데 소유권이 반대다. 앞의 것은 만든 뒤로 사람 것이고, 뒤의 것은 계속 도구 것이다. 지금 열어 둔 파일이 둘 중 어느 쪽인지는 파일 맨 위를 보면 알 수 있다.

codegen 산출물에는 보통 “이 파일은 자동 생성됩니다. 직접 수정하지 마세요” 같은 주석이 맨 위에 붙어 있다. 고칠 곳은 그 파일이 아니라 스펙이다. 스펙을 고치고 다시 생성한다.

7. 스캐폴딩과 보일러플레이트와 템플릿

세 낱말이 거의 같은 자리에서 쓰인다. 구분 없이 섞어 쓰기 쉬운데, 가리키는 게 서로 다르다.

용어 뜻 차이
Scaffolding 규칙 기반으로 뼈대를 생성하는 행위/도구 변수 입력 → 파일 생성. 한 번 생성 후 자유롭게 수정
Boilerplate 어디서나 거의 똑같이 반복되는 상용구 코드 자체 “결과물”을 가리킴 (스캐폴딩이 만들어내는 게 보일러플레이트)
Template / Starter 복사해서 시작하는 완성형 예제 프로젝트 변수 치환·로직 없이 통째로 clone (degit user/repo)

표 2. 비슷한 자리에서 쓰이는 세 낱말의 차이

한 문장으로 줄이면, 스캐폴딩은 동작이고 보일러플레이트는 그 결과물이고 템플릿은 그냥 복사해 오는 완성품이다.

코드 생성(code generation)과의 관계도 자주 나오는데, 스캐폴딩은 코드 생성의 한 갈래다. 코드 생성이 더 넓은 말이라 OpenAPI로 타입 뽑는 것, Protobuf로 코드 만드는 것, ORM이 모델 만드는 것까지 다 들어간다. 그중 개발을 시작하는 시점에 파일 뼈대를 놓아 주는 쪽이 스캐폴딩에 가깝다.

8. 언제는 안 쓰는 게 나을까

도구를 붙이면 유지할 게 하나 늘어난다. 템플릿도 코드라서 프로젝트 규칙이 바뀌면 같이 고쳐야 하고, 안 고치면 낡은 구조를 계속 찍어 낸다. 복사할 때 낡은 게 따라오던 문제가 그대로 옮겨 오는 셈이다.

붙일 때를 정하는 기준은 두 가지다. 얼마나 자주 만드는가, 그리고 그 구조가 얼마나 오래 그대로 갈 것 같은가. 둘 다 높으면 값어치가 크고, 한 달에 한 번 만드는 것을 위해 생성기를 유지하는 건 남는 게 없다.

이럴 땐 그냥 복사해 오는 게 낫다. 자동화할 대상은 반복 그 자체가 아니라 자주 반복되는 것이다.

9. 자동화한 뒤 달라진 작업

껍데기는 이제 손으로 만들지 않는다. 생성기가 세 파일을 한 번에 찍어 내고, 손으로 쓰는 건 로직뿐이다.

생성기를 붙이고 나면 파일을 복사할 일이 없어진다. 어느 파일이 빠졌는지, 이름을 어디서 덜 바꿨는지 확인하는 일도 같이 없어진다. 대신 템플릿 파일 하나를 새로 관리한다.

Comments

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다