Skip to main content

Yeoman 으로 프로젝트 찍어내보기

· 24 min read
이준형

안녕하세요. 항상 서문이 힘드네요. 처음이라 가벼운 글로 시작해보고자 합니다.

저같은 월급루팡이 아니라면 다들 프로젝트를 여러 개 만들어보셨을텐데요. 프로젝트를 여러 개 만들다보면 특수한 형태가 아니고서는 공통적인 요소들을 발견하게 됩니다. 폴더구조, DB커넥션, 의존성설정, 로그인, 띄어쓰기, 깃 설정 등등... 개인이 공부를 위해 이것저것 해보는 게 아닌 이상, 특히 MSA(Micro Service Architectures)를 지향하는 회사라면 작은 기능 하나하나에도 똑같이 생겨먹은 프로젝트를 여럿 만들게 될겁니다.

그러다보면 어느 우스갯소리처럼 환경설정하다 몇 날 며칠 꼬박 지나갑니다. 마감은 다가오고... 그러다보면 비슷한 생김새의 프로젝트 틀을 약간의 설정만으로 만들어주는 걸 바라게 됩니다. 이번에 알아볼 요먼(Yeoman) 은 그런 도구의 일종입니다.

요먼(Yeoman) 으로 프로젝트 찍어내보기

요먼?

요먼(Yeoman)은 웹 애플리케이션을 위한 오픈 소스 클라이언트 사이드 스캐폴드 도구이다. Yeoman은 Node.js로 작성된 명령 줄 인터페이스로 실행되며 여러 기능(스타터 템플릿 생성, 의존성 관리, 유닛 테스트 실행, 로컬 디플로이먼트 서버 제공, 디플로이먼트를 위한 운영 코드 최적화 등)을 한 곳에 병합해놓았다.

