웹/React

React 18 + webpack5 + babel 로 리액트 프로젝트 설정 및 생성하기

디정 2023. 12. 10. 03:20

웹 제작 프레임워크를 고민하다가 가장 많이 사용한다는 프레임워크 중 React 프레임워크를 선택해 공부해보기로 했다. 

많이 알려져있는대로 React는 페이스북에서 만든 프레임워크이며, 페이스북에 사용된 React 컴포넌트는 무려 5만개를 넘어간다고 한다. 

 

아직 공부하는 중이지만 React의 장점은 state라는 변수 하나로 html 요소를 편하게 변경하고, 컴포넌트 별로 개발 및 관리가 용이하다는 점에 있는 것 같다. state 변수를 변경하는 것 만으로 React는 전체 페이지 재접속 없이 해당 컴포넌트 영역만 자동으로 리렌더링 해준다. (렌더링이란 페이지를 다시 로드하는 동작을 뜻한다.)

 

독학을 하려는 중이고, 아직 유료 강의까지 결제할 마음은 없어서 웹 상에서 괜찮은 무료 강의를 찾아봤다. 그 중 제로초님의 초보자용 React 강의를 선택해 수강 중이다. 

 

<제로초님 리액트 강의 (초보자용)>

https://inf.run/LWuoL

 

[무료] 웹 게임을 만들며 배우는 React - 인프런 | 강의

웹게임을 통해 리액트를 배워봅니다. Class, Hooks를 모두 익히며, Context API와 React Router, 웹팩, 바벨까지 추가로 배웁니다., 8개의 간단한 웹게임을 만들어보며 배우는 리액트 강좌입니다.React Hooks에

www.inflearn.com

 

하지만 강의 내용을 따라가는 것 만으로는 전반적인 내용 이해가 힘들어서 한번 더 복습하며 정리하는 과정이 필요했다. 특히 강의 초반 부분은 가장 기초적인 내용을 다루고있다고 생각해 더 꼼꼼히 체크하며 공부하는 중이다. 

 

그래서 리액트 카테고리의 첫 글에서는, 리액트 개발에 필요한 모듈 설치부터 시작해 개발 환경 구축을 완료하는 단계까지 쭉 정리해보려고 한다. (node와 npm이 이미 설치되어있는 환경에서 시작한다.)

 

 

0. npm이 프로젝트를 관리할 수 있게 해주기.

개발툴의 cmd창에 아래의 명령어를 입력해, npm이 프로젝트의 라이브러리들을 관리할 수 있도록 해준다.

 

(리액트 프로젝트 root 경로)> npm init

 

그러면 빈 프로젝트 파일에 package.json 파일이 생성된다. 앞으로 npm을 통해 설치되는 모든 라이브러리는 해당 파일에 기록된다. 또, 한 개 이상의 라이브러리를 설치 시 'package-lock.json' 파일이 생성되는데, 해당 파일에서는 보다 구체적인 라이브러리 정보를 확인할 수 있다.

 

 

1. 라이브러리(모듈 모음) 설치

