[Node.js] 12.2 편: GULP – 응용하기 (babel, webpack, nodemon, browser-sync)


list

이 강좌는 12.1편 강좌와 이어지는 Gulp 강좌 입니다. GULP에 대한 이해가 부족하신 분들은 전 강좌를 읽고와주세요. 오늘 배워 볼 내용은 백엔드와 프론트엔드에서 ES6 를 사용하는 방법, 클라이언트 코드가 수정 됐을 때, 브라우저를 자동으로 새로고침하고, 서버 코드가 수정 됐을 때, 서버를 자동으로 재시작 하는 방법을 알아보겠습니다.

# 복습하기

GULP는 JavaScript 빌드 자동화 툴 입니다. 저번 강좌에서는 CSS, JS, 및 IMAGE 의 파일 크기를 최소화 시키는 과정과 파일의 변경을 감시하는 watch 에 대해 알아봤었죠? 저번에도 언급하였지만, GULP 에는 무수하게 많은 플러그인들이 있답니다. 오늘은 더 나아가 클라이언트와 서버쪽에서 ES6 를 사용하는 방법과 서버코드가 변경될때마다 서버를 재시작하게 해주는 것에 대하여 알아볼 것입니다.

참! 오해 할 수도 있을까봐 미리 알려드리겠습니다. GULP는 “빌드 자동화 툴” 입니다. 그 말은 즉슨, 위의 작업들은 gulp가 없어도 할 수 있는 것 들입니다. (예를 들어, 저번 강좌에서 사용했던 플러그인인 gulp-uglify, gulp-clean-css, gulp-htmlmin, gulp-imagemin 은, NPM에 따로 의존 모듈인 uglify-js, clean-css, htmlmin, imagemin이 있습니다) 단, 여러가지 작업을 일일히 하기엔 번거롭고, 빌드 코드를 따로 작성해도 되지만, 이를 더 편하게 작성하기 위하여 gulp가 사용되는 것이랍니다.

따라서, 오늘 배울 것 들 또한, gulp 없이도 할 수 있답니다. ‘ gulp 에 대해서 알아보자 ‘ 라는 생각보다, ‘ JavaScript 프로젝트 빌드 도구들에 대해 알아보자 ‘ 라는 생각으로 강좌를 읽어주시길 바랍니다.


# 뭘 하고 싶니?

오늘 gulp 를 통해 무엇을 자동으로 하고 싶은지 적어봅시다.

  • 서버 사이드에서 ES6 사용하기
  • 서버 사이드 코드가 변경 됐을 때 서버 다시 시작하기
  • 클라이언트 사이드에서 ES6 사용하기
  • 클라이언트 사이드 코드 변경 시 브라우저 자동으로 새로고침하기

이에 필요한 플러그인들은?

자 이제 설치 해 볼까요?

참고로 12.1편 강좌에서 사용하던 프로젝트를 계속해서 사용하므로 꼭 전 강좌를 읽고 진행해주세요 🙂


# 설치하기

$ npm install --save-dev gulp-babel gulp-nodemon gulp-webpack browser-sync gulp-file-cache babel-loader

추가적으로, 서버사이드에서 웹서버를 열 때 필요할 express 도 설치해주세요.

$ npm install --save express

준비 끝!

혹시 도중에 이런 오류가 발생하셨나요?

npm WARN optional Skipping failed optional dependency /chokidar/fsevents:
npm WARN notsup Not compatible with your operating system or architecture: fsevents@1.0.12

node_modules 폴더를 삭제하고 다시 npm install 명령어를 통해 설치를 하면 오류가 해결됩니다.


# 적용하기

# 서버사이드에서 ES6 사용하기

먼저, 이 프로젝트에서 사용 할 예제 서버를 작성해보겠습니다.

서버의 디렉토리 구조는 다음과 같습니다.

server
    ├── main.js
    └── routes
           └── articles.js

 # server/routes/articles.js – 라우터 작성

import express from 'express';

const router = express.Router();

router.use((req, res, next) => {
    console.log('Time: ', Date.now().toString());
    next();
});

router.get('/', (req, res) => {
    res.send('articles');
});

router.get('/read/:id', (req, res) => {
    res.send('You are reading article ' + req.params.id);
});

export default router;

ES6 ALERT!

