[React] React + TypeScript + webpack + babel 프로젝트 초기 셋팅하기

CRA 왜 써?

CRA 는 Create React App의 약자이다.
React를 처음 시작하는 사람들이 쓰기 좋다! 왜냐면 알아서 최적의 개발환경을 만들어주기 때문이다.
CRA는 Webpack과 Babel을 따로 설치, 설정하지 않아도 된다. 그래서 편리하지만 프로젝트 규모가 커지면서 CRA로 빌드하면 속도가 느리고 무거워진다.
그래서 Webpack과 Babel을 이용해 가벼운 React 프로젝트를 배포하기 위해 도전해봤다!
CRA없이 React 설정! 시작해보자.

webpack이,,,뭐야,,?

Webpack은 모듈 번들러이다. Webpack은 의존 관계를 가지고 있는 자바스크립트, CSS, 이미지 등의 리소스들을 하나(또는 여러 개)의 파일로 번들링하게 도와준다.

Bundler : 여러개의 파일을 묶어주는 도구

아직 모든 브라우저가 ES2015 모듈을 지원하지 않아서 모듈 단위로 패키지를 관리할 수 없다.
이런 경우 전역 스코프를 공유하기 때문에 변수명 충돌이나 로딩 순서에 취약해질 수 있다.
webpack을 사용하면 모듈 지원이 불가한 브라우저에서의 의존성 관리 문제를 해결할 수 있다.
또한 webpack 설정을 통해 다양한 기능을 제공받을 수 있다.

내가 생각하는 Webpack을 사용하는 이유는 3가지이다.

  1. 파일 단위의 자바스크립트 모듈 관리
  2. 브라우저별 HTTP 요청 숫자의 제약
  3. Dynamic Loading & Lazy Loading

1. 파일 단위의 자바스크립트 모듈 관리

여러개의 자바스크립트 파일에서 선언된 식별자의 유효 범위는 기본적으로 전역 범위를 갖는다.
따라서 넓은 범위를 갖기 때문에 어디에서나 접근이 가능해서 편리하다.
하지만

<!-- index.html -->
<html>
  <head>
    <!-- ... -->
  </head>
  <body>
    <!-- ... -->
    <script src="./app.js"></script>
    <script src="./main.js"></script>
  </body>
</html>
// app.js
var num = 10;
function getNum() {
  console.log(num);
}
// main.js
var num = 20;
function getNum() {
  console.log(num);
}

위와 같은 경우에 결과는 20이 나온다.
이처럼 만약 복잡한 애플리케이션을 개발하게 되면 변수의 충돌이나 로딩 순서 등에 취약하다는 단점이 있다.
Webpack은 이를 모듈이라는 단위로 관리할 수 있게 도와줘서 이러한 문제를 해결해준다.

2. 브라우저별 HTTP 요청 숫자의 제약

브라우저에서 한 번에 서버로 보낼 수 있는 HTTP 요청 숫자는 제약되어 있다.
보통 크롬의 경우 한 번에 6개의 요청을 할 수 있는데 Webpack을 통해 번들링 하면 요청해야 하는 파일의 수 또한 적어지므로 더 빠른 성능 개선이 가능하다.
또한 요청 횟수를 줄이면 웹 사이트의 로딩 속도가 높아져 사용자에게 좋은 경험을 제공한다.

3. Dynamic Loading & Lazy Loading

SPA의 단점은 초기에 필요하지 않은 페이지에 대한 스크립트들도 불러온다는 것이다.
하지만 웹팩을 통한 모듈 번들링을 이용하면, 동적으로 나중에 필요한 자원들은 나중에 요청하는 것이 가능하다.

webpack 환경 설정

webpack.config.js 을 통해 설정을 진행해보자.

1. Entry point

Entry point는 내부 종속성 그래프 작성을 시작하기 위해 번들링 프로세스를 시작할 지점(진입점)이다.
webpack은 Entry point에 의존하는 다른 모듈과 라이브러리를 파악한다.

HTML 페이지 하나당 하나의 entry point

  • SPA : 1개 entry point
module.exports = {
  entry: "./src/index.tsx",
}
  • MPA : 여러개의 entry point
