2차 공부/TIL

24.08.19 CRA/Vite없이 리액트 프로젝트 생성하기

공대탈출 2024. 8. 19. 20:03

1. 화면에 뿌려줄 index.html을 생성한다.

기존에 리액트에서 화면에 뿌려주듯이 사용하기 위해 root를 id로 갖는 div를 body요소 안에 만들어준다.

 

2. React, ReactDOM CDN 추가

<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>

별도의 설치없이 CDN을 import해와 React를 사용할 수 있다.

 

3. JSX to JS

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
        <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
        <title>Document</title>
    </head>
    <body>
        <div id="root"></div>

        <script>
            const App = () => {
                return (
                    <>
                        <h1>hello world!!</h1>
                    </>
                );
            };
            const root = ReactDOM.createRoot(document.getElementById("root"));
            root.render(<App />);
        </script>
    </body>
</html>

기존에 컴포넌트를 만들고 해당 컴포넌트를 root div에 할당해 render하는 방식이다.

하지만 syntax Error가 출력된다. 이유는 뭘까?

우리가 작성한 함수형 컴포넌트 방식은 JSX문법이다. 하지만 바닐라 자바스크립트는 JS를 사용한다.

따라서 JSX를 JS로 바꿔주는 과정이 필요하다.

 <script>
  const App = () => {
    return React.createElement("h1", null, "Hello, World!");
  };
  const root = ReactDOM.createRoot(document.getElementById("root"));
  root.render(React.createElement(App));
</script>

물론 이렇게 사용해도 되지만, 코드작성이 불편하므로 JSX를 JS로 바꿔주는 babel을 도입하자.

 

4. Babel 적용하여 JSX문법 사용하기

<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

 

CDN을 통해 babel standalone을 추가해준다.

그리고 아까 작성한 script에 type = 'text/babel'을 추가하여 babel이 인식할 수 있도록 한다.

<script type="text/babel">
    const App = () => {
        return (
            <>
                <h1>hello world!!</h1>
            </>
        );
    };
    const root = ReactDOM.createRoot(document.getElementById("root"));
    root.render(<App />);
</script>

정상적으로 원하는 h1태그와 내용이 출력됨을 알 수 있다.

하지만 콘솔창에서 배포를 위해 스크립트를 precompile하라는 경고가 출력되고 있다.

 

5. 패키지 설치 및 사용

npm init -y
npm install --save-dev @babel/core @babel/cli @babel/preset-react

npm 패키지 매니저를 설치해주고, 빌드타임에 바벨을 실행시키기 위한 도구인 babel-cli와 @babel/core을 설치해준다.

또한 React용 바벨 문법 변환 패키지인 @babel/preset-react도 설치해준다.

그리고 바벨을 위한 rc, 설정파일을 생성해준다. 파일명은 꼭 .babelrc 이어야 한다.

{
  "presets": ["@babel/preset-react"]
}

아까 만들었던 함수형 컴포넌트를 src폴더를 생성하여 app.js로 만들어준다.

pakage.json의 scripts부분을 아래와 같이 수정한다.

"scripts": {
    "build": "babel src -d dist"
 },

 

이후 터미널에 npm run build를 입력하면 dist폴더와 app.js가 생긴다.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>No CRA</title>
        <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
        <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    </head>
    <body>
        <div id="root"></div>
        <script src="./dist/app.js" type="module"></script>
    </body>
</html>

index.html을 다음과 같이 수정해준다.

이렇게 하면 dist폴더의 app.js를 불러오는 것인데, dist폴더의 app.js는 JSX문법으로 작성한 App.js를 바벨이 빌드단계에서 해석해놓은 형태이다.

 

하지만 우리가 계산을 위한 js파일이나, 추가 컴포넌트 파일이 필요하여 js파일이 엄청나게 늘게 된다. 하지만 브라우저는 모듈화 된 js파일을 한번에 6개씩밖에 처리하지 못하여 페이지 UX의 문제가 발생할 수 있다.

따라서 여러개의 파일로 구성된 코드나 리소스를 하나의 파일로 묶어주는 Webpach번들러를 사용해 배포 및 로드 성능을 개선해야 한다.

 

6. Webpach 설치 및 세팅

npm install --save-dev webpack webpack-cli babel-loader

빌드타임에 바벨을 실행시키기 위한 CLI명령어 도구와 바벨과 통합하기 위한 바벨 로더를 설치한다.

 

프로젝트 루트에 webpack.config.js 파일을 생성하고 아래와 같이 설정한다.

const path = require("path");

module.exports = {
    entry: "./src/app.js",
    output: {
        path: path.resolve(__dirname, "dist"),
        filename: "bundle.js",
    },
    module: {
        rules: [
            //.css .js 등 서로 다른 확장자를 가진 파일을 처리할 때 어떤 규칙을 적용할지 정의
            {
                test: /\.js$/, // 어떤 파일을 대상으로 할지 정규표현식으로 작성
                exclude: /node_modules/, // node_modules 폴더는 제외
                use: {
                    loader: "babel-loader", // babel-loader를 사용
                },
            },
        ],
    },
    mode: "development", // 없으면 warning 이 남
};