React 개발을 위해 가장 먼저 필요한 작업은 당연히 React 라이브러리 설치이다. 하지만 React 개발을 위해서는 이 외에도 필요한 모듈이 몇 가지 더 있다. 그 목록과 대략적인 정체는 아래와 같다. (이걸 적는 스스로도 아직 모든 내용을 공부하지 못했다. 구체적 내용이 궁금하신 분들은 직접 공식문서를 참고해보시길 바란다..)

  • webpack : 웹팩의 핵심 모듈 (webpack의 역할 : 수만개로 생성될 수 있는 React 컴포넌트를 하나의 파일로 뭉쳐줌)
  • webpack-cli : 웹팩의 커맨드라인 도구 (webpack의 명령 세트를 제공한다는 모양.)
  • react : 리액트 핵심 모듈
  • react-dom : 리액트와 HTML DOM을 연결하는 도구인듯. (불확실.)
  • babel-loader : 웹팩과 바벨을 연동시켜주는 도구. 아래 @붙은 모듈들은 babel의 플러그인이라고 한다.
  • @babel/core : babel의 핵심적인 모듈들을 가지고 있음.
  • @babel/preset-env : 코드 내의 최신 javascript를 읽을 수 있게 해준다고 함. (ES6 문법을 말하는 듯. ES6 문법 중 읽지 못하는 라인을 하위 문법으로 교체해주는 역할을 하는 것 같다. 하위 브라우저 호환 기능도 함.)
  • @babel/preset-react : 브라우저가 react 프로젝트 내의 jsx(javascript xml) 코드를 읽을 수 있게 해주는 모듈을 갖고있는 듯. (브라우저가 페이지를 읽기 전, babel이 먼저 선수쳐서 jsx 코드를 js 코드로 바꿔버린다는 모양.)

목록이 생각보다 많지만 하나하나 의미를 생각하며 설치하면 나름 기억하기 쉽다.

 

위 라이브러리 중 'react', 'react-dom' 라이브러리를 제외하면 전부 개발 상에서만 사용되는 모듈 들이다.

따라서 라이브러리 설치 시 -D 옵션을 붙인다.

 

<react, react-dom 설치 커맨드>

(리엑트 프로젝트 root 경로)> npm i react react-dom

 

<그 외 라이브러리들 설치 커맨드>

(리엑트 프로젝트 root 경로)> npm i -D webpack webpack-cli babel-loader @babel/core @babel/preset-env @babel/preset-react 

 

위 옵션은 개발에 영향을 주는 내용은 없지만, pakage.json 파일에 'devDependencies' 항목으로 분류되어 라이브러리 관리의 가독성을 높여준다.

 

라이브러리 설치를 마치고 나면 package.json의 내용이 아래처럼 된다. 혹시 -D 옵션을 깜빡했다면 devDependencies 객체에서 'dependencies' 객체로 옮겨주자. 

 

처음 파일이 생성됐을 때 "script" 객체에는 "text" 항목만 등록되어있다. 해당 객체에서는 npm에서 사용할 명령어를 지정할 수 있다. "start" : "webpack" 으로 명령어를 설정하면 후에 'npm run start' 명령어로 웹팩을 실행시킬 수 있다.

* {"start" : "webpack --mode=development", "build" : " webpack --mode=development"} 로 명령어를 두 가지 지정해 개발모드와 배포모드를 나눠놓는 분도 계시는 듯 한데, 이 둘의 명확한 차이를 알기 전 까지는 그냥 편하게 "start" : "webpack" 으로 쓰려고 한다.

{
  "name": "gugudan",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start" : "webpack" //명령어를 따로 지정해준 것.
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@babel/core": "^7.23.5",
    "@babel/preset-env": "^7.23.5",
    "@babel/preset-react": "^7.23.3",
    "babel-loader": "^9.1.3",
    "webpack": "^5.89.0",
    "webpack-cli": "^5.1.4"
  }
}

 

나는 현재 리액트는 18버전을, 웹팩은 5버전을 사용하고 있다. 웹팩 4에서는 웹팩 사용에 필요한 폴리필들을 기본으로 지원했었지만, 모듈의 무게를 줄이기 위해 웹팩 5 부터는 사용자가 필요한 폴리필을 직접 찾아 붙여줘야 한다는 모양이다.

 

사실 이 이슈 관련해서 공부 중 오류가 우수수 발생했었는데, 어찌저찌 (좀 허무하게..) 잘 해결했다. 해당 내용은 글 마지막에 따로 덧붙여두려고 한다.

 

 

2. 웹팩 환경설정