export 키워드는 ES6 에 새로 도입된 문법으로서, ES5 의 module.export 와 exports 의 용도를 가지고 있습니다.
여러가지 사용 방법이 있으니 자세한 내용은 JavaScript 참고자료 [1] 를 읽어주세요.

Express 는 Node.js 에서 사용하는 웹 프레임워크입니다. 이에대한 이해가 부족하신분은 이전 강좌메뉴얼 [2] 을 읽어주세요.

 # server/main.js – 서버 작성

import express from 'express';

const app = express();

app.use('/', express.static(__dirname + '/../dist'));

app.get('/hello', (req, res) => {
    return res.send('Can you hear me?');
});

import articles from './routes/articles';

app.use('/articles', articles);

const server = app.listen(3000, () => {
    console.log('Express listening on port 3000');
});

3000 포트로 열리는 Express 서버를 작성하였습니다.

# gulpfile – ES6 코드 변환

ES6 코드를 일반 Node 환경에서도 호환되도록 ES5 형태로 변환하는 과정을 알아보겠습니다.

여기서 사용 할 플러그인은 gulp-babel 입니다. 먼저 gulpfile 의 상단에 해당 플러그인을 import 해주세요.

import babel from 'gulp-babel';

다음, 서버 파일의 디렉토리와 컴파일된 코드를 저장 할 디렉토리를 상수로 정의하겠습니다.

const SRC = {
    JS: DIR.SRC + '/js/*.js',
    CSS: DIR.SRC + '/css/*.css',
    HTML: DIR.SRC + '/*.html',
    IMAGES: DIR.SRC + '/images/*',
    SERVER: 'server/**/*.js'
};

const DEST = {
    JS: DIR.DEST + '/js',
    CSS: DIR.DEST + '/css',
    HTML: DIR.DEST + '/',
    IMAGES: DIR.DEST + '/images',
    SERVER: 'app'
};

이제 babel task 를 작성하겠습니다. 코드의 위치는 watch task 의 상단에 작성해주세요.

gulp.task('babel', () => {
    return gulp.src(SRC.SERVER)
    .pipe(babel({
        presets: ['es2015']
    }))
    .pipe(gulp.dest(DEST.SERVER));
});

이 코드엔 조~금 문제가 있습니다. 이렇게 하면, 나중에 watch를 할 때, 서버에서 사용하는 js 파일 중 하나만 수정 되도 모든 파일들을 다시 컴파일하게됩니다. 좀 비효율적이죠? 이를 고치려면, gulp-file-cache 플러그인을 통해 변동이 있는 파일만 컴파일 하도록 만들어봅시다.

# gulpfile – gulp-file-cache 불러오기

import Cache from 'gulp-file-cache';

let cache = new Cache();

위 코드를 gulpfile의 상단에 작성한 다음,

babel task를 수정하세요.

gulp.task('babel', () => {
    return gulp.src(SRC.SERVER)
           .pipe(cache.filter())
           .pipe(babel({
              presets: ['es2015']
           }))
           .pipe(cache.cache())
           .pipe(gulp.dest(DEST.SERVER));
});

이 플러그인의 원리는 파일 경로와 수정시각을 캐시에 등록하여 .gulp-cache 파일에 저장 한 후,
수정된 파일, 혹은 캐시에 등록되지 않은 파일만 작업 한 후 그 파일의 정보를 다시 기록하는 방식입니다.

다 작성하였다면, 다음 명령어를 통해 코드를 컴파일하고 서버를 열어보세요.

$ gulp babel

[16:49:41] Requiring external module babel-register
[16:49:42] Using gulpfile ~/node_tutorial/gulp-es6-webpack/gulpfile.babel.js
[16:49:42] Starting 'babel'...
[16:49:42] Finished 'babel' after 295 ms

$ gulp babel

[16:49:45] Requiring external module babel-register
[16:49:46] Using gulpfile ~/node_tutorial/gulp-es6-webpack/gulpfile.babel.js
[16:49:46] Starting 'babel'...
[16:49:46] Finished 'babel' after 53 ms

명령어를 두번 입력하니까, 파일들이 이미 캐시에 등록되어있어서 작업시간이 많이 단축됐죠?

# gulpfile – babel watch 작성하기

    let watcher = {
        js: gulp.watch(SRC.JS, ['js']),
        css: gulp.watch(SRC.CSS, ['css']),
        html: gulp.watch(SRC.HTML, ['html']),
        images: gulp.watch(SRC.IMAGES, ['images']),
        babel: gulp.watch(SRC.SERVER, ['babel'])
    };

