[React.JS] 강좌 11편 Express.js 서버 + 개발 서버 Hot Module Replacement 사용하기


liste

지금까지, React.js 에 대한 기본적인 공부는 어느정도 마쳤습니다. 이제, REST API 를 만들어서 서버와의 통신에 손을 뻗을 차례입니다! 지금까지는 webpack-dev-server 에만 의존해왔습니다. 이걸로는 서버 작업을 전혀 할 수가 없었죠. 이번 강좌에서는 React.js 를 Node.js 환경의 인기있는 웹프레임워크 중 하나인 Express.js 서버와 함께 사용하는 방법에 대해서 알아보겠습니다.

이 강좌에서는 서버사이드 코드 또한 ES6 으로 작성 할 것이며, 서버가 개발모드 일 때는, Express.js 서버와 webpack-dev-server 를 함께 실행하며, webpack-dev-server 에 proxy를 적용하여 해당 서버에서도 Express.js 서버에 구현된 라우트에 접근 하는 방법을 알아볼것입니다.

지난강좌들에선 기존의 webpack-dev-server 에서 특별한 설정을 하지 않아서 React.js 컴포넌트 코드가 수정 될때 모든 스크립트 자체가 새로고침 됐었는데요, 이번강좌에서는 더 나아가 react-hot-loader 를 적용하여 바뀐 컴포넌트만 리로딩하는 방법을 배우겠습니다.

주의: 이 강좌에서는 Express.js 에 대한 자세한 설명은 생략할 것입니다. 이에 대한 지식이 부족하신분들은 Express.js 메뉴얼 혹은 제 블로그에 작성된 Node.js 강좌 를 읽어주세요.

# 시작하기

# 로컬모듈 설치

이번 강좌에서는 비어있는 폴더에서부터 프로젝트를 작성하겠습니다.

# Node.js 프로젝트 생성하기
$ npm init

# 의존모듈 설치하기
$ npm install --save express react react-dom

# 개발 의존 모듈 설치하기
$ npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react react-hot-loader webpack webpack-dev-server

기존 React.js 프로젝트에서 사용하지 않던 모듈 두가지:

  • express: 웹 프레임워크 모듈
  • react-hot-loader특정 컴포넌트파일만 리로딩 할 수 있게 해주는 모듈

# 글로벌 모듈 설치

$ npm install -g babel-cli

babel-cli 모듈은 ES6 문법으로 작성된 코드를 ES5 문법으로 컴파일시켜주는 babel 모듈을 커맨드라인 인터페이스에서 사용 할 수 있게 해줍니다. 이는 나중에 서버사이드 코드를 빌드 할 때 사용됩니다.

이 강좌에서는 package.json 에 빌드스크립트를 작성하고 npm run <script> 기능을 통하여 처리하는 방식을 사용하겠습니다.

