Hygen을 이용한 컴포넌트 템플릿 만들기

Jun.23.2022 신성환/웹프론트개발그룹(blueStragglr)

Web Frontend

이 글에서 다루는 것

이 글을 따라가기 위해서는 React에 대한 가벼운 지식과 CLI를 활용할 수 있는 환경이 필요합니다.

이 글은 Node.js 환경에서 직접 작성한 템플릿을 이용해 컴포넌트를 생성할 수 있는 CLI 기반 시스템 구축 방법을 담고 있습니다. 글을 따라가면 아래와 같은 CLI 기반 파일 생성 템플릿을 구축할 수 있습니다.
hygen 데모

완성된 데모는 GitHub 리포지토리에서 확인하실 수 있습니다.

컴포넌트 템플릿을 구성한 이유

CLI 기반 컴포넌트 템플릿을 구성한 것은 디자인 시스템을 개발하면서였습니다. 디자인 시스템에는 기본 버튼부터 복잡한 입력 폼 등 수많은 컴포넌트가 필요합니다. 현재 개발 중인 디자인시스템에도 약 50개의 컴포넌트가 있으며, 지금도 하나둘씩 늘어가고 있습니다.

한 사람이 모든 컴포넌트를 만드는 것이 아니라 여러 명이 함께, 동시에 작업하여야 하다 보니 매번 기존 컴포넌트를 복사해 새 컴포넌트를 만드는 방식은 아래와 같은 어려움들이 예상되었습니다.

  • 컴포넌트 개발을 위해 파일 및 디렉터리 구조를 파악하는 데 시간이 걸린다.
  • 컴포넌트 초기 선언과 같이 반복되는 부분을 매번 직접 작성해야 한다.
  • 글로벌 import가 필요한 패키지나 컴포넌트별 설정을 매번 반복해서 작성해야 한다.

이를 해결하기 위해 Hygen을 기반으로 CLI를 통해 컴포넌트 이름만 입력받아 적절한 위치와 구성의 컴포넌트 초기 파일을 생성할 수 있는 템플릿을 구성하게 되었습니다.

Hygen으로 템플릿 만들기

Hygen이 무엇인가요?

hygen

Hygen 공식문서 바로가기

Hygen은 자바스크립트 기반의 코드 제너레이팅 툴입니다. fs-extra와 같은 npm 패키지를 기반으로 파일시스템에 직접 접근하고 파일을 추가하는 작업을 자동화할 수 있으며, 핵심 기능을 간결하게 제공해 빠르게 배울 수 있습니다.

Hygen은 기본적으로 Node.js 환경에서 잘 동작하며, Node.js 없이도 사용할 수 있는 옵션(Standalone)을 제공합니다. 이번 글에서는 이미 Node.js 기반이 갖춰진 React 프로젝트에서 템플릿을 생성하고 활용하므로, Node.js 환경을 기준으로 예제 코드를 작성하였습니다.

Hygen의 파일 생성 방식과 Generator

Hygen은 사전 정의된 템플릿을 바탕으로 파일을 생성할 수 있습니다. Hygen에서 사용하는 템플릿을 Generator라고 부르며, 구성한 Generator를 이용해 (기본적으로는) 다음과 같은 형태의 명령어를 실행하고 원하는 형태가 갖추어진 파일을 생성할 수 있습니다.

$ hygen [GENERATOR] [TEMPLATE]

템플릿은 정적인 파일은 물론 설정에 따라 동적인 내용을 담을 수도 있습니다. 템플릿은 ejs 문법으로 구성되며, 앞서 소개한 바와 같이 Node.js 런타임에 의해 실행됩니다. 템플릿 작성 방법은 아래를 참고하세요.

기본적인 Generator 생성해 보기

Hygen 템플릿은 프로젝트 루트에 /_templates 디렉터리를 생성하여 추가합니다. 디렉터리를 직접 생성하여 추가하여도 되고, 다음 스크립트를 이용하여 추가할 수도 있습니다.

$ hygen init self

위 스크립트를 실행하면, 프로젝트 루트에 아래와 같은 파일들이 생성됩니다.

기본 생성 제너레이터 구조

스크립트가 제공하는 템플릿은 3개의 Action을 갖는 하나의 Generator를 생성합니다. 자동으로 생성되는 Generator의 이름이 generator 이어서 조금 헷갈릴 수 있음에 유의하세요. Generator의 이름은 꼭 generator 이어야 하는 것이 아니며, 원하는 이름을 지정할 수도 있습니다. 예를 들어, 글 시작과 말미에 소개한 데모 레포지토리는 다음과 같이 구성되어 있습니다.

