웹팩을 활용한 리액트 개발 환경 세팅 1

Updated:
7 minute read

본 글에서 사용된 개발 도구들의 버전정보 입니다.

ubuntu npm node.js

webpack webpack-cli

babel-loader @babel/core @babel/preset-env @babel/preset-react

react react-dom

Initialization

가장 먼저 프로젝트 디렉토리를 생성하고 npm을 시작합니다. 그 후에 필요한 dependency를 설치해줍니다.

일단 웹팩 관련 패키지를 설치해보겠습니다. 웹팩은 개발 도중 빌드 단계에서 사용하며 완성된 웹페이지에선 아무런 기능을 하지 않기 때문에 --save-dev 옵션으로 설치합니다.

$ mkdir my-react-app && cd my-react-app
$ npm init -y
$ npm i -D webpack webpack-cli

webpack-cli는 커맨드 라인 상으로 웹팩과 관련한 명령을 수행하는 패키지입니다. webpack.config.js와 같은 설정파일 없이도 webpack-cli를 통해 빌드 시 옵션을 부여할 수 있습니다. webpack v4 이후 버전을 사용하기 위해서는 webpack-cli를 함께 설치해야 한다고 합니다.

설치된 웹팩을 실행하기 위해 package.jsonscript를 추가합니다.

  {
    "name": "my-react-app",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1",
+     "build": "webpack"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "devDependencies": {
      "webpack": "^5.3.1",
      "webpack-cli": "^4.1.0"
    },
  }

추가했다면 다음 커맨드로 빌드를 실행할 수 있습니다.

$ npm run build

또는 script에 커맨드를 추가하지 않더라도 터미널에서 다음 커맨드를 입력하는 방법으로 웹팩을 실행하는 방법도 있습니다.

$ node_modules/.bin/webpack
## 혹은
$ npx webpack

Configuration

이제 설치한 웹팩의 설정을 해보겠습니다.

$ touch webpack.config.js

웹팩 설정 파일 이름의 기본값은 webpack.config.js 입니다. webpack.config.js에 다음과 같이 기본적인 옵션을 설정하겠습니다.

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "main.js",
    path: __dirname + "/dist"
  }
};
  • entry: 번들링의 시작이 되는 파일
  • output: 빌드된 번들의 저장 옵션

entry

entry는 번들링의 시작이 되는 파일을 나타냅니다. 리액트 프로젝트의 경우에도 컴포넌트를 import하는 상위 컴포넌트를 따라 올라가다보면 결국 virtual DOM과 root div를 연결해주는 index.js로 수렴하게 되고, index.js가 바로 빌드의 시작점, entry point가 되는 것입니다.

복수의 entry를 지정하거나 entry 파일을 동적으로 지정하는 기능 등이 가능하며 자세한 내용은 다음 링크에서 확인할 수 있습니다.

https://webpack.js.org/configuration/entry-context/

https://webpack.js.org/concepts/entry-points/

output

output은 빌드된 번들을 어디에, 무슨 이름으로 저장할지 지정해주는 옵션입니다. output.filename은 빌드된 파일의 이름을, output.path는 파일이 저장되는 경로를 나타냅니다. 위에 코드에선 문자열을 직접 만들어서 경로를 지정해줬지만, 아래처럼 node의 path 모듈을 이용해서 경로를 지정할 수도 있습니다.

+ const path = require("path");

  module.exports = {
    entry: "./src/index.js",
    output: {
      filename: "main.js",
-     path: __dirname + "/dist"
+     path: path.resolve(__dirname, "dist")
    }
  };

multiple entry point를 설정했다면 당연히 복수의 output을 설정해줘야 할 것입니다. 웹팩은 output.filenamesubstitutions를 제공하며 이를 통해 자동적으로 unique한 이름의 파일을 생성할 수 있습니다.

output에 대한 자세한 설정은 다음을 참고하면 됩니다.

https://webpack.js.org/configuration/output/

참고 사항

기본값

방금 설정한 entryoutput 값들은 기본값이어서 webpack.config.js에 따로 entryoutput을 지정하지 않아도 빌드 시 웹팩이 자동적으로 ./src/index.js를 entry point로 삼아 ./dist/main.js에 번들링 결과를 저장합니다.

config 파일 이름

다른 이름으로 된 config 파일을 사용할 수 있습니다.

