Basic of Gulp

Alpaca·2021년 5월 10일
0

Gulp

목록 보기
1/1

Basic of Gulp and Pug compilation

Gulp가 어떻게 webpack을 대체할 수 있는지에 대해 가볍게 알아보도록하자

Gulp는 흔히 Task runner로 알려져있다
여기서 Task란 예를 들어 최신 javascript를 사용한 것을 구형브라우저가 이해할 수 있도록 기존코드로 compile하는 것처럼 반복적인 귀찮은 작업들을 gulp 통해 쉽게 처리해 주는 것을 말합니다

주로 우리는 pug, scss, modern javascript 등을 compile하게 될 것이다

먼저 global route에 gulp를 설치해줘야 한다
(나는 wsl2를 사용하므로 윈도우 사용자는 윈도우에 맞는 명령어를 사용하길 바란다)

npm install --global gulp-cli // install gulp
mkdir gulp-course // create gulp-course folder(directory)
cd gulp-course
mkdir src dist
mkdir src/scss src/js src/img src/templates src/partials
touch src/index.pug
cd src/js
touch init.js main.js
cd ..
cd scss
touch style.scss _variables.scss _reset.scss
cd ..
touch partials/header.pug partials/footer.pug
touch templates/layout.pug

img에는 아무거나 넣어도 되지만 나는 강의대로 Gulp logo를 넣기로 하겠다
srccompile되기 원하는 파일들이 들어갈 것이고 distcompile된 파일들이 들어가게 될 것이다

다시 route folder로 돌아와서

npm init

을 입력해서 package.json을 만들어주자

question은 그냥 엔터로 넘어가도 되고 본인이 원하는 값을 적어도 된다

다 됐으면 이제 간단히 최신기술이 어떻게 compile되는지 알아보자

util.js

export const random = (max) => Math.floor(Math.random() * max);

main.js

import { random } from "./util";

const rOne = random(10);
const rTwo = random(20);

console.log(`${rOne} ${rTwo}`);

layout.pug

doctype html
html(lang="en")
  head
    meta(charset="UTF-8")
    meta(http-equiv="X-UA-Compatible", content="IE=edge")
    meta(name="viewport", content="width=device-width, initial-scale=1.0")
    link(rel="stylesheet", href="css/style.css")
    title Super Gulp
  body 
    include ../partials/header

    block content 

    include ../partials/footer

    script(src="js/main.js") 

index.pug

extends templates/layout 

block content
  img(src="img/logo.svg", alt="logo")
  h1 Awesome Minimalism

footer.pug

footer 
  span I ❤ Gulp

header.pug

header 
  span Hello world!

_reset.scssreset.css를 복붙해서 넣도록 하겠다

_variables.scss

$red: #ce4647;

style.scss

@import "_reset";
@import "_variables";

body {
  background-color: $red;
}

각 파일에 위와 같이 작성을 한 후 route folder에서

touch gulpfile.js
npm install gulp --save-dev

그리고 package.json

  "scripts": {
    "dev": "gulp dev",
    "build": "gulp build"
  }

scripts를 추가해준 후 terminal

npm run dev

를 입력하면

Using gulpfile <yourdirectory>/gulpfile.js

위와 같이 나오면서 일단 실행은 되는 걸 볼 수 있다

이제 gulpfile.js를 작성해보도록 하자

import gulp from "gulp";

근데 위와 같은 modern javascript문법을 이해하지 못해 실행시 오류가 발생하는 것을 볼 수 있다
그래서 우리는 gulpfile.jsgulpfile.babel.js로 이름을 바꿔줘 최신 문법을 이해할 수 있도록 만들어주자
그리고 .babelrc파일을 하나 만들어 준 후 거기에

{
  "presets": ["@babel/preset-env"]
}

위와 같이 작성해 준 후 다시 실행해보면

gulp dev

Failed to load extenal module blabla~라는 문구가 나올 것이다
이는 해당 module이 없다는 얘기인데 최신버전의 경우 항상 @가 prefix로 붙기 때문에 우리는 @babel/register@babel/core를 설치해주도록 하자