watch task 의 watcher 객체만 변경해주면 됩니다. 정말 쉽죠?


# 서버사이드 코드가 변경 되었을 때 자동으로 재시작하기

이 부분은 gulp-nodemon 플러그인이 해결 해 준답니다!

# gulpfile – gulp-nodemon 불러오기 및 start task 작성하기

import nodemon from 'gulp-nodemon'
gulp.task('start', ['babel'], () => {
    return nodemon({
        script: DEST.SERVER + '/main.js',
        watch: DEST.SERVER
    });
});

nodemon이 DEST.SERVER 디렉토리를 감시하고 있다가 변화가 감지되면, main.js 를 재시작합니다.

gulp-nodemon [3] 의 사용예제에서는, babel 과 함께 사용 할 때, babel watch 를 따로 등록하지 않고, nodemon 자체에서 SRC.SERVER 디렉토리를 감시하고있다가 변화가 감지되면 babel task 를 실행 한 다음에 서버를 재시작 하게 합니다. 그러나 서버가 재시작 할 때 기존 서버가 제대로 종료가 되지 않는 버그가 있습니다. 허나 이 버그가 제 환경에서만 그런건지는 불확실합니다. watch 의 갯수를 한개 줄이고싶은 분들은 한번 시도해보시길 바랍니다.

default task 수정하기

gulp.task('default', ['clean', 'js', 'css', 'html',
                      'images', 'watch', 'start'], () => {
    gutil.log('Gulp is running');
});

이제 screen 에서 gulp 를 실행하고, server 파일을 수정해보세요.
screen 을 사용하지 않고, 터미널을 두개 열으셔서 수정하시거나.. 에디터로 수정하셔도 무방합니다.

# 저번 강좌에서 스크린을 안만들었다면 screen -S gulp 를 입력하세요.
$ screen -x gulp
$ gulp
[00:30:03] Requiring external module babel-register
# 생략..
[00:30:05] [nodemon] starting `node app/main.js`
Express listening on port 3000
# CTRL + A + D
# main.js 수정.. 
# screen -x gulp
[17:04:06] File /home/vlpt/node_tutorial/gulp-es6-webpack/server/main.js was changed
[17:04:06] Starting 'babel'...
[17:04:06] [nodemon] restarting due to changes...
[17:04:06] [nodemon] restarting due to changes...
[17:04:06] [nodemon] starting `node app/main.js`
[17:04:07] Finished 'babel' after 824 ms
Express listening on port 3000

# 클라이언트 사이드에서 ES6 및 import 기능 사용하기

클라이언트 사이드에서 단순히 ES6 문법을 사용하려면 위에서 했던 것 처럼 babel 을 사용하면 됩니다.
단, 이걸 한다고 해서 import 기능 까지 호환 되지는 않죠.

클라이언트 사이드에서도 import 기능을 사용 하려면 필요한것은 바로 Module Bundler 입니다. Module Bundler 는 브라우저단에서도 CommonJS 스타일을 사용 할 수 있게 해주는 도구입니다. 이는 대표적으로 Browserify와 Webpack이 있는데요, 이 포스트에서는 여러 로더를 지원하고 자체적으로 최적화가 이미 되어있는 webpack 을 사용하도록 하겠습니다.

Module Bundler 에 대한 자세한 설명 및 Browserify와 Webpack의 차이에 대한 설명은 coderifleman 님의 “Browserify와 Webpack” 포스트를 참고해주세요.

# webpack.config.js – Webpack config 파일 작성하기

webpack은 자체로 할수있는게 정말 많아서,  config 파일을 따로 작성해야합니다.

var webpack = require('webpack');

module.exports = {
    entry: './src/js/main.js',

    output: {
        path: __dirname + '/dist/js/',
        filename: 'bundle.js'
    },

    module: {
        loaders: [
            {
                test: /\.js$/,
                loader: 'babel-loader',
                exclude: /node_modules/,
                query: {
                    cacheDirectory: true,
                    presets: ['es2015']
                }
            }
        ]
    },

    plugins: [
        new webpack.optimize.UglifyJsPlugin({
            compress: {
                warnings: false
            }
        })
    ]
};

config 파일을 작성하는건 webpack 메뉴얼 을 참고하세요.

