React.js 와 Webpack2 프로젝트 생성하기

1시간 만에 해보려다 삽질하기

React.js 로 작동하는 1 페이지 앱을 만들려고 합니다.

npm 최신버전으로 업데이트합니다.

> npm i -g npm

프로젝트를 초기화합니다.

> mkdir react-webpack2 && cd react-webpack2
> npm init --yes

recat를 설치합니다.

> npm i react react-dom react-hot-loader@3.0.0-beta.6 -S

webpack2를 설치합니다.

> npm i webpack webpack-dev-server loader-utils html-webpack-plugin extract-text-webpack-plugin style-loader css-loader sass-loader node-sass -D

package.json 내용은 다음과 같습니다.

> cat package.json
{
"name": "react-webpack2",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
"react": "^15.4.2",
"react-dom": "^15.4.2",
"react-hot-loader": "^3.0.0-beta.6"
},
"devDependencies": {
"babel-core": "^6.24.0",
"babel-loader": "^6.4.1",
"babel-preset-es2015": "^6.24.0",
"babel-preset-react": "^6.23.0",
"babel-register": "^6.24.0",
"css-loader": "^0.27.3",
"extract-text-webpack-plugin": "^2.1.0",
"html-webpack-plugin": "^2.28.0",
"loader-utils": "^1.1.0",
"node-sass": "^4.5.1",
"sass-loader": "^6.0.3",
"style-loader": "^0.16.0",
"webpack": "^2.3.2",
"webpack-dev-server": "^2.4.2"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}

이제부터는 에디터를 사용할 일이 많으므로 위에서 생성한 프로젝트 디렉토리부터 Intellij IDEA 프로젝트를 생성합니다.

프로젝트 소스 디렉토리(src/)와 빌드 디렉토리(dist/)를 생성합니다. 소스 하위 디렉토리에는 React 컴포넌트를 생성합니다.

React App 은 1 페이지이며 간단히 Header, Content, Footer 세 부분으로 구성됩니다.

> vi index.html
<!DOCTYPE html>

<html>
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>First React App</title>
</head>
<body>
<div id="main"></div>
</body>
</html>

> vi index.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import {AppContainer} from 'react-hot-loader';
import App from './components/app/App.jsx';
import './index.scss';

ReactDOM.render(
<AppContainer>
<App />
</AppContainer>
, document.getElementById('main'));

if (module.hot) {
module.hot.accept('./components/app/App.jsx', renderApp);
}

> vi index.scss
@import url(https://fonts.googleapis.com/earlyaccess/notosanskr.css);

$font-stack: 'Noto Sans KR', sans-serif;

html {
height: 100%;
font-family: $font-stack;
}

body {
height: 100%;
margin: 0;
display: flex;
flex-direction: column;
}

#main {
height: 100%;
display: flex;
flex-direction: column;
}

다음은 App Component 입니다.

> vi App.jsx
import React from 'react';
import Header from '../header/Header.jsx';
import Content from '../content/Content.jsx';
import Footer from '../footer/Footer.jsx';
import './App.scss';

function App() {
return (
<div className="site-wrapper">
<Header />
<Content />
<Footer />
</div>
);
}

export default App;

> vi App.scss
.site-wrapper {
min-height: 100%;
display: flex;
flex-direction: column;
}

다음은 Header Component 입니다.

> vi Header.jsx
import React from 'react';
import './Header.scss';

function Header() {
return (
<header>
<h1>React Header</h1>
</header>
);
}

export default Header;

> vi Header.scss
header {
width: 100%;
height: 80px;
background-color: #3e89c1;
text-align: center;

h1 {
margin: 0;
text-transform: uppercase;
font-size: 32px;
padding-top: 20px;
font-weight: 400;
color: #fff;
}
}

다음은 Content Component 입니다.

> vi content.jsx
import React from 'react';
import './Content.scss';

class Content extends React.Component {
render() {
var dt = new Date();
var time = dt.toISOString();

return (
<div className="content">
<span>Hello React</span>
<br/>
<span>{time}</span>
</div>
);
}
}