npm run dev, yarn dev, gulp dev는 모두 같은 실행 명령어다

npm install @babel/register
npm install @babel/core
npm install @bable/preset-env // 이것도 없어서 결국 Error가 한번더 발생하기에 미리 설치하도록 하자

그 후 다시 gulp dev를 해보면 정상적으로 실행되는 것을 볼 수 있다
하지만 아직도 Task never defined: dev라는 문구가 나와 굉장히 거슬린다
이제부터 Task 에 대해 자세히 배워보도록 하자

일단 gulp에서 task는 어떻게 만들까?
방법은 아주 간단하다 그냥 function을 export하거나 const로 declaration하면 된다
우리는 script"dev": "gulp dev"를 만들어 뒀기 때문에 dev라는 함수를 하나 만들어 보자

export const dev = console.log("i will dev");

그리고 gulp devgulp를 불러오면 i will dev라는 문구를 볼 수 있을 것이다

Task never defined: dev

하지만 아직도 위와 같은 오류가 발생한다
왜냐하면 아직 우리는 dev라는 함수에서 단순히 console.log를 실행했을 뿐 사실 아무 것도 안한 것과 같기 때문이다(Task를 만들어주지 않았기 때문이다)

일단 이처럼 gulptask와 같이 일하고 그렇기 때문에 우리는 task를 만들어서 gulp가 동작하게 만들면 된다

위 그림과 같이 task는 어떠한 sourcecompile해서 browser가 이해 할 수 있도록 build해주는 역할을 한다고 보면 된다
그럼 minify는 뭘까? 우리가 살면서 어떤 libraryframework를 사용하다보면
somelibrary.jssomelibrary.min.js 이렇게 두가지로 나눠져 있는 것을 자주 접하게 된다
min은 기본적으로 가독성 등을 포기하고 용량을 최소화 한 파일이라고 생각하면 된다
그래서 gulpcompilecss파일을 보면 줄바꿈 없이 한 줄로 쭉 써져있는 것을 볼 수 있다

이 개념을 좀 더 쉽게 이해하기 위해서 pug to html을 할 수 있는 task를 사용해보자
만드는 것도 물론 좋지만 gulp에는 이미 수많은 plugin들이 존재한다
만약 webpack을 좀 알고있다면 webpack에 있는 loaders plugin을 생각하면 된다
(나도 webpack에 대해서는 잘 모른다 ㅎㅎ;)

npm i gulp-pug -d //install gulp-pug plugin

위 명령어를 통해 설치한 다음에 여기에 나와있는 것 처럼

var pug = require('gulp-pug');
 
gulp.task('views', function buildHTML() {
  return gulp.src('views/*.pug')
  .pipe(pug({
    // Your options in here.
  }))
});

gulp가 어떻게 동작하는지를 익힌 후 사용해보도록 하자
gulp는 다양한 Object를 갖고 있는데 Documentation을 보면 src(), dest(), lastRun() 등이 있는 것을 알 수 있다

이 중에 가장 중요한 것은 src()dest()라고 개인적으로 생각하므로
여기서는 두가지에 대해 중점적으로 다뤄보도록 하겠다

먼저 코드 상단에서 import gPug from "gulp-pug";로 import를 해준 후 루트를 지정해 주면 되는데

const routes = {
  pug: {
    src: "src/*.pug",
    dest: "build" //destination
  }
};

일단 나는 src폴더 안에 있는 pug파일들만 compile하기 위해 위와 같이 지정했지만 다른 폴더안에 있는 모든 pug파일들도 compile하고 싶다면 src/**/*.pug로 수정해주면 된다

그 후

const pug = () =>
  gulp.src(routes.pug.src).pipe(gPug()).pipe(gulp.dest(routes.pug.dest));

export const dev = gulp.series([pug]);

를 해주고 gulp dev로 실행해주면 dist라는 폴더에 compileindex.html파일을 볼 수 있을 것이다

gulp는 기본적으로 pipe라는 개념을 쓰는데 말 그대로 A와 B를 연결해주는 배관이라고 생각하면 된다

  1. gulp.src(routes.pug.src)를 통해 source file의 위치를 지정했고
  2. pipe(gPug())gulp-pug모듈과 연결을 해주고
  3. compile된 파일을 pipe(gulp.dest(routes.pug.dest))라는 지정된 폴더에 저장

