Grunt 란?

웹 과 관련된 프로젝트를 진행하다 보면 단순히 프로그램을 
열심히(?) 작성하는 것으로 끝나지 않습니다.
프로그램도 작성하고, 웹 화면별 테스트도 해야 하고 ,
프로그램 유닛 테스트도 해야 하고,
패키지 관리도 해야 하고, 배포판 처리도 해야 하고,
끝도 없습니다. 
이걸 일일히 수작업으로 하면 수명이 단축되는 것을 느끼고
생산성도 확실히 떨어 집니다. 
이런 문제를 해결하려면 역시 자동화가 답입니다. 
자동화가 답이긴 하지만 
자동화 유틸리티를 작성하는 것도 일이죠..
그것도 간단한 것이 아닙니다. 
자..
노드로 작성한다는 것은 자바스크립트에 익숙한 거고
자동화 유틸리티도 자바스크립트와 노드 프레임으로 작성하는 것이 좋고
욕심도 납니다. 
그런데..
이걸 직접 작성하자니 더 많은 일이 발생합니다. 
하지만 고맙게도 다른 분들이 이런 것을 만들고 공개해 주십니다.
이런 종류로 가장 유명한 것이 Grunt 입니다. 
Grunt 란 자바스크립트로 자동화 유틸리티를 쉽게 작성할 수 있도록 
해주는 핵심 프레임워크입니다. 
여러분이 유닉스 쉘 스크립트가 하는 것을 자바스크립트로 작성하고 싶다.
이걸 가능하게 해주는 것이 Grunt 입니다. 
다른 곳에서 Grunt 를 다양하게 소개하기는 하지만 
제가 보기에는 Make 나 쉘 스크립트 처럼 Node.js 를 이용하여 
자바스크립트로 무언가 처리하는 유틸리티 프레임워크 입니다. 
이번 강좌는 이 Grunt 가 어떤 것인지 아주 간단한 체험을 해 보려고 합니다. 
Grunt 체험 방법

보통 Grunt 강좌를 보면 Node.js 를 이용한 서버 프로그램에 대한 
css 나 자바스킵트 최적화를 예로 드는데..
저는 조금 다른 예를 이용해서 개념을 잡도록 하겠습니다. 
여러분은 프로젝트를 초기에 빌드할때 특정 디렉토리를 프로젝트 이름으로 지정하고 
그 하부에 몇가지 디렉토리를 매번 만들어야 한다고 가정합시다. 
예를 들면 다음과 같은 겁니다. 
프로젝트명 :  AnyProject
생성될 구조 : 
AnyProject/main_javascript
AnyProject/database
AnyProject/web_root
물론 이런거 손으로 직접 명령을 쳐도 되지만 정확한 스펠링도 기억 안나고 
매번 손으로 귀찮으니깐 , 
그리고 간단한 Grunt 동작을 이해하기 위한 샘플로 사용해 봅시다. 
우리는 이런식으로 명령을 치면
$ grunt first
이 명령을 수행한 디렉토리 하부에 
main_javascript/
database/
web_root/

와 같은 디렉토리가 자동으로 생기도록 하는 겁니다. 

이걸 어떻게 할까요?
사전 조건 

Grunt 는 node.js 기반입니다. 
Node.js 는 설치되어 있다는 것을 가정합니다. 
grunt 명령 설치 및 수행 체험

앞에 설명에서 
$ grunt first

처럼 수행하여야 한다고 했습니다. 

이때 grunt 가 바로 Grunt 빌드 툴의 명령입니다. 
Make 툴을 사용할 때 make 란 명령을 사용하는것과 같은 겁니다. 
이 grunt 명령은 다음과 같이 설치 합니다. 
$ npm install -g grunt-cli
-g 옵션은 Grunt 의 실행 명령인 grunt 를 아무 곳이나 수행 디렉토리 위치와 상관없이 사용하기 위한 겁니다. 
설치가 끝나면 "grunt --help" 란 명령을 수행해서 제대로 설치 되었나 확인하시면 됩니다. 
다음이 수행 예 입니다. 
~$ grunt --help
grunt-cli: The grunt command line interface. (v0.1.13)

If you're seeing this message, either a Gruntfile wasn't found or grunt
hasn't been installed locally to your project. For more information about
installing and configuring grunt, please see the Getting Started guide:

http://gruntjs.com/getting-started

grunt 의 적용 대상이 되는 패키지 초기화 

grunt 는 node.js 기반입니다 .
그래서 grunt 의 적용 대상이 되는 node.js 를 이용한 
전형적인 패키지 초기 처리를 사전에 처리해야 합니다. 
우리는 AnyProject 프로젝트라는 것을 만들기로 했습니다. 
그래서 다음과 같이 node.js 가 하는 전형적인 패턴을 진행해 봅니다. 
AnyProject 는 다음 위치에 만들기로 가정합니다. 
~/grunt/AnyProject
여기서 ~/grunt는 이 강좌를 진행하기 위해서 구분한 디렉토리이므로 

