[gulp] 크로스브라우징 및 빌드자동화를 위한 걸프(gulp) 알아보기
by 한만섭
TaskRunner
정의된 작업을 수행해주는 프로그램으로, 가벼움과 환경구축의 편리함으로 node.js기반의 TaskRunner들이 많다.
- Robo (http://robo.li/) : PHP기반의 TaskRunner
- Rake (https://ruby.github.io/rake/) : ruby기반의 TaskRunner
- Blade (http://otm.github.io/blade/) : lua언어 기반 TaskRunner
- Grunt (http://gruntjs.com/) : Node.js 기반의 TaskRunner
Node.js
- 서버사이드에서 동작이 가능한 javascript
- 확장성이 있는 네트워크 애플리케이션? 개발에 사용되는 소프트웨어 플랫폼
- Multi Thread가 아닌 이벤트 기반 비동기 방식(non-blocking IO)
npm
- Node Packaged Modules
- Node.js에서 사용되는 모듈을 패키지로 모아놓고 이를 관리해주는 툴
- Package.json에 의존 모듈로 관리함.
Gulp
- gulp의 기본적인 특징
- Streaming build system
.scss
->.css
- Auto prefixer(자동 접두어)
- 라이브 리로딩
- gulp task의 동작 방식
- gulp 사용법
- gulp.src(globs[, options])
- gulp.dest(path[, options])
- gulp.task(name[, deps], fn)
- gulp.watch(glob [, opts], tasks) 또는 gulp.watch(glob [, opts, cb])
gulp.src(globs[, options])
- 목표 파일을 지정하는 명령어
- globs
- type :
String
,Array
- 읽어들일 glob 혹은 glob배열을 작성합니다.
- type :
- options
- type :
Object
- 기본적으로
options.base
는 많이 사용함. 모든 glob가 시작되는 위치를 지정할 수 있는 옵션
- type :
gulp.dest(path[, options])
- 작업이 종료된 파일을 내보내는 명령어
- path
- 결과를 내보낼 위치를 정합니다.
- 문장으로 표현합니다.
gulp.task(name[, deps], fn)
- 작업할 태스크를 만드는 명령어
- name
- 태스크의 이름을 지정합니다.
- deps
- 해당 태스크가 실행되기 이전에 실행되어야 할 태스크를 지정합니다.
- 배열로 표현합니다.
- fn
- 태스크의 상세 내용을 정합니다.
- 함수로 표현합니다.
gulp.watch(glob [, opts], tasks) 또는 gulp.watch(glob [, opts, cb])
- 지정한 파일을 감시하여 지정된 task혹은 함수를 실행합니다.
- glob
- gulp.src 의 glob과 같습니다.
- opts
- watch를 위해 사용되는 gaze 모듈과 관련된 option을 설정할 수 있습니다.
- tasks
- 실행되는 태스크를 배열로 표현합니다.
- cb
- 실행되는 함수를 표현합니다.
gulp 실습
1. Node.js설치 및 버전 확인
$ node -v
v12.13.0
2. gulp-cli 설치
sudo npm install -g gulp-cli
3. package.json 생성
npm init -y
Package.json에 필요한 모듈을 추가하거나 삭제할 수 있습니다.
npm install --save-dev 모듈명
npm uninstall --save-dev 모듈명
4. Gulp 모듈 설치
gulp-cli는 gulp를 쉽게 실행하게 해주는 모듈이며 실제 동작하는 gulp모듈은 프로젝트에 설치해야합니다.
npm install --save-dev gulp@3.9.1
5. copy task만들어보기
-
gulp모듈 변수에 담기
var gulp = require('gulp');
-
Task 만들기
// copy task 만들기 // 파이프 라인으로 작성 후 콜백 함수로 리턴해줍니다. gulp.task('copy', function() { return gulp.src('*.txt') // 모든 .txt파일을 복사할 것 .pipe(gulp.dest('test/')); // test폴더 하위에 복사 });
-
task 실행
$ gulp copy
gulp@3.9.1
를 받았을 경우 아래와 같은 에러가 발생합니다[18:18:32] Requiring external module babel-register fs.js:27 const { Math, Object, Reflect } = primordials;
기존에 있던 버전을 지우고 최신 버전으로 설치하면 해결됩니다.
$ npm uninstall --save-dev gulp $ npm install --save-dev gulp@4.0.2
6. Gulp-sass 모듈 사용하기
-
gulp-sass
모듈 설치하기$ npm install --save-dev gulp-sass
-
.scss
파일 작성// scss/test.scss .test_wrap { text-align: right; .test { font-size: 12px; } }
-
gulpfile.js
파일 수정// gulpfile.js var sass = require('gulp-sass'); gulp.task("sass", function() { return gulp .src("scss/*.scss") // scss폴더 하위의 모든 .scss파일을 .pipe(sass()) // .css파일로 변경하고 .pipe(gulp.dest("css/")); // css폴더 하위에 복사하겠다. });
7 . autoprefixer모듈 사용하기
-
gulp-autoprefixer
모듈 설치하기npm install --save-dev gulp-autoprefixer
// gulpfild.js gulp.task('sass', function() { return gulp.src('scss/*.scss') .pipe(sass()) .pipe(autoprefixer({ // autoprefixer 추가 browsers: ['chrome > 0', 'ie > 0', 'firefox > 0'] })) .pipe(gulp.dest('css/')); });
위 방식대로 작성하면 아래 에러를 발생시킴
Replace Autoprefixer browsers option to Browserslist config. Use browserslist key in package.json or .browserslistrc file. Using browsers option can cause errors. Browserslist config can be used for Babel, Autoprefixer, postcss-normalize and other tools. If you really need to use option, rename it to overrideBrowserslist. Learn more at: https://github.com/browserslist/browserslist#readme https://twitter.com/browserslist
보면 package.json에 작성하라는 얘기가 있습니다. 그래서 아래와 같이 package.json으로 분리해줍니다.
그리고
gulp-autoprefixer
문서를 보면 아래와 같이 나와있기 때문에 코드를 수정해줍니다."browserslist": [ "ie > 0", "chrome > 0", "firefox > 0" ]
browerslist를 작성하는 순서는 상관이 없다.
cascade : false
에 대해서는 정리 필요
8. spritesmith를 이용하여 sprite이미지 만들어보기
spritesmith는 조각 이미지를 하나의 판으로 만든 후, css, sass 등에서 사용하기 쉽도록 해주는 모듈입니다.
-
Nom 모듈 설치하기
$ npm install --save-dev gulp.spritesmit
-
Gulp task 만들기
gulp.task("sprite", function() { const spriteData = gulp.src("sprites/*.png").pipe( spritesmith({ imgName: "sprite.png", cssName: "_sprite.scss", imgPath: "../img/sprite.png" // 필요 없음. (공식문서에 없는 속성) }) ); const imgStream = new Promise(function(resolve) { spriteData.img.pipe(gulp.dest("img/")).on("end", resolve); }); const cssStream = new Promise(function(resolve) { spriteData.css.pipe(gulp.dest("scss/")).on("end", resolve); }); return Promise.all([imgStream, cssStream]); });
-
sprite이미지 , sprite.scss만들기
$ gulp sprite
-
결과
해당 경로에 이미지 3개가 합쳐진 스프라이트 이미지가 생성된 것을 볼 수 있습니다.
생성된 스프라이트 이미지에 대한 position, width,height 와 같은 정보들이 들어가 있는것을 알 수 있습니다.
// _sprite.scss // SCSS variables are information about icon's compiled state, stored under its original file name // // .icon-home { // width: $icon-home-width; // } // // The large array-like variables contain all information about a single icon // $icon-home: x y offset_x offset_y width height total_width total_height image_path; // // At the bottom of this section, we provide information about the spritesheet itself // $spritesheet: width height image $spritesheet-sprites; $account-name: 'account'; $account-x: 0px; $account-y: 0px; $account-offset-x: 0px; $account-offset-y: 0px; $account-width: 18px; $account-height: 18px; $account-total-width: 36px; $account-total-height: 36px; $account-image: '../img/sprite.png'; $account: (0px, 0px, 0px, 0px, 18px, 18px, 36px, 36px, '../img/sprite.png', 'account', ); $check-circle-name: 'check_circle'; $check-circle-x: 18px; $check-circle-y: 0px; $check-circle-offset-x: -18px; $check-circle-offset-y: 0px; $check-circle-width: 18px; $check-circle-height: 18px; $check-circle-total-width: 36px; $check-circle-total-height: 36px; $check-circle-image: '../img/sprite.png'; $check-circle: (18px, 0px, -18px, 0px, 18px, 18px, 36px, 36px, '../img/sprite.png', 'check_circle', ); $delete-name: 'delete'; $delete-x: 0px; $delete-y: 18px; $delete-offset-x: 0px; $delete-offset-y: -18px; $delete-width: 18px; $delete-height: 18px; $delete-total-width: 36px; $delete-total-height: 36px; $delete-image: '../img/sprite.png'; $delete: (0px, 18px, 0px, -18px, 18px, 18px, 36px, 36px, '../img/sprite.png', 'delete', ); $spritesheet-width: 36px; $spritesheet-height: 36px; $spritesheet-image: '../img/sprite.png'; $spritesheet-sprites: ($account, $check-circle, $delete, ); $spritesheet: (36px, 36px, '../img/sprite.png', $spritesheet-sprites, ); // The provided mixins are intended to be used with the array-like variables // // .icon-home { // @include sprite-width($icon-home); // } // // .icon-email { // @include sprite($icon-email); // } // // Example usage in HTML: // // `display: block` sprite: // <div class="icon-home"></div> // // To change `display` (e.g. `display: inline-block;`), we suggest using a common CSS class: // // // CSS // .icon { // display: inline-block; // } // // // HTML // <i class="icon icon-home"></i> @mixin sprite-width($sprite) { width: nth($sprite, 5); } @mixin sprite-height($sprite) { height: nth($sprite, 6); } @mixin sprite-position($sprite) { $sprite-offset-x: nth($sprite, 3); $sprite-offset-y: nth($sprite, 4); background-position: $sprite-offset-x $sprite-offset-y; } @mixin sprite-image($sprite) { $sprite-image: nth($sprite, 9); background-image: url(#{$sprite-image}); } @mixin sprite($sprite) { @include sprite-image($sprite); @include sprite-position($sprite); @include sprite-width($sprite); @include sprite-height($sprite); } // The `sprites` mixin generates identical output to the CSS template // but can be overridden inside of SCSS // // @include sprites($spritesheet-sprites); @mixin sprites($sprites) { @each $sprite in $sprites { $sprite-name: nth($sprite, 10); .#{$sprite-name} { @include sprite($sprite); } } }
test.scss
에서 스프라이트 이미지를 사용해서 작성해보겠습니다.@import "sprite"; .icon_account { @include sprite($account); vertical-align: top; } .icon_check { @include sprite($check-circle); vertical-align: top; } .icon_delete { @include sprite($delete); vertical-align: top; }
.icon_account { background-image: url(sprite.png); background-position: 0px 0px; width: 18px; height: 18px; vertical-align: top; } .icon_check { background-image: url(sprite.png); background-position: -18px 0px; width: 18px; height: 18px; vertical-align: top; } .icon_delete { background-image: url(sprite.png); background-position: 0px -18px; width: 18px; height: 18px; vertical-align: top; }
위와 같이 spritesmith를 사용해서 여러 이미지들을 하나의 스프라이트 이미지로 만들고 해당 스프라이트 이미지의 정보를 scss파일로 만들고, scss파일을 @import해서 사용함으로써 여러 이미지를 사용할 떄 필요한 sprite기법을 손쉽게 적용할 수 있었습니다.
9. imagemin
과 vinyl-buffer
사용해서 이미지 용량 줄이기
지금까지의 방법으로도 스프라이트 이미지를 사용할 수 있으나, 이미지의 용량을 줄여서 성능향상에 도움을 줄 수 있는 방법이 있습니다.
-
모듈 설치
$ npm install --save-dev gulp-imagemin vinyl-buffer
-
Gulpfile.js 수정
imagemin
을 사용하기 위해서는 먼저buffer
작업을 거쳐야합니다.설치한 모듈을 사용하기 위해서 변수에 담아줍니다.
var imagemin = require("gulp-imagemin"); var buffer = require("vinyl-buffer");
이전에 작성했던 sprite task에 buffer와 imagemin을 추가해줍니다.
gulp.task("sprite", function() { const spriteData = gulp.src("sprites/*.png").pipe( spritesmith({ imgName: "sprite.png", cssName: "_sprite.scss" }) ); const imgStream = new Promise(function(resolve) { spriteData.img .pipe(buffer()) .pipe(imagemin()) .pipe(gulp.dest("img/")) .on("end", resolve); }); const cssStream = new Promise(function(resolve) { spriteData.css.pipe(gulp.dest("scss/")).on("end", resolve); }); return Promise.all([imgStream, cssStream]); });
-
압축 전
-
압축 후
이미지의 용량이 커지면 커질수록 압축률이 증가할 것 같습니다.
-
10. Browser-sync와 watch 이용하기
Browser-sync를 사용하면 .scss
파일이 수정되어 새로운 .css
파일이 생성될 때 브라우저를 새로고침하지 않고 바로 적용된 화면을 보거나 .html
파일이 수정되면 자동으로 새로고침을 시키는 등 작업 효율을 올릴 수 있습니다.
-
모듈 설치
$ npm install --save-dev browser-sync
-
index.html 생성
$ touch index.html
-
Browser-sync 변수 생성
var browserSync = require("browser-sync");
-
Browser-sync task생성
gulp.task("browser-sync", function() { browserSync.init({ server: { baseDir: "./" } }); });
-
Watch task 생성
.scss
,.html
파일이 수정된 후에 자동으로 인식하고 동작하도록 위한 태스크gulp.task("watch", ["browser-sync"], function() { gulp.watch("scss/*.scss", ["sass"]); gulp.watch("*.html").on("change", browserSync.reload); });
-
sass task 동작 후 css갱신 할 수 있도록 코드 수정
gulp.task("sass", function() { return gulp .src("scss/*.scss") .pipe(sass()) .pipe( autoprefixer({ cascade: false }) ) .pipe(gulp.dest("css/")) .pipe(browserSync.stream({ match: "**.*.css" })); // 추가된 부분 });
-
default task 생성
gulp.task("default", ["watch"]);
-
default gulp
$ gulp
하지만 위와 같이 코드를 작성하면 아래와 같은 이슈를 확인할 수 있습니다.
스택오버플로우를 확인해보니 gulp의 버전이 4.x로 넘어간 후에 gulp.task를 작성하는 방식이 변경되어서 발생한 이슈였습니다.
아까 작성한 코드를 아래처럼 수정해줘야 합니다.
// gulp 3.x
gulp.task("watch", ["browser-sync"], function() {
gulp.watch("scss/*.scss", ["sass"]);
gulp.watch("*.html").on("change", browserSync.reload);
});
gulp.task("default", ["watch"]);
// gulp 4.x 버전
gulp.task(
"watch",
gulp.series("browser-sync", function() {
gulp.watch("scss/*.scss", ["sass"]);
gulp.watch("*.html").on("change", browserSync.reload); })
);
gulp.task("default", gulp.series("watch"));
최종 gulpfile.js
var gulp = require("gulp");
var sass = require("gulp-sass");
var autoprefixer = require("gulp-autoprefixer");
var spritesmith = require("gulp.spritesmith");
var imagemin = require("gulp-imagemin");
var buffer = require("vinyl-buffer");
var browserSync = require("browser-sync");
gulp.task("sass", function() {
return gulp
.src("scss/*.scss")
.pipe(sass())
.pipe(
autoprefixer({
cascade: false
})
)
.pipe(gulp.dest("css/"))
.pipe(browserSync.stream({ match: "**.*.css" }));
});
gulp.task("sprite", function() {
const spriteData = gulp.src("sprites/*.png").pipe(
spritesmith({
imgName: "sprite.png",
cssName: "_sprite.scss"
})
);
const imgStream = new Promise(function(resolve) {
spriteData.img
.pipe(buffer())
.pipe(imagemin())
.pipe(gulp.dest("img/"))
.on("end", resolve);
});
const cssStream = new Promise(function(resolve) {
spriteData.css.pipe(gulp.dest("scss/")).on("end", resolve);
});
return Promise.all([imgStream, cssStream]);
});
gulp.task("copy", function() {
return gulp.src("*.txt").pipe(gulp.dest("text/"));
});
gulp.task("browser-sync", function() {
browserSync.init({
server: {
baseDir: "./"
}
});
});
// gulp 3.x
// gulp.task("watch", ["browser-sync"], function() {
// gulp.watch("scss/*.scss", ["sass"]);
// gulp.watch("*.html").on("change", browserSync.reload);
// });
// gulp.task("default", ["watch"]);
// gulp 4 버전
gulp.task(
"watch",
gulp.series("browser-sync", function() {
gulp.watch("scss/*.scss", ["sass"]);
gulp.watch("*.html").on("change", browserSync.reload);
})
);
gulp.task("default", gulp.series("watch"));
이제 default gulp 명령어를 실행시킬 수 있습니다.
$ gulp
gulp v3.x 에서 v4.x로 넘어오며 바뀐점
위 방법대로 하면 browser-sync
,watch
사용할 때 정상적으로 동작을 하지 않습니다. 그 이유를 알기 위해서는 기존에 gulp가 어떻게 동작을 했고, 현재는 어떻게 동작하는지 알아야합니다.
gulp는 task를 만들고 여러 task들을 연결하여 특정 상황에서 원하는 task를 호출해서 자동화를 만드는 방식입니다. gulp v3.x에서는 task들을 연결하는 방법에 대해 알아보겠습니다.
series
,parallel
아래 코드는 3.x버전에서 my task
가 호출될 때 first
와second
를 먼저 호출해야 할 때 사용하는 방법이었습니다.
gulp.task("my task", ["first" , "second"], function() {
// last
});
gulp.task("first",function() {/* task 내용 */})
gulp.task("second", function() {/* task 내용 */})
하지만 4.x
이상 버전부터는 새로 나온 parallel
,series
두개를 사용함으로써 기존에 사용하던 task연결 방법을 대체할 수 있습니다. 그렇다면 parallel과 series의 차이점에 대해 알아보도록 하겠습니다.
-
parallel (병렬)
-
parallel은 사전적 의미로
평행
이라는 뜻을 갖고 있습니다. task를 처리하는 방법을 평행하게 즉, 서로 연관 없이 진행한다는 의미입니다. 서로 상관 없는task
들을 각각처리해야하는 병렬처리 상황에서 사용할 수 있습니다. -
기존에.
["task","task2" ]
로 작성하던 방식이 병렬 구조였습니다. -
이전 버전에서 사용한 방법을
4.x
에서 작성한다면 아래와 같습니다.
-
-
series (직렬)
series
는parallel
과 반대로task
를 직렬로 처리하는 방법입니다. 직렬이라함은 순차적으로 task의 콜백함수를 호출함으로써 태스크를 처리합니다.
parallel
과 series
를 이용해서 3.x
를 수정해보면 아래와 같습니다.
function first() {/* task 내용 */}
function second() {/* task 내용 */}
function last () {/* task 내용 */}
gulp.task("default", gulp.series(gulp.parallel( first , second ), last );
Task는 function이다.
4.x
Subscribe via RSS