위와 같은 일이 일어나고 있다는 것을 숙지하길 바란다

complie을 잘못했을 때 파일을 직접 삭제하고 다시 compile을 진행하는 건 굉장히 비효율적이다
그래서 우리는 자동으로 삭제해주고 그 뒤에 compile이 진행될 수 있도록 만들어주기 위해
npm i delDel module을 설치해주자

그리고 코드 상단에 import del from "del";를 추가해주고

const routes = {
  pug: {
    src: "src/*.pug",
    dest: "dist",
  },
};

const pug = () =>
  gulp.src(routes.pug.src).pipe(gPug()).pipe(gulp.dest(routes.pug.dest));

const clean = () => del(["dist"]);

export const dev = gulp.series([clean, pug]);

새로운 taskclean을 만들어 주고 pug를 실행하기 전에 clean을 먼저 실행해주도록 하면 자동으로 compile하기 전에 dist안의 모든 것들을 먼저 지워줄 것이다

여기서 계속 export라는 것이 나오는데 이건 package.json에서 우리가 지정한 사용할 command들에게만 사용해주면 된다

여기서 나는 compile을 하기 전 전처리 과정과 실제 compile을 하는 과정을 나눠 두 단계의 step으로 만들어 관리를 하고 싶다

const pug = () =>
  gulp.src(routes.pug.src).pipe(gPug()).pipe(gulp.dest(routes.pug.dest));

const clean = () => del(["dist/"]);

const prepare = gulp.dest([clean]);
const assets = gulp.series([pug]);

export const dev = gulp.series([prepare, assets]);

그래서 위와 같이 조금 수정해 주기로 하자

이렇게 하면 전처리 과정에 무언가 추가되면 우리는 prepare에 추가를 해주면 되기 때문에 좀 더 이해하기 좋은 소위 말하는 가독성이 좋은 코드가 되는 것이다(assets도 마찬가지다)

Create development server

이번에는 개발서버를 만드는 작업을 진행해보자
localhost처럼 우리는 개발서버가 필요한데 gulp webserver를 설치해서 쉽게 구축할 수 있다
npm i gulp-webserver로 설치해 준 다음 코드 상단에 import ws from "gulp-webserver;"를 추가해주자

그리고

const pug = () =>
  gulp.src(routes.pug.src).pipe(gPug()).pipe(gulp.dest(routes.pug.dest));

const clean = () => del(["dist/"]);

const webServer = () =>
  gulp.src("dist").pipe(ws({livereload: true, open: true}));

const prepare = gulp.series([clean]);
const assets = gulp.series([pug]);
const postDev = gulp.series([webServer]);

export const dev = gulp.series([prepare, assets, postDev]);

위와 같이 webServer task를 하나 만든 후 원하는 옵션들을 넣고 gulp dev로 실행시켜보면 이제 자동으로 dist에 있는 파일이 켜지고 수정시 자동으로 reload도 될 것이다

라고 생각했지만 되지 않았다🤮 왜냐하면 gulp가 한번 compile한 후 끝나버렸기 때문이다
이를 위해 수정될 때마다 계속 내 파일을 주시하고 있다가 실시간으로 compile을 할 수 있도록 약간의 코드의 수정이 필요하다

일단 routes에서 src에 해당하는 내용은 결국 index.pugcompile하겠다는 의미인데 내가 실시간으로 주시하고 싶은 파일은 모든 pug파일들이다
그래서 routeswatch라는 경로를 따로 설정해주고 이를 사용해서 코딩을 해보도록 하자

import gulp from "gulp";
import gPug from "gulp-pug";
import del from "del";
import ws from "gulp-webserver";

const routes = {
  pug: {
    watch: "src/**/*.pug",
    src: "src/*.pug",
    dest: "dist",
  },
};

const pug = () =>
  gulp.src(routes.pug.src).pipe(gPug()).pipe(gulp.dest(routes.pug.dest));

const clean = () => del(["dist/"]);

