CRA 없이 리액트 시작하기-(2) 웹팩 설정하기

가연·2024년 7월 14일
2

우테코

목록 보기
4/10
post-thumbnail

패키지 매니저를 정했으니 이제 필요한 패키지들을 설치해보자

📦 패키지 설치 한번에 보기

  1. yarn init-y
    package.json 파일을 생성해준다.
    직접 package.json 파일을 커스텀해 줄 것 이기 때문에 -y(--yes) 로 질문들을 스킵해준다.

  2. yarn add react react-dom
    yarn add -D typescript @types/react @types/react-dom
    리액트와 리액트 돔, 타입스크립트를 설치해준다.
    이때 타입스크립트는 개발할때만 필요하기 때문에 devDependency로 설치해준다.

  3. yarn add -D webpack webpack-cli webpack-dev-server
    웹팩과 같은 번들러는 개발단계에서만 필요하기 때문에 이 아래부터는 -D 로 설치해준다.
    webpack-cli : 웹팩 커맨드를 터미널에서 실행할 수 있게 해줌.
    webpack-dev-server : 개발서버를 열 수 있게 해줌.

  4. yarn add -D ts-loader file-loader
    yarn add -D html-webpack-plugin copy-webpack-plugin
    로더와 플러그인을 설치해준다.
    (아래에서 자세한 설명을 볼 수 있다.)

🎀 webpack

의존성이 있는 모듈들을 찾아 하나의 자스 파일로 번들링 해주는 정적 모듈 번들러 이다.

🧱 모듈, 번들러란?

모듈

모듈이란 재사용 가능한 코드 블럭 이다.

웹팩에서의 모듈이란 웹을 구성하는 모든 자원을 뜻한다.( .js 파일, html, css, font 등…)

만약 js 파일이 모듈이 아닌 일반 스크립트라면 변수의 유효 범위가 없어서

<script src="./app.js"></script>
<script src="./main.js"></script>

html 파일에서 이와 같이 두 자스 파일을 로딩하게 된다면 app.js 과 main.js 가 서로 영향을 미칠 수 있게 된다.

그렇기 때문에 파일 단위로 변수를 관리하고 싶어 필요한 모듈을 불러오거나 코드를 모듈 단위로 구성해주는 모듈시스템이라는 라이브러리가 등장했다. (CommonJS , AMD 등)

💡 모듈은 일반 스크립트와 다음과 같은 점에서 구분된다.

  • 모듈은 항상 defer 속성을 붙인 것 처럼 지연 실행 된다.(body 끝에 script 를 넣어주지 않더라도!)
  • 모듈은 엄격모드로 실행된다.
  • 모듈은 자신만의 스코프를 가진다.
  • 모듈은 한번만 실행되고 공유된다.
  • 모듈 최상위 레벨 this 는 undefined 다.
  • import.meta 객체로 정보를 얻을 수 있다.

브라우저 환경에서는 보통 모듈을 단독으로 사용하기보다 번들링 해서 배포서버에 올리는 방식을 사용한다.

번들러

번들러란 js,css,이미지 등의 파일을 묶어주는 역할을 하는 것을 뜻한다. 번들링이 끝난 모듈은 일반 스크립트처럼 취급한다.(import,export 가 사라지며 type=”module”이 필요 없어짐)

모듈 번들링이란 모듈을 브라우저가 인식할 수 있도록 함수 형식으로 변환시킨 다음 모듈 간에 참조할 수 있도록 하나의 파일로 합치는 것을 의미한다.

❔ 웹팩은 왜 쓸까?

  • http 요청 횟수를 줄여 빠른 서비스를 제공해준다.
  • 웹서버 배포 시 html, css, js, 이미지 압축 및 css 전처리기 변환 과 같은 일을 자동화 해준다.
    • 모듈을 import 해주기만 하면 웹팩이 알아서 빌드해준다. 따라서 새로고침 없이 빌드 결과를 확인할 수 있다.
    • 기존에는 코드 수정 시 다시 빌드하고 새로고침 해야 빌드 결과를 확인할 수 있었다.
  • 웹팩의 code splitting 으로 원하는 타이밍에 모듈을 불러올 수 있다.

👩‍👩‍👧‍👦 웹팩의 구성