다른 이름의 config 파일을 사용할 땐 웹팩을 실행할 때 --config 옵션으로 해당 파일 이름을 지정하여 빌드하면 됩니다.

{
  "scripts": {
    "build": "webpack --config prod.config.js"
  },
}
$ npx webpack --config prod.config.js

config 파일 확장자

다른 확장자로 끝나는 config 파일을 사용할 수 있습니다.

.ts, .coffee가 가능하며 babel 설치 후 .js 파일에 jsx로 작성하는 것 또한 가능합니다.

https://webpack.js.org/configuration/configuration-languages/

config 파일 자동 생성

$ npx webpack-cli init

@webpack-cli/init 패키지가 설치돼 있어야지만 명령어가 실행되며, 설치되지 않았다면 설치 승인 여부를 묻고 설치를 진행합니다. 그 후에 몇 가지 질문들에 답하면 자동적으로 webpack.config.js 파일을 생성됩니다.

자세한 옵션은 아래 링크에서 확인 가능합니다.

https://webpack.js.org/configuration/#options

Loaders

웹팩은 JavaScript와 JSON 밖에 이해하지 못합니다. 때문에 CSS나 이미지 파일 등 JS 이외의 파일들을 처리하기 위해 loader를 사용합니다. 웹팩은 각 파일 유형에 맞는 loader들을 제공하며 transpiling, testing, linting 등의 전처리 또한 수행합니다.

오늘은 웹팩이 리액트와 JSX를 처리하도록 해보겠습니다. 이를 위해 babel-loader와 관련 패키지를 설치합니다. babel-loader를 잘 활용한다면, ES6+를 ES5로 변환한다던가, TypeScript를 JS로 변환한다던가, React, JSX를 JS로 변환하는 등 babel에서 제공하는 트랜스파일링 기능을 웹팩의 번들링과 연동할 수 있습니다.

$ npm i -D babel-loader @babel/core @babel/preset-env @babel/preset-react
  • @babel/core는 코드 변환과 관련된 API를 지닌 패키지 입니다.
  • @babel/preset-env는 ES6+를 ES5로 변환해주는 plug-in의 모음입니다.
  • @babel/preset-react는 React, JSX를 JS로 변환해주는 plug-in의 모음입니다.

그러면 앞서 작성한 webpack.config.js 파일에 babel-loader 설정을 추가하겠습니다.

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "main.js",
    path: __dirname + "/dist"
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        use: [
          {
            loader: "babel-loader",
            options: {
              presets: ["@babel/preset-env", "@babel/preset-react"]
            }
          }
        ]
      }
    ]
  }
};

loader 관련 설정은 module.rules에서 다룹니다. module.rules에는 어떤 파일을 어떤 로더로 처리할지 등에 관한 규칙rule을 객체로 묶어서 저장합니다.

  • test: 패턴에 해당하는 파일을 대상으로 rule을 적용
  • exclude: 패턴에 해당하는 파일을 대상에서 제외
  • loader: 적용할 로더와 로더의 개별 설정을 지정

test

test는 정규표현식을 사용했습니다.

test 값은 /\.jsx?$/ 라는 정규표현식인데, .js 혹은 .jsx로 끝나는 이름을 가진 파일을 대상으로 한다는 의미입니다. 같은 패턴의 다른 표현으론 /\.(js|jsx)$/이 있겠습니다.

exclude

exclude 또한 정규표현식을 사용했습니다. 패턴에 해당하는 파일은 빌드 시 로더를 적용시키지 않습니다.

위와 같이 exclude: /node_modules/로 하였다면 설치된 dependency 중에서 ./src에 있는 파일이 import 하는 모듈에겐 babel-loader를 적용하지 않게 됩니다.

use

test에서 지정한 파일에 적용할 로더와 로더의 개별 설정을 지정합니다.

use.loader에선 사용할 로더를 나타내며 use.options에선 해당 로더에 적용할 옵션을 의미합니다. 여기선 .js, .jsx 파일에 @babel/preset-env@babel/preset-react를 사용하는 babel-loader를 적용하게 되겠습니다.