const webServer = () =>
  gulp.src("dist").pipe(ws({ livereload: true, open: true }));

const watch = () => {
  gulp.watch(routes.pug.watch, pug);
};
const prepare = gulp.series([clean]);
const assets = gulp.series([pug]);
const postDev = gulp.series([webServer , watch]);

export const dev = gulp.series([prepare, assets, postDev]);

최종적으로 이런 모습이 되는데 watch() method는 여러 파일경로들을 받아오고 그것들을 주시하고 있다가 수정이 되면 task를 실행한다
여기서는 다시한번 compile을 해주길 원하기 때문에 task중에 pug를 실행하면 된다

실행되고 있는 것을 잠시 끊고 다시 gulp dev로 실행해보도록 하자

terminal에서 Ctrl + C로 종료시킬 수 있다

만약 두 개 이상의 task를 동시에 실행시키고 싶다면 series() method를 사용해서는 안된다
그 대신 parallel()을 사용해야 한다
그래서 위 코드 중 postDevconst postDev = gulp.parallel([webServer , watch]);로 수정해주도록 하자

dev같은 경우는 순서대로 실행이 되어야 되기 때문에 parallel() method를 사용하지 않는다

Compile Images

이제 다음으로는 image폴더 안의 사진들을 compile해보도록 하자
지금까지는 아마 pug들을 compileindex.html에서의 이미지가 깨져있었을 것이다
이를 하기 위해 다시 gulp-imagenpm i gulp-image를 통해 설치하도록 하자

설치가 완료되었다면 상단에 import image from "gulp-image";를 하고

const routes = {
  pug: {
    watch: "src/**/*.pug",
    src: "src/*.pug",
    dest: "dist",
  },
  img: {
    src: "src/img/*",
    dest: "dist/img",
  },
};

위와 같이 routesimg를 추가해주도록 하자
그 후

const img = () =>
  gulp.src(routes.img.src).pipe(image()).pipe(gulp.dest(routes.img.dest));

gulp-image와 연결해서 compile을 진행하는 task를 만든 후 전처리 과정에서 진행되도록 다음과 같이 수정해 주도록 하자

const prepare = gulp.series([clean, img]);

전처리 과정에 두는 이유는 그냥 개인적으로 전처리라고 생각하기 때문에
개인의 취향에 맞게 assets에 넣어줘도 무방하다

gulp-image에는 꽤 많은 옵션들이 존재하기 때문에
좀 더 자세히 알고 싶다면 확인해보길 바란다

하지만 지금은 img폴더에 다른 이미지들을 새로 다운받으면 자동으로 compile해주지는 않는다
왜냐하면 watch경로에 img폴더는 지정되어 있지 않기 때문이다

나는 개인프로젝트를 할 때에는 지나치게 용량이 큰 파일을 compile하지 않아

const watch = () => {
  gulp.watch(routes.pug.watch, pug);
  gulp.watch(routes.img.src, img);
};

위와 같이 img도 실시간으로 확인하고 compile해주도록 설정해두겠다

Scss compilation

계속 그래왔듯이 일단 gulp-sass를 설치해보자

npm install node-sass gulp-sass --save-dev

여기서 node-sass가 포함되어 있는 이유는
gulp-sassnode-sasssass파일을 전해주기 때문이다

설치했다면 이제 필수코스인 import를 해보도록 하자

import sass from "gulp-sass";

sass.compiler = require("node-sass");

그리고 새로운 task를 만들어서 이를 통해 compile해보도록 할텐데 그전에 routesscss에 관한 내용을 추가해주도록 하자

const routes = {
  pug: {
    watch: "src/**/*.pug",
    src: "src/*.pug",
    dest: "dist",
  },
  img: {
    src: "src/img/*",
    dest: "dist/img",
  },
  scss: {
    watch: "src/scss/*.scss",
    src: "src/scss/style.scss",
    dest: "dist/css",
  },
};

그 후에

const styles = () =>
  gulp
    .src(routes.scss.src)
    .pipe(sass().on("error", sass.logError))
    .pipe(gulp.dest(routes.scss.dest));