사실 웹팩이 없어도 리액트 파일 자체는 잘 동작한다. 하지만 그럼에도 웹팩이 리액트 프로젝트에서 필수적인 이유는, 후에 무수히 늘어날 수 있는 리액트 컴포넌트를 파일 하나로 관리하는 것은 도저히 불가능하기 때문이다. 몇 만개가 넘어가는 컴포넌트들이 한 파일에 전부 들어간다면 스크롤바가 개미 허리보다 짧아질지도 모른다.

 

리액트 컴포넌트는 컴포넌트 하나 당 한 개의 jsx 파일을 생성하는 것이 일반적이라고 한다. 웹팩은 이 사혼의 구슬조각마냥 나뉘어버린 .jsx 파일들을 하나로 뭉쳐서, html에서 하나로 뭉친 해당 js 파일만 가져올 수 있도록 해준다. 

 

웹팩을 사용하지 않는다면, 컴포넌트 파일들을 하나하나 일일이 html 문서에 임포트해주어야 한다. 상상만으로도 너무 끔찍한 일이 아닐 수 없다....

 

webpack은 실행 시 자신이 어떤 확장자의 파일을 읽어야 하는 지, 이 프로젝트의 개발 모드는 무엇인 지, 하나로 압축해야 하는 파일명과 경로는 무엇인지 등의 정보를 'webpack.config.js' 파일을 통해 입력받는다. 

프로젝트 root 경로 바로 아래에 webpack.config.js 파일을 수동으로 생성해준다.

 

설정은 개발자 마다 세팅하기 나름이겠지만, 나는 일단 제로초님의 강의와 다른 학습 자료들을 참고해 아래와 같이 작성했다. 

 

const path = require('path'); //node의 기본 라이브러리인 path 모듈을 가져온다.

module.exports = {
    mode : 'development',
    resolve : {
        extensions : [".js",".jsx"], 
        //webpack이 읽어야 하는 확장자 명들을 명시해준다. .jsx는 해당 명시를 해줘야지 오류가 없더라.
    },

    entry: {
        thisapp : ['./index'] 
        // entry. webpack의 입력 단이다. 웹팩으로 합칠 파일명들을 적어준다. 
        // 이 프로젝트는 해당 index.jsx에 제작한 컴포넌트들이 전부 임포트 되어있어서, index.jsx만 입력하면 됐다.
        // webpack은 파일 내에 임포트되어있는 파일들이 있다면 알아서 인식하고 가져와 합쳐준다. (편리!)
    },

    module : { // 웹팩 실행에 필요한 모듈들을 세팅하는 란인 것 같다.
        rules : [{
            test : /\.jsx?$/, 
            //규칙 세팅. 정규식을 모르다보니 어떤 의미인지 아직 정확하게는 모르겠다.
            loader : "babel-loader", // 사용할 로더 입력!
            options : {
                presets : ['@babel/preset-env', '@babel/preset-react'] 
                //웹팩 실행 과정에서 이 두 가지 모듈을 이용해야 한다.
            }
        }]
    },


    output: { //output. webpack의 출력에 대한 설정을 세팅한다.
        path : path.join(__dirname, 'dist'),  //하나로 합쳐질 파일이 위치할 경로
        filename : 'main.js' //그리고 합쳐진 파일의 이름을 설정해준다.
        //filename을 지정하지 않을 경우, 위 entry에서 속성명으로 지정해준 텍스트가 파일 이름이 된다. 
 		//(이 파일의 세팅에 의하면 'thisapp'이 될 것이다.)
    }
};

 

위 세팅에서 필수적으로 작성해야 하는 항목은 mode, resolve, module 세 가지다. 만약 프로젝트에서 jsx 확장자를 사용하지 않는다면 resolve도 필요 없을 것이다. mode는 왜 필수 항목인 지 모르겠는데, 아무튼 안썼을 때 오류메시지로 명시하라고 시키더라.

 

entry와 output 항목은 생략 가능하다. 작성하지 않았을 경우 웹팩은 초기 값으로 './src' 폴더를 입력 경로로, './dist' 폴더를 출력 경로로 가진다.

 

