리액트 웹팩으로 개발 환경 구축하기(without CRA)

uchanlee.dev·2020년 4월 27일
34

React

목록 보기
1/1
post-thumbnail

지인의 블로그를 보다가 충격적인 사실을 발견했다.

"내가 회사에 가서도 CRA를 통해 개발할까? " 🤔

에 대한 답변이 X(아니?) 라는 사실이였다. 몇개월 뿐이지만 리액트를 공부한 시점부터 현재까지 리액트로 개발한 프로젝트를 모두 CRA를 통해 진행하였다. 그래서 난 이게 당연하고 정답인줄 알았다. 하지만 이곳의 세계는 너무나도 깊었다. 해답을 찾던중 webpack으로 CRA를 대신할수 있다는 사실을 알았고 이에 대해 공부하고 모르는 개념을 정리하는 시간을 가져보도록 하겠다.

목차

  1. 웹팩이란?
    1.1 모듈
    1.2 번들러
    1.3 웹팩
  2. 실습(webpack-react)
    2.0 node 설치
    2.1 프로젝트 폴더 생성 및 package.json 생성
    2.2 라이브러리 설치
    2.3 Babel 설정
    2.4 Webpack 설정
    2.5 리액트 애플리케이션 만들기
    2.6 Hot Module Replacement (HMR) 설정
    2.7 프로젝트 빌드
  3. 느낀점 및 앞으로 계획

⬆ 위로가기

웹팩이란?

먼저 웹팩에 대해 알아보겠다. 구글에 웹팩을 검색하게되면 "자바스크립트 모듈 번들러"라고 나온다. 근데 모듈 번들러가 뭘까?

모듈(Module)

  • 프로그램을 구성하는 독립적인 요소이다.
  • 데이터와 함수들을 묶어서 모듈을 형성하고 파일 단위로 나눈다.
  • 모듈화 프로그래밍은 기능별로 파일을 나눠서 프로그래밍을 하는 것으로 유지보수가 쉽다는 장점이 있다.

번들러(Bundler)

  • 번들러는 여러개의 파일을 하나의 파일로 만들어주는 라이브러리이다.
  • 번들러를 사용하면 소스 코드를 모듈별로 작성할 수 있다.
  • 대표적인 예로 웹팩(wepback), Parcel 등이 있다.

웹팩(Webpack)

  • 웹팩(Webpack)은 자바스크립트 모듈 번들러이다
  • 웹팩에서 모든 것은 모듈이다. 자바스크립트, CSS, 이미지 등 모든 것을 모듈로 관리한다.
  • 웹팩의 주요 네 가지 개념으로 Entry, Output, Loader, Plugin이 있다.

⬆ 위로가기

1.3.1 Entry

  • 위 사진처럼 모듈을 많이 불러올경우 의존성이 증가한다.
  • 의존성 그래프의 시작점을 웹팩에서는 엔트리(Entry)라고 한다.
  • 웹팩은 엔트리를 통해서 필요한 모듈을 로딩하고 하나의 파일로 묶는다.
  • 여러개의 엔트리가 존재할 수 있다.

1.3.2. Output

  • 엔트리에 설정한 자바스크립트 파일을 시작으로 하나로 묶는다.
  • 번들된 결과물을 처리할 위치를 output에 기록한다.

1.3.3 Loader

  • 웹팩은 JavaScript와 Json만 이해할 수 있다.
  • 로더는 다른 type의 파일(img, font, stylesheet 등)을 웹팩이 이해할 수 있게 변경해준다.

1.3.4 Plugin

  • Loader은 모듈을 처리하지만 Plugin은 번들된 파일을 처리한다.
  • Plugin은 번들된 파일을 난독화 하거나 압축하는데 사용된다.
  • 예를 들어 bundle한 css파일과 js파일을 각각 html 파일에 link 태그와 script태그로 추가해줘야 하는데 HtmlWebpackPlugin은 이것을 자동화해준다.

⬆ 위로가기

실습(webpack-react)

0. node 설치

실습하기 전 node가 준비되어야 한다.

⬆ 위로가기

1. 프로젝트 폴더 생성 및 package.json 생성

(의존성 초기화)
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"
}

⬆ 위로가기

2. 라이브러리 설치