위와 같이 작성해주자
만약 여기서 Error가 생긴다면 scss만의 Error가 출력될 것이다

그리고 무언가가 수정된다면 실시간으로 확인해주기 위해서

const watch = () => {
  gulp.watch(routes.pug.watch, pug);
  gulp.watch(routes.img.src, img);
  gulp.watch(routes.scss.watch, styles);
};

위와 같이 watchstyles에 관한 내용을 추가해주도록 하자
그리고 나는 assets에 넣어주기로 했다

const assets = gulp.series([pug, styles]);

그 후에 잘 동작하는지 gulp dev로 확인해보자! 아마 잘 동작되는 것을 볼 수 있을 것이다 :)

이제 왜 _reset.scss_variables.scss에 underscore(_)를 앞에 붙였는지 살펴보도록 하자
이유는 아직 간단하다 이 prefix(_)가 붙어있는 파일들은 compile하지 말고 사용만 하라고 알려주기 때문이다

그렇다면 만약 _가 없었다면 어떻게 되었을까? 수정해서 확인해 보기는 귀찮으니까 상상을 해보자
아마도 dist/css폴더에 reset.cssvariables.css파일이 생기고 style.css안에

@import "reset.css";
@import "variables.css";

위와 같이 import되어있는 것을 목격하게 될 것이다
(확인하기 귀찮아서 안하지만 궁금한 사람들은 한번 해보길 권한다😁)

그럼이제 우리가 해볼 것은 style.scss를 수정해보는 것이다

@import "_reset";
@import "_variables";

body {
  background-color: $red;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  color: #fff;
  font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
  font-size: 30px;
  height: 100vh;
}

img {
  width: 100px;
}

footer {
  display: none;
}

h1 {
  color: #fff;
  font-size: 40px;
  font-weight: 200;
  margin-top: 10px;
}

나는 대충 위와 같이 수정해보았는데 수정하면서 dist/css/style.css를 보면 실시간으로 수정되고 있는 것을 볼 수 있다
근데 Float에 관한 고찰에서 한번 다뤘듯이 아직 한국에서 Explorer(IE)를 사용하고있는 사람의 비율이 약 11%나 된다(Apr.2020 ~ Apr.2021 기준)

그래서 우리는 cross browsing issue에 대해 항상 대비해야한다
이를 위해 MinifyAutoprefixer에 대해 알아보도록 하자

gulp-autoprefixer

gulp-autoprefixer는 기본적으로 우리가 작업한 코드를 알아듣지 못하는 구형 브라우저도 호환 가능하도록 만들어준다

npm i gulp-auto-prefixer로 설치 후 import autop from "gulp-autoprefixer";import해준다
gulp-autoprefixer여러가지 옵션들이 있는데 이 중 몇가지 옵션을 사용해보도록 하자

const styles = () =>
  gulp
    .src(routes.scss.src)
    .pipe(sass().on("error", sass.logError))
    .pipe(autop()) // auto-prefixer
    .pipe(gulp.dest(routes.scss.dest));

위와 같이 코드를 수정해주고 package.json"browserslist": ["last 2 versions"]를 추가해 주도록 하자

그 후 npm rund dev로 실행해보고 compilestyle.css을 보면 webkit등 다양한 것들이 추가된 것을 볼 수 있다
위에서 추가한 last 2 versions모든 브라우저에서 가장 최근과 그 하위버전에서 cross browsing issue가 발생하지 않도록 해주는 옵션이다

gulp-csso(minify)

우리는 가독성을 위해 cssjs등 띄어쓰기와 줄바꿈 등을 한다
하지만 이 공간 하나하나는 결국 용량이므로(띄어쓰기 = 1byte)이를 최소화하기 위해서는 우리는 잘 작성된 파일의 공백을 없애주는 방식이 필요하다
이를 minify(or optimize)라고 하는데 gulp-csso가 이를 자동으로 해준다

npm i gulp-csso로 설치를 하고 import miniCSS from "gulp-csso";를 추가해주자

const styles = () =>
  gulp
    .src(routes.scss.src)
    .pipe(sass().on("error", sass.logError))
    .pipe(autop())
    .pipe(miniCSS()) // gulp-csso
    .pipe(gulp.dest(routes.scss.dest));

