리액트18 웹팩 수동 설정

 

 

이번 포스팅에서는 리액트 18 버전을 기준으로 웹팩 설정을 수동으로 진행해 보겠습니다. 리액트가 18 버전으로 나온지 조금 지났는데, 저는 회사에서 Vue를 계속 쓰다 보니 리액트를 공부할 시간이 없었습니다.

 

 

먼저 간략하게 CRA(create-react-app) 폴더 구조와 비슷하게 구성해보고 수동으로 웹팩설정과 가장 유용한 기능 중 하나인 HMR(Hot Module Replacement)도 설정해보겠습니다. 마지막으로 이미지 파일을 Webp 확장자로 변환해주는 웹팩 플러그인이 있더라구요. 이 것도 적용하려고 했으나.. 다음 포스팅으로 양보하겠습니다 :)

 

 

 

1. 폴더 구조


//React18
├─ dist
├─ package-lock.json
├─ package.json
├─ public
│  └─ index.html 
├─ src
│  ├─ App.js
│  ├─ index.js
│  └─ index.css
└─ webpack.config.js

 

CRA 명령어로 생성한 것 처럼 위와 같이 구성했습니다.  각 폴더별 쓰임새는 아래와 같습니다.

 

  • public: HTML 템플릿과 이미지 파일 등 절대경로로 쓰이는 파일이 위치하는 장소
  • src: 기본적으로 작성하는 소스코드가 위치하는 장소
  • dist: 웹팩이 번들링된 결과를 저장하는 장소

 

이렇게 생성한 구조대로 npm 패키지 매니저를 통해 필요한 패키지들을 설치하고 웹팩 설정을 진행하도록 하겠습니다. 먼저 모듈 번들러나 웹팩에 대한 지식이 없는 분들은 아래 포스팅을 먼저 참고해주세요.

 

 

 

JS 모듈 번들러와 웹팩(Webpack)

요즘 회사에서는 Vue를 사용해 프로젝트를 진행하고 있습니다. 이번 1차 프로젝트가 곧 끝나가는데요. 2차도 마찬가지로 Vue를 사용해 개발한다고 합니다. 저는 리액트가 좋은데 말이죠? 아니면

juni-official.tistory.com

 

2. 패키지 생성


npm init

터미널을 통해 npm 명령어로 패키지를 생성해줍니다. 작성자 및 프로젝트 이름은 개별 설정해줍니다.

 

3. 패키지 설치


리액트

npm i react react-dom

웹팩

npm i -D webpack webpack-cli webpack-dev-server

웹팩과 플러그인은 개발환경에서만 필요하므로 설치 시 '-D' 또는 '--save-dev' 옵션을 추가해줍니다.

웹팩 플러그인

npm i -D babel-loader html-webpack-plugin clean-webpack-plugin css-loader style-loader cross-env dotenv dotenv-webpack
  • babel-loader: 웹팩에서 바벨을 사용할 수 있도록 처리
  • CleanWebpackPlugin: 이전에 번들된 파일 자동 삭제
  • HtmlWebpackPlugin: HTML 템플릿 설정
  • css-loader/style-loader: CSS 관련 파일 처리

바벨

npm i -D @babel/core @babel/preset-env @babel/preset-react
 

트랜스파일러(Transpiler)와 바벨(Babel)

자바스크립트를 공부하다 보면 쉽게 들을 수 있는 폴리필과 바벨에 대해서 다시 공부해보며 기록해본다. 자바스크립트는 현재까지 계속 업데이트되고 있는 언어이다. 그리고 브라우저마다 자

juni-official.tistory.com

3. 웹팩 설정


const path = require('path')
const webpack = require('webpack')
const dotenv = require("dotenv");
const Dotenv = require("dotenv-webpack");
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const ESLintPlugin = require('eslint-webpack-plugin') // eslint 사용할 경우

const isDevelopment = process.env.NODE_ENV !== "production";
const envPath = `./.env.${isDevelopment ? "development" : "production"}`;

dotenv.config({
  path: envPath,
});

const config = {
    name: 'React18-webpack-babel-setting', // 설정 이름
    mode: 'development', // production, development // 설정 모드
    devtool: 'eval',
    entry: {
        app: './src/index.js',
    },
    resolve: {
        extensions: ['.js', '.jsx'],
    },
    module: {
        rules: [
            {   // 리액트 바벨 설정
                test: /\.js/, 
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env', '@babel/preset-react']
                    }
                }
            },
            {
                test: /\.css$/i,
                use: ["style-loader", "css-loader"],
            },
        ]
    },
    plugins: [
        new Dotenv({
          path: envPath,
        }),
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin(
            { 
                template: './public/index.html', // 템플릿 설정
                minify: true, // 압축 설정
            }
        ),
        new webpack.ProvidePlugin({ // 리액트 자동 로드
            "React": "react",
        }),
        new ESLintPlugin()
    ],
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'app.js',
        publicPath: '/',
    },
    devServer: { // 개발 서버 설정
        static: './dist',
        port: 3000,
        hot: true, // 핫 모듈 교체(HMR) 활성화
        compress: true,
        open: true,
        historyApiFallback: true,
    }
}

