CRA 없이 React와 webpack 사용하기

한호수 (The Lake)·2023년 5월 12일
5

서론

기본적으로 React를 시작한다고 하면 CRA(Create-React-App) 을 통해서 환경을 구축하고 코드를 작성했습니다. 하지만 실무에서 npm run eject를 통해 webpack.config.js을 수정하거나 craco 라이브러리를 사용해서 웹팩 설정을 변경해야할 수 있는데, 직접 구축을 해본다면 웹팩을 이해하는데 많은 도움이 될 것 같아서 실행으로 옮기게 되었습니다.

번들러

webpack을 사용하는 이유는 번들링을 통해 여러 개의 파일을 하나로 묶어줄 수 있기 때문에 사용자의 브라우저에서는 여러번 보낼 요청을 한번으로 줄일 수 있게 됩니다. 그로 인해서 여러번의 통신으로 낭비되는 시간을 아낄 수 있게됩니다.

그 외에도 파일 단위로 모듈 시스템을 사용하여 각각의 스코프를 가지게 되고 유지보수와 가독성면에서 이점을 가질 수 있으며, 다양한 서드파티 기능을 이용하여 크로스브라우징을 지원하거나 최적화하는등 다양한 기능을 사용할 수 있습니다.

직접 구현해보기

webpack 기본 동작

처음 빈 폴더에서 터미널에 npm init -y을 입력하여 package.json을 생성합니다.

npm init -y

그 다음 저희에게 필요한 웹팩과 터미널에서 웹팩을 사용하기 위한 webpack-cli를 설치합니다.

npm i -D webpack webpack-cli   

이렇게 되면 첫번째 단계는 끝났습니다. 기본 설정을 통해 간단히 웹팩을 사용해보도록 합니다. package.json과 같은 위치에 src 폴더를 만들고 index.js 파일과 test.js 파일을 만들어 import와 export를 통해서 사용해보겠습니다.

//index.js
import { hi } from "./test";
console.log("hello");
console.log(hi);
//test.js
export const hi = "hi";

그 후 npx webpack을 터미널에 입력하여 웹팩을 실행시키면 /dist 경로에 번들링된 main.js 파일이 생성되게 되고 다른 모듈에 있는 "hi" 라는 텍스트도 확인할 수 있었습니다.

// dist/main.js
(()=>{"use strict";console.log("hello"),console.log("hi")})();

이렇게 가장 간단하지만 번들링을 직접 실행해볼 수 있었습니다. 하지만 지금은 웹팩의 메인화면에 나타나는 그림처럼 css나 이미지같은 에셋들을 처리할 수 없습니다. 간단하게 style.css 파일을 만들어 index.js에 import하면 번들링 시 에러가 발생하게 됩니다.
( config에서 mode를 정하지 않아 나오는 에러도 있지만 나중에 같이 해결하기로 합니다. )

CSS와 Asset 번들링하기

위 에러메세지를 확인해본다면 해당 파일 타입에는 loader가 필요다는 경고와 해결방법이 있는 링크를 주게됩니다. 해당 링크를 참고해서 webpack.config.js 라는 설정파일을 만들고 css 파일을 import 하기위한 로더를 설치해줍니다.

// 두가지 loader 설치
npm i -D style-loader css-loader
// webpack.config.js
const path = require("path"); // 경로처리를 위한 path 모듈 import

module.exports = {
  mode: "development",  
  entry: "./src/index.js",
  output: {
    filename: "main.js",
    path: path.resolve(__dirname, "dist"),
  },
  module: {
    rules: [{ test: /\.css$/, use: ["style-loader", "css-loader"] }, // CSS 파일 처리
            { test: /\.(png|svg|jpg|jpeg|gif)$/i, type: 'asset/resource' }], // Asset 파일 처리
    
  },
};

webpack.config.js 파일은 node.js에서 실행되며 node.js는 기본적으로 CommonJS 모듈 시스템을 사용하기 때문에 require로 모듈을 불러와야합니다. 그 외에는 웹팩의 공식문서를 참고하여 형식에 맞게 작성하면됩니다.

  • mode : mode는 현재 번들링이 개발환경에 사용되는지 실제 Product로 사용되는지에 대한 설정입니다. 저희는 개발환경에서 사용하니 "development"로 사용하였습니다.