간단히 위 파일을 설명하자면…

  • entry: ./src/js/main.js 파일을 가장 처음으로 읽습니다.
    그리고 그 파일에서부터 import 된 파일들을 계속해서 읽어가면서 연결시켜줍니다.
  • output: 읽은 파일을 모두 합쳐서 /dist/js/bundle.js 에 저장합니다.
  • module: 읽은 파일들을 babel-loader 를 통하여 ES6 스크립트를 컴파일해줍니다.
  • plugins: UglifyJsPlugin 을 사용하여 컴파일한 스크립트를 minify 합니다.

네. 보시다시피 webpack에서 uglifyjs를 사용하므로,, 더 이상 gulp-uglify필요 없어졌습니다. 
npm uninstall gulp-uglify --save-dev 를 통하여 모듈을 삭제하시고,
gulpfile 에서도 관련 부분을 지워주세요. (import, js task, watch, default)

# gulpfile – gulp-webpack 불러오기 및 webpack task 작성하기

import webpack from 'gulp-webpack';
import webpackConfig from './webpack.config.js';
gulp.task('webpack', () => {
    return gulp.src('src/js/main.js')
           .pipe(webpack(webpackConfig))
           .pipe(gulp.dest('dist/js'));
});

# gulpfile – watch 수정하기

    let watcher = {
        webpack: gulp.watch(SRC.JS, ['webpack']),
        css: gulp.watch(SRC.CSS, ['css']),
        html: gulp.watch(SRC.HTML, ['html']),
        images: gulp.watch(SRC.IMAGES, ['images']),
        babel: gulp.watch(SRC.SERVER, ['babel'])
    };

# gulpfile – default 수정하기

gulp.task('default', ['clean', 'webpack', 'css', 'html',
                      'images', 'watch', 'start'], () => {
    gutil.log('Gulp is running');
});

여기까지 잘 따라오셨나요? gulp-webpack이 잘 되는지 확인하려면 우선.. 저희 js 코드에서도 ES6 및 import 를 사용하는 예제를 만들어봐야겠죠?
예제파일은 자유롭게 작성해보세요.

# src/js/Sample.js – 예제 모듈

class Sample {
    constructor(name) {
        this.name = name;
    }

    say() {
        console.log("HI, I AM ", this.name);
    }
}

export default Sample;

# src/js/main.js – 예제 스크립트

import Sample from './Sample';

let sample = new Sample("velopert");
sample.say();

# src/index.html – 스크립트 불러오기

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hi</title>
</head>

<body>
Hello Hello World!
<script src = './js/bundle.js'></script>
</body>

</html>

# 테스팅

gulp 명령어를 입력하여 빌드 후 서버를 가동 후 페이지에 들어가보세요.

이미지 28

브라우저단에서도 잘 작동하는군요 !


# 클라이언트 사이드 코드 변경 시 브라우저 자동으로 새로고침하기

자, 드디어 마지막 단계입니다 🙂

# gulpfile – browser-sync 불러오기 및 browser-sync task 작성하기

import browserSync from 'browser-sync';
gulp.task('browser-sync', () => {
    browserSync.init(null, {
        proxy: "http://localhost:3000",
        files: ["dist/**/*.*"],
        port: 7000
    })
});

proxy 부분은 express 서버에서 사용하는 포트를 넣어주세요.
이 설정을 넣어주면 static 파일들 외에도 내부 API에 접근 할 수 있게됩니다.

port 부분은 browser-sync 서버의 포트입니다.

# gulpfile – default 수정하기

gulp.task('default', ['clean', 'webpack', 'css', 'html',
                      'images', 'watch', 'start', 'browser-sync'], () =>                                                                                                                                                             {
    gutil.log('Gulp is running');
});

# 테스팅

liveserver

이렇게 따로 새로고침을 하지 않아도 파일이 저장될 때 자동으로 새로고침이 됩니다.
CSS 파일을 변경 할 떈, 전체 페이지를 새로고침하지 않고 CSS 만 갈아끼워주는 멋진 모듈이랍니다 !


# 마치면서..

수고하셨습니다! 이 튜토리얼을 끝까지 읽어주셨다면,  gulp 를 자율적으로 사용 할 수 있으실겁니다.
필요한 플러그인들을 찾아서 적용하세요 ! webpack으로도 할 수 있는게 많으니 검색해보세요.

이 프로젝트에서 사용된 코드는 GitHub에서 참고하실 수 있습니다.