if (isDev && config.plugins) {
  config.plugins.push(new webpack.HotModuleReplacementPlugin())
  config.plugins.push(new ReactRefreshWebpackPlugin())
}

module.exports = config
변경 히스토리
2022-07-27: config.resolve & react-refresh-webpack-plugin 추가

+추가)

..
plugins: [
    ..
    new webpack.ProvidePlugin({
        "React": "react",
    })
    ..
]
..

 

컴포넌트마다 import React from 'react' 선언을 꼭 해줘야 하는데요. ProviderPlugin을 사용하면 리액트 라이브러리가 필요한 곳에는 웹팩이 알아서 넣어줍니다. 물론 CRA를 통해 프로젝트를 만들어도 동일하지만요 :)

 

moment 같이 자주 사용하는 라이브러리가 있을 경우 추가해 놓으면 상단에 선언 없이 사용할 수 있어 편리합니다.

 

devServer HMR & 핫 리로딩


npm i -D @pmmmwh/react-refresh-webpack-plugin
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')

..
    plugins: [
    	...
        new webpack.HotModuleReplacementPlugin(),
        new ReactRefreshWebpackPlugin()
        ...
    ],
    devServer: { // 개발 서버 설정
        port: 3000, // 포트 설정
        hot: true, // 핫 모듈 교체(HMR) 활성화
        compress: true, // 압축 유무
        open: true, // 기본 브라우저에서 실행
        historyApiFallback: true, // connect-history-api-fallback error 방지
    }

..

 

기존 리로딩은 변경사항이 발생하면 전체가 새로고침 되는데, 변경된 부분만 빠르게 바꿔주는 react-refresh-webpack-plugin을 적용합니다. 해당 라이브러리를 설치하고 플러그인에 추가해줍니다.

 

devServer와 관련된 설정이 엄청 많아 세부 설정 내용은 아래에서 참고해주세요.

 

 

DevServer | 웹팩

웹팩은 모듈 번들러입니다. 주요 목적은 브라우저에서 사용할 수 있도록 JavaScript 파일을 번들로 묶는 것이지만, 리소스나 애셋을 변환하고 번들링 또는 패키징할 수도 있습니다.

webpack.kr

 

4. package.json


필요한 패키지 및 웹팩 설정이 끝났습니다. package.json 파일에 스크립트를 작성해주도록 하겠습니다.

 

// package.json
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "cross-env webpack serve --env development",
    "build": "cross-env NODE_ENV=production webpack"
  }

 

  • npm run dev: 웹팩 데브 서버 실행 명령어
  • npm run build: 웹팩 번들링 명령어

 

이제 본격적으로 리액트 코드를 작성할 수 있는 환경을 만들어놨습니다. index.html -> index.js -> app.js 순으로 코드를 작성해보도록 하겠습니다.

 

 

5. 코드작성


public/index.html

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello, React18!</title>
</head>
<body>
    <div id="root"></div>
</body>
</html>

 

비주얼 스튜디오에서 ! -> tab 에밋 기능으로 만들어줍니다. <body /> 태그에는 <div /> 태그 하나를 만들어 줍니다.

 

src/index.js

import ReactDom from 'react-dom/client'
import App from './App'
import './index.css'

const root = document.getElementById('macjjuni')

ReactDom.createRoot(root).render(
    <React.StrictMode>
        <App/>
    </React.StrictMode>,
);

// 리액트 18 이전 버전
// ReactDom.render(
//    <App/>,
//    document.getElementById("root")
// )

 

리액트 18 이전 버전에서는 ReactDom.render 함수를 사용했으나 18 이후 ReactDom.createRoot().render 함수로 변경되었습니다. 

 

여기서 리액트 자체를 선언하지 않았는데, 그 이유는 웹팩에서 설정해줬기 때문입니다.

 

src/app.js

export default function App() {

    return(
        <>
            <div className="wrap">
                <h1 className="title">Hello, React18!</h1>
            </div>
        </>
    )

}

 

src/index.css

@charset "utf8";

* {
    box-sizing: border-box;
    margin: 0 auto;
}

.wrap {
    display: flex;
    flex-direction: column;
    justify-content: space-evenly;
    align-items: center;
    width: 100vw;
    height: 100vh;
}

.title {
    font-size: 50px;
    font-weight: bold;
    color: skyblue;
}

 

이제 웹팩 개발 서버를 실행해서 결과를 확인해보겠습니다.

개발서버 실행


npm run dev

 

리액트 웹팩 개발서버 실행