여러분은 적당한 위치로 이 부분을 수정하셔도 됩니다. 
자 다음과 같이 초기화 합니다. 

$ cd ~/grunt/
$ mkdir AnyProject
$ cd AnyProject
$ npm init
$ npm install grunt --save-dev
npm init 명령을 수행하는 과정에서는 그냥 엔터만 치세요

나중에 수정해도 됩니다. 
 
이 명령은 package.json 파일을 만들 뿐입니다. 

npm install grunt --save-dev 명령은 grunt 명령을 수행하기 위한 모듈을 설치하는 과정입니다. 
--save-dev 옵션은 npm init 에 의해서 만들어진 
package.json 파일에 grunt 모듈에 대한 정보를 기록하도록 합니다. 
grunt first 명령 인식하게 만들기 

설치된 ~/grunt/AnyProject 에서
단순하게 
$ grunt first
을 수행하면 다음과 같은 에러가 나옵니다. 

$ grunt first
A valid Gruntfile could not be found. Please see the getting started guide for
more information on how to configure grunt: http://gruntjs.com/getting-started
Fatal error: Unable to find Gruntfile.
grunt first 와 같은 명령을 인식하게 만들려면 
make 명령이 Makefile 파일이 필요하듯이 
grunt 는 Gruntfile.js
와 같은 파일이 필요합니다. 
Gruntfile.js은 node.js 에서 흔히 정의하는 모듈 파일입니다. 
특별한 문법을 가진 스크립트가 아니라는 점을 먼저 기억해야 합니다. 
먼저 아무것도 없는 Gruntfile.js 파일을 만들어 봅시다. 
그리고 
$ grunt first
을 수행하면 다음과 같은 에러가 나옵니다. 
$ grunt first
Warning: Task "first" not found. Use --force to continue.

Aborted due to warnings.
first 란 태스크가 없다고 나옵니다. 

이 태스크라는 것은 grunt 로 수행하고자 하는 목적 구분입니다. 
우리는 first 라는 것을 수행하고 싶은 거죠..
우리는 이런 부분에 대한 고민을 하지 말죠..
우선 저 에러는 Gruntfile.js 파일을 다음과 같은 형태로 만들어서 해결해야 합니다. 
즉 first 란 태스크를 정의 하는 겁니다. 
--[Gruntfile.js]----------------------------------------------
module.exports = function(grunt) {
grunt.registerTask('first', []);
};
--------------------------------------------------------------
태스크 정의는 
grunt.registerTask('first', []);
와 같은 형태로 하는 겁니다. 
[] 는 자바스크립트 배열을 의미하는데 
fisrt 란 태스크는 여러개의 수행 항목으로 이루어 질 수 있음을 알수 있습니다.
이 예제에서는 fisrt 태스크를 선언만 하고 빈 배열 []를 넣으므로서
실제로는 아무런 행동을 하지 않고 있습니다. 

그리고
module.exports = function(grunt) {
:
};
Gruntfile.js 파일자체가 node.js 모듈이라고 앞에서 이야기 했듯이
노드 모듈 프로그램 형식을 지원하기 위한 구조죠..
자 이제 태스크를 지정했으니 수행해 봅시다. 
$ grunt first

Done, without errors.

뭐 잘 수행해서 에러가 없다고 나오네요 ^^

쉘 명령 수행을 하는 grunt 플러그 인 설치 

Grunt 는 자체적으로 할수 있는 것이 없습니다. 
왜냐하면 Grunt 는 grunt 란 명령을 수행하기 위한 프레임워크이기 때문입니다. 
이 프레임워크 구조에서 실제로 무언가 하는 것은 모두 플러그인 들이 합니다. 
이런 플러그인은 자체적으로 만들 수도 있지만 
필요로 하는 것들이 이미 만들어져 있습니다. 
어디서 이런 플러그인을 찾냐 하면 
http://gruntjs.com/plugins
여기서 찾으면 됩니다. 
엄청나게  많은 것들이 있습니다. 
이중 우리가 하고자 하는 플러그인을 찾아야 하는데 
앞에서 제시했듯이 우리는 디렉토리를 만드는 것이 필요합니다. 
디렉토리를 만드는 것은 보통 shell 명령으로 처리하죠 
그래서  shell 명령을 수행하는 플로그 인이 필요합니다. 
이런 플러그 인으로
grunt-shell
이라는 것이 있습니다. 
다음과 같이 설치합니다. 
$ npm install --save-dev grunt-shell
grunt-shell 을 위한 Gruntfile.js 수정 

