본 번역문서는 개인적인 취지로 번역되어 제공되는 문서로, 원문을 비롯한 모든 저작권은 마이크로소프트사에 있습니다. 마이크로소프트사의 요청이 있을 경우, 언제라도 게시가 중단될 수 있습니다. 본 번역문서에는 오역이 포함되어 있을 수 있으며 주석도 번역자 개인의 견해일뿐입니다. 마이크로소프트사는 본 문서의 번역된 내용에 대해 일체의 보장을 하지 않습니다. 번역이 완료된 뒤에도 제품이 업그레이드 되거나 기능이 변경됨에 따라 원문도 변경되거나 보완되었을 수 있으므로 참고하시기 바랍니다.
원문: https://docs.asp.net/en/latest/client-side/using-grunt.html
Grunt 사용하기
원문 작성 : Noel Rice
Grunt는 일종의 JavaScript 태스크 러너로서, 스크립트 축소화(minification), TypeScript 컴파일, 코드 품질 “lint” 도구, CSS 전-처리기 및 클라이언트 개발을 지원하기 위해서 필요한 모든 반복적인 잡일들을 자동화할 수 있게 해주는 태스크 러너입니다. 비록 ASP.NET 프로젝트 템플릿은 기본적으로 Gulp를 사용하고 있긴 합니다만(Gulp 사용하기 참고), Grunt 또한 Visual Studio에서 완벽하게 지원되고 있습니다 (역주 : 사실 실무에서는 Gulp나 Grunt 둘 중 하나만 사용해도 무방하며 개발자의 취향에 따라서 혹은 개발팀에서 정한대로 선택하면 될 듯 합니다).
이번 예제는 클라이언트 빌드 프로세스를 자동화하는 방법을 맨 처음부터 보여줄 예정인데요. 빈 ASP.NET Core 프로젝트를 사용해서 진행하고자 합니다.
완성된 예제는 대상 배포 디렉토리를 정리(clean)하고, JavaScript 파일들을 결합하고, 코드 품질을 검사하고, JavaScript 파일 내용을 농축해서 웹 응용프로그램의 루트로 배포를 할 겁니다. 이번 예제에서는 다음의 패키지들을 사용할 예정입니다.
- grunt: Grunt 태스크 러너 패키지
- grunt-contrib-clean: 파일 및 디렉토리를 제거하는 플러그인
- grunt-contrib-jshint: JavaScript 코드 품질을 검토하는 플러그인
- grunt-contrib-concat: 여러 파일들을 단일 파일로 결합하는 플러그인
- grunt-contrib-uglify: JavaScript의 크기를 줄이기 위해서 축소화하는 플러그인
- grunt-contrib-watch: 파일의 활동을 감시하는 플러그인
응용프로그램 준비하기
예제는 새로운 빈 웹 응용프로그램을 만들고 TypeScript 예제 파일을 추가하는 것으로 시작할 겁니다. TypeScript 파일들은 Visual Studio의 기본 설정을 사용해서 자동으로 JavaScript로 컴파일되는데요. 그렇게 생성되는 js 파일들은 Grunt를 사용해서 처리할 원재료로 사용할 겁니다.
- Visual Studio에서 새로운
ASP.NET Core Web Application
을 만듭니다.
- [새 ASP.NET 프로젝트] 대화상자에서 ASP.NET Core의 [빈(Empty)] 템플릿을 선택하고, OK(확인) 버튼을 클릭합니다.
- [솔루션 탐색기]에서 프로젝트 구조를 살펴봅니다.
src
폴더가 빈 wwwroot
와 Dependencies
노드를 포함하고 있는 것을 확인합니다.
TypeScript
라는 이름의 새로운 폴더를 프로젝트에 직접 추가합니다.
- 그 밖의 파일들을 추가하기에 앞서, Visual Studio에서 TypeScript 파일에 대해 [저장 시 컴파일] 옵션이 체크되어 있는지 확인합니다. 메뉴에서 도구 > 옵션 > 텍스트 편집기 > Typescript > 프로젝트를 선택하시면 확인할 수 있습니다.
-
TypeScript
디렉토리에 마우스 오른쪽 클릭을 하고 컨텍스트 메뉴에서 [추가] > [새 항목]을 선택합니다. TypeScript 파일 항목을 선택하고 Tastes.ts 파일명을 입력합니다(*.ts 확장자에 주의합니다). 다음의 TypeScript 코드를 복사해서 파일에 붙여 넣습니다(파일을 저장하면 새로운 Tastes.js 파일이 JavaScript 소스를 포함한 채로 보여질 겁니다).
enum Tastes { Sweet, Sour, Salty, Bitter }
- 두 번째 파일을 TypeScript 디렉토리에 추가하면서 그 이름을
Food.ts
라고 명명합니다. 그리고, 다음의 코드를 파일에 복사해 넣습니다.
class Food {
constructor(name: string, calories: number) {
this._name = name;
this._calories = calories;
}
private _name: string;
get Name() {
return this._name;
}
private _calories: number;
get Calories() {
return this._calories;
}
private _taste: Tastes;
get Taste(): Tastes { return this._taste }
set Taste(value: Tastes) {
this._taste = value;
}
}
NPM 구성하기
다음으로는 grunt와 grunt-tasks를 다운로드 하도록 NPM을 구성합니다.
- 솔루션 탐색기에서 프로젝트에 마우스 오른쪽 클릭을 하고 컨텍스트 메뉴에서 [추가] > [새 항목]을 선택합니다. NPM configuration file 항목을 선택하고 이름은 기본 이름인
package.json
으로 남겨둡니다. 그리고 [추가] 버튼을 클릭합니다.(파일을 추가했는데도 Visual Studio에서 보이지 않는다면, 솔루션 탐색기 메뉴에서 [모든 파일 표시]를 선택하시면 됩니다)
- package.json 파일 안에 있는
devDependencies
개체 중괄호 안에다가 “grunt”를 입력합니다. 인텔리센스 목록에서 grunt
를 선택하고 엔터를 누릅니다. Visual Studio는 grunt 패키지 이름을 기입하고 콜론을 추가할 겁니다. 콜론의 우측에서는 패키지의 가장 안정적인 최신 버전을 인텔리센스 목록의 상단에서 선택합니다(만일 인텔리센스가 나타나지 않으면 Ctrl-Space
를 누릅니다). (역주 : 하단 그림에서는 가장 안정적인 최신 버전이 0.4.5입니다만, 번역 시에는 최신 안정적 버전이 1.0.1 이었습니다)
노트 : NPM은 종속성을 관리하기 위해서 시멘틱 버저닝(semantic versioning)을 사용합니다. SemVer이라고도 하는 시멘틱 버저닝은 숫자를 사용하는 스킴 <major>.<minor>.<patch>로 패키지를 식별합니다. 인텔리센스는 오직 몇몇 보편적인 버전들만을 보여줌으로써 시멘틱 버저닝을 간편하게 해 줍니다. 인텔리센스 목록의 상단 항목은 가장 안정적인 최신 버전의 패키지(그림에서는 0.4.5)라고 생각하면 됩니다. 캐럿(^) 기호는 가장 최근의 메이저 버전과 매치되며 틸드(~) 기호는 가장 최근의 마이너 버전과 매치됩니다. SemVer이 제공하는 전체 표현에 대한 가이드가 궁금하다면 NPM semver version parser reference를 참고하시기 바랍니다.
- clean, jshint, concat, uglify 및 watch와 같은 추가적인 grunt-contrib* 패키지들을 로드하려면 다음과 같이 하면 됩니다. 다만, 버전이 예제와 동일할 필요는 없습니다(역주: 번역 시점에 각 패키지들의 최신 버전은 1.0.0 혹은 1.0.1 입니다).
"devDependencies": {
"grunt": "1.0.1",
"grunt-contrib-clean": "1.0.0",
"grunt-contrib-jshint": "1.0.0",
"grunt-contrib-concat": "1.0.1",
"grunt-contrib-uglify": "1.0.1",
"grunt-contrib-watch": "1.0.0"
}
package.json
파일을 저장합니다.
각각의 devDependencies 항목에 대한 패키지들은 각 패키지가 요구하는 파일들과 함께 다운로드될 것입니다. 패키지 파일들은 node_modules
디렉토리에서 찾을 수 있습니다만, 기본적으로는 보이지 않기에 솔루션 탐색기에서 [모든 파일 표시] 버튼을 눌러야 나타납니다.
노트 : 필요하다면 솔루션 탐색기에서 종속성을 수동으로 복구할 수도 있습니다. Dependencies\NPM
에서 마우스 오른쪽 클릭을 하고 Restore Packages 메뉴 옵션을 선택하면 됩니다.
Grunt 구성하기
Grunt는 Gruntfile.js
라는 이름의 매니페스트를 사용하여 구성되는데요. 이 매니페스트는 다양한 태스크들을 정의하고 로드하고 등록하기 위해서 사용하며, 각 태스크들은 수동으로 실행하거나 또는 Visual Studio의 특정 이벤트 시에 자동으로 실행하게끔 구성할 수 있습니다.
- 프로젝트에 마우스 오른쪽 클릭을 하고 추가 > 새 항목을 선택합니다. Grunt Configuration file 옵션을 선택하고 파일 이름은 기본명인
Gruntfile.js
로 그대로 두세요. 그리고 추가 버튼을 클릭합니다.
초기 코드는 모듈 정의와 grunt.initConfig()
메서드를 포함하고 있습니다. initConfig()
는 각각의 패키지에 대한 옵션을 설정하는 데 사용되며, 모듈의 이하 부분에서는 태스크들을 로드하고 등록할 것입니다.
module.exports = function (grunt) {
grunt.initConfig({
});
};
initConfig()
메서드 안에 다음과 같이 clean 태스크에 대한 옵션을 추가합니다. clean
태스크는 디렉토리 문자열의 배열을 갖는데요. 이 태스크는 wwwroot/lib 안에 있는 파일들을 제거하고 /temp 디렉토리 전체를 삭제합니다.
module.exports = function (grunt) {
grunt.initConfig({
clean: ["wwwroot/lib/*", "temp/"],
});
};
- initConfig() 메서드 다음에는
grunt.loadNpmTasks()
에 대한 호출을 추가합니다. 이렇게 하면 태스크를 Visual Studio에서 실행할 수 있게 됩니다.
grunt.loadNpmTasks("grunt-contrib-clean");
- Gruntfile.js를 저장합니다. 이제 파일은 다음의 스크린샷과 같을 겁니다.
- Gruntfile.js에서 마우스 오른쪽 클릭을 하고 컨텍스트 메뉴에서 Task Runner Explorer를 선택합니다. 그러면 Task Runner Explorer가 열릴 겁니다.
- Task Runner Explorer에서 Tasks 밑에 있는
clean
이 있는지 확인합니다(안 보인다면, 좌측의 새로고침 모양의 버튼을 클릭합니다).
- clean 태스크에 마우스 오른쪽 클릭을 하고 컨텍스트 메뉴에서 Run을 선택합니다. 그러면 명령 창에 태스트의 진행 상황이 나타납니다.
노트 : 아직은 정리할 파일이나 디렉토리가 존재하지 않기에 별다른 효과가 없어 보일 수 있습니다. 그렇기에 원한다면 테스트 삼아서 수동으로 솔루션 탐색기에서 아무 폴더나 파일을 만들고 난 다음에 clean 태스크를 실행해 봅니다. 그러면 휘리릭~ 삭제되는 것을 볼 수 있습니다.
- initConfig() 메서드 안에 다음과 같은 코드를 사용하여
concat
을 위한 엔트리를 추가합니다.
src
속성 배열에는 결합할 파일들을 결합될 순서대로 나열하고 있습니다. dest
속성에는 결합된 파일이 생성되는 경로를 설정합니다.
concat: {
all: {
src: ['TypeScript/Tastes.js', 'TypeScript/Food.js'],
dest: 'temp/combined.js'
}
},
노트 : 코드에서 all
속성은 대상(target)의 이름입니다. 대상은 몇몇 Grunt 태스크에서 다중 빌드 환경을 지원하기 위해서 사용되는데요. 인텔리센스를 사용하여 내장되어 있는 대상들을 살펴볼 수도 있고 자체적인 명칭을 할당할 수도 있습니다.
- 다음의 코드를 사용하여
jshint
를 추가합니다.
jshint 코드품질 유틸리티는 temp 디렉토리에 존재하는 모든 JavaScript 파일에 대해서 실행됩니다.
jshint: {
files: ['temp/*.js'],
options: {
'-W069': false,
}
},
노트 : “-W069” 옵션은 JavaScript가 속성을 할당하기 위해서 dot 표기법 대신에 괄호 구문을 사용할 경우, 예를 들면 Tastes.Sweet
대신 Tastes["Sweet"]
를 사용할 경우 jshint에 의해 생성되는 에러입니다. 이 옵션은 프로세스의 남은 부분이 계속해서 수행되도록 경고를 끄는 역할을 합니다.
- 다음의 코드를 사용하여
uglify
태스크를 추가합니다.
이 태스크는 temp 디렉토리에 존재하는 combined.js 파일을 축소화하고, 결과 파일을 표준 명명 규약인 <파일명>.min.js의 형태로 명명하여 wwwroot/lib 폴더에 생성합니다.
uglify: {
all: {
src: ['temp/combined.js'],
dest: 'wwwroot/lib/combined.min.js'
}
},
- 앞서 grunt-contrib-clean을 로드했던 grunt.loadNpmTasks() 호출 아래로 다음과 같은 코드를 사용하여 jshint, concat, uglify에 대한 호출도 추가해 줍니다.
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
Gruntfile.js
를 저장합니다. 이제 파일은 다음과 같을 겁니다.
- Task Runner Explorer 목록에
clean
, concat
, jshint
및 uglify
태스크들이 포함되어 있는지 확인하세요. 각각의 태스크를 순서대로 실행하고 솔루션 탐색기에서 그 결과를 살펴보세요. 각 작업들은 에러없이 실행되어야 합니다.
concat 태스크는 새로운 combined.js 파일을 temp 디렉토리 안에 만들어 줍니다. jshint 태스크는 그냥 실행되기만 하고 어떠한 출력도 보여주진 않습니다. uglify 태스크는 combined.min.js 파일을 wwwroot/lib 경로에 만들어 줍니다. 모두 완료되고 나면 솔루션은 다음의 스크린샷과 같을 겁니다(역주 : Visual Studio 2015의 최신 업데이트 버전에서는 스크린샷이 약간 다릅니다만, 대부분 이해할 수 있는 수준으로 비슷한 모습일 겁니다. 예를 들면 .ts 파일의 경우 생성된 .js 파일이 TypeScript 폴더 아래에 동등한 수준으로 나열되어 있는 것이 아니라 ts 파일의 하위로 접혀 있습니다. 직접 보시면 무슨 말인지 이해되실 겁니다)
노트 : 각 패키지에 대한 옵션들에 대해서 궁금하다면, https://www.npmjs.com/에서 메인 페이지의 검색 창에 패키지 이름을 입력해서 살펴보면 됩니다. 예를 들면, grunt-contrib-clean 패키지에 대한 문서 링크를 찾아서 그의 모든 매개변수들을 살펴볼 수 있을 겁니다.
모두 함께 묶어보기
일련의 태스크들을 특정 순서대로 실행하고 싶다면 Grunt의 registerTask()
메서드를 사용하면 됩니다. 예를 들어, clean -> concat -> jshint -> uglify와 같은 순서로 예제 태스크들을 실행하고 싶다면, 다음과 같이 코드를 모듈에 추가하면 됩니다. 이 코드가 놓여질 위치는 initConfig 바깥 쪽에 loadNpmTasks() 호출과 같은 수준에 추가되어야 합니다.
grunt.registerTask("all", ['clean', 'concat', 'jshint', 'uglify']);
새로운 태스크가 Task Runner Explorer에서 Alias Tasks 밑에 나타날 겁니다. all
태스크에 마우스 오른쪽 클릭을 하고 다른 태스크들과 마찬가지로 실행합니다.
all
태스크는 clean
, concat
, jshint
및 uglify
를 순서대로 실행할 겁니다.
변경사항 감시하기
watch
태스크는 파일과 디렉토리들을 감시하는 역할을 합니다. watch는 변경을 탐지하는 경우 자동으로 태스크들을 시작하는데요. 다음과 같은 코드를 initConfig 안에 추가해서 TypeScript 디렉토리 안에 있는 *.js 파일들에 대한 변경사항을 감시해 보겠습니다. 이제 JavaScript 파일이 변경되면 watch
는 all
태스크를 실행할 겁니다.
watch: {
files: ["TypeScript/*.js"],
tasks: ["all"]
}
Task Runner Explorer에서 watch
태스크를 나타내기 위해서 loadNpmTasks()
호출도 추가합니다.
grunt.loadNpmTasks('grunt-contrib-watch');
Task Runner Explorer에서 watch 태스크에 마우스 오른쪽 클릭을 하고 컨텍스트 메뉴에서 실행을 선택합니다. watch 태스크의 실행을 보여주는 명령 창에 "Waiting…" 메세지가 출력될 겁니다. TypeScript 파일 중 아무것이나 하나를 열고 빈 칸을 하나 추가한 다음 저장을 해봅니다. 이 행동은 watch 태스크를 발생시키기에 다른 태스크들이 순서대로 호출될 겁니다. 다음의 스크린샷은 실행되는 모습을 보여주고 있습니다(역주 : 현재는 TypeScript 컴파일 기능이 개선되어서 단순히 공백을 입력한다고 js 파일이 다시 생성되거나 하진 않습니다. 그래서 watch도 발생하지 않아요. 그렇기에, *.js 파일을 삭제하거나 ts 파일을 다시 저장해야 watch가 동작할 겁니다)
Visual Studio 이벤트와 바인딩하기
Visual Studio에서 어떤 작업을 할 때마다 여러분의 태스크들을 수동으로 시작하길 원하지 않는다면, 그러한 태스크들을 Before Build, After Build, Clean 및 Project Open 시에 동작하도록 바인딩할 수도 있습니다.
Visual Studio가 열릴 때마다 watch
가 실행되도록 바인딩을 해보도록 해요. Task Runner Explorer에서 watch 태스크에 마우스 오른쪽 클릭을 하고 컨텍스트 메뉴에서 Bindings > Project Open를 선택합니다.
이제 프로젝트를 닫고 다시 열어 봅니다. 프로젝트가 다시 로드되는 경우에, watch 태스크가 자동으로 시작되는 것을 볼 수 있을 겁니다.
요약
Grunt는 강력한 태스크 러너이며 대부분의 클라이언트 빌드 작업들을 자동화하기 위해서 사용될 수 있습니다. Grunt는 NPM을 사용하여 패키지를 전달하며 모든 기능 도구들이 Visual Studio와 통합되어 있습니다. Visual Studio의 Task Runner Explorer는 설정 파일들의 변경을 감지하며, 편리한 인터페이스를 제공하여 태스크를 실행하거나, 실행 중인 태스크들을 살펴보거나, Visual Studio 이벤트와 태스크들을 바인드할 수 있도록 합니다.