데모 레포지토리 디렉터리 구조

각 제너레이터 안에는 ejs.t 파일이 들어있으며 각 파일 안에는 파일을 생성할 경로와 파일 내용, 상호작용 프롬프트 등이 있습니다.

기본적인 템플릿 구성 알아보기

템플릿은 기본적으로 파일 디렉터리나 이름 등을 정의하는 시작부(frontmatter section)와 본문(body section)으로 구성됩니다. 다음은 $ hygen init self 로 생성할 수 있는 기본적인 정적 템플릿 예시입니다.

파일 섹션 설명

$ hygen init self 로 생성한 템플릿은 ‘템플릿을 생성하는 템플릿’이다 보니 시작부가 하나인지 두 개인지 혼란스러울 수 있습니다. 첫 번째로 등장하는 시작부만 실제 동작하는 시작부이며, 이후에 같은 모양으로 등장한 부분은 템플릿 본문 영역에 포함됨에 유의하세요.

시작부에서는 파일이 생성될 위치를 정의하며, 본문은 생성될 파일 안에 들어갈 본문입니다. Generator 템플릿은 ejs 문법을 사용합니다. 즉, “로 둘러쌓인 형태의 구문을 이용하면 프롬프트로부터 전달받은 입력을 사용할 수 있습니다. 예를 들어, 아래와 같이 작성해 보겠습니다.

실행 출력 예시

to: _templates/<%= name %>/<%= action || 'new' %>/hello.ejs.t

구문의 name 부분에 'sample' 이, action || 'new' 부분에 'task' 가 들어가서 다음과 같이 _templates/sample/task/hello.ejs.t 가 생성됩니다.

실행 출력 예시

ejs 변수는 시작부가 아닌 본문에도 사용할 수 있습니다. 아래 템플릿 예를 살펴보겠습니다.

템플릿 예시

‘SampleComponent’라는 값을 받아서 다음과 같이 파일을 생성할 수 있습니다(pascal에 ‘SampleComponent’를 전달)

템플릿 예시

입력 상호작용 추가하기

기본적인 상호작용과 form validation

CLI에서 유저 입력을 받아 컴포넌트를 생성하기 위해, Enquirer라는 라이브러리를 기반으로 상호작용 프롬프트를 구성하였습니다. 아래는 Enquirer를 이용해서 만들 수 있는 상호작용 예시입니다.

카테고리형 상호작용 예제

Enquirer 기반 CLI 데모 (출처: enquirer 레포지토리)

앞서 소개한 템플릿처럼 파라미터가 여러 개인 경우, 각각의 파라미터를 직접 작성해야 하기에 CLI를 통해 입력하기가 상당히 번거로울 수 있습니다. Hygen은 CLI 상호작용을 지원하는 npm 패키지인 enquirer를 내장하고 있으며, 이를 이용해서 아래와 같이 사용자와 상호작용할 수 있는 CLI를 만들 수 있습니다.

프롬프트는 제너레이터 폴더 루트에 prompt.js 라는 이름으로 생성합니다. prompt.js는 예약어처럼 미리 등록된 파일 이름으로, 생성 후 별도로 import하거나 설정해주는 등의 동작을 필요로 하지 않습니다. 데모 레포지토리에는 두 개의 프롬프트가 구성되어 있습니다.

프롬프트 구성 파일

두 개의 프롬프트 파일 중, 조금 더 간단한 프롬프트 파일부터 살펴보겠습니다. Node.js 기반 모듈인 만큼 require과 같은 기존 모듈 방식 프로그래밍을 이용할 수 있으며 프롬프트 설정 자체도 module.export 를 이용해 내보내게 됩니다. prompter 객체는 별도로 입력하지 않아도 자동으로 주입되며, args는 CLI 단계에서 입력받은 argument를 활용하고 싶을 때 사용할 수 있습니다. 아래 예제에서는 args 를 사용하지 않습니다.

var { toPascalCase } = require('../utils.js')