module.exports = {
  //...
  entry: {
    home: './home.tsx',
    about: './about.tsx',
    contact: './contact.tsx',
  },
};

2. Output

output 옵션은 번들링 결과가 저장되는 경로(path)파일명(filename)이다.
entry point는 여러개 있을 수 있지만,
output은 오직 하나만 지정할 수 있다.

module.exports = {
  output: {
    path: path.join(__dirname, "/dist"),
    filename: 'bundle.js', // entry point가 1개인 경우
    // filename: '[name]_bundle.js', // entry point가 여러개인 경우 
  }
};

public 폴더에 name_bundle.js 파일이 생성된다.

3. Loader

입력한 assets을 loader를 통과시키면 가공하여 원하는 output을 만들어준다. (= 가공 공장)

webpack은 기본적으로 js, json만 이해한다.
loader 사용하여 파일을 사전 처리할 수 있다.
이를 통해 JavaScript 이외의 모든 정적 리소스(이미지나 css파일)를 번들로 묶을 수 있다.
왜냐하면 번들링 과정에서 loader가 JavaScript로 변환시켜주기 때문이다.
cf. rules의 뒤쪽의 loader가 먼저 실행된다.

대표적인 loader 몇가지 소개하겠다.

  • css-loader : css파일 읽어와 webpack으로 가져오는 역할
  • style-loader : 가져온 css 코드를 web page 안에 style 형식으로 DOM에 삽입
  • file-loader : 폰트나 이미지 등의 타입의 파일을 해석
module.exports = {
  module: {
    rules: [
      {
        test: /\.(ts|tsx)$/,
        exclude: /node_modules/,
        use: ["babel-loader", "ts-loader"],
      },
      {
        test: /\.(jpg|png|gif|svg)$/,
        use: ["file-loader"],
      },
      {
        test: /\.css$/, // loader를 적용할 파일(정규식으로 작성)
        use: ['style-loader', 'css-loader'], // 사용할 loader
      },
    ],
  },

};

참고로 나는 CSS-in-JS 방식을 사용할 것이기 때문에 css-loader와 style-loader 가 필요 없다.

4. Plugin

Plugin은 웹팩의 기본적인 동작에 추가적인 기능을 제공하게 해준다.
이는 Loader가 할 수 없는 일들을 수행한다.
번들된 파일을 난독화, 압축할 수도 있고, 파일복사, 파일추출, 별칭사용 등과 같은 작업을 수행할 수도 있다.

Loader : input을 output으로 만들어나가는 과정에 관여하여 모듈을 처리한다.
Plugin : 최종적인 output(번들링된 파일)을 변환한다.

대표적인 플러그인부터 소개하겠다.
HtmlWebpackPlugin
번들 파일을 포함하는 HTML 파일을 자동으로 생성해서 번들 주입에 신경쓸 필요가 없다.
결과로 생성된 번들을 사용하려면 script 태그를 추가해줘야하는데, 자주 변경된다면 매번 수정하기 번거롭다. 이를 해당 플러그인이 해결해준다.

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

module.exports = {
  	//...
    plugins: [
        new HtmlWebpackPlugin({
            template: './index.html', // 기준이 되는 html
            filename: 'indexResult.html', // 결과적으로 생성되는 파일 이름
            chunks:['entry'], // entry point -> index.js가 번들링된 결과가 HTML에 추가 
        }),
    ]
};

이 외에 자주 쓰이는 plugin들

  • dotenv-webpack : .env에 있는 변수를 가져올 수 있도록 해주는 플러그인
  • clean-webpack-plugin : 성공적으로 빌드 후, 기존 빌드 폴더를 제거해주는 플러그인
  • mini-css-extract-plugin : 별도 css 파일을 만들어서 빌드해주는 플러그인

5. webpack-dev-server

웹팩을 이용하는 개발 서버 도구이다. 자동으로 새로고침해주며, 실제 배포서버와 비슷한 환경을 제공한다.
사용한 번들링 결과는 매번 새로고침해서 확인해야 하는데, 이러한 방식은 실제 배포 서버 환경과 다를 수 있고, 매번 새로고침하면 개발 생산성이 떨어진다.

  • static : 정적 파일 제공하는 폴더 경로. (기본 : public)
  • port : 포트 번호 (기본 : 8080)
  • https : https 프로토콜 사용 (기본 : http)
  • host : 쿠키를 사용하거나 인증이 필요한 경우 동일한 도메인으로 맞춘다.
  • hot : Hot Module Replacement(HMR) 활성화