export default Content;

> vi content.scss
.content {
background-color: #222;
flex: 1 0 auto;
font-size: 40px;
color: #fff;
align-items: center;
display: flex;
flex-direction: column;
justify-content: center;

span {
max-width: 50%;
}

img {
height: auto;
}

input {
padding: 10px;
background-color: #2f2f2f;
border: none;
font-size: 24px;
color: #535353;
text-align: center;

&:focus {
outline-color: #1e1e1e;
}
}
}

다음은 Footer Component 입니다.

> vi Footer.jsx
import React from 'react';
import './Footer.scss';

function Footer() {
return (
<footer>
<h1>React Footer</h1>
</footer>
);
}

export default Footer;
> vi Footer.scss
footer {
width: 100%;
height: 80px;
background-color: #e78a0d;
text-align: center;

h1 {
margin: 0;
text-transform: uppercase;
font-size: 24px;
padding-top: 20px;
font-weight: 400;
color: #fff;
}
}

webpack2 설정 파일과 babel 리소스 파일을 생성합니다.

> vi .babelrc
{
"presets": [
"es2015",
"react"
],
"plugins": [
"react-hot-loader/babel"
]
}
> vi webpack.config.babel.js
import path from 'path';
import webpack from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import ExtractTextPlugin from 'extract-text-webpack-plugin';

process.noDeprecation = true;

const defaultEnv = {
dev: false,
production: true
};

export default (env = defaultEnv) => ({
entry: [
...env.dev ? [
'react-hot-loader/patch',
'webpack-dev-server/client?http://localhost:8080',
'webpack/hot/only-dev-server',
] : [],
path.join(__dirname, 'src/index.jsx')
],
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: '/'
},
devtool: env.dev ? 'inline-source-map' : 'cheap-module-source-map',
devServer: {
inline: true,
host: '0.0.0.0',
port: 8080,
contentBase: path.join(__dirname, 'dist'),
hot: true,
publicPath: '/'
},
plugins: [
...env.dev ? [
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin()
] : [
new ExtractTextPlugin('[name].css')
],
new HtmlWebpackPlugin({
filename: 'index.html',
template: './src/index.html'
})
],
module: {
rules: [
{
test: /.(js|jsx)$/,
exclude: /node_modules/,
include: path.join(__dirname, 'src'),
use: [ {
loader: 'babel-loader',
options: {
babelrc: true,
presets: [
['es2015', { modules: false }],
'react',
],
plugins: ['react-hot-loader/babel'],
}
}
]
},
{
test: /\.(css|sass|scss)$/,
include: path.resolve(__dirname, 'src'),
use: env.dev ?
[
'style-loader',
'css-loader',
'sass-loader',
]
: ExtractTextPlugin
.extract({
fallback: 'style-loader',
use: [
{ loader: 'css-loader', query: { modules: true, sourceMaps: true } },
{ loader: 'sass-loader'},
]
}),
}
]
}
});

package.json 파일에 개발 환경과 운영 환경 webpack 실행 스크립트를 추가합니다.

> vi package.json
...
"scripts": {
"dev": "webpack-dev-server --env.dev",
"production": "webpack -p --env.production"
},
...

Hot Module Replacement 개발 환경 설정은

  • webpack.config.babel.js 에서 HotModuleReplacementPlugin 과 react-hot-loader 그리고 devServer.hot = true 설정을 참조합니다.
  • index.jsx 에서 AppContainer 가 변경 사항이 있을 때 페이지를 다시 그려야 합니다.

운영 환경 빌드시 webpack 에서 css 파일을 생성 설정은

  • webpack.config.babel.js 에서 extract-text-webpack-plugin 과 ExtractTextPlugin.extract()의 loader 설정을 참조합니다.

마지막으로 개발 환경으로 실행해서 결과를 확인하겠습니다.

> npm run dev

모바일 환경에서도 접속해보니 잘 보입니다.

이제 삽질을 마치고 산책하러 가야겠습니다.

참조

Like what you read? Give readbetweenthelines a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.