구글 I/O 2012에서 공개되었다. from [위키백과](https://ko.wikipedia.org/wiki/%EC%9A%94%EB%A8%BC_(%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4)

위키백과의 글을 발췌하였습니다. 여기서 스캐폴드라는 말이 등장하는데요. 스캐폴드(Scaffold) 란 비계 건물 지을 때 잡아주는 틀 뭐 그런 단어입니다. 이 단어가 프로그래밍으로 온 건데요. 간단하게 말하면 사용자가 쉽게 뭘 할 수 있도록 틀을 만들어주는 것이라고 생각하시면 됩니다. 우스갯소리 좀 넣으면 피라미드 만들 때 썼던 더 큰 피라미드 같은 느낌이죠. 근데 여기서 "클라이언트 사이드" 라는 단어가 사람 헷갈리게 하는데요. 이 뜻은 서버 프로그래머들은 건드리지 말라는 뜻이 아니라 뼈대를 만드는 초점이 DB가 아닌 애플리케이션이라는 뜻입니다. 그러니 백엔드 개발자분들도 뒤로가기를 누르지 말아주세요.

Getting Started 해보기

만만한게 Getting Started 입니다. 흔히들 튜토리얼이라고 하죠. 먼저 yeoman 을 사용하기 위한 클라이언트 도구인 yonpm 으로 설치해보도록 하겠습니다.

  • 제 환경은 Mac OS X v10.14.6 입니다. 리눅스나 윈도로 따라하실 분들은 그 부분 신경써주세요.
  • $ 는 커맨드 라인의 시작을 나타냅니다. 복붙할 때 얘는 끼우지 마세요.
  • XXX 라 썼다고 진짜 XXX 라고 입력하지 마세요.

$ npm install -g yo

클라이언트 도구를 설치했으니 써봐야죠. generator 라는 것을 다운받아야 하는데요. 이는 프로젝트의 생김새를 결정하는 주물 같은 거라고 보면 됩니다. 해당 사이트에서는 웹앱을 골랐네요. 목록을 보고싶다면 Generators | Yeoman 을 확인해보시거나 구글에 yeoman generator XXX(직접 입력) 이라고 검색을 해보세요. 이 이야기는 나중에 하고 webapp 제네레이터를 설치해줍니다.

$ npm install -g generator-webapp

설치가 되었는지 확인 겸, 도움말도 알아볼 겸 아래 명령을 쳐보겠습니다.

$ yo webapp --help

요래 치면 좌라락 나옵니다. 더 자세한 도움말은 generator-webapp 을 만든 사람을 찾아보면 됩니다. 구글에서 검색하거나 $ npm home generator-webapp 이라고 터미널에 입력하면 되겠습니다.

아무튼, 설치가 완료되었다면 직접 프로젝트를 만들어보겠습니다. 작업을 하기 위한 아무 디렉터리나 가서 아래 명령을 쳐봅시다.

  • 이 제네레이터는 하위 디렉터리를 만들어주지 않으니 빈 디렉터리에서 작업하시기 바랍니다.
$ cd /아무/디렉터리나/가서
$ yo webapp

입력하면 몇 가지를 물어봅니다. 적당히 골라서 한 번 해봅시다. 그러면 뭔가 좌라락 뜨고 한참을 기다리다보면 프로젝트가 하나 생성되었을 것입니다. 너무 오래 걸리면 캔슬하고 $ npm install && npm rebuild 를 한 번 해줍시다. 설치가 끝났다면 $ npm run start 명령을 쳐서 웹 페이지를 한 번 띄워보겠습니다. 잘 뜨면 성공입니다.

Generator 만들어보기

아까까지는 사용법이었습니다. 사용법만 알아도 어지간한 건 다 할 수 있습니다만 저희도 하나 만들어봐야죠. 이제 가이드에 따라 직접 generator 를 만들어보도록 하겠습니다.

가이드 링크: Writing Your Own Generator

yeoman 에서는 generator 를 쉽게 만들 수 있는 generator-generator 를 제공해주고 있습니다. 가이드는 어떻게 저런 코드가 나왔는지를 중점적으로 설명하고 있습니다. 한 번 따라해보도록 합시다.

주의사항

  • 위에서 말했듯이 $ 는 커맨드 라인의 시작을 나타냅니다.
  • 중요하니 한 번 더 제 환경은 Mac OS X v10.14.6 입니다. 리눅스나 윈도로 따라하실 분들은 그 부분 신경써주세요.
  • yeoman generator 는 javascriptnode.js 에 대한 지식을 필요로 합니다. 1도 모르는 분이시라면 옆에 계신 전문가에게 도움을 요청하세요.
  • ... 은 생략을 나타냅니다. 이 부분까지 복사하시고 저한테 뭐라고 하시면 안됩니다.

먼저 빈 폴더를 하나 만들고 그 안에서 프로젝트를 초기화합시다. 이 때 프로젝트명은 generator- 로 시작하도록 해줍시다.

$ mkdir -p /아무/디렉터리/generator-만들자
$ cd /아무/디렉터리/generator-만들자
$ npm init

그 다음 package.json 을 열어 아래 내용을 추가해줍시다.

{
...
"files": [
"generators",
],
"keywords": ["yeoman-generator"],
...
}

yeoman-generator 가 되기 위한 가장 중요한 요소는

  • "name" 이 generator- 로 시작해야 한다는 것
  • "keywords" 에 "yeoman-generator" 가 있어야 한다는 것
  • "description" 에 설명을 깔쌈하게 작성해야 한다는 것

입니다. "files" 는 다음에 말씀드리겠습니다.

이제 yeoman-generator 라이브러리를 설치해보도록 합시다.

$ npm install --save yeoman-generator

설치가 완료되었으면 아까 "files" 의 안에 있는 "generators" 와 같은 이름의 디렉터리를 만들어보겠습니다. 이 generators 안에 index.js 를 만들고 그 안에 내용을 삽입하면 됩니다.

// generators/index.js
const Generator = require('yeoman-generator');

module.exports = class extends Generator {
constructor(args, opts) {
super(args, opts);

this.option('babel');
}

method1() {
this.log('method 1 just ran');
}

method2() {
this.log('method 2 just ran');
}
};

감이 오시겠지만 yo <어쩌고> 명령을 치게 되면 generators 안의 index.js 를 실행하여 셋팅을 하게 되는 것입니다.

그리고 커맨드 창에서 $ npm link 라고 치시면 이 프로젝트가 임시로 로컬 npm 레지스트리에 등록이 되어서 generator 를 사용할 수 있게 됩니다. 실행할 때엔 아까 packge.json 의 "name"에 generator- 부분을 뺀 이름을 yo 뒤에 치시면 됩니다.

$ yo <name> <- 아까 위에 말했던 그 이름

그러면 대충 콘솔 로그 2번 뜨고 끝나게 됩니다. 여기까지가 간단한 generator 를 만드는 부분이었고, 더 복잡한 내용에 대해서는 다음에 다루도록 하겠습니다.

(진짜) Generator 만들어보기

위 단락을 보고 "그래서 이게 뭔데 X덕아" 하시는 분들 많으실텐데요. 이제 좀 쓸만한 Generator 를 한 번 만들어보도록 하겠습니다.

yeoman generator 의 실행 순서

yeoman generator 는 다음 순서로 함수를 실행합니다.

  1. initializing - 초기화 과정입니다. 프로젝트 상태나 설정값 등을 불러옵니다.
  2. prompting - 프롬프트 과정입니다. 사용자로부터 입력을 받습니다.
  3. configuring - 설정을 저장하고 프로젝트를 설정합니다.(.editorconfig 파일과 메타데이터 등을 만듭니다.)
  4. default 이름이 default 가 아닙니다. 사전 정의된 함수를 제외한 다른 함수는 이 시점에 실행됩니다.
  5. writing - 이 시점에서 파일을 작성하게 됩니다.(router, controllers 등등등)
  6. confilcts - 충돌 발생 시 대응합니다.(내부적으로 실행됩니다)
  7. install - 설치 작업을 실행합니다.(npm install, pip install 등)
  8. end - 마무리입니다. 파일 정리 및 안녕 인삿말 등을 넣습니다.

이 순서대로 진행되므로 구현하고 싶은 함수들을 구현하시면 되겠습니다. 내가 호출할 때 이외에 실행 시키기 싫다면 private 함수라고 선언하시면 됩니다. JS 에선 딱히 선언방법이 없으므로 함수명 앞에 _ 를 넣으면 됩니다.

사용자 입력 받아보기

내부적으로 Inquirer.js 를 이용하여 구현하므로 자세한 사용법은 해당 사이트로 떠넘겼습니다를 찾아보라고 합니다. 최신 yeoman 은 promise 를 지원하므로 async/await 패턴으로 구현이 가능합니다. 간단한 예시코드를 보겠습니다.

// generators/index.js
const Generator = require('yeoman-generator');

module.exports = class extends Generator {
constructor(args, opts) {
super(args, opts);
}

async prompting() {
this.answers = await this.prompt([
{
type: 'input',
name: 'name',
message: '프로젝트명을 입력하세요.',
default: this.appname, // Default to current folder name
},
{
type: 'confirm',
name: 'cool',
message: "쿨한 기능을 써볼래요?",
}
]);

this.log('app name', this.answers.name);
this.log('cool feature', this.answers.cool);
}
};

이렇게 하면 실행하였을 때 두 질문을 하게 되고 질문 결과가 저장됩니다. 그 밖에 커맨드라인에서 입력을 받는 부분(arguemnts, options) 는 공식 문서를 참고하시기 바랍니다.

파일 시스템과 상호 작용

진짜 중요한 게 하나 남았습니다. 바로 파일 저장입니다. 아무리 입력을 받고 뭘 해도 파일을 수정 못하면 아무 소용 없잖아요? 먼저 파일들이 어디에 위치하고, 파일 경로를 어떻게 불러오는 지부터 보도록 하겠습니다.

  • this.destinationRoot() - 명령을 실행한 디렉터리
  • this.destinationPath(path) - 명령을 실행한 디렉터리와 path 를 합칩니다.
  • this.sourceRoot() - 템플릿 파일이 위치한 경로입니다. 제네레이터 디렉터리의 templates 디렉터리를 나타냅니다.
  • this.templatePath(path) - 템플릿 디렉터리와 path 를 합칩니다.

폴더 구조로 나타내면 다음과 같습니다.

/
|____ path/to/.../node_modules
| |____ generator-name
| |____ generators
| |____ templates <- this.sourceRoot();
| |____ foobar.json <- this.templatePath('foobar.json');
| |____ index.js
|
|
|
|____ project <- this.destinationRoot();
|____ index.js <- this.destinationPath('index.js');

해보기

generators/templates 안에 index.html 하나 만들어봅시다.

<html>
<head>
<title><%= title %></title>
</head>
</html>

그 다음 generators/index.js 파일을 작성해줍시다.

// generators/index.js
const Generator = require('yeoman-generator');

module.exports = class extends Generator {
constructor(args, opts) {
super(args, opts);
}

async prompting() {
this.answers = await this.prompt([
{
type: 'input',
name: 'title',
message: '제목을 입력하세요.',
},
]);
}

writing() {
const templateHtmlPath = this.templatePath('index.html');
const destHtmlPath = this.destinationPath('public/index.html');
this.fs.copyTpl(templateHtmlPath, destHtmlPath, {
title: this.answers.title,
});
}
};

여기서 this.fs.copyTpl 이라는 함수가 보이는데 이는 EJS 템플릿을 치환하여 복제하는 유틸리티성 함수입니다. 파일시스템은 인 메모리 방식이며, 사용법은 여기 를 참고하시기 바랍니다.

파일이 더럽게 많은데 다 바꿔야 해요?

유명 제너레이터(react-webpack 등)들을 보면 다음과 같은 방법으로 처리를 합니다.

  1. 미리 템플릿 관련 파일을 받아놓고 다른 곳에 저장해둠
  2. 그 파일을 일단 복사
  3. 몇몇 파일만 자체 템플릿 파일을 이용하여 수정하여 덮어씀
  4. 뒷처리

저희 회사에서 쓰고있는 템플릿 작성 제네레이터도 비슷한 방법을 사용하여(깃으로 직접 받아오더군요 ㄷㄷ) 스캐폴딩을 하고 있었습니다. generator 프로젝트 내에 넣어두느냐(의존성이 명확할 때), 다른 곳에서 땡겨오느냐 에 대해서는 사용하시는 분들께서 직접 판단하시는 게 낫습니다.

똑닥에서는...

저희 회사의 경우, AWS 코드커밋(Codecommit)에만 80개, 깃랩(Gitlab) 에서 이전하지 못한 코드들까지 포함하면 100개가 넘어갑니다. MSA를 완벽하게 적용하는 회사라면 더 많을 것으로 예상됩니다. 저희는 이미 깃 저장소에 템플릿 프로젝트를 만들어 이를 다운로드 받는 식으로 사용하고 있었습니다. 그런데 이를 어쩌나... 다운 받아서 README.md 바꾸랴 git 저장소 remote 바꾸랴 환경변수 초기값 넣으랴 정신이 없네요. 가끔씩 과정 하나 빼먹어서 디비 커넥션 정보를 템플릿에 푸시해버리는 사고도 칩니다. 이를 자동화하기 위해 요구되었던 것이 yeoman 이고 저희 회사의 뛰어나신 개발자분께서 처리를 하셨습니다.

플로우는 다음과 같습니다.

  1. yo 와 generator 설치(사설 저장소에 있어서 설치 시 --registry 옵션을 붙여야 합니다.)

  2. 프롬프트를 띄워서 입력을 받음

    $ yo ddocdoc
    ? 생성할 프로젝트를 선택해주세요. (Use arrow keys)
    똑닥 API 서버
    ❯ 똑닥 배치
    ? 배치 프로젝트명을 입력해주세요. ddocdoc-batch-hello
    ? 배치 프로젝트명(한글)을 입력해주세요. 헬로
    ? 설명을 입력해주세요. ㅎㅎ
    ? 버전을 입력해주세요. 0.0.1
    ? package.json 에 추가정보를 입력하시겠습니까? No

    템플릿을 어떻게 고르냐에 따라 서브 generator 를 호출하게 됩니다. 상속까지 들어가서 코드 보기가 좀 힘들었습니다.

  3. AWS codecommit 에 있는 템플릿 프로젝트 클론

    1. 첫 실행 시 임시 디렉터리에 다운로드 합니다.
      • AWS codecommit 으로 다운받을 때 저희 회사는 SSH 만 허용하였기 때문에 사전에 SSH 설정을 해야합니다. 안하면 뻑납니다.
    2. 그 다음 실행시에는 다운로드 받는 대신 최신화만 합니다. (git fetch and git checkout 이라고 썼지만 사실 git reset)
  4. 다운받은 템플릿 코드 복사 + 설정 파일들 덮어씌우기

    • JSON 은 직접, README.md 는 ejs 를 사용하였습니다. 저희 회사의 배치 코드는 serverless 프레임워크를 일부 사용하고 있기에 해당 프레임워크에 맞게 기초 설정파일들을 저희 회사의 스테이지(development, test, production) 에 맞춰서 다 만들어줬습니다.
  5. npm install

  • 다만 현재 상태가 좀 불안한 지 실패해버렸네요. 그래도 코드는 다 생겼으니 수동으로 npm install 하면 깔끔하게 됩니다. install 함수는 end 직전에 실행되기 때문이죠.
  1. 설치가 끝났다면 직접 $ git init 을 하고 remote 설정 하고 작업하면 됩니다.

제가 직접 한 일은 설치, 프롬프트에 대충 값 입력, git init 밖에 없네요. 이렇게 최신 템플릿을 다운받고 설정값 채우고 깃 초기화하고 하는 과정들을 모두 생략할 수 있어서 정말 편해졌습니다. 이렇듯, 다양한 프로젝트를 템플릿을 통하여 쉽게 만들어내게 됩니다. 제네레이터는 템플릿 저장소가 바뀔 때에만 수정하면 되며, 템플릿 코드만 최신화하면 됩니다.

마무으리

여기까지 yeomanyeoman generator 를 사용할 수 있는 최소한의 단위만 알아보았습니다. 저도 급히 배우면서 작성하는데다가 글이 너무 길어져서 자세한 내용에 대해서는 다루지 못했던 게 아쉽네요. 더 자세한 아키텍쳐와 심각한 주의사항, 그리고 트릭 등에 대해서는 기회가 되면 다시 적어보던가 하도록 하겠습니다. 여기까지 길고 장황하고 알맹이 없는 글 읽어주셔서 감사합니다.

참고한 자료

Yeoman 공식 홈페이지

사족

  1. generators 디렉터리 안에 하위 디렉터리를 만들면 $ yo name:subcommand (subcommand 는 하위 디렉터리명) 와 같은 방법으로 호출이 가능합니다. 이 때 app 디렉터리 는 기본값으로 사용하도록 되어있으니 유의바랍니다.
  2. registerTransformStream() 을 이용하여 vinyl 형태(gulp에서 많이 사용함) 스트림을 등록할 수 있습니다. 이를 이용하면 yeoman 의 파일 시스템을 이용한 모든 파일처리를 할 때 스트림을 거쳐서 오게 됩니다. gulp-if 나 gulp-filter 등을 이용하여 제외할 파일 설정을 한다던가 그렇게요.
  3. 파일 다 옮긴 다음 설치할 때 쓰는npm install, yarn install, bower install 등의 명령을 쓰는 건 내부 함수로 제공해줍니다. 그 외의 명령 실행도 쉽게 할 수 있게 추상화된 함수를 제공해줍니다.
  4. yeoman-environment 를 이용하여 yo 를 사용하지 않고 직접 짠 node.js 코드로 yeoman 을 실행할 수 있습니다. 사용법은 여기 를 확인해보세요.