Redux 를 개발한 Dan Abramov, React 가 릴리즈 된 이후에 React.js 생태계에 가장 큰 영향을 끼친 인물이 아닐까요?
2016년 7월 22일, 그가 React 블로그를 통하여 멋진 소식을 전했는데요.
React 작업환경을 명령어 하나로 설정 할 수 있는 “공식 도구” 를 릴리즈하였습니다! 더 이상 webpack / babel 등을 설정하느라 시간을 투자하지 않아도 되고, unofficial hot boilerplate 를 사용 할 필요도 없어요.
그는 며칠전 트위터에 다음과 같은 글을 남겼었는데요:
Ember CLI is really cool. Autodiscovery of dependencies and no config: React community could learn from this.
— Dan Abramov (@dan_abramov) 2016년 7월 12일
그는 Ember 웹프레임워크 컨퍼런스인 EmberCamp 에 갔었는데, Ember 웹프레임워크 작업환경 설정을 쉽게 해주는 Ember CLI 을 보고는 감명을 받았다고 합니다. 그리곤, React.js 커뮤니티도 이런걸보고 배워야해! 라고 언급을 했었는데요.
그 말을 한지 10일만에 위 같은 멋진 도구를 릴리즈하였답니다.
그럼, 한번 사용을 해봐야겠죠?
이 포스트에서는 create-react-app 을 사용해보고, 추가적으로, Express 도 함께 사용하는 방법을 알아보도록 하겠습니다.
기본적으로는, react-hot-loader가 적용되어있지 않아서 이를 적용하는 방법 또한 알아보겠습니다.
시작하기
설치하기
npm install -g create-react-app
글로벌 패키지를 설치해주세요. Node 버전은 4.x 이상이여야 합니다.
App 생성하기
create-react-app hello-world
위 명령어를 실행하면, create-react-app 도구를 통하여 react 프로젝트를 initialize 하구요,
그 다음에 자동으로 스크립트를 통하여 dependency package 들을 설치합니다 (webpack babel eslint 등..)
(dependency package 를 루트 프로젝트에 설치하는게 아니라 node_modules/react-scripts 디렉토리에 설치합니다)
DigitalOcean 싱가폴 서버 기준으로 이 작업은 약 3분이 걸립니다.
작업이 끝나면 다음과 같이 실행 할 수 있는 커맨드가 출력됩니다.
package.json 을 살펴볼까요?
{ "name": "hello-world", "version": "0.0.1", "private": true, "devDependencies": { "react-scripts": "0.1.0" }, "dependencies": { "react": "^15.2.1", "react-dom": "^15.2.1" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "eject": "react-scripts eject" } }
babel / webpack 등의 dependency package 들은 node_modules/react-scripts 에 설치되어있기에 여기서 보이지 않습니다.
서버 실행하기
npm start
명령어를 통하여 webpack-dev-server 를 시작 할 수 있습니다.
hot-reload 가 적용되어있기 때문에, 이 개발 서버를 키고, 코드를 수정하면 자동으로 적용이 됩니다.
주의: 개발서버를 열 때 0.0.0.0 이 아닌 localhost 로 열기 때문에 외부에서 접근이 불가능합니다. 이 포스트의 하단에서 커스터마이징을 할거니까 걱정하지마세요 🙂
ESLint 를 통하여 경고가 있는 경우 이를 출력해줍니다
Production 을 위한 빌드
npm run bulid
명령어를 통하여 production 을 위한 빌드를 할 수 있습니다. 코드가 minify 되고, envify 되고, 또 assets에 content hashes 를 통하여 캐싱이 적용됩니다.
설정이 없음!
hello-world/ README.md index.html favicon.ico node_modules/ package.json src/ App.css App.js index.css index.js logo.svg
구조가 정말 깔끔하죠? 설정 관련 파일이 하나도 없어요.
빌드 설정은 preconfigured 되어있고 변경 할 수 없습니다 (아 물론 node_modules/react-scripts/ 경로에 들어가시면 설정 할 수 있습니다만.. 그렇게 하면 안돼요)
이 “도구“에선 커스터마이징이 불가능합니다. 엥? 그럼 어쩌라구요!
어이가 없죠? 당황하지 마세요. 도구 글씨체가 bold 로 되어있는점 유의하세요. 이 도구에서만 커스터마이징을 불가능합니다.
그럼.. 이 도구를 제거하면 되지 않을까요? 아, 그렇다고 무작정 npm remove 를 통하여 삭제하란건 아닙니다.
다음 기능을 주목해주세요!
도구 Eject
이 도구의 개발자는 그렇게 사용자들을 ‘그 환경에 가둬놓으면’ 안되는걸 알기 때문에 이를 대비하여 eject라는 기능을 만들어줬습니다.
이 기능의 역할은 현재 프로젝트의 모든 설정 / 스크립트를 여러분의 프로젝트로 옮겨줍니다. eject를 하고나서는, 여러분 마음대로 커스터마이징이 가능합니다.
npm run eject
명령어를 통하여 이 기능을 사용해보세요!
이 명령어가 사용되면, 스크립트를 통하여 react-scripts 를 제거합니다.
그리고, react-scripts 에서 사용하던 스크립트들을 그대로 여러분들의 프로젝트에 생성합니다.
작업이 끝나면 디렉터리 구조가 다음과 같이 변경됩니다:
hello-world ├── build │ ├── 84287d09b8053c6fa598893b8910786a.svg │ ├── favicon.ico │ ├── index.html │ ├── main.7cbecfc47e1de8546c1a31e27e545145.css │ ├── main.7cbecfc47e1de8546c1a31e27e545145.css.map │ ├── main.d1cfd322df65a833bf6c.js │ └── main.d1cfd322df65a833bf6c.js.map ├── config │ ├── babel.dev.js │ ├── babel.prod.js │ ├── eslint.js │ ├── flow │ │ ├── css.js.flow │ │ └── file.js.flow │ ├── webpack.config.dev.js │ └── webpack.config.prod.js ├── favicon.ico ├── index.html ├── package.json ├── README.md ├── scripts │ ├── build.js │ ├── openChrome.applescript │ └── start.js └── src ├── App.css ├── App.js ├── index.css ├── index.js └── logo.svg
package.json 은 어떻게 변했는지 확인해볼까요?
{ "name": "hello-world", "version": "0.0.1", "private": true, "devDependencies": { "autoprefixer": "6.3.7", "babel-core": "6.10.4", "babel-eslint": "6.1.2", "babel-loader": "6.2.4", "babel-plugin-syntax-trailing-function-commas": "6.8.0", "babel-plugin-transform-class-properties": "6.10.2", "babel-plugin-transform-object-rest-spread": "6.8.0", "babel-plugin-transform-react-constant-elements": "6.9.1", "babel-preset-es2015": "6.9.0", "babel-preset-es2016": "6.11.3", "babel-preset-react": "6.11.1", "chalk": "1.1.3", "cross-spawn": "4.0.0", "css-loader": "0.23.1", "eslint": "3.1.1", "eslint-loader": "1.4.1", "eslint-plugin-import": "1.10.3", "eslint-plugin-react": "5.2.2", "extract-text-webpack-plugin": "1.0.1", "file-loader": "0.9.0", "fs-extra": "^0.30.0", "html-webpack-plugin": "2.22.0", "json-loader": "0.5.4", "opn": "4.0.2", "postcss-loader": "0.9.1", "rimraf": "2.5.3", "style-loader": "0.13.1", "url-loader": "0.5.7", "webpack": "1.13.1", "webpack-dev-server": "1.14.1" }, "dependencies": { "react": "^15.2.1", "react-dom": "^15.2.1" }, "scripts": { "start": "node ./scripts/start.js", "build": "node ./scripts/build.js" } }
패키지들이 이 프로젝트에 설치되었네요. 그리고 실행/빌드 스크립트는 똑같이 사용 할 수 있답니다.
꽤 멋지지 않나요? 다음 섹션에선 현재 프로젝트에서 Express 작업환경을 설정하는 방법을 알아보도록 하겠습니다.
Express 를 사용하지 않는 분들은 스킵하셔도 됩니다.
포스트 최하단에 webpack-dev-server 의 host 를 0.0.0.0 으로 바꾸고 개발서버의 포트도 변경하는 방법이 적혀있습니다.
추가적으로 현재 기본설정으로는 react-hot-loader 가 적용되어있지 않습니다. 포스트의 하단에 react-hot-loader 를 적용하는 과정도 설명되어있으니 참고하세요.
Express 작업환경 설정하기
React 프로젝트에서 Express 작업환경을 설정하는건 강좌 11편 에서 했었는데요, 시작하기전에 한번 참고해보시길 바랍니다.
우리는, create-react-app 을 통하여 만든 프로젝트에 Express 작업환경을 추가적으로 설정해보겠습니다.
강좌 11편에서는 서버와 클라이언트를 한 프로젝트에서 같이 사용했었는데요, 이번엔 프로젝트 안에 또 다른 프로젝트를 만드는 방식으로 진행해보겠습니다.
글로벌 패키지 설치
npm install -g babel-cli
ES6 문법으로 작성된 코드를 transpile 하고,
개발환경에선 babel-node 를 통하여 바로 코드를 실행하기 위하여 babel-cli 를 설치하세요.
로컬 패키지 설치
npm install --save express body-parser path npm install --save-dev babel-core babel-preset-es2015 nodemon
path는 의존경로를 절대경로로 변환해주는 모듈입니다.
nodemon은 코드에 변화가 있을 때 서버를 재시작 해주는 모듈입니다.
디렉토리 구조 이해
server ├── build ├── package.json ├── scripts │ └── development.js └── src ├── main.js └── routes ├── index.js └── post.js
src 폴더에 ES6로 작성된 코드가 저장되고, 나중에 이를 transpile 하여 build 폴더에 저장합니다.
서버 메인 파일 – src/main.js 작성
import express from 'express'; import bodyParser from 'body-parser'; const app = express(); let port = 8080; // SETUP MIDDLEWARE app.use(bodyParser.json()); // SERVE STATIC FILES - REACT PROJECT app.use('/', express.static(__dirname + '/../../build')); // LOAD API FROM ROUTES // TO BE IMPLEMENTED app.listen(port, () => { console.log('Express is listening on port', port); });
parent 디렉토리에 있는 React build 파일들을 정적파일로서 제공합니다.
예제 라우트 – src/routes/post.js 작성
import express from 'express'; const router = express.Router(); router.post('/', (req, res) => { console.log(req.body.contents); return res.json({ success: true }); }); router.get('/:id', (req, res) => { console.log('reading post ', req.params.id); return res.json({ index: req.params.id }); }); export default router;
인덱스 라우트 – src/routes/index.js 작성
import express from 'express'; import post from './post'; const router = express.Router(); router.use('/post', post); export default router;
서버 메인 파일 – src/main.js 수정
// LOAD API FROM ROUTES import api from './routes'; app.use('/api', api);
여기까지 코드 작성이 완료되었다면,
babel-node main.js --presets=es2015
명령어를 입력하여 서버를 테스팅해보세요.
자, 이제 스크립트를 작성해보겠습니다.
개발환경 전용 스크립트 작성 – server/src/scripts/development.js
process.env.NODE_ENV = 'development'; var nodemon = require('nodemon'); nodemon('--exec babel-node --presets=es2015 ./src/main.js --watch ./src'); nodemon.on('start', function() { console.log('[nodemon] App has started'); }).on('quit', function() { console.log('[nodemon] App has quit'); }).on('restart', function(files) { console.log('[nodemon] App restarted due to:', files); });
이 코드는 ES6 문법으로 작성하지말고 ES5문법으로 작성하세요.
그 다음, package.json 파일에 스크립트를 작성하세요.
package.json 에 build, start, development 스크립트 작성
"scripts": { "build": "babel src -d build --presets=es2015", "start": "node ./build/main.js", "development": "node ./scripts/development.js", "test": "echo \"Error: no test specified\" && exit 1" },
development 스크립트를 굳이 스크립트파일을 따로 만든 이유는, 윈도우 / 유닉스 계열에서 NODE_ENV 설정하는 방식이 다르기 때문입니다.
"development": "NODE_ENV=development nodemon --exec babel-node --presets=es2015 ./server/main.js --watch server", "win_development": "set NODE_ENV=development&nodemon --exec babel-node --presets=es2015 ./server/main.js --watch server"스크립트 파일을 따로 만들기 싫으면 위와같이 package.json 에 스크립트를 작성해도 됩니다.
이제 Express 서버에서 해야 할 기본 설정들은 끝났습니다.
이제, webpack 개발서버에서도 Express 서버에있는 API를 사용 할 수 있도록 config 를 수정해줍시다.
config/webpack.config.dev.js 수정하기
/* CODES */ module.exports = { devtool: 'eval', entry: [ require.resolve('webpack-dev-server/client') + '?http://0.0.0.0:8081', require.resolve('webpack/hot/dev-server'), path.join(srcPath, 'index') ],
localhost:3000 에서 0.0.0.0:8081 로 바꿨습니다.
0.0.0.0 으로 해야 외부에서도 개발서버에 접속이 가능합니다. (포트는 변경하지 않아도 무방합니다)
scripts/start.js 수정하기
/* CODES */ new WebpackDevServer(compiler, { historyApiFallback: true, hot: true, // Note: only CSS is currently hot reloaded publicPath: config.output.publicPath, quiet: true, proxy: { "*": "http://localhost:8080" } }).listen(8081, '0.0.0.0', function (err, result) { if (err) { return console.log(err); } clearConsole(); console.log(chalk.cyan('Starting the development server...')); console.log(); openBrowser(); });
하단부의 WebPackDevServer 의 설정에 proxy 를 추가하시고,
listen 할 때 host를 localhost 가 아닌 0.0.0.0 으로 설정하세요.
그리고 (포트를 바꾸고싶다면) 코드 전체에서 3000 을 8081로 Replace 하세요.
이 두 파일을 수정하셨으면, webpack-dev-server에서도 Express.js 에서 만든 API를 사용 할 수 있게 됩니다 😀
마지막으로, react-hot-loader를 적용하는 방법을 알아보겠습니다.
react-hot-loader 적용하기
npm install --save-dev react-hot-loader
config/webpack.config.dev.js 수정하기
/* CODES */ loaders: [ { test: /\.js$/, include: srcPath, loaders: ['react-hot', 'babel?' + JSON.stringify(require('./babel.dev'))] }, /* CODES */
loaders 의 .js 부분에 기존의 loader 과 query 를 제거하고, loaders: 를 추가하세요.
로더가 두개이므로 query를 할 수 없습니다. 그 대신에 babel? 를 통하여 babel 설정을 JSON.stringify 하여 전달해주면 됩니다.
주의: 최근 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"] } } ] },
마무리
다 하셨나요? 이제 개발 서버를 열 땐, express 서버를 따로 실행 한 다음에 (터미널 창을 하나 더 열거나, screen 기능 사용) webpack 개발서버를 실행하시면 됩니다.
저같은 경우는, 원래 boilerplate 를 잘 사용하지 않고, (필요하지 않는 기능이 있는 경우가 있어서) 처음부터 직접 작업환경을 설정하는걸 좋아하는데.
이 create-react-app 은 핵심적인 기능이 다 있고, 무려 공식적으로 React 팀에서 내놓은거니까, 꽤 쓸만 한 것 같습니다 (음.. 심리적인 느낌도 살짝 없지않아 있습니다)
이 도구는, sass 로더라던지, redux 설정이라던지 그런것들이 안되어있으므로, 작업환경을 직접 설정 하는것이 번거롭다고 생각하시는분들은 익숙한 react boilerplate가 있다면, 그걸 사용하셔도 무방 할 것 같습니다.
제 생각엔 이 도구는 초보자들에게도 매우 쓸만 할 것 같습니다 (처음 시작 할 땐 webpack을 이해하고 설정하는게 조금 귀찮긴 합니다..)