이제 first 란 태스크를 수행하기 위해
grunt-shell 플러그인에 필요한 정보를 
Gruntfile.js 에 기술해야 합니다. 
Gruntfile.js은 다음과 같은 구조를 가지게 됩니다. 
1. 수행해야 하는 플러그인 태스크의 정보 - grunt.initConfig();
2. 수행해야 하는 플러그인 로드 - grunt.loadNpmTasks();
3. grunt 사용자 태스크 정의 - grunt.registerTask();
보통은 다음과 같은 Gruntfile.js 파일 구조를 가지게 됩니다. 
module.exports = function(grunt) {
grunt.initConfig(...);
grunt.loadNpmTasks(...);
  :

grunt.registerTask(...);
  :
};
이런 구조에 맞게 다음과 같이 작성해 봅니다. 
아주 간단하게 기본 구조를 만들어 봅시다. 
--[Gruntfile.js]----------------------------------------------
module.exports = function(grunt) {

grunt.initConfig  ({
shell:{
first: {  command:'echo "hello fisrt"' },
},
});
grunt.loadNpmTasks('grunt-shell');
grunt.registerTask('first', ['shell:first']); 

};
--------------------------------------------------------------
굉장히 복잡해 보이지만 뜯어 보면 다 필요한 부분입니다. 
자 우선 태스크 선언 부분 부터 봅시다. 
grunt.registerTask('first', ['shell:first']);
이 선언은 grunt first 란 명령을 수행할 때 
first 라는 것을 어떻게 해석하는 것인가를 기술하는 겁니다. 
수행 항목은 []을 이용해서 지정하는데 여기서는 한개밖에 지정하지 않고 있습니다. 
바로 'shell:first' 이죠..
첫번째 shell 문자열은 shell 이라는 플러그인 태스크를 지정합니다. 
이 부분이 동작하기 위해서는 grunt-shell 플러그인이 로드 되어야 하므로 
grunt.loadNpmTasks('grunt-shell');
를 Gruntfile.js 에 기술하여 플러그인을 로드 합니다. 
shell 플러그인 설정 내용을 
shell:{
first: {  command:'echo "hello fisrt"' },
},
형식으로 기술합니다. 

각 플러그인에 필요한 형태는 공통된 형식이 없습니다. 
그래서 Grunt 설정 문법은 따로 없습니다. 
각각의 플러그인을 따로 따로 공부해야 합니다.
grunt-shell 은 
shell : {
}

형태로 기술합니다. 
그 다음에 이 안에 필요로 하는 명령 수행 구문을 정의 합니다. 
first: {  command:'echo "hello fisrt"' },
은 first 란 shell 하부의 태스크를 지정하고 

command 속성을 이용해서 실제로 수행하는 쉘 스크립트를 지정합니다. 
이 쉘 스크립트는 "hello fisrt" 란 문자열을 echo  란 명령을 이용하셔 화면에 출력하고 있습니다. 
이렇게 Gruntfile.js을 만들고 실행하면 다음과 같이 수행 됩니다. 
$ grunt first
Running "shell:first" (shell) task
hello fisrt

Done, without errors.
자동 디렉토리 생성 구현

이제 처음의 목적인 

AnyProject/main_javascript
AnyProject/database
AnyProject/web_root

의 폴더를 만드는 것을 수행하기 위한 Gruntfile.js 파일을 
다음과 같이 만들면 됩니다. 
--[Gruntfile.js]----------------------------------------------
module.exports = function(grunt) {

grunt.initConfig  ({
shell:{
first: {  command: [
'mkdir main_javascript',
'mkdir database',
'mkdir web_root',
 ].join('&&')
  },
},
});
grunt.loadNpmTasks('grunt-shell');
grunt.registerTask('first', ['shell:first']); 

};
--------------------------------------------------------------

명령을 여러개 수행하기 위해서는 command 속성에 배열을 이용하면 됩니다.

그리고 배열에  join('&&')  를 붙이는데..
이 의미는 전 실행이 성공했을때에만 다음 명령이 수행된다는 의미 입니다. 
다음과 같이 수행해 봅니다. 
$ grunt first
Running "shell:first" (shell) task

Done, without errors.

수행되었다는 심플한 메세지가 나옵니다. 

결론 

지금까지 Grunt 에 대한 체험을 한번 해 보았지만
결론을 보면
1. Grunt 는 자바스크립트로 무언가를 수행하는 스크립트를 지원하기 위한 프레임 워크 이다. 
2. Grunt 를 이용하여 무언가 수행하기 위해서는 플러그인을 사용하여야 한다. 
3. 플러그인 모듈은 npm 으로 설치되어야 한다.
4. 플러그인은 Gruntfile.js 에서 grunt.loadNpmTasks()를 이용하여 로드 되어야 한다. 
5. 플러그인이 동작하기 위한 설정은 grunt.initConfig()를 이용하여 지정되어야 한다. 
6. 플러그인이 동작하기 위한 설정의 설정 형식이나 내용은 각 플러그인의 규정으로 표준이 없다. 
7. grunt 란 명령이 수행되기 위해서는 Gruntfile.js 파일이 필요하다. 
8. 무언가 수행하는 구분은 grunt.registerTask() 함수를 이용하여 등록하며 이 것은 grunt 외부에서 지정한다.