Grunt는 자바스크립트 기반의 애플리케이션의 빌드와 배포를 자동화 하기 위해서 사용하는 툴이다. Grunt를 이용해서 minification, compilation, unit testing, linting 등의 작업을 수행 할 수 있다. C/C++ 언어에서 사용하는 Makefile과 비슷한 역할을 하는 Gruntfile의 설정 내용을 읽어서, 빌드작업을 수행한다.
자바스크립트와 JSON을 이용해서 Gruntfile을 만들 수 있기 때문에, 노드(node)를 경험해본 적이 있다면 쉽게 빌드룰을 만들 수 있다.
다루는 이유
node 기반 프로젝트를 관리해야 할 일이 생겼다. 이 노드 애플리케이션은 배포 목적에 따라서, 서로 다른 형태로 빌드가 되야 한다. 예컨데,
공개 소프트웨어로 배포 할 경우 : 상용에서 제공하는 기능들을 제외하고 배포해야 한다.
상용 소프트웨어로 배포 할 경우 : 대부분의 기능들을 포함한다. 이 경우 주요 자바스크립트 파일들은 바이너리 코드 형태로 컴파일 해서 배포해야 한다.
standalong 인지 server 타입인지에 따라서 빌드가 달라진다.
AWS에 배포하는지, 애저에 배포하는지 혹은 (테스트나 개발용도로) PC에 배포하는지에 따라서 빌드가 달라진다.
이들 다양한 환경에 능동적으로 대응 할 수 있는 빌드 환경을 만들기 위해서 Grunt를 사용 하기로 했다. 사용 하기로 한 것 까지는 좋은데, node도 제대로 해본적이 없으니 처음 부터 공부하는 수 밖에 없는 처지가 됐다. 그래 이번 기회에 Grunt를 공부해 보기로 했다.
Grunt를 이용해서, minification, compilation, linting 등의 작업을 자동화 한다.
다양한 환경에 간단하게 배포 할 수 있는 빌드 환경을 구축한다.
Jenkins와 같은 CI 툴과 연동해서 아름다운 개발 환경을 만들기 위한 아이디어를 만든다.
Grunt 사용
빌드 테스트 환경 및 시나리오
운영체제 : 우분투 리눅스 15.10
nodejs : v0.10.25. Grunt를 사용하려면 node가 필요하다.
Foundation : Grunt를 테스트 하는데 사용한다. 굳이 돌아가는 node 애플리케이션을 가지고 테스트 할 필요는 없다. 그래서 css와 자바스크립트 파일을 가지고 있는 Foundation css 프레임워크를 가지고 테스트 하기로 했다. minification, complation, concat 같은 핵심 기능을 테스트하기에 충분하다. 파운데이션은 http://foundation.zurb.com/develop/download.html 에서 다운로드 할 수 있다.
foundation이라는 디렉토리를 만들고 여기에 foundation 압축을 풀었다. 파일 구조는 대략 아래와 같다.
--+---+--- index.html
| |
| +--- img/ -+- 이미지 파일들
| |
| +--- js/ --+- foundation : foundation에서 제공하는 자바스크립트 파일
| | |
| | +- vendor : jquery와 같은 외부 자바스크립트 파일
| |
| +--- css/ --- css 파일들
|
--+---+--- Build : 빌드 디렉토리
빌드 결과물은 Build 디렉토리 밑으로 복사한다. Build는 직접 만들었다.
Grunt 설치
Node.js 버전 0.8.0 이상이 필요하다. npm을 이용해서 grunt를 설치한다.
# npm install -g grunt
# npm install -g grunt-cli
기본 사용 법
concat와 uglify를 이용한 예제로 사용방법을 간단히 살펴보고, 응용에 들어간다.
foundation 디렉토리로 이동 한 다음, Gruntfile.js파일을 만든다. concat와 uglify를 하기로 했다.
concat : 여러 개의 자바스크립트를 하나로 만든다. CSS 파일에 대해서도 concat를 수행한다. 로딩 시간을 줄일 수 있다.
uglify : 코드에 있는 공백과 주석을 제거하고, 변수명과 함수명을 짧은 문자열로 치환 한다. 선언만 하고 사용하지 않는 변수들도 정리해 준다. 성능향상과 난독화를 위해 사용한다.
Grunt는 플러그인 형식으로 기능을 확장할 수 있다. concat, uglify 역시 플러그인으로 지원한다. 먼저 플러그인을 설치하자.
Gruntfile.js를 만든다.
Grunt는 task 단위로 작동한다. 위 예제는 concat와 uglify 두 개의 task를 사용하고 있는데, 이들 테스크는 플러그인 형식으로 제공이 된다. 플러그인은 grunt.loadNpmTasks 메서드로 먼저 로딩을 해야지만 사용 할 수 있다.
grunt.initConfig에는 task의 설정이 들어간다.
concat : "js/foundation" 디렉토리에 있는 모든 js 파일을 연결해서 빌드디렉토리에 복사한다.
uglify : 빌드 디렉토리에 있는 foundation.min.js 파일을 읽어서 uglify적용된 파일을 만든다.
로딩한 테스크들을 grunt.registerTask 메서드로 등록 하면 사용 할 수 있다. registerTask는 아래와 같이 구성된다.
나는 애플리케이션을 product와 develop 두 개의 버전을 만들기로 했다. 이들은 서로 다른 빌드 설정을 가질 것이다. 각 태스크가 하나 이상의 설정을 가질 수 있다는 점을 이용해서, product와 develop 두개의 설정을 만들기로 했다.
concat: {
// 정식 제품을 위한 빌드
product: {
src: ['js/foundation/*.js'],
dest: '<%= dir.target %>js/foundation.min.js'
},
// 개발을 위한 빌드
develop: {
src: ['js/foundation/foundation.dropdown.js'],
dest: '<%= dir.target %>js/foundation.min.js'
}
},
uglify: {
product: {
...
},
develop: {
...
}
}
...
grunt.registerTalk('production': ['concat:product', 'uglify:product'])
grunt.registerTalk('develop': ['concat:develop', 'uglify:develop'])
# grunt production // production을 위한 빌드
# grunt develop // develop를 위한 빌드
# grount concat:product
빌드 계획
빌드 계획은 개발과 운영 정책을 어떻게 가져갈지에 따라서 달라진다.
Dev, Staging, OP 3개의 단계를 거친다.
Dev를 위해서 빌드 할 경우 uglify, concat등은 필요없다. 디버깅 코드들과 각종 라이브러리들을 포함한다.
Staging : 배포 전 단계로 uglify, concat를 포함 하며, 운영(OP)와 거의 동일한 빌드룰이 적용된다.
Enterprise, Community
Enterprise : 비용을 지불한 버전. 모든 기능을 포함한다.
Community : 비용을 지불하지 않고 자유롭게 사용할 수 있는 버전이다. Enterprise에서 몇 개 기능이 빠진다.
프로젝트는 git으로 관리를 한다. 하나의 trunk를 가진다. 기능 별 브랜치, 핫픽스 브랜치를 포함하는 관리는 오픈소스에는 적당할 지 모르지만 기업형 제품을 만들기 위해서는 쓸데 없이 복잡하다.
하나의 trunk를 가져가기 위해서는 빌드 마스터가 개입을 해야 하는데, 개발조직을 제어 할 수 있는 조직에서는 크게 문제될게 없다. 이 방식은 OP가 다양해 질 경우 문제가 발생 할 수 있다. 예를들어 차기 버전의 사전 체험을 위한 베타서비스가 OP에 추가된다고 가정해 보자. 이 경우 기능이 추가/삭제 뿐만 아니라 디자인의 변경, 기존 기능의 개선등을 포함하기 때문에 하나의 브랜치 만으로는 운영이 어려워 질 수 있다. 서비스 운영 정책에 따른 설계가 필요한 시점이다.
빌드 파일 관리
수백개의 디렉토리 수백개의 파일을 Gruntfile 안에서 관리 할 수는 없는 노릇이다. 방법을 살펴보자.
먼저 프로파일 디렉토리를 만드는 방법이 있다. Apache에서 모듈을 로딩 할 때 사용하는 방법이다. 각 환경에 맞는 디렉토리를 만들고 필요로 하는 파일들을 링크해서 관리 한다. git을 포함한 대부분의 SCM이 심볼릭 링크를 지원하니 적용에 문제는 없을 거다.
---+-- js --+--- 모든 js 파일들
|
+--- Enterprise : 기업 환경 용 파일 link
|
+--- Community : 커뮤니티용 파일 link
직접 링크를 관리 해야하기 때문에, 파일의 양이 많아지면 관리하기 힘들어 질 수 있다는 문제점이 있다.
위 문제는 프로파일 디렉토리에 "빌드에서 제거할 파일들의 링크"를 관리 하는 식으로 해결 할 수 있다. 프로파일 디렉토리에 링크를 추가하기 전까지는 빌드에 포함되므로 관리하기가 쉽다. 하지만 빌드에 추가 할 코드를 관리하는게 직관적이다. 애초에 기능들을 플러그인으로 관리하는 등, 소프트웨어 구조 설계를 잘 잡고 시작하는게 좋다.
빌드 설정 파일을 관리하는 방법도 있다. 이 파일의 내용은 아래와 같을 거다.
module.exports = function(grunt) {
var fs=require('fs')
var config = JSON.parse(fs.readFileSync('build.json', 'utf8'))
...
grunt.initConfig({
concat: {
// 정식 제품을 위한 빌드
product: {
src: config.file.product
dest: ['../Build/js/foundation.min.js']
},
...
}
})
}
빌드 후 테스트
개발을 위해서 빌드 할 경우 전체를 빌드하면 많은 시간이 걸릴 것이다. 이 경우 추가/수정된 파일만 업데이트 하면, 빌드와 테스트간의 간격을 줄일 수 있을 것이다. grunt의 watch플러그인을 이용해서 특정디렉토리와 파일에 대한 변경을 모니터링 할 수 있다.
js/foundation 밑에 있는 파일이 변경되면, 빌드 디렉토리로 파일을 복사하도록 Grunt 파일을 수정했다.
whatch:js 태스크는 "js/foundation/*.js"를 모니터링한다. 변경 혹은 추가되는 파일이 있으면, copy:update.js 태스크를 수행해서 변경된 파일을 빌드 디렉토리에 복사한다.
Html및 자바스크립트 코드 빌드
product와 develop 각 배포 환경에 따라서 코드가 달라질 수도 있다. 보통 전처리문을 이용해서 처리하는데, grunt는 이를 위한 몇 개의 플러그인을 제공한다. 나는 "preprocess" 플러그인을 이용해서 처리하기로 했다.
테스트를 위한 HTML 코드를 하나 만들었다.
지금까지 만들어진 gruntfile의 모습이다.
이 코드는 잘 작동한다. 하지만 관리해야 하는 코드의 양이 늘어나면, 그에 따라 gruntfile도 복잡해지고 지저분해 질 것이다. 배포 환경이 다양 할 경우 500라인을 넘어가는 gruntfile이 만들어지기도 한다. 코드의 양이 늘어날 때를 대비해서 gruntfile을 단순화 하기로 했다.
grunt.loadNpmTasks 없애기
플러그인의 갯수가 늘어나면, grunt.loadNpmTasks도 그에 따라 늘어난다. load-grunt-tasks 를 이용해서, 불필요한 태스크 로딩 줄을 없앨 수 있다.
load-grunt-config를 이용하면 alias파일을 분리 할 수도 있다. grunt 디렉토리 밑에 aliases파일을 만들고 여기에 alias를 설정하면 된다. js, json, yaml, coffee등 다양한 포멧을 지원한다.
예제에 있는 alias들을 aliases.js에 등록해보자.
Contents
Grunt 소개
다루는 이유
Grunt 사용
빌드 테스트 환경 및 시나리오
Grunt 설치
기본 사용 법
제품 별 빌드 설정
빌드 계획
빌드 파일 관리
빌드 후 테스트
Html및 자바스크립트 코드 빌드
Gruntfile의 관리
grunt.loadNpmTasks 없애기
task 파일의 분리 관리
Alias 파일 분리
기타 유용한 플러그인 들
Recent Posts
Archive Posts
Tags