module.exports는 생성된 모듈을 다른 파일로 넘길 수 있는 js 기본 명령어다. 아마 웹팩 설정 내용을 이름 없는 객체로 만들어 webpack 본 실행 파일에 넘겨주는 모양새일 것이라고 추측하고 있다.

 

 

3. html, js, jsx 파일들 작성하기

세팅이 잘 됐는 지 확인하기 위해 실제 파일들을 만들어본다.

 

루트 경로에 아래 세 가지 파일을 생성해줬다. 

  • index.html : 웹팩이 압축한 js 파일을 임포트받아 실제 화면에 출력될 html 시작 문서.
  • index.jsx : 제작한 컴포넌트를 임포트해오고, ReacDOM 객체를 이용해 HTML에 React 컴포넌트를 붙여주는 명령을 입력.
  • GugudanClass.jsx : 컴포넌트 파일. 제로초님의 구구단 웹게임 예제를 수행 중이었어서 컴포넌트명이 이렇지만, 이 글에서는 그냥 화면에 state 값 하나 찍어보고 말거다.

 

<index.html>

<!DOCTYPE html>
<html>
    <head>
        <title>구구단 게임</title>
        <meta charset="utf-8" />
        <script defer src ="./dist/main.js"></script>
        <!-- 웹팩이 뭉친 파일을 가져옴. -->
    </head>
    <body><section id="root"></section></body>
</html>

<!-- .은 현재 파일을, ..은 부모 파일을 의미한다. 혹시나 싶어 메모. -->

 

<index.jsx>

const ReactDOM = require('react-dom/client');
//클라이언트 용 ReactDOM 객체를 가져온다. 
//클라이언트 용 react-dom은 클라이언트(웹 접속자의 브라우저)에서 React 구성 요소를 랜더링 할 수 있게 해준다고 한다.
//서버용은 서버에서 React를 랜더링 할 수 있게 해준다고 함. 이게 왜 필요한지는 아직 모르겠다...

const Gugudan = require('./GugudanClass')
//컴포넌트 가져옴

const rootNode = document.getElementById('root');
//root 태그의 자식으로 컴포넌트를 붙일 것이기 때문에, 해당 태그 객체를 가져온다.

ReactDOM.createRoot(rootNode).render(<Gugudan />);


/**/
React v17 까지는
ReactDOM.render(<Gugudan />, rootNode); 로 사용되었음.

babel은 위 <Gugudan /> 이라고 되어있는 jsx 요소 구문을 브라우저가 읽기 전에 js로 바꿔준다. 
(js코드) React.createElement('태그명', {태그 속성}, value값);
ex) React.createElement('button', {onClick: () => this.setState({liked: true})}, 'Like');

 

<GugudanClass.jsx>

const React = require('react');

//클래스 컴포넌트
class GugudanClass extends React.Component {
    state = {
        text : "Hello React~"
    }

    render() { //state 값이 변할 때 마다 render 메소드가 호출 됨
        return (<h1>{this.state.text}</h1>) 
        //state 객체의 text 값을 h1 태그에 넣었다.
    }
}

module.exports = GugudanClass; //모듈 exprot

 

 

위 파일을 전부 생성하고 난 후의 파일 구조는 아래와 같다.

 

 

4. 웹팩 실행 및 웹 실행해보기

 

준비가 모두 끝났으면, 이제 웹팩을 실행해서 파일을 뭉친 후 정상작동하는 지 실행해보면 된다.

아래 명령어를 입력해 웹팩을 실행해본다.

 

(리액트 프로젝트 root 경로)> npm run start

 

코드에 특별히 이상이 없으면 아래 처럼 dist 폴더가 생성되고, 그 안에는 웹팩이 생성한 파일 main.js가 생성된다. 웹팩 실행도 뭐라뭐라 말이 많지만 'successfully'라고 잘 표시된 모습니다.

 

