Grunt 설치
Grunt는 Node.js로 만들어졌고 npm에 등록이 되어 있으므로 일반적인 Node.js 커맨드라인 도구처럼 글로벌로 설치하면 된다.(당연히 Node.js는 설치가 되어 있어야 한다.)
$ npm install -g grunt
$ grunt --version
grunt v0.3.17
이 글을 쓰는 시점에 최신 버전은 0.3.17이고 곧 0.4가 나올듯하다.(0.4에 얼마나 바뀌는지는 잘 모르겠다.) 버전이 정상적으로 출력된다면 Grunt를 사용할 준비는 다 된 것이다.
템플릿
Grunt는 init 명령어를 통한 템플릿기능으로 프로젝트를 시작하기 위한 스캐폴딩 파일과 구조를 자동으로 생성해준다. 즉, 어떤 프로젝트에 기반이 되는 파일과 구조를 프로젝트에 맞게 자동으로 만들어주고 있는데 현재 Grunt(0.3.17) 기준으로 다음의 템플릿을 지원하고 있다.
- commonjs - CommonJS 프로젝트
- gruntplugin - Grunt Plugin 프로젝트
- jquery - jQuery Plugin 프로젝트
- node - Node.js 프로젝트
- gruntfile - grunt.js
위 화면처럼 Node.js 프로젝트를 하려면 grunt init:node라고 하면 프로젝트에 필요한 정보를 묻는 프롬프트가 나오고 필요한대로 입력하면 그에 맞게 템플릿 파일이 생성된다. 괄호안에 있는 값이 기본값이므로 기본값을 그냥 사용하려면 엔터만 쳐도 되고 새로운 값을 입력하거나 빈값으로 하려면 none을 입력하면 된다. 템플릿 명이 기억이 나지 않으면 grunt init만 입력하면 이름이 지정안되었다고 오류 내면서 사용할 수 있는 템플릿이름을 보여준다.
- .npmignore
- LICENSE-MIT
- README.md
- grunt.js
- package.json
- lib/
- example.js
- test/
- example_test.js
node 템플릿을 기준으로 위와같은 구조의 파일들을 자동으로 만들어준다. 기본적인 프로젝트 구조들은 매번 작업할 때마다 해야하는 귀찮은 작업이기 때문에 이 기능은 좋은 시작점이 된다.
grunt.js
템플릿 기능은 사실 좀 부가기능으로 보이고 Grunt가 빌드도구 이므로 프로젝트 중에 계속 반복해야하는 작업들을 Grunt로 편리하게 사용할 수 있다. 프로젝트에 Grunt를 사용하려면 프로젝트의 루트경로에 grunt.js라는 파일이 존재해야 한다. 다음은 grunt init:gruntfile을 실행하면 자동으로 생성되는 grunt.js 파일이다.
/*global module:false*/
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: '<json:package.json>',
meta: {
banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' +
'<%= grunt.template.today("yyyy-mm-dd") %>\n' +
'<%= pkg.homepage ? "* " + pkg.homepage + "\n" : "" %>' +
'* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' +
' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */'
},
lint: {
files: ['grunt.js', 'lib/**/*.js', 'test/**/*.js']
},
qunit: {
files: ['test/**/*.html']
},
concat: {
dist: {
src: ['<banner:meta.banner>', '<file_strip_banner:lib/<%= pkg.name %>.js>'],
dest: 'dist/<%= pkg.name %>.js'
}
},
min: {
dist: {
src: ['<banner:meta.banner>', '<config:concat.dist.dest>'],
dest: 'dist/<%= pkg.name %>.min.js'
}
},
watch: {
files: '<config:lint.files>',
tasks: 'lint qunit'
},
jshint: {
options: {
curly: true,
eqeqeq: true,
immed: true,
latedef: true,
newcap: true,
noarg: true,
sub: true,
undef: true,
boss: true,
eqnull: true,
browser: true
},
globals: {}
},
uglify: {}
});
// Default task.
grunt.registerTask('default', 'lint qunit concat min');
};
소스가 길기는 한데 일반적인 Node.js용 자바스크립트 파일이라고 생각하면 된다. Node.js로 만들어졌으므로 module.exports = function(grunt) {}로 전체 소스를 감싸주어야 하고 실제 프로젝트의 설정과 테스크설정은 grunt.initConfig()안에 모두 들어간다. grunt.js파일에서 grunt.initConfig()는 딱 한번만 와야한다. grunt.initConfig()의 각 설정부분을 하나씩 살펴보자.
pkg
pkg: '<json:package.json>',
pkg는 이름에서 알수 있듯이 패키지의 약자인데 CommonJS 스펙에 정의된 package.json 파일을 가리키는 용도로 사용한다. <json:package.json>와 같이 사용한 것은 grunt가 제공하는 json 디렉티브인데 실제로 현재 프로젝트의 package.json파일을 읽어온다. package.json에는 보통 프로젝트에 대한 메타정보가 들어있는데 이 정보를 그대로 읽어오기 때문에 <%= pkg.name %>와 같은 식으로 package.json에 정의된 내용을 가져와서 사용할 수 있다.
meta
meta: {
banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' +
'<%= grunt.template.today("yyyy-mm-dd") %>\n' +
'<%= pkg.homepage ? "* " + pkg.homepage + "\n" : "" %>' +
'* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' +
' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */'
}
메타정보인데 내부 속성으로 banner만 가지고 있다. banner에는 자바스크립트 파일을 합치거나(concatenation) 압축했을 때(minify) 자바스크립트 파일 상단에 자동으로 넣을 주석을 지정한다. 앞에서도 얘기했듯이 <%= %>같은 스크립틀릿을 사용할 수 있고 여기서는 자바스크립트 함수도 실행할 수 있다.
lint
lint: {
files: ['grunt.js', 'lib/**/*.js', 'test/**/*.js']
}
자바스크립트 파일을 lint할 대상을 배열로 지정한다. 여기서 lint는 더글라스 크록포드의 lint라기 보다는 코드를 검사해준다는 면에서 범용적인 의미의 동사로 사용한 것으로 보인다. 실제 검사는 JSHint를 사용해서 검사가 이뤄진다. 그리고 대상을 지정할 때 위에서는 files라는 이름을 주었는데 원하는 이름을 사용해서 여러가지로 지정할 수도 있다.
lint: {
all: [],
client: [],
server: []
}
이런식으로 대상의 세트를 여러가지로 만들 수 있다. lint의 실행은 grunt lint:대상이름 으로 실행한다. grunt lint:all을 하면 all에 지정된 대상 파일들을 lint하고 이름을 주지않고 grunt lint하면 모든 대상을 다 lint한다.
qunit
qunit: {
files: ['test/**/*.html']
}
jQuery의 테스트 프레임워크인 QUnit의 대상파일을 지정한다. grunt qunit으로 실행할 수 있는데 원래의 QUnit이 브라우저로 실행하는데 반해서 grunt에서는 브라우저 없이 PhantomJS를 사용해서 실행한다.(지정한 HTML파일을 띄운것처럼) 그러므로 Qunit을 사용하려면 PhantomJS가 설치되어 있어야 한다. lint와 마찬가지로 대상파일의 세트를 여러가지로 지정할 수 있다.
concat / min
concat: {
dist: {
src: ['<banner:meta.banner>', '<file_strip_banner:lib/<%= pkg.name %>.js>'],
dest: 'dist/<%= pkg.name %>.js'
}
},
min: {
dist: {
src: ['<banner:meta.banner>', '<config:concat.dist.dest>'],
dest: 'dist/<%= pkg.name %>.min.js'
}
}
concat은 자바스크립트 파일을 합치는 것을 의미한다. 개발과정에서는 모듈화로 파일을 분리해서 개발하지만 배포단계에서는 하나의 파일로 합쳐서 나가는것이 좋기 때문에 합치는 과정을 말하고 min은 불필요한 공백이나 줄바꿈을 없애서 파일사이즈를 줄이는걸 말한다.(난독화와는 다르다.) 그래서 concat과 min에서는 설정이 depth가 한단계 더 있다. src에 설정한 파일들을 dest로 내보내게 되고 이러한 설정을 당연히 여러세트로 만들 수 있다.
watch
watch: {
files: '<config:lint.files>',
tasks: 'lint qunit'
}
watch는 files에 지정된 파일들의 변경사항을 감시하다가 변경사항이 발생하면 tasks에 지정된 동작을 실행한다. 그러므로 앞의 설정과는 다르게 여러 세트를 설정할 수는 없다. 여기서는 Grunt의 디렉티브를 사용해서 lint 설정에서 files로 지정한 파일들을 감시하다가 변경되면 lint와 qunit을 실행하도록 했다.
jshint
jshint: {
options: {
curly: true,
eqeqeq: true,
immed: true,
latedef: true,
newcap: true,
noarg: true,
sub: true,
undef: true,
boss: true,
eqnull: true,
browser: true
},
globals: {}
}
처음에 볼때 헷갈리던 부분인데 JSHint 검사의 설정사항이고 이는 위에서 본 lint를 실행할때 적용된다. 즉, grunt jshint같은건 없다. 각 JSHint의 옵션은 JSHint의 문서를 참고하면 되고 globals에는 전역으로 사용할 변수등을 지정한다. 예를 들어 다른 라이브러리에서 자동으로 설정되는 전역변수나 require()등으로 불러와지는 변수들이 있는데 이런 경우 lint가 선언안되었다고 오류를 내보내기 때문에 globals에 설정해 주어야 한다. 예를 들어 mocha를 사용하는데 describe에서 오류가 난다면 globals: { describe: true } 처럼 설정하면 된다. lint에서 타겟을 여러개로 주었는데 JSHint의 설정도 다르게 적용하고 싶다면 다음처럼 하면 된다.
grunt.initConfig({
lint: {
a: [],
b: []
},
jshint: {
// Defaults.
options: {},
globals: {},
a: {
options: {},
globals: {}
},
b: {
options: {},
globals: {}
}
}
});
uglify
uglify: {}
앞에서 지정한 min에서 사용할 UglifyJS의 설정이다. lint를 위해서 JSHint를 설정한 것이라 같다고 보면 된다.
test
test: {
files: ['test/**/*.js']
}
위의 기본생성된 grunt.js에는 나오지 않았었지만 테스트파일에 대한 설정도 할 수 있다. 앞에서 qunit부분이 클라이언트측 자바스크립트 테스트라면 test는 서버사이드 자바스크립트 테스트이다. 테스트는 Nodeunit을 사용하는데 mocha를 쓰는 나로써는 테스트명령어를 임의로 할수 없는게 좀 불만이다.
태스크(Task)
설정을 간단히 살펴보았는데 앞에서도 좀 언급을 했지만 Grunt는 기본으로 제공하는 테스크들이 있다.
- init
- lint
- concat
- min
- qunit
- watch
- test
- server
플러그인
여기까지는 Grunt가 기본으로 제공하는 기능들이고 Grunt가 API를 제공하기 때문에 이를 이용해서 플러그인을 만들 수 있다. 플러그인 목록은 Grunt 홈페이지에 나와있고 Grunt가 제공하지 않는 기능들을 내장 태스크처럼 사용할 수 있도록 설정할 수 있다. 자세한 사용방법은 각 플러그인의 가이드를 참고해야 하는데 실제로 Grunt 플러그인은 NPM 모듈들이다. 그래서 프로젝트 로컬에 NPM 모듈을 설치한 뒤 다음과 같이 grunt.js에서 불러들어서 사용하는 구조이다.
grunt.loadNpmTasks('플러그인 이름');
이 방법이 그리 깔끔한 방법인지 약간 고민되긴 하지만 npm 모듈을 설치해야하니 package.json에 의존성을 명시해서 설치하도록 해놓고 함께 사용하는게 좋은 방법일 것 같다.
별도로 직접 작성한 플러그인은 다른 폴더에 작성한 후 다음 명령어로 불러와서 사용할 수도 있다.
grunt.loadTasks('폴더명')
별칭
Grunt에서는 테스크에 대한 별칭(Alias)을 지정할 수 있다. 위의 grunt.js 파일에서 마지막에 다음과 같은 라인이 있는 것을 볼 수 있다.
grunt.registerTask('default', 'lint qunit concat min');
기본 동작에 대한 등록인데 사실 이는 lint, qunit, concat, min 태스크에 대한 별칭을 default로 준 것이고 default는 예약된 별칭으로 grunt만 입력하면 grunt default와 동일하다. 그렇기 때문에 자주 사용하는 것을 default에 입력하면 편리하고 말그대로 별칭이기 때문에 한꺼번에 자주 사용하는 것들은 묶어서 별칭을 만들어두면 더 편리하게 사용할 수 있다.
Yeoman 사용하면서 Grunt에 대해 궁금했었는데 깔끔하게 잘 정리해주셔서 감사합니다.
전 Yeoman은 안써봤는데 내부에서 Grunt를 쓴다고 하더라구요... 요즘 yeoman 얘기도 많이 들어서 조만간 써봐야겠어요 ㅎ
grunt 소개가 너무 잘 정리되어 있네요.
감사하게 보고 갑니다. (__)
지금은 0.4로 업글되서 변경된 내용으로 확인해 보셔야 할겁니다.
저는 윈도우 7입니다.
node.js 를 설치한후 명령프롬프트에서 작업폴더로 간다음 npm install -g grunt 를 실행해서 grunt 를 설치했습니다.
그 다음 grunt init:템플릿이름 이라고 하셧는데 엔터를 쳐도 아무것도 안나옵니다.
예를들어 grunt init:node 라고 입력해도 그렇고, grunt init:gruntfile 라고 해도 Gruntfile 가 없다고 나오네요.....
순서가 어떻게 되는지? 어는 부분이 잘못된건가요?
글이 오래되서 그렇습니다. grunt는 0.4이후에 구조가 대폭 개선되고 init은 별도로 분리되었습니다. http://blog.outsider.ne.kr/910 이글을 참고해 주세요.
관리자만 볼 수 있는 댓글입니다.
안녕하세요. 말씀하신 내용만으로는 상황을 파악하기 어렵습니다.
그런트자체는 빌드 테스크를 실행하는 도구 일 뿐이고 웹사이트를 어떻게 빈드하느냐는 설정에 따라 다르기 때문에 그런트 문제로 보이지는 않습니다.
캐싱 문제라면 크롬에서도 안나와야 한는데 익스 8에서만 안된다고 하시니 웹폰트 설정이 woff만 되어 있다거나 IE 호환성이 안되거나 하는게 아닐가 합니다.