지금까지, 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-31: HMR 을 사용하기위한 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
- “Webpack-dev-server”. Webpack.
- “Hot module replacement with webpack”. Webpack.
- “React-hot-loader”gaearon@GitHub
- “Webpack HMR Tutorial”. Modern JS with React