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의 동작 방식

image

image

  • 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배열을 작성합니다.
  • options
    • type : Object
    • 기본적으로 options.base는 많이 사용함. 모든 glob가 시작되는 위치를 지정할 수 있는 옵션

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
    

    image

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으로 분리해줍니다.

    image

    그리고 gulp-autoprefixer문서를 보면 아래와 같이 나와있기 때문에 코드를 수정해줍니다.

    image-20200215201656518

    "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개가 합쳐진 스프라이트 이미지가 생성된 것을 볼 수 있습니다.

    image

    생성된 스프라이트 이미지에 대한 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. imageminvinyl-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]);
    });
    
    • 압축 전

    image

    • 압축 후

      image

    이미지의 용량이 커지면 커질수록 압축률이 증가할 것 같습니다.

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
    

하지만 위와 같이 코드를 작성하면 아래와 같은 이슈를 확인할 수 있습니다.

image

스택오버플로우를 확인해보니 gulp의 버전이 4.x로 넘어간 후에 gulp.task를 작성하는 방식이 변경되어서 발생한 이슈였습니다.

image

참고

image

참고

아까 작성한 코드를 아래처럼 수정해줘야 합니다.

// 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

image

gulp v3.x 에서 v4.x로 넘어오며 바뀐점

위 방법대로 하면 browser-sync,watch사용할 때 정상적으로 동작을 하지 않습니다. 그 이유를 알기 위해서는 기존에 gulp가 어떻게 동작을 했고, 현재는 어떻게 동작하는지 알아야합니다.

gulp는 task를 만들고 여러 task들을 연결하여 특정 상황에서 원하는 task를 호출해서 자동화를 만드는 방식입니다. gulp v3.x에서는 task들을 연결하는 방법에 대해 알아보겠습니다.

series,parallel

아래 코드는 3.x버전에서 my task 가 호출될 때 firstsecond를 먼저 호출해야 할 때 사용하는 방법이었습니다.

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 (직렬)

    • seriesparallel과 반대로 task를 직렬로 처리하는 방법입니다. 직렬이라함은 순차적으로 task의 콜백함수를 호출함으로써 태스크를 처리합니다.

parallelseries를 이용해서 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

참고사이트1

참고사이트2