웹팩은 번들링 과정에서 의존성 그래프를 그린다.

특정 지점(Entry)에서 출발해서 애플리케이션에 필요한 모든 모듈을 포함하는 그래프를 완성해 나간다. 그래프를 완성하면 이 모든 모듈을 번들링 후 브라우저에 로드 할 준비를 한다.

🛫 Entry

웹팩이 어떤 모듈을 사용하여 번들을 시작할지를 알려주는 역할.

entry: './src/index.tsx',

🛬 Output

웹팩이 생성한 번들을 출력할 위치를 지정하는 속성.
번들링된 파일의 이름과 저장될 경로를 정할 수 있다.

    output: {
    path: path.resolve(__dirname, 'dist'),
    clean: true,
      // build 시작 전 dist 파일을 정리해준다.
      // 이걸 설정해주면 clean-webpack-plugin 을 설치 할 필요가 없다!

  },
  

🚏Loader

웹팩은 기본적으로 js 와 JSON 파일만 이해할 수 있다.

사용하려는 포맷에 대응하는 로더를 설정해주면 js 가 아닌 다른 포맷의 리소스도 디펜던시 그래프에 추가할 수 있다.(파일 해석, 변환 과정에서 필요)

loader 설정에는 test, use 라는 필수 속성을 설정해줘야 한다.

test 는 어떤 파일을 변환할지 지정,
use 는 파일을 변화할 때 어떤 로더를 사용해야 하는지 명시하는 속성이다.

module: {
  rules: [
    {
      test: /\.(ts|js)x?$/,
      exclude: /node_modules/,
      use: ['ts-loader'],
    },
     {
      test: /\.(png|svg|jpg|gif)$/,
      use: {
        loader: 'file-loader',
        options: {
          name: '[name].[ext]',
        },
      },
       
    // {
    //   test: /\.css$/,
    //   use: ["style-loader", "css-loader"],
    // },
    // css-loader는 css 파일들을 읽어주고 style-loader는 읽은 css 파일들을 style 태그로 만들어 head 태그 안에 넣어준다.
    // 우리는 panda css 를 사용하며 css 파일을 따로 두지 않을 것이기 때문에 설치해주지 않았다.
    },
  ],
},

ts-loader, file-loader 를 설치했다.

❓ ts-loader vs babel (vs esbuild)
babel(babel-loader) : 트랜스파일러. tsconfig.json 의 옵션을 확인하지 않는다. (대신 @babel/preset-typescript 의 기본 옵션 사용)
타입체크를 진행하지 않기 때문에 ts-loader 보다 빠르다.
ts-loader(tsc) : 트랜스파일링을 진행할때 tsconfig.json 의 옵션을 따라 타입 체킹을 한다.
esbuild(esbuild-loader) : 타입체크를 하지 않지만 tsconfig 옵션을 참고한다.(일부만) 빌드 속도가 가장 빠르다고 한다.
속도 차이가 궁금하다면 npx webpack 명령어를 통해 확인해보자!

우리는 빌드 속도 보다는 타입 체크를 하는게 더 중요하다고 생각해서 ts-loader 를 설치해주었다.
그리고 로고 이미지를 넣을 예정이라 file-loader 도 같이 설치,설정해주었다.

🕹️ Plugin

플러그인은 웹팩의 기본적인 동작에 추가적인 기능을 제공하는 속성이다.(결과물의 형태를 바꿈)

설치한 플러그인

  1. html-webpack-plugindist 의 main.js를 스크립트 파일로 포함하는 html 문서를 dist 디렉토리 내에 자동으로 생성하는 역할을 한다. 이 플러그인을 설치해주지 않는다면 빌드 시 직접 html 파일을 작성해서 js,css 파일들을 넣어주어야 한다.(내용이 변경될때마다 html 파일도 수정해야됨!)
  1. copy-webpack-plugin 웹팩 빌드 시점에서 파일을 복사하는 플러그인. 웹팩으로 빌드한 결과물 외에 원하는 파일 추가로 복사할 수 있다. public 아래에 존재하는 이미지, 문서 등을 빌드 후에도 사용하기 위해 설치해주었다. 만약 이 플러그인이 없다면 public 폴더의 정적 파일들을 수동으로 dist 폴더에 복사해주어야 할 것이다.
    plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
      filename: './index.html',
    }),
    new CopyWebpackPlugin({
      patterns: [{ from: 'src/assets', to: 'assets/' }],
    }),
    ],