HMR(Hot Module Replacement)
페이지 변경을 처리할 때 전체를 다시 리로드하는 것이 아닌 변경 사항만 업데이트하는 기능이다. 이미 네트워크상에서 fetch한 데이터가 있거나 이미 계산된 상태를 다시 계산하는 것이 비효율적인 경우 HMR을 사용하여 개발 속도를 절약할 수 있다.

module.exports = {
  //...
  devServer: {
    port: 3000,
    open: true,// 서버가 시작된 후 브라우저를 열도록 dev-server에 지시한다. 
    hot: true,
  },
};

6. devtools

번들링된 파일과 원본 파일을 서로 매핑하여 원활한 디버깅을 도와준다.

module.exports = {
  //...
  devtool: "eval-source-map", //  원본 소스를 그대로 나타내준다.
};

7. Mode

각 환경에 해당하는 webpack의 내장 최적화를 사용할 수 있다.

module.exports = {
  mode: 'development', // or "production" or "none"
};
  1. development : 개발자 모드
  2. production : 제품으로 배포되는 모드 (default)
  3. none : 아무것도 setting하지 않는 모드

babel은 누구세요,,?

babel은 자바스크립트 컴파일러이다.

ECMAScript 2015+ 코드를 이전 버전의 JavaScript로 변환하는데 사용되는 도구이다. 런타임이 아닌 빌드 타임에 번들러와 같은 도구를 사용하여 실행된다.
1. 변환 구문
2. 대상 환경에 누락된 폴리필 기능
3. 소스 코드 변환

폴리필(polyfill)

Promise, Map 처럼 트랜스파일만으로 해결할 수 없는 명세를 구현한 것이다.
구형 브라우저에 런타임 시 주입하여 해당 명세가 구현된 것처럼 사용한다.
babel을 사용하여 간단하게 추가할 수 있다.

babel 설정은 webpack같은 번들러를 통해 트랜스파일을 진행한다.

webpack을 통해 babel 설정을 진행해보자!

babel 환경 설정

npm i core-js
npm i -D babel-loader

core-js의 폴리필을 사용하기 위해 useBuiltIns와 corejs 옵션을 추가해야한다.
useBuilIns : usage(자동으로 필요한 폴리필 주입) | entry (직접 주입)
corejs : 사용할 core-js 패키지 버전

babel.config.js

module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        useBuiltIns: "usage",
        corejs: 3,
      },
    ],
    "@babel/preset-react",
    "@babel/preset-typescript",
  ],
  plugins: ["@babel/plugin-proposal-class-properties"],
};

webpack.config.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.(ts|tsx)$/,
        exclude: /node_modules/,
        use: ["babel-loader", "ts-loader"],
      },
      //...
    ],
  },
};

잠시만,, 왜 인터프리터 언어에 컴파일러가 필요하지?
바벨은 JavaScript로 결과물을 만들어주는 컴파일러다.
프론트엔드의 발전은 너무 빠르기 때문에 이러한 새로운 ESNext의 문법을 기존 브라우저에 사용하기 위해서 Babel은 필수이다.

추가할 내용.
1. CRA로 했을 때 webpack, babel 설정하는 방법. 복잡한 이유.
2. craco 사용하면 CRA 웹팩 설정을 덮어씌워주는 건가?
3. CRA 안하고 babel만 사용하면 craco 사용 안해도 되나?
4. craco의 경로설정 이외의 기능
5. mode에 따른 설정 파일 나눠서 관리했을 때 장점과 단점
6. react18에서 바뀐 내용들 정리.

https://velog.io/@eastshine94/webpack-webpack-%EC%84%A4%EC%B9%98%EB%B6%80%ED%84%B0-%EC%8B%A4%ED%96%89%EA%B9%8C%EC%A7%80
https://chanyeong.com/blog/post/7
https://byul91oh.tistory.com/387

좋은 웹페이지 즐겨찾기