babel-cli 를 사용하지 않더라도, Node 버전을 최신버전으로 업그레이드하여 컴파일 과정을 생략해도 되고 (단, 아직 노드 최신버전은 일부모듈들에는 호환되지 않으며, 93% 의 ES6 문법만 호환됩니다. 또는, babel-node 모듈을 사용하여 ES6 코드를 직접 실행하셔도 됩니다. (단, babel-node 는 production 모드에서는 권장되지 않습니다) 그 외에도, gulp 모듈을 설치하여 gulp-babel 을 통하여 컴파일하는 방법도있습니다. gulp 를 아직 잘 모르시고, 사용법을 배우고싶은 분들은 GULP 강좌 를 읽어주세요.


# 설정하기

이 프로젝트의 babel-cli 과 webpack 은 설정파일을 필요로합니다.

# .babelrc

{
    "presets": ["es2015"]
}

# webpack.config.js

module.exports = {
    // 가장 처음 읽을 스크립트파일
    // 여기서부터 import 되어있는 다른 스크립트를 불러온다.
    entry: './src/index.js',

    // 파일을 합치고 ./public/bundle.js 에 저장한다.
    output: {
        path: __dirname + '/public',
        filename: 'bundle.js'
    },

    // ES6 문법과 JSX 문법을 사용한다
    module: {
        loaders: [
            {
                test: /\.js$/,
                loader: 'babel',
                exclude: /node_modules/,
                query: {
                    cacheDirectory: true,
                    presets: ['es2015', 'react']
                }
            }
        ]
    }
};

# 디렉토리 이해하기

./
├── .babelrc                # babel 설정파일
├── build                   # 서버 빌드 디렉토리
├── package.json		
├── public                  # 클라이언트 디렉토리
│    ├── bundle.js          # 컴파일된 스크립트
│    └── index.html         # 메인 페이지
├── server                  # 서버 디렉토리 (ES6)
│    ├── main.js            # 서버 사이드 메인 스크립트
│    └── routes
│        └── posts.js       # 예제 라우터
├── src
│    ├── App.js             # App 컴포넌트
│    └── index.js           # 클라이언트 사이드 메인 스크립트
├── webpack.config.js       # webpack 설정파일
└── webpack.dev.config.js   # webpack-dev-server 를 위한 설정파일

파일을 먼저 만들고 진행하는것을 선호한다면 다음 명령어를 통해 미리 만드세요.

$ mkdir build server public src server/routes && touch public/index.html server/main.js server/routes/posts.js src/App.js src/index.js webpack.dev.config.js

물론, 강좌를 진행하면서 하나 하나 새로 만들어가도 무방합니다.


# 서버 사이드 코드 작성하기

# server/main.js

import express from 'express';

const app = express();

let port = 3000;


// 경로 '/' 로 들어오는 요청들은 public 폴더로 정적 라우팅합니다.
app.use('/', express.static(__dirname + '/../public'));

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

// 라우트 예제입니다.
import posts from './routes/posts';
app.use('/posts', posts);


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

# server/routes/posts.js

import express from 'express';

const router = express.Router();

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

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

export default router;

# 서버 사이드 코드 컴파일 하기

$ babel server --out-dir build

프로젝트 루트 디렉토리에서 이 코드를 통해 컴파일 해보세요. 잘 됐나요?

# 서버 테스트 해보기

$ node build/main.js
Express listening on port 3000

서버를 테스팅해봅시다. 현 상태로는 아직 public 폴더를 작성하지 않았으니 빈페이지가 뜨는것이 정상입니다.

한번 http://localhost/hello 페이지를 열어보세요.


# 클라이언트 사이드 코드 작성하기

# public/index.html

<!DOCTYPE html>

<html>
    <head>
        <meta charset="utf-8">
        <title>React App on Express Server</title>
    </head>

    <body>
        <div id="root"></div>
        <script src="bundle.js"></script>
    </body>
</html>

# src/App.js

import React from 'react';

export default class App extends React.Component {
    render() {
        return (
            <h1>This is HOT!</h1>
        )
    }
}

# src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

let rootElement = document.getElementById('root');

ReactDOM.render(<App/>, rootElement);

# webpack 을 통하여 코드를 컴파일하고 합치기

# webpack 을 이미 글로벌설치를 한 상태라면 디렉토리를 생략하고 webpack 만 입력해도 됩니다.
$ ./node_modules/.bin/webpack

컴파일이 성공적으로 완료됐다면, 한번 다시 서버를 열어서 http://localhost/ 페이지를 열어보세요.

제대로 된 페이지가 뜨나요?


# NPM 스크립트 작성하기

서버 및 클라이언트 사이드 코드 컴파일을 명령어를 그때그때 일일히 입력하기엔 귀찮죠?

간편하게 실행 할 수 있게끔 package.json 파일에서 스크립트를 작성해봅시다.

/* ... */
  "scripts": {
    "clean": "rm -rf build public/bundle.js",
    "build": "babel server --out-dir build && ./node_modules/.bin/webpack",
    "start": "NODE_ENV=production node ./build/main.js",
    "development": "NODE_ENV=development node ./build/main.js"
  },
/* ... */

스크립트를 실행 할 때는 npm run <script-name> 으로 실행합니다.

start 스크립트에선 NODE_ENV 를 production 으로 설정하고 development 스크립트에선 development 로 설정합니다.

Node.js 을 잘 아시는 분에게는 익숙하겠지만, 이 부분은 Node Application 에서 환경 상태를 확인하여,
환경에 따라 다른 작업을 설정하고 싶을 때 사용합니다.

저희는 development 모드일때는 webpack-dev-server 도 함께 실행하도록 설정 할 것입니다.

Window 에서는 NODE_ENV 를 설정하는 방법이 다릅니다:

"start": "set NODE_ENV=production&&node ./build/main.js",
"development": "set NODE_ENV=development&&node ./build/main.js"

# 서버사이드 코드 수정하기 – 개발모드 만들기

# 개발모드 전용 webpack 설정파일 작성하기

저희는 webpack-dev-server 에선 다른 config 을 사용 할 것이므로, 새로운 config 파일인 webpack.dev.config.js 를 만들어주세요.

편의를 위하여 기존 파일을 복사하고 필요한 부분만 수정하겠습니다.

$ cp webpack.config.js webpack.dev.config.js

왜 다른 config 를 사용하나요?

기존 config 는 output인 bundle.js 를 public 디렉토리에 저장하도록 설정이 되어있습니다.
webpack-dev-server 에서도 동일한 설정을 적용한다면, public 에 있는 파일이 계속 덮어씌워지겠죠? 저희 webpack-dev-server 에선 bundle.js 를 메모리에 저장한후, 나중에 브라우저에서  bundle.js 를 요청 할 시 public 디렉토리에 이미 있는 bundle.js 보다 우선권을 가져서 메모리에 있는걸 리턴하게됩니다.

또한, 추후 react-hot-loader 를 통해 변경된 컴포넌트만 리로드 하는 시스템을 구현할 건데요, production 모드에선 이게 필요하지 않으므로 다른 config 를 설정합니다.

# webpack.dev.config.js

 var webpack = require('webpack');

module.exports = {

    entry: [
        './src/index.js',
        'webpack-dev-server/client?http://0.0.0.0:3001',
        'webpack/hot/only-dev-server'
    ],

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

    devServer: {
        hot: true,
        filename: 'bundle.js',
        publicPath: '/',
        historyApiFallback: true,
        contentBase: './public',
        proxy: {
            "**": "http://localhost:3000"
        }
    },

    plugins: [
        new webpack.optimize.OccurenceOrderPlugin(),
        new webpack.HotModuleReplacementPlugin(),
        new webpack.NoErrorsPlugin()
    ],

    module: {
        loaders: [
            {
                test: /\.js$/,
                loaders: ['react-hot', 'babel?' + JSON.stringify({
                    cacheDirectory: true,
                    presets: ['es2015', 'react']
                })],
                exclude: /node_modules/,
            }
        ]
    }
};
  • LINE 1: webpack 플러그인을 사용하기위하여 해당 모듈을 import 합니다.
  • LINE 5-9: webpack-dev-server 의 hot-module-replacement 를 지원하기위해 entry에 추가해줍니다. webpack-dev-server 의 포트를 7번 줄의 뒷부분에 적어줘야 HMR이 제대로 작동합니다.
  • LINE 12: 메모리에 저장하기 위하여 path를 ‘/’ 로 설정합니다.
  • LINE 16-25: webpack-dev-server 를 위한 설정입니다. proxy 부분은 Express.js 서버 URI를 넣어주어야합니다.
  • LINE 28-31HMR 을 사용하기위한 webpack 플러그인들입니다.
  • LINE 34-43: 바뀐부분은 ‘react-hot’ 로더를 추가한거밖에 없습니다. 단, 여러 모듈을 한꺼번에 적용하기 때문에 babel 을 위하여 따로 query 를 하진 못하고 ? 뒤에 JSON.stringify(query) 를 추가하여 query를 추가합니다.

 

주의: 최근 react-hot-loader 가 업데이트 되어서, 그냥 설치하시면 “react-hot-loader”: “^3.0.0-beta.3” 가 설치됩니다.

설치 하실 때, npm install –save react-hot-loader@1.3.0 을 하시거나, 버전 3을 쓰고 싶다면 수정을 다음과 같이 하세요:

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

# 서버 메인파일 수정하기

자, 이제 저희는 NODE_ENV 값이 development 이면 webpack-dev-server를 실행하도록 서버 메인파일을 수정해보겠습니다.

import express from 'express';
import WebpackDevServer from 'webpack-dev-server';
import webpack from 'webpack';

const app = express();
const port = 3000;
const devPort = 3001;


if(process.env.NODE_ENV == 'development') {
    console.log('Server is running on development mode');

    const config = require('../webpack.dev.config');
    let compiler = webpack(config);
    let devServer = new WebpackDevServer(compiler, config.devServer);
    devServer.listen(devPort, () => {
        console.log('webpack-dev-server is listening on port', devPort);
    });
}
app.use('/', express.static(__dirname + '/../public'));

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


import posts from './routes/posts';
app.use('/posts', posts);

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

# 테스트하기

$ npm run build
$ npm run development

다음, 브라우저로 페이지를 열은 후, src/ 디렉토리 내의 App.js를 수정해보세요.

어때요? 페이지가 새로고침 되지 않고도 내용이 변경되었나요? (http://localhost:3001/ 로 들어가세요)

webpack-dev-server 의 주소에서 Express.js 에서 구현한 라우터에 접근해보세요: http://localhost:3001/posts/read/5

잘 됐나요?


마치면서..

수고하셨습니다. 이번 강좌는 Node.js 강좌에 더 가까웠던 것 같군요.

이 포스트에서 사용된 코드는 GitHub 에서 열람가능합니다.

다음 강좌에서는 오늘 만들었던 프로젝트 환경에서 react-router 를 제대로 사용하는 방법과,
Ajax 도구를 통하여 Express.js 서버에서 구현한 REST API 를 사용하여 데이터를 서버와 교류하는 방법에 대해서 알아보겠습니다.

질문이 있거나 포스트에 오탈자가 있으면 언제나 덧글 달아주세요 🙂

 

Reference

  1. “Webpack-dev-server”. Webpack.
  2. “Hot module replacement with webpack”. Webpack.
  3. “React-hot-loader”gaearon@GitHub
  4. “Webpack HMR Tutorial”Modern JS with React

liste