위와 같이 추가해주고 npm run dev로 실행하고 다시 style.css를 보면 한줄로 쭉 이어져있는 것을 볼 수 있다

browserify(javascript run on babel)

browser는 기본적으로 import, export같은 문법들을 이해하지 못한다
그래서 이를 이해할 수 있도록 도와주는 것이 browserify

npm i gulp-bro로 설치한 후 npm i babelify도 설치해 주도록 하자
(babelify를 설치하는 이유는 우리의 코드를 babel에서도 사용하고 있기 때문이다)

그리고

import bro from "gulp-bro";
import babelify from "babelify";

로 추가하여 사용해주도록 하자
(자세한 사용법은 링크를 통해 확인하자)

먼저 routes에 경로를 추가해주고

  js: {
    watch: "src/js/*.js",
    src: "src/js/main.js",
    dest: "dist/js",
  },

새로운 task를 만들어 보자

const js = () =>
  gulp
    .src(routes.js.src)
    .pipe(
      bro({
        transform: [
          babelify.configure({ presets: ["@babel/preset-env"] }),
          ["uglifyify", { global: true }],
        ],
      })
    )
    .pipe(gulp.dest(routes.js.dest));

만약 react preset을 사용하고 싶다면 configurepresets을 바꿔주면 되고,
그러기 위해선 당연히 .babelrc파일도 수정해야 한다

그리고 실시간으로 수정되는 것을 반영하길 원하기 때문에

const watch = () => {
  gulp.watch(routes.pug.watch, pug);
  gulp.watch(routes.img.src, img);
  gulp.watch(routes.scss.watch, styles);
  gulp.watch(routes.js.watch, js);
};

위와 같이 작성해주자

마지막으로 나는 이게 assets과정이라고 생각하기 때문에

const assets = gulp.series([pug, styles, js]);

위와 같이 추가하고 npm run dev로 실행해보자
그리고 F12를 눌러서 console을 보면 랜덤으로 만들어진 숫자 2개가 출력된 것을 볼 수 있다

deploying with gulp-gh-pages

프로젝트가 끝났다면 보통 github에 업로드를 할 것이다
그러니 일단 github과 연결해주도록 하자

git init
git remote add origin <your repository url>

이제 git remote -v로 확인해 보면 정상적으로 연결되 있는 것을 볼 수 있다

.gitignore를 만들어서 node_modules는 업로드되지 않도록 해주자

이제 관련 module을 찾아서 설치하고 사용해보도록 하자
npm i gulp-gh-pages로 설치하고 import ghPages from "gulp-gh-pages";를 추가하자

그리고

const gh = () => gulp.src("dist/**/*").pipe(ghPages());

라는 새로운 task를 만들어 준 후 우리는 하단에

export const build = gulp.series([prepare, assets]);
export const dev = gulp.series([build, postDev]); // 기존에 있던 것을 수정
export const deploy = gulp.series([build, gh]);

위와 같이 새로운 명령어를 추가해주도록 하자

그리고 package.jsonscripts부분에

    "build": "gulp build",
    "deploy": "gulp depoly"

을 추가해 주도록 하자

그리고 npm run dev, npm run build, npm run deploy가 각각 잘 작동하는지 확인해 보면
npm run deploy후에 .publish라는 폴더가 생겨나는데 우리에겐 필요하지 않으므로

const clean = () => del(["dist/", ".publish"]);

위와 같이 .publish를 추가하여 삭제해주도록 하자

그리고

export const deploy = gulp.series([build, gh, clean]);

위와 같이 git push후에 clean이 실행되도록 수정해주면 끝이다

우리는 gulp의 기본 flow를 이해하고 실제로 어떻게 사용되는지에 대해 배워봤다
앞으로 더 추가하고 발전하는 건 여러분들과 내 몫이므로 여기서 마치도록 하겠다😉






reference

가치관 제작소: Gulp란?
Nomadcoders: Free Gulp course
Gulp.js

profile
2020년 10월 15일 퇴사하고 개발자의 길에 도전합니다.

0개의 댓글