mode는 생략한다면 production이 디폴트로 설정되며 development 보다 많은 최적화를 진행하게됩니다.

  • entry : 번들링을 시작할 시작파일을 선택합니다.

  • output : 번들링된 파일을 어떤 위치에 어떤 파일명으로 내보낼지 명시합니다.

  • module : 해당 옵션은 웹팩에서 모듈을 어떻게 처리할지에 대한 정의로 현재 저희가 사용하는 rules만 확인해보기로 합니다. 해당 옵션을 사용하면 미리 다운받은 로더를 사용하여 webpack에 CSS 파일을 로드하거나 TypeScript를 JavaScript로 변환하도록 지시할 수 있습니다.
    rules에 배열로 넘겨준 객체에는 testuse 또는 type으로 명시해주었는데 test는 정규표현식을 사용해서 해당 로더를 적용할 파일확장자를 선택해주고 use 에는 적용할 로더를 순서대로 넣어줍니다. typeasset처리를 위해 웹팩에서 지원하는 내장모듈을 사용하는데 어떤 타입을 사용할지 명시합니다.

로더의 순서는 오른쪽에서 왼쪽으로(또는 아래에서 위로) 평가/실행됩니다.

위 처럼 webpack.config.js를 설정했다면 npx webpack을 통해서 실행할 수 있게 되고 더이상 에러 없이 번들링되는것을 볼 수 있습니다.

package.json 파일에 아래 스크립트를 추가하면 npm run build로 번들링을 할 수 있습니다.

Babel loader

저희는 이제 js 파일과 css 그리고 이미지관련 asset들을 번들링할 수 있게 되었습니다. 하지만 저희가 js의 최신문법을 사용한다면 그대로 번들링되며 이전 브라우저에서는 제대로 작동되지 않을 수 있습니다. 이걸 해결하기 위해서 Babel loader를 추가해서 번들링 시 ES5 문법으로 트랜스파일링해서 전달할 수 있습니다.

npm i -D @babel/core @babel/preset-env babel-loader 
// webpack.config.js
const path = require("path");

module.exports = {
  mode: "development",  
  entry: "./src/index.js",
  output: {
    filename: "main.js",
    path: path.resolve(__dirname, "dist"),
  },
  module: {
    rules: [{ test: /\.css$/, use: ["style-loader", "css-loader"] }, 
            { test: /\.(png|svg|jpg|jpeg|gif)$/i, type: 'asset/resource' },
            {
              test: /.m?js$/i, 
              exclude: /node_modules/, // 번들링 시 해당 로더의 사용을 예외처리할 경로를 명시
              use: {
                loader: "babel-loader", 
                options: {
                  presets: ["@babel/preset-env"], // babel이 실제 동작할때 필요한 플러그인들을 추가
                },
              },
            }
           ], 
  },
};

플러그인 추가 (HtmlWebpackPlugin)

저희는 이제 최신문법을 사용하여 개발하고 이전 브라우저에서 지원할 수 있도록 바벨을 통해 변환도 시킬 수 있게 되었습니다. 하지만 실행해보려면 dist 폴더에 일일히 index.html 만들어줘야하는 번거로움이 생깁니다. 이걸 자동화할 수 있는 기능이 HtmlWebpackPlugin 라는 플러그인을 추가하는 것 입니다.

npm i -D html-webpack-plugin
// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin"); // html-webpack-plugin import

module.exports = {
  mode: "development",
  entry: "./src/index.js",
  plugins: [
    new HtmlWebpackPlugin({
      title: "Generation HTML",		// 생성할 HTML TITLE 지정
    }),
  ],
	//...중략
  },
};

해당 설정을 완료하고 npm run build를 실행하면 아래와 같이 index.html 파일이 생성되고 main.js가 연결되있음을 확인 할 수 있습니다.

webpack-dev-server