babel-loader의 경우, use.options.babelrcbabel.config.js와 같은 바벨 설정 파일로 대체할 수 있습니다. babel-loader의 옵션을 use.options에 적느냐, .bablerc에 적느냐에 차이는 바벨이 읽을 수 있느냐에 있습니다.

  • use.options를 사용하면, 웹팩에서 babel-loader를 실행할 때 인식할 수 있지만, 바벨을 따로 돌릴 땐 인식할 수 없습니다.
  • .babelrcbabel.config.js를 사용하면, 웹팩에서 babel-loader를 실행할 때는 물론, 바벨을 따로 돌릴 때도 인식할 수 있습니다.

참고로 바벨의 preset은 선언한 역순으로 적용이 됩니다.

options.preset["@babel/preset-env", "@babel/preset-react"]와 같이 설정한다면, 코드엔 preset-react가 먼저 적용되고 그 결과에 preset-env가 적용됩니다.

React 코드 -> Vanilla JS의 DOM 조작 코드 -> ES5 코드 처럼 말입니다.

그리고 복수의 로더를 사용할 때도 선언한 역순으로 적용됩니다. 로더 적용 순서에 대한 자세한 내용은 다음 링크에서 확인할 수 있습니다.

https://stackoverflow.com/questions/48339152/webpack-loaders-order-what-are-webpack-pre-and-post-loaders-and-how-they-differ

만약 하나의 로더만을 사용한다면 use.loaderloader로, use.optionsoptions으로 간단히 표현할 수도 있습니다.

  module.exports = {
    entry: "./src/index.js",
    output: {
      filename: "main.js",
      path: __dirname + "/dist"
    },
    module: {
      rules: [
        {
          test: /\.jsx?$/,
          exclude: /node_modules/,
+         loader: "babel-loader",
+         options: {
+           presets: ["@babel/preset-env", "@babel/preset-react"]
+         }
-         use: [
-           {
-             loader: "babel-loader",
-             options: {
-               presets: ["@babel/preset-env", "@babel/preset-react"]
-             }
-           }
-         ]
-       }
      ]
    }
  };

이 외에도 웹팩은 다양한 옵션을 제공하지만, 워낙 수가 많아 하나하나 열거하긴 어려울 것 같습니다. 대신 공식문서에 잘 정리된 자료가 있어 공유하겠습니다.

https://webpack.js.org/configuration/

React

이제 본격적인 리액트 코드를 작성해보겠습니다.

우선 리액트 관련 패키지를 설치하겠습니다.

$ npm i react react-dom

다음으론 entry에 설정한 entry point에 맞추기 위해 src란 디렉토리와 index.js 파일을 생성하겠습니다.

$ mkdir src
$ touch ./src/index.js

index.js에 아래와 같이 작성합니다.

import React from "react";
import ReactDOM from "react-dom";

import App from "./App";

ReactDOM.render(<App />, document.getElementById("root"));

importApp 컴포넌트를 생성하고 아래 코드를 입력합니다.

$ touch ./src/App.js
import React from "react";

export default function App() {
  return <div>Hello, world!</div>;
}

이대로 빌드한다면 output에 적힌대로 ./dist 디렉토리의 main.js에 결과가 저장될 것입니다. 이 main.js 파일을 읽어오는 html 파일을 만들고 아래와 같이 입력합니다.

$ mkdir dist
$ touch ./dist/index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>my-react-app</title>
</head>
<body>
  <div id=root></div>
  <script src="./main.js"></script>
</body>
</html>

Build

이제 준비는 다 끝났습니다.

빌드 명령어를 입력합니다.

$ npm run build

./dist/main.js가 생성된 것을 확인할 수 있습니다. index.html을 열어 리액트가 제대로 랜더링 되는지 확인하면 됩니다.

$ google-chrome ./dist/index.html

References

Initialization

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

https://webpack.js.org/guides/installation/

Configuration

https://webpack.js.org/concepts/

https://webpack.js.org/concepts/entry-points/

https://webpack.js.org/concepts/output/

Loaders

https://webpack.js.org/concepts/

https://webpack.js.org/loaders/

https://babeljs.io/docs/en/core-packages

https://babeljs.io/docs/en/presets

https://stackoverflow.com/questions/43206062/why-do-i-have-to-put-babel-presets-inside-babelrc-and-webpack-config-js

https://stackoverflow.com/questions/48339152/webpack-loaders-order-what-are-webpack-pre-and-post-loaders-and-how-they-differ

https://webpack.js.org/configuration/

Back to top ↑

Leave a comment