웹팩 로더

 

기존 dist폴더를 삭제하고 npm run build를 해보면 아래와 같이 파일이 생성된다.

Webpack이 다양한 js파일을 한곳에 묶어주는 것이다.

 

7. 플러그인 설치

우리가 코드를 작성하고 저장을 누르고 npm run build를 할 때마다 dist폴더를 삭제하고 할 불편을 줄이기 위해 자동으로 파일 이름에 해시값을 추가하여 업데이트 된 js파일을 캐시하지 않도록 해줄 수 있다.

webpack.config.js파일의 output부분을 위와같이 수정한다.

npm install --save-dev html-webpack-plugin

플러그인 설치를 해주고 플러그인을 추가해준다.

const HtmlWebpackPlugin = require("html-webpack-plugin");

plugins: [
    new HtmlWebpackPlugin({
        template: 'index.html',
        filename: 'index.html',
    })
]

그리고 기존 index.html에서 불러오던 script를 제거하고 npm run build를 실행시킨다.

위와같이 해시값이 적용된 js파일을 참조하게되는 것을 볼 수 있다.

Webpack 플러그인

 

하지만 우리는 여태 저장을 하면 자동으로 HTML파일이 업데이트되고, 개발자가 일일히 삭제하지 않았다. 따라서 해당 동작을 자동화해주는 플러그인을 설치해보자.

 

npm install --save-dev clean-webpack-plugin
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

 plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: "index.html",
      filename: "index.html",
    }),
  ],

Webpack.config.js에 위와동일하게 코드를 추가해준다. 플러그인은 순서에따라 동기적으로 동작하기 때문에 지워주고 추가해줘야 한다.

이후 npm run build를 실행해보면 자동으로 해시값이 변경된 js파일과 변경된 js파일이 적용된 index html을 볼 수 있다.

 

8. 개발 서버 설정

npm install --save-dev webpack-dev-server

devServer: {
    static: {
      directory: path.join(__dirname, "dist"),
    },
    port: 9000,
    open: true,
    hot: true,
},

 

port - 포트 번호 / open - 서버가 실행되면 자동으로 브라우저를 연다 / hot - 코드 변경사항을 실시간으로 반영한다.

이렇게 개발서버에서 우리가 저장을 하면 실시간으로 반영되는 기능을 얻을 수 있다.

 "scripts": {
    "build": "webpack",
    "start": "webpack serve"
 },

개발 서버를 시작하기 위한 명령어를 pakage.json에 추가해준다.

이제 우리는 npm start명령어로 개발 서버를 실행하고, 코드를 수정한 뒤 저장하면 바로 적용이 되어 화면에 보여지는 기능을 사용할 수 있는 것이다.

 

9. 환경 변수 관리

우리가 큰 프로젝트를 진행할 때에 개발, 스테이징(qa), 프로덕션 환경에 맞게 다른 설정을 적용해야한다.

webpack과 함께 dotenv-webpack을 사용해 환경변수를 관리하자.

npm install --save-dev dotenv-webpack

 

//webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const DotenvWebpack = require("dotenv-webpack");

const mode = process.env.NODE_ENV || "development";

module.exports = {
    entry: "./src/app.js",
    output: {
        path: path.resolve(__dirname, "dist"),
        filename: "bundle.[contenthash].js",
    },
    module: {
        rules: [
            //.css .js 등 서로 다른 확장자를 가진 파일을 처리할 때 어떤 규칙을 적용할지 정의
            {
                test: /\.js$/, // 어떤 파일을 대상으로 할지 정규표현식으로 작성
                exclude: /node_modules/, // node_modules 폴더는 제외
                use: {
                    loader: "babel-loader", // babel-loader를 사용
                },
            },
        ],
    },
    mode, // 없으면 warning 이 남

    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            template: "index.html",
            filename: "index.html",
        }),
        new DotenvWebpack({
            path: `./.env.${process.env.NODE_ENV || "development"}`,
        }),
    ],
    devServer: {
        static: {
            directory: path.join(__dirname, "dist"),
        },
        port: 9000,
        open: true,
        hot: true,
    },
};

env파일의 값을 참조하도록 webpack.config.js를 수정해준다.

 

.env파일을 생성하여 단계에 맞는 환경변수를 만들어준다.

"scripts": {
    "build": "webpack",
    "start": "NODE_ENV=production webpack serve"
},

 

이렇게 한다면 mac은 잘 될것이지만, window는 안될 것이다. 

그 이유는 window는 pakage.json에서 env파일을 사용할 때 다르게 설정해야 하기 때문인데, 이를 위해 cross-env 패키지까지 사용하여 각각 환경에서 오류가 나지 않도록 설정한다.

"scripts": {
    "build": "webpack",
    "start": "cross-env NODE_ENV=production webpack serve"
},

 

App컴포넌트에서 env파일의 값에 접근한다면, 

정상적으로 접근할 수 있는 것을 알 수 있다.