대부분의 작업이 끝났습니다. 저희는 CSSasset 파일들을 js파일로 import하여 사용할 수 있으며 최신문법을 사용하지만 실제 빌드된 파일은 이전 브라우저들을 지원합니다. 하지만 문제점이 하나 있습니다. 코드가 변경되면 매번 명령어를 사용해 빌드하고 파일을 새로 열어봐야하는 번거로움이 있습니다. 이를 해결하기위해 웹팩에서 지원하는 webpack-dev-server 모듈을 설치하여 사용할 수 있습니다.

npm i -D webpack-dev-server
// webpack.config.js

module.exports = {
    // ...중략
  
  devServer: {
    static: "./dist",  // devServer에서 실행할 html이 있는 경로
  }
	// ...중략
  },
};

추가적으로 package.json에서 server를 구동시킬 명령어와 코드가 변경되었을때 자동으로 빌드시켜줄 옵션을 추가해야합니다.

  "scripts": {
    "start": "webpack serve --open",   // 데브 서버 실행 스크립트
    "build": "webpack --watch"         // watch 옵션을 사용하여 파일 변경 시 자동 빌드
  },

그리고 각각의 터미널을 통해 두 명령어 모두 실행시켜 줍니다.

npm run start // 데브 서버 실행
npm run build // 자동 빌드 시작

이 후부터는 코드를 변경하고 저장 시 새로고침 없어 자동으로 적용되는것을 볼 수 있습니다.

React 설치

마지막으로 React를 설치하면서 마무리하도록 하겠습니다. React를 사용하기 위해 패키지를 설치합니다.

npm i react react-dom

그리고 src 폴더에 index.js파일을 수정하고 App.js 파일을 만들어줍니다.

import React from "react";
import ReactDom from "react-dom/client";
import App from "./App";

const root = ReactDom.createRoot(document.querySelector("#root"));

root.render(<App />);
import React from "react";

const App = () => {
  const [state, setState] = React.useState(0);
  const handleButton = () => {
    setState((prev) => prev + 1);
  };
  return (
    <div>
      Hello React!
      <button onClick={handleButton}>{state}</button>
    </div>
  );
};

export default App;

React는 기본적으로 index.htmlroot Container에서 동작하니 index.html 파일도 수정해야합니다. package.json과 같은 경로에 public 폴더를 추가하고 index.html 파일을 추가해줍니다.

<!-- public/index.html -->
<!DOCTYPE html>
<html lang="en">
  <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>React App</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

webpack.config.js에서 빌드마다 public/index.html를 템플릿으로 사용하여 복사할 수 있도록 플러그인 설정을 바꿔줍니다.

// ...중략

  plugins: [
    new HtmlWebpackPlugin({
      template: `./public/index.html`,
    }),
  ],
 
// ... 중략

그리고 config가 변경되었으니 현재 돌아가는 서버와 webpack builder를 재실행해줍니다. 정상적으로 작동할 것이라고 생각했지만 아래와 같은 에러가 발생하였습니다. 이것은 우리가 평소 react를 사용할때 처럼 jsx문법을 사용해서 그렇습니다. 웹팩에서 해당 문법을 알 수 있도록 Babel 설정을 변경해주어야 합니다.

npm i -D @babel/preset-react
// webpack.config.js
  module: {
    rules: [
      { test: /\.css$/, use: ["style-loader", "css-loader"] },
      {
        test: /\.(js|jsx)$/i,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: { presets: ["@babel/preset-env", "@babel/preset-react"] },
        },
      },
    ],
  },

이 후 웹팩을 재실행하면 정상적으로 동작하는것을 확인 할 수 있습니다.

맺음말

웹팩을 직접 설정해서 사용하지 않았더니 이전에는 관련 에러가 발생하면 막막했었는데 이번 기회를 통해서 한걸음 다가간것 같아서 보람을 느낍니다. 웹팩뿐만 아니라 다른 번들러인 viteEsbuild에 대해서도 다루는 포스트를 작성해보도록 하겠습니다.

Reference

https://webpack.js.org/guides/getting-started/

profile
항상 근거를 찾는 사람이 되자

0개의 댓글