지인의 블로그를 보다가 충격적인 사실을 발견했다.
"내가 회사에 가서도 CRA를 통해 개발할까? " 🤔
에 대한 답변이 X(아니?) 라는 사실이였다. 몇개월 뿐이지만 리액트를 공부한 시점부터 현재까지 리액트로 개발한 프로젝트를 모두 CRA를 통해 진행하였다. 그래서 난 이게 당연하고 정답인줄 알았다. 하지만 이곳의 세계는 너무나도 깊었다. 해답을 찾던중 webpack으로 CRA를 대신할수 있다는 사실을 알았고 이에 대해 공부하고 모르는 개념을 정리하는 시간을 가져보도록 하겠다.
먼저 웹팩에 대해 알아보겠다. 구글에 웹팩을 검색하게되면 "자바스크립트 모듈 번들러"라고 나온다. 근데 모듈 번들러가 뭘까?
실습하기 전 node가 준비되어야 한다.
(의존성 초기화)
webpack-react-study
폴더를 생성하고 package.json
파일 생성
mkdir webpack-react-study && cd $_
yarn init -y
-y 옵션은 All Yes로 yarn init만 했을시 나타나는 질문에 모두 yes로 넘어가게 해준다. 원하는대로 설정해도 상관 없으나 필자는 -y옵션으로 진행했다.
package.json
파일은 아래와 같을 것이다.
{
"name": "webpack-react-study",
"version": "1.0.0",
"main": "index.js",
"license": "MIT"
}
이번 실습을 진행할때 필요한 초기 라이브러리를 설치하겠다. 초기 프로덕션 의존성(production dependencies) 과 개발 의존성(development dependencies)을 설치한다. 개발 의존성은 개발 단계에서만 사용되는 의존 라이브러리이며, 프로덕션 의존성은 배포 단계에서 사용되는 라이브러리를 말한다.
yarn add react react-dom
yarn add @babel/core babel-loader @babel/preset-env @babel/preset-react css-loader style-loader html-webpack-plugin webpack webpack-dev-server webpack webpack-cli -D
설치된 라이브러리가 무엇인지 간단하게 알아보자.
- react: 리액트
- react-dom: 브라우저 DOM을 제어한다. UI를 렌더링할때 사용한다.
- @babel/core: babel의 핵심 라이브러리로 es6를 es5로 컴파일해준다.
- babel-loader: babel과 webpack을 사용해서 자바스크립트 파일을 컴파일한다.
- @babel/preset-env: es6, es7 버전을 지정안해도 babel이 자동 탐지한다.
- @babel/preset-react: 리액트(JSX)를 js로 인식가능하게 한다.
- css-loader: css 파일을 import 또는 require할 수 있게 한다.
- style-loader: css파일을 style태그로 만들어 head에 삽입한다.
- html-webpack-plugin: 웹팩 번들에 html파일을 제공한다.
- webpack: 웹팩을 사용하기위한 주 이다.
- webpack-dev-server: 웹팩 개발 서버를 구동할수 있다.
- webpack-cli: 웹팩 커맨드라인을 사용할 수 있다.
babel이 무엇인가 하면 babel은 es6코드를 이전 버전에서 실행 가능하도록 변환해주는 컴파일러이다. babel에 대한 사용법은 나중에 아래에서 살펴보자.
프로젝트 최상위 webpack-react-study
에 바벨 설정 파일을 만든다.
touch .babelrc
.babelrc
파일을 열어 아래와 같이 코드를 추가한다.
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
- babel 그 자체로는 아무것도 동작하지 않는다. 무언가 하게하려면 plugin을 설치한다.
- 먼저 .babelrc라는 파일은 바벨에 대한 플러그인들을 설정해주는 파일이다.
- 매번 플러그인을 설치하고 등록하기 귀찮아 "preset"이라는 기능을 만들었다.
- "presets"이 무엇이냐면 플러그인들을 포함한 번들파일인데 preset에 한번만 설정하면 플러그인들이 자동으로 설치된다.
지금부터 웹팩을 설정해보자. webpack.config.js
라는 파일을 생성하자.
touch webpack.config.js
그 다음 아래 코드를 작성하자.
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const port = 3000;
module.exports = {
// webpack 설정 코드 작성. 작성된 코드는 module.export로 내보냅니다.
};
- 기본적으로 webpack과 html-webpack-plugin이 필요하다.
- 4번째 줄에 보통 "const port = process.env.PORT || 3000;" 이렇게 사용하는데 나는 환경변수(process.env)를 어떻게 사용하는지 몰라 지웠다.
webpack.config.js
파일을 열어 아래 코드를 추가하도록 하자.
...
module.exports = {
mode: 'development',
};
- mode옵션은 웹팩 설정이 development(개발)모드인지 production(프로덕션) 모드인지 정한다.
- development 모드는 개발자 경험에 초점이 맞춰진 모드이고 production모드는 배포에 초점이 맞춰진 모드이다.
- 공부 목적이므로 development를 선택했다.
module.exports= {
...
entry:'./src/index.js',
output:{
path: __dirname + '/dist',
filename: 'bundle.[hash].js'
}
}
- entry 옵션은 앱이 있는 위치와 번들링 프로세스가 시작되는 지점이다.
- Webpack4(웹팩4)부터는 entry 옵션을 생략할 수 있습니다. 생략할 경우 ./src/index.js를 기본으로 가정한다.
- output 옵션은 번들링 프로세스가 끝난 뒤 번들링된 파일을 저장할 장소와 이름을 지정한다.
- 번들링된 파일 이름을 bundle.[hash].js로 하고 그 파일을 ./dist에 저장하라는 의미이다.
- filename의 [hash]는 어플리케이션이 수정되어 다시 컴파일될 때마다 Webpack(웹팩)에서 생성된 해시로 변경해 캐싱에 도움이 된다.
...
module.exports = {
...
module: {
rules: [
// 첫 번째 룰
{
test: /\.(js)$/,
exclude: /node_modules/,
use: ['babel-loader']
},
// 두 번째 룰
{
test: /\.css$/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
modules: true,
camelCase: true,
}
}
]
}
]
},
};
- module옵션은 번들링과정에서 사용할 규칙을 설정한다.
- ES6, ES7문법으로 작성된 javascript 파일을 ES5로 바꾸기 위해 작성한 .babelrc를 babel-loader를 이용해 규칙에 적용한다.
- node_moudules 폴더를 제외한 모든 .js파일에 babel-loader(.babelrc에 설정한 파일)를 적용한다.
- .js파일에서 import 또는 require로 .css파일을 가져올수 있게 해주는 css-loader와 .css파일을 style태그로 만든뒤 head태그 안에 삽입해주는 style-loader를 규칙에 적용한다.
- css-loader의 options는 css-loader에 적용할 옵션이다.
- modules - CSS Module 사용한다.
- camelCase - CamelCase로 CSS를 사용한다.
/src/App.css
.main_wrapper {
background-color: blue;
}
라는 클래스가 있다면
/src/App.js
import { mainWrapper } from 'App.css`;
와 같이 사용가능하다.
module.exports = {
...
plugins: [
new HtmlWebpackPlugin({
template: 'public/index.html',
})
],
};
- plugins옵션은 웹팩 번들과정에 적용할 플러그인을 설정해준다.
- HtmlWebpackPlugin은 html파일이나 favicon을 번들링과정에 포함한다.
- 예를들어 번들된 파일
bundle.[hash].js
를 index.html에 자동 삽입해준다.
module.exports = {
...
devServer: {
host: 'localhost',
port: port,
open: true,
historyApiFallback: true
}
};
- devServer 개발서버를 정의하는 옵션이다.
- host는 로컬호스트로 지정하고 port는 상단에서 정의한 값으로 설정해준다.
- open(true)은 서버를 실행했을 때 자동으로 브라우저를 열어주는 옵션이다.
- historyApiFallback은 브라우저에서 URL을 변경할 수 있도록 도와주는 옵션이다.
webpack.config.js
코드const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const port = 3000;
module.exports = {
mode: 'development',
entry:'./src/index.js',
output:{
path: __dirname + '/dist',
filename: 'bundle.[hash].js',
publicPath: '/'
},
module: {
rules: [
// 첫 번째 룰
{
test: /\.(js)$/,
exclude: /node_modules/,
use: ['babel-loader']
},
// 두 번째 룰
{
test: /\.css$/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
modules: true,
localsConvention: 'camelCase',
}
}
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'public/index.html',
})
],
devServer: {
host: 'localhost',
port: port,
open: true,
historyApiFallback: true,
hot: true
}
};
먼저 HtmlWebpackPlugin에 사용되는 index.html
을 아래와 같이 생성한다.
mkdir public && cd $_ && touch index.html
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>webpack-react-study</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
루트 디렉토리로 돌아와 index.js
를 생성한다.
mkdir src && cd $_ && touch index.js
components/App.js와 App.css를 생성하고 아래와 같이 코드를 작성한다.
mkdir components && cd $_ && touch App.js App.css
src/components/App.js
import React from 'react';
import { mainWrapper } from './App.css';
const App = () => {
return (
<div className={mainWrapper}>
<h1>Hello, Webpack!!! with React</h1>
</div>
);
};
export default App;
src/components/App.css
.main_wrapper {
background-color: blue;
}
리액트 프로젝트가 거의 다 완성됐다.
package.json
에 script를 추가하자.
...
"scripts": {
"start": "webpack-dev-server"
},
...
package.json
전체 코드는 아래와 같다.
{
"name": "webpack-react-study",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"start": "webpack-dev-server",
"build": "webpack"
},
"dependencies": {
"react": "^16.13.1",
"react-dom": "^16.13.1"
},
"devDependencies": {
"@babel/core": "^7.9.0",
"@babel/preset-env": "^7.9.5",
"@babel/preset-react": "^7.9.4",
"babel-loader": "^8.1.0",
"css-loader": "^3.5.3",
"html-webpack-plugin": "^4.2.0",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3"
}
}
이제 터미널에서 yarn start
로 개발 서버를 작동한다.
아래 사진과 같이 번들 파일이랑 css가 잘 적용된 모습을 볼수 있다.
먼저 react-hot-loader란 코드가 변경되었을 때 페이지를 새로고침하지 않고 바뀐부분만 빠르게 교체해주는 라이브러리이다.
그럼 터미널에서 react-hot-loader를 설치하자.
yarn add react-hot-loader -D
그다음 .babelrc
파일을 열고 plugin을 추가해주자.
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": ["react-hot-loader/babel"]
}
webpack.config.js
도 수정해주자.
...
module.exports = {
entry: './src/index.js',
output: {
...
publicPath: '/'
},
...
plugins: [
new webpack.HotModuleReplacementPlugin(),
...
],
devServer: {
...
hot: true
}
};
- publicPath: '/' Hot reloading은 중첩된 경로에서 동작하지 않는다고 하여 설정해주지 않을경우 핫리로딩이 작동이 안된다 하는데 나는 자세하겐 모르겠다.
- webpack.HotModuleReplacementPlugin — HMR 업데이트시 브라우저 터미널에 표시해 알아보기 쉽게 한다고 하는데 어떻게 표시가 되는진 모르겠다. 또 어떤 글에서 hot옵션을 주고 실행하게되면
webpack.HotModuleReplacementPlugin
이 자동으로 추가된다고 했는데 확실하겐 모르겠다.webpack.HotModuleReplacementPlugin
이 없고hot: true
만 있을때 핫리로딩이 잘 작동하긴 한다.- hot: true - 서버에 HMR 작동을 허락한다
App.js
에 hot을 적용시키자.
import { hot } from 'react-hot-loader';
...
export default hot(module)(App);
yarn start
로 실행후 App.js
를 수정후 저장해보자. 브라우저가 새로고침하지 않고 변경사항이 반영된 모습을 볼수 있다. 또 크롬 개발자 도구에서 Rendering -> Paint를 선택하면 사진과 같이 변겨오딘 부분에 블럭이 생겨 눈에 띈다.
⬆ 위로가기
이번엔 프로젝트를 빌드해보도록 하겠다.
yarn build
로 빌드를 하자.
아래 사진과 같이 정상적으로 dist폴더 밑에 bundle.[hash].js와 html이 생기는걸 볼수 있다.
오늘 처음으로 CRA를 사용하지 않고 webpack으로만 리액트 프로젝트를 구동시켜 봤다. 무작정 예제를 따라하고 에러가 났다. 그래서 공식 문서를 찾고 고쳐가면서 웹팩을 구성하는 부분이 무엇인지 알았고 이해할수 있엇다.
하지만 오늘 내가 정리한게 다가 아니다. webpack의 일부분만 배운것이다. 앞으로 웹팩의 다양한 기능을 공부하고 어느정도 작지않은 프로젝트를 webpack으로 개발해 볼것이다.
2년 전 글임에도 정말 많은 도움 받았습니다!!! 감사합니다 :)
조금 첨언하자면, css-loader 에서 camelCase 속성이 없어졌는지 오류가 계속 뜨네요!
두 번째 룰에서,
use: ["style-loader", "css-loader"],
로 기본 로더로 바꿔주니 정상 작동합니다
(https://webpack.js.org/loaders/style-loader/)
그리고 더불어 hot: true 속성 역시 자동으로 들어가 있어 따로 지정 안 해도 된다는 문구가 출력되네요 !!
항상 CRA로 작업하다가 이번에 처음으로 웹팩을 배우고 있는데, 찾아본 블로그 중에서 가장 이해가 잘 되게 정리해주신 것 같아요! 잘 배우고 갑니다 •͈ᴗ•͈