우리는 이정도의 플러그인만 설치하고 나머지는 프로젝트 중간에 직접 불편함을 겪은 후 추가로 설치해보기로 했다.

(+) webpack dev server

package.json 에서

  "scripts": {
    "start": "webpack serve",
    "build": "webpack --mode=production"
    // 아직 프로덕션과 개발 환경을 분리하지 않아서 임의로 작성했다.
  },

로 설정해주어서 yarn start 를 통해 개발서버를 열 수 있다.
개발 서버를 설정해주자.

  devServer: {
    client: {
      overlay: true, // 컴파일 오류나 경고가 발생하면 브라우저 화면에 표시
      progress: true, // 브라우저 콘솔에 빌드 진행 상태를 표시
    },
    compress: true, // 모든 served 파일에 대해 gzip 압축을 활성화
    hot: true, 
    open: true, // 서버 시작 시 브라우저를 자동으로 연다.
    port: 3000, // 개발 서버가 실행될 포트
    historyApiFallback: true, 

  },
  
  • hot :
    모듈의 핫 리플레이스먼트를 활성화한다. (변경된 모듈만 갱신)
    즉 코드 수정 시 자동으로 컴파일하여 개발서버에 적용하는 기능이다.
  • historyApiFallback :
    HTML5 History API를 사용하는 SPA 개발 시 필요한 설정이다. useNavigate나 Link 도 내부적으로 HTML5 History API를 사용하기 때문에 꼭 설정해주자.
    +) 개발서버에서는 요청된 경로에 대한 정적 파일을 제공하는데 리액트는 SPA 라서 동적으로 생성된 컴포넌트를 렌더링 해주어야 한다.
    이 설정을 해 주어야 모든 경로에 대해 index.html을 반환하게 해서 적절한 라우팅을 할 수 있게 된다.
    관련 레퍼런스

🚨 (++) 마주친 에러

  1. ReferenceError: __dirname is not defined
    CommonJS가 아닌 ES6의 type: "module" 를 사용할 경우 에러가 발생한다. 우리는 es6 로 통일하기 위해 __dirname 변수를 따로 선언해주었다.
    https://velog.io/@suyeonpi/Node.js-dirname-is-not-defined-ES6

  2. typescript emitted no output
    webpack + ts-loader 환경에서는 noEmit 설정을 false 로 해주어야 한다.
    https://github.com/TypeStrong/ts-loader/issues/1602

💻 전체 코드

// webpack.config.js
import path from 'path';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import CopyWebpackPlugin from 'copy-webpack-plugin';
import { fileURLToPath } from 'url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

const config = {
  mode: 'development',
  entry: './src/index.tsx',
  output: {
    path: path.resolve(__dirname, 'dist'),
    clean: true,
  },
  module: {
    rules: [
      {
        test: /\.(ts|js)x?$/,
        exclude: /node_modules/,
        use: ['ts-loader'],
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: {
          loader: 'file-loader',
          options: {
            name: '[name].[ext]',
          },
        },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
      filename: './index.html',
    }),
    new CopyWebpackPlugin({
      patterns: [{ from: 'src/assets', to: 'assets/' }],
    }),
  ],
  resolve: {
    extensions: ['.js', '.jsx', '.ts', '.tsx'],
  },
  devServer: {
    client: {
      overlay: true,
      progress: true,
    },
    compress: true,
    hot: true,
    open: true,
    port: 3000,
    historyApiFallback: true,
  },
};

export default config;

참고한 레퍼런스

https://365kim.tistory.com/35

https://joshua1988.github.io/webpack-guide/motivation/why-webpack.html#웹팩의-등장-배경

https://engineering.linecorp.com/ko/blog/write-you-a-webpack-for-great-good

https://kagrin97-blog.vercel.app/other/webpack-basic-plugin

3개의 댓글

comment-user-thumbnail
2024년 7월 17일

썸네일이 너무 깜찍하네요~💕

1개의 답글
comment-user-thumbnail
2024년 9월 11일

와 이제 이 글읽고 웹팩 배워야겠네요~

답글 달기