이번 실습을 진행할때 필요한 초기 라이브러리를 설치하겠다. 초기 프로덕션 의존성(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에 대한 사용법은 나중에 아래에서 살펴보자.

⬆ 위로가기

3. Babel 설정

프로젝트 최상위 webpack-react-study에 바벨 설정 파일을 만든다.

touch .babelrc

.babelrc파일을 열어 아래와 같이 코드를 추가한다.

{
    "presets": ["@babel/preset-env", "@babel/preset-react"]
}
  • babel 그 자체로는 아무것도 동작하지 않는다. 무언가 하게하려면 plugin을 설치한다.
  • 먼저 .babelrc라는 파일은 바벨에 대한 플러그인들을 설정해주는 파일이다.
  • 매번 플러그인을 설치하고 등록하기 귀찮아 "preset"이라는 기능을 만들었다.
  • "presets"이 무엇이냐면 플러그인들을 포함한 번들파일인데 preset에 한번만 설정하면 플러그인들이 자동으로 설치된다.

⬆ 위로가기

4. Webpack 설정

지금부터 웹팩을 설정해보자. 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)를 어떻게 사용하는지 몰라 지웠다.

mode

webpack.config.js파일을 열어 아래 코드를 추가하도록 하자.

...
module.exports = {
  mode: 'development',
};
  • mode옵션은 웹팩 설정이 development(개발)모드인지 production(프로덕션) 모드인지 정한다.
  • development 모드는 개발자 경험에 초점이 맞춰진 모드이고 production모드는 배포에 초점이 맞춰진 모드이다.
  • 공부 목적이므로 development를 선택했다.

entry, output

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

...
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`;

와 같이 사용가능하다.

plugin

module.exports = {
  ...
  plugins: [
    new HtmlWebpackPlugin({
      template: 'public/index.html',
    })
  ],
};
  • plugins옵션은 웹팩 번들과정에 적용할 플러그인을 설정해준다.
  • HtmlWebpackPlugin은 html파일이나 favicon을 번들링과정에 포함한다.
  • 예를들어 번들된 파일 bundle.[hash].js를 index.html에 자동 삽입해준다.

devServer

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
    }
};

⬆ 위로가기

5. 리액트 애플리케이션 만들기

먼저 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가 잘 적용된 모습을 볼수 있다.

⬆ 위로가기

6. Hot Module Replacement (HMR) 설정

먼저 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를 선택하면 사진과 같이 변겨오딘 부분에 블럭이 생겨 눈에 띈다.

⬆ 위로가기

7. 프로젝트 빌드

이번엔 프로젝트를 빌드해보도록 하겠다.
yarn build로 빌드를 하자.
아래 사진과 같이 정상적으로 dist폴더 밑에 bundle.[hash].js와 html이 생기는걸 볼수 있다.

느낀점 및 앞으로 계획

오늘 처음으로 CRA를 사용하지 않고 webpack으로만 리액트 프로젝트를 구동시켜 봤다. 무작정 예제를 따라하고 에러가 났다. 그래서 공식 문서를 찾고 고쳐가면서 웹팩을 구성하는 부분이 무엇인지 알았고 이해할수 있엇다.

하지만 오늘 내가 정리한게 다가 아니다. webpack의 일부분만 배운것이다. 앞으로 웹팩의 다양한 기능을 공부하고 어느정도 작지않은 프로젝트를 webpack으로 개발해 볼것이다.

profile
늦게 시작하는 것을 두려워 하기보단 하다가 중단 하는 것을 두려워 하자!

3개의 댓글

comment-user-thumbnail
2020년 10월 18일

항상 CRA로 작업하다가 이번에 처음으로 웹팩을 배우고 있는데, 찾아본 블로그 중에서 가장 이해가 잘 되게 정리해주신 것 같아요! 잘 배우고 갑니다 •͈ᴗ•͈

1개의 답글
comment-user-thumbnail
2022년 3월 20일

2년 전 글임에도 정말 많은 도움 받았습니다!!! 감사합니다 :)


조금 첨언하자면, css-loader 에서 camelCase 속성이 없어졌는지 오류가 계속 뜨네요!
두 번째 룰에서,

 use: ["style-loader", "css-loader"],

로 기본 로더로 바꿔주니 정상 작동합니다

(https://webpack.js.org/loaders/style-loader/)


그리고 더불어 hot: true 속성 역시 자동으로 들어가 있어 따로 지정 안 해도 된다는 문구가 출력되네요 !!

답글 달기