html 문서를 작성했던 index.html 파일을 브라우저로 열어보면, 의도했던 대로 Gugudan 컴포넌트가 잘 랜더링된다.

 

 

5. 각종 오류들

글 작성을 끝내고 프로젝트를 다시 실행해봤는데, 희안하게 바로 직전까지 발생하지 않았던 에러가 갑자기 나타났다. 이유는 아직 모르겠고, 급하게 구글링을 해서 코드를 고쳤다. 

 

1) uncaught referenceerror: react is not defined

갑자기 리액트를 인식할 수 없다는 에러가 발생했다. 위 에러메시지 그대로 복사해서 구글링해봤다. babel에서 발생한 문제였는데, 웹팩 설정 파일(webpack.config.js)의 presets 부분을 아래처럼 바꾸니 해결됐다.

@babel/preset-react 모듈에 runtime 속성이 추가됐다. 해당 속성이 어떤 역할을 하는 지는 차차 알아보는 걸로....

presets : ['@babel/preset-env', ['@babel/preset-react', { "runtime": "automatic" }]]

 

2) ncaught Error: ES Modules may not assign module.exports or exports.*, Use ESM

위 문제를 해결하고 나니 'module.exports' 명령문이 ES문법이 아니므로 변경하라는 에러 메시지가 발생했다. 그간 잘 되다가 갑자기 왜 안된건지 모르겠지만 어쨌든 오류 이해는 했다. 좀 더 공부가 필요해 보인다.....

컴포넌트를 모듈로 내보내고 가져오는 부분을 아래와 같이 수정했다. 

import { Gugudan } from './GugudanClass';
//index.jsx의 컴포넌트 모듈 가져오는 부분 (1행(

export const Gugudan = GugudanClass;
// GugudanClass.jsx 에서 module.exports 해줬던 부분.

 

ES 문법 js 문법 비교 참고 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/export

 

 

3) webpack < 5 used to include polyfills for node.js core modules by default. This is no longer the case. Verify if you need this module and configure a polyfill for it.

 

웹팩 실행 시 발생하는 오류였다. 가장 해결에 오래 걸렸지만 굉장히 허무했던 오류다. 구글링을 해보니 같은 이슈를 앓고 있는 사람들이 많았는데, 보통 위 한 줄만 띡 나오지 않고 여러가지 모듈명을 들이대며 같은 내용이 수없이 반복된다. 나 같은 경우는 이 polyfill 관련 오류로 무려 92개의 에러와 13개의 경고 메시지를 받았었다.

 

오류를 읽어보면 대강 웹팩 5부터는 4에서 지원했던 기본 폴리필들을 제공하지 않으니, 필요한 폴리필을 알아서 판단해 설치하고 웹팩 설정 파일에 폴리필을 추가했다는 라인 한 줄 적어달라는 내용이다.

 

정석대로 npm을 활용해 폴리필들을 하나씩 다운받아줘봤지만, 웹팩 4에서 지원했던 폴리필들을 전부 설치해도 내 오류는 해결되지 않았다. 구글링을 통해서는 웹팩 버전을 다운그레이드해라, webpack-rewire 모듈을 이용해라 등등 다양한 해결법이 있었지만 하나같이 끌리지가 않아서 계속 다른 방법을 찾았다. 

 

그리고 나는... 별다른 조치 없이 내 코드의 어딘가에서 아래 한 줄 지웠더니 오류가 말끔하게 사라졌다.

const webpack = require('webpack');

 

아무래도 코드 내에 webpack을 직접 가져오는 과정에서 발생한 오류였던 모양이다. 커맨드를 통해 작동시키는 웹팩을 왜 본문에다가 임포트 했던 건지 나 스스로도 의문이다. 

 

위 줄을 어디선가 사용하는 지는 잘 모르겠으나, 그건 그 때 가서 다시 해결해보기로 했다...

 

 

 

아무튼 모든 과정을 잘 수행 후 브라우저에서 확인할 수 있는 화면.

 

 

 

END.