요즘 React.js 에 빠져있어서 다음 Node.js 강좌는 언제 올라올지 모르겠네요 :]

Reference

  1. “export – JavaScript”. Mozilla Developer Network.
  2. “Express 라우팅”. Express
  3. “Using gulp-nodemon with React, Browserify, Babel, ES2015, etc.”. npmjs.
  4. “Browserify 와 Webpack”. Coderifleman.
  5. “Configuration”. Webpack.

list

  • Chanjin Park

    gulp랑 webpack 처음 접해보는데 정말 많은 도움이 되고 있습니다^^ 이 포스팅으로 es6까지 입문하고 있습니다.
    감사합니다!

    한가지 궁금한 점이 있어서 질문 드립니다.

    gulp-webpack task 설정에서

    gulp.task(‘webpack’, () => {
    return webpack(webpackConfig);
    }

    와 같이 쓰니까 webpack이 실행만 되고 파일 output이 안돼서 다음과 파이프로 실행하니 잘 작동하더라구요

    gulp.task(‘webpack’, () => {
    return gulp.src(DIR.SRC)
    .pipe(webpack(webpackConfig))
    .pipe(gulp.dest(DEST.JS));
    });

    webpack 가이드를 보니 src에 entry 파일을 지정하도록 되어 있던데 사실상 gulp.src(DIR.SRC) 부분이 무시되는 것 같아서 좀 찝찝합니다. 어떻게 사용하는게 best parctice 일까요?

    • 요새 바빠서 답변이 늦었네요 ㅠ 죄송합니다

      우선.. 그렇게하는게 맞아요, 깃헙에 커밋한 코드에는
      gulp.task(‘webpack’, () => {
      return gulp.src(‘src/js/main.js’)
      .pipe(webpack(webpackConfig))
      .pipe(gulp.dest(‘dist/js’));
      });

      이렇게 했는데 강좌엔 제가 다르게 작성했네요.. 왜 이렇게 적었는지는 기억이 잘..
      npm 에 있는 usage (https://www.npmjs.com/package/gulp-webpack) 에서도 이렇게 사용해요.

      제가 요즘은 gulp 를 잘 사용안해서 오늘 좀 알아봤는데,
      best practice는 아무래도 gulp-webpack 이 아닌 webpack-stream 을 사용하는게 좋을 것 같아요
      https://webpack.github.io/docs/usage-with-gulp.html
      https://www.npmjs.com/package/webpack-stream
      사용법은 비슷해요. 요즘은 사람들이 이걸 더 많이 사용하는 것 같더라구요.

      그럼 즐거운 코딩 되세요~

      • Chanjin Park

        친절한 답변 감사합니다^^

  • 엉엉

    답답한마음에 여쭈어봅니다.
    ES5로 제작한 Express 서버는 Google App Engine에 잘 배포가 되었는데
    말씀해주신 예제를 기반으로 ES6를 사용하여 잘 작성하고 배포하려하는데 에러가 나면서 배포가 되지않네요

    로그를 보면 node ./src/js/main.js 를 계속 시도하여 import 문을 이해못하고 에러가 나는것 같습니다.

    배포전 gulp bable을 한번 한뒤
    package.json의 scripts부분에는
    “start”:”node ./app/main.js”
    이렇게 배포를 하는데 틀린건지 궁금합니다.

    • 혹시 package.json이 제대로 커밋된게 아니지 않을까요? start에선 app에있는걸 실행하게 했는데 src를 실행한다는건 조금 문제가 있는것같은데.. 구글앱엔진을 사용한적이 없어서 정확한 답변은 못 드릴 것 같습니다.

      계속 해보시고 안된다면, gulp를 사용하지 말구 babel-cli를 사용하여 컴파일한다음에 커밋, 푸쉬 해보세요.

      • 엉엉

        후 ㅠㅠ 잘모르겠어서 일단은 ES6 전부겉어내고
        첨부터 좀 봐야겠네요 답변감사합니다.

  • CrabBaker John

    하루종일 1부터 여기까지 공부하였는데 정말 많은 도움이 되었습니다.

  • Hyo Sun Hong

    gulp + webpack + express 사용하고 싶었는데 정말 감사합니다! 수정하여 사용하려 했더니 어렵더라고요.
    리엑트 덕분에 velopert님 블로그 알고 있었는데 이렇게 또 도움을 받네요 ^^* 감사합니다!!