module.exports = {
  prompt: ({ prompter, args }) =>
    prompter
      .prompt({
        type: 'input',
        name: 'name',
        message: '컴포넌트 이름을 kebab-case 로 입력하세요.'
      })
      .then(({ name }) => {
        if (!name) {
          throw new Error('컴포넌트 이름을 입력하세요!')
        }
        if (new RegExp(/[^a-z\-]/).test(name)) {
          throw new Error('컴포넌트 이름은 kebab-case 이어야 합니다.')
        }
        return { name: name, pascal: toPascalCase(name), args }
      })
}

위 프롬프트는 사용자에게 'name' 을 입력받아 입력이 kebab-case인지 확인한 후 kebab-case 및 PascalCase로 변환하여 Hygen Generator에 전달합니다. 입력이 비어 있거나 유효하지 않은 경우에는 에러를 throw하여 프롬프트 입력 동작을 중단할 수 있습니다. 위 프롬프트는 아래와 같이 동작합니다.

hygen 동작 예제

마지막과 같이 name에 'test-component' 를 전달하게 되면 아래와 같은 객체가 Hygen Generator에 전달됩니다.

{
  name: 'test-component',
  pascal: "TestComponent',
  ...args
}

전달된 객체는 앞서 소개했던 것과 완전히 같은 방식으로 활용할 수 있습니다. 즉, 위와 같이 프롬프트를 통해 전달된 입력은 아래 명령어로부터 생성된 입력와 동일합니다.

$ hygen component general --name test-component --pascal TestComponent

옵션을 기반으로 입력하기

유저 입력을 직접 받는 경우 중에서 특정 입력들만 허용해야 하는 경우가 있습니다. 예를 들어, 컴포넌트의 카테고리가 정해져 있어 몇몇 유효한 카테고리 입력만 받을 수 있는 경우가 있습니다. 이 경우 유저로부터 직접 텍스트 입력을 받는 대신 특정 옵션들 중에 값을 선택하도록 할 수 있습니다. 옵션을 받을 수 있는 프롬프트는 아래와 같이 일반적인 프로미스 체이닝처럼 구성할 수 있습니다.

// 여기서부터...
var { toPascalCase, categoryIcon } = require('../utils.js')

module.exports = {
  prompt: ({ prompter, args }) =>
    prompter
      .prompt({
        type: 'input',
        name: 'name',
        message: '카테고리 컴포넌트 이름을 kebab-case 로 입력하세요.'
      })
      .then(({ name }) => {
        if (!name) {
          throw new Error('컴포넌트 이름을 입력하세요!')
        }
        if (new RegExp(/[^a-z\-]/).test(name)) {
          throw new Error('컴포넌트 이름은 kebab-case 이어야 합니다.')
        }
        // 여기까지는 앞의 예제와 동일합니다.

        // Object 대신 propmpter.select.then() 을 반환합니다.
        // 현재 scope 안에서 정의된 name은 자식 스코프에서도 사용할 수 있습니다.
        return prompter
          .select({
            type: 'input',
            name: 'category',
            message: '카테고리 컴포넌트의 카테고리를 선택하세요.',
            choices: ['animation', 'common', 'core', 'util']
          })
          .then((choice) => {
            // 최종적으로 반환하는 객체의 형태입니다.
            return {
              category: choice,
              name: name,
              pascal: toPascalCase(name),
              categoryIcon: categoryIcon[choice],
              args
            }
          })
      })
}

위 프롬프트는 앞서 소개한 것처럼 name을 kebab-case로 받아 검증한 뒤, 추가로 사전 정의된 카테고리 유형 중 하나를 선택해서 받을 수 있도록 동작합니다. 즉, 아래와 같이 동작합니다.(에러 핸들링 동작은 생략하였습니다.)

hygen 동작 예제

결과물

위에서 소개한 과정을 거쳐 데모 레포지토리를 구성하면, 다음과 같이 카테고리별 컴포넌트와 일반 컴포넌트를 템플릿 기반으로 생성하고 개발하도록 할 수 있습니다.

풀 데모

디자인 시스템을 개발하는 과정에서 새로운 컴포넌트를 만들 때마다 기존 컴포넌트를 복사하게 되면 어떤 컴포넌트를 복사할지 고민하는 데도 시간이 오래 걸리고, 특정 컴포넌트에 존재하는 문제가 계속 반복될 수도 있습니다. Hygen을 이용해서 템플릿을 세팅하는 일은 생각보다 굉장히 쉽고 간단합니다. 잘 다듬어진 프로젝트를 설정하려면 템플릿을 한번 이용해 보는 걸 추천합니다!