💎 들어가며

프로젝트를 세팅하면서 항상 골치 썩었던 webpack과 babel 설정을 해보면서 정리한 내용을 바탕으로 포스팅을 작성하였습니다.

이번 포스팅에서는 webpack에 좀더 초점을 두고 작성하였습니다.


프로젝트 스펙

  • node: v20.10.0
  • react: 18.2.0
  • typescript: 4.9.5
  • webpack: 5.90.3
  • babel: 7.23.9

1. babel

1.1 babel이란?

babel이란 javascript compiler로 최신 버전(ES6)의 자바스크립트를 구형 버전(ES5)의 자바스크립트로 변환 시켜줍니다.


1.2 babel 설정

모듈 추가

yarn -D @babel/core @babel/preset-env
@babel/preset-react @babel/preset-typescript
  • core: babel의 기본적인 것들
  • preset-env : babel이 자동으로 최신 문법으로 변경해준다.
  • preset-react, preset-typescript : 리액트, 타입스크립트를 자바스크립트로 지원

설정 파일 생성

프로젝트 상단에 babel.config.js를 생성합니다.

touch babel.config.js

babel 설정 파일에 앞서 설치한 플러그인들을 넣어줍니다.

module.exports = {
    presets: [
        "@babel/preset-react",
        "@babel/preset-env",
        "@babel/preset-typescript",
    ],
};


2. Webpack

2.1 Webpack이란?

webpack 로고

웹팩은 모듈 번들러입니다.

모듈 번들러는 웹 어플리케이션을 구성하는 자원(HTML, CSS, JavaScript, Image 등)을 모두 각각의 모듈로 보고 이를 조합해서 병합(번들링, bundling)하는 역할을 수행합니다.


2.2 Webpack의 장단점

장점

  • 여러 리소스를 압축하여 최적화하기 때문에 로딩에 대한 네트워크 비용 감소 효과를 가져온다.
  • 모듈 단위로 개발이 가능하여, 가독성과 유지보수가 쉽다.
  • 최신 자바스크립트 문법을 지원하지 않는 브라우저에서도 사용할 수 있다.
  • 개발자가 개발 작업의 빌드 시스템을 완전히 컨트롤할 수 있으며 이를 통해 모던 프론트엔드 웹 개발 생태계를 더욱 잘 이해할 수 있다.

단점

  • 비교적 복잡한 configuration
  • 개발 모드 속도 (프로젝트가 크면 클수록; 하지만 이점은 Webpack 5에 새로 추가된 caching 옵션을 통해 개선되었다고 본다)
  • Webpack의 번들 크기

문제점과 해결 방법

  • 번들러의 사용으로 많은 리소스가 하나의 파일로 병합되면 초기 로딩 비용이 커질 수 있다.
  • 이러한 이슈에 대해 웹팩은 청크, 캐시, 코드 스플릿과 같은 기능를 도입하여 해결하였다.

2.3 Webpack 설정

모듈 추가

yarn add -D webpack webpack-cli webpack-dev-server
  • webpack: 번들 작업을 하는 웹팩 코어
  • webpack-cli: 웹팩 터미널 도구, 웹팩을 커맨드 라인에서 사용
  • webpack-dev-server: 웹팩을 메모리 상에 빌드하여 개발 서버 구동

설정 파일 생성

웹팩의 기본 설정 파일 명은 webpack.config.js입니다. 루트 폴더에 웹팩 설정 파일 webpack.config.js을 생성합니다.

# 설정 파일 생성
touch webpack.config.js

# webpack 플러그인 모듈 추가
yarn add -D html-webpack-plugin clean-webpack-plugin

# 로더(loader) 모듈 추가
yarn add -D babel-loader ts-loader file-loader style-loader css-loader

# process 모듈 추가
yarn add process

완성된 webpack.config.js을 먼저 공개하자면 아래와 같습니다.

const HtmlWebpackPlugin = require("html-webpack-plugin");
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
const path = require("path");
const webpack = require("webpack");

module.exports = (env, argv) => {
    const prod = argv.mode === "production";

    return {
        mode: prod ? "production" : "development",
        devtool: prod ? "hidden-source-map" : "eval",
        entry: {
            main: path.resolve(__dirname, "src/index.tsx")
        },
        output: {
            path: path.resolve(__dirname, "dist"),
            filename: "[name].bundle.js",
            clean: true,
        },
        devServer: {
            // 포트 번호 설정
            port: 9000,
            // 핫 모듈 교체(HMR) 활성화 설정
            hot: true,
            // gzip 압축 활성화
            compress: true,
            // History 라우팅 대체 사용 설정
            historyApiFallback: true,
            // 개발 서버 자동 실행 설정
            open: true,
        },
        resolve: {
            extensions: [".ts", ".tsx", ".js", ".jsx", "..."],
        },
        module: {
            rules: [
                {
                    test: /.(ts|js)x?$/,
                    exclude: /node_modules/,
                    use: ["babel-loader", "ts-loader"],
                },
                {
                    test: /\.css$/,
                    use: ["style-loader", "css-loader"],
                },
                {
                    test: /\.(jpg|png|gif|svg|ico)$/,
                    use: [
                        {
                            loader: "file-loader",
                            options: {
                                name: "[name]_[hash].[ext]",
                                outputPath: "images",
                            },
                        },
                    ],
                },
            ],
        },
        plugins: [
            new webpack.ProvidePlugin({
                process: "process/browser.js",
            }),
            new webpack.ProvidePlugin({
                React: "react",
            }),
            new HtmlWebpackPlugin({
                template: "./template/index.html",
                favicon: "./src/assets/images/favicon.png",
                minify:
                    process.env.NODE_ENV === "production"
                        ? {
                            collapseWhitespace: true, // 빈칸 제거
                            removeComments: true, // 주석 제거
                        }
                        : false,
            }),
            new CleanWebpackPlugin(),
        ],
    };
};
  • mode: 개발, 프로덕션 모드 확인
  • devtool: 모드에 따라 SourceMap 확인 여부
  • entry: 시작점 경로를 지정하는 옵션, 해당 파일부터 필요한 모듈 로딩 및 하나의 파일로 번들링
  • output: webpack이 번들링 결과물을 위치할 경로 및 이름
  • devServer: 개발 서버 포트(port) 및 실시간 여부(hot)
  • resolve: 배열 안 확장자에 따라서 처리
  • module: loader 설정 / babel-loader, ts-loader
  • plugins: 부가 기능을 수행할 플러그인 추가

2.4 애플리케이션 실행

먼저 애플리케이션 실행 옵션을 변경하기 위해 package.jsonscripts 옵션을 수정해줍니다.

{
  "scripts": {
    "build": "webpack --mode=production --progress",
    "start": "webpack serve --mode=development --open --hot --progress",
  },
}

터미널을 켜고 아래 명령을 수행합니다.

npm run start

혹은

yarn start

2.5 Webpack Command

웹팩은 번들러, 즉 컴파일 도구입니다. 이렇게 컴파일된 코드는 컴파일러 런타임에 의해 실행되어야됩니다.

따라서 웹팩을 쓰는 순간 빌드 및 실행은 웹팩에 위임하게 됩니다. 웹팩의 명령어에는 다음과 같습니다.

  • build: (bundle, b): webpack 실행 (default command, 생략 가능)
  • configtest: (t): webpack 설정 파일이 올바른지 테스트
  • info: (i): 내 시스템 정보 확인
  • serve: (server, s): webpack 개발 서버를 실행하고, 서버가 실행하는 동안 소스파일 변화 감시
  • version: (v): webpack과 관련된 모듈들의 버전을 보여준다. info와 결과값 동일
  • watch: (w): webpack을 실행하고, 실행하는 동안 소스파일 변화 감시

build

웹팩의 주요 기능인 번들링(컴파일) 기능입니다.

아래와 같은 커맨드로 webpack을 실행할 수 있습니다.

> webpack
> webpack build
> webpack bundle
> webpack b

웹팩을 실행하면 번들링 결과를 얻을 수 있고, 번들링에 실패하면 에러가 발생합니다.


configtest

webpack 설정 파일이 올바른지 테스트합니다.

> webpack configtest
> webpack t

[webpack-cli] Validate 'D:\Git\hjkim1004.github.io\webpack.config.js'.
[webpack-cli] There are no validation errors in the given webpack configuration.

info

웹팩을 실행하는 런타임에 관련된 모든 정보를 보여줍니다.

> webpack info
> webpack version
> webpack i
> webpack v

webpack info 실행 결과


serve

웹팩 개발 서버를 실행합니다.

> webpack serve
> webpack server
> webpack s

webpack serve 실행 결과


2.6 Webpack Concepts

webpack은 위의 명령어 및 설정 파일을 이해하면 쉽게 접근할 수 있습니다.

webpack Config 파일 구조 분석
출저 - 하늘네트 블로그

mode

mode는 현재 프로젝트가 운영되는 환경을 정의합니다.

mode 값을 설정하지 않을 경우 default 값은 none이나, 실제로 mode로 동작하지는 않습니다.

그 외에는 development, production 의 상태를 갖을 수 있습니다. 각 모드에 따라 프로젝트 구동방식이 달라집니다.

  • development: 소스 매핑을 쉽게 사용하여 오류를 찾아내고 원본 파일 추적이 쉬워짐.
  • production: 파일 축소, 최적화 우선
module.exports = {
  mode: "development", // or "production"
};

Entry

Entry에서는 애플리케이션의 진입점을 설정합니다.

단일 페이지 애플리케이션(SPA)의 경우 하나의 진입점이 있어야 하고, 다중 페이지 애플리케이션(MPA)의 경우 여러개의 진입점이 있어야 합니다.

const path = require("path");

module.exports = {
  /* ... */
  entry: {
    main: path.resolve(__dirname, "src/index.js"),
  },
};

Output

마지막으로, 애플리케이션 번들링 프로세스가 완료된 후 번들된 리소스들을 출력할 위치를 선언합니다.

const path = require("path");

module.exports = {
  /* ... */

  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "index.bundle.js", // using static name
    // OR
    // using the entry name, sets the name to main,
    // as specific in the entry object.
    filename: "[name].bundle.js", 
  },
};

Loader

기본적으로 webpackJavaScriptJSON(JavaScript Object Notation) 을 이해하지만 이미지 파일이 무엇인지, HTML, CSS, SASS 또는 SVG 파일(다른 유형의 파일 포함)은 알 수 없습니다. 그것들을 처리하는 방법을 모릅니다.

그렇기에 여러가지 loader를 설치하여 다른 유형(typescript, css, file etc)의 파일을 Webpack 이 이해하고 처리가능한 모듈로 변환시킬 수 있습니다.

예를 들어, css를 처리하는 module을 추가하는 방법입니다.

yarn add -D style-loader css-loader
const path = require("path");

module.exports = {
  /* ... */
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
    ]
  }
};

devServer

개발 서버 실행과 관련된 옵션을 적어줍니다.

{
  devServer: {
    // 포트 번호 설정
    port: 9000,
    // 핫 모듈 교체(HMR) 활성화 설정
    hot: true,
    // gzip 압축 활성화
    compress: true,
    // History 라우팅 대체 사용 설정
    historyApiFallback: true,
    // 개발 서버 자동 실행 설정
    open: true,
  },
}

3. 경로명 정의하기

3.1 절대 경로와 상대 경로

절대 경로란 최상위 디렉토리가 반드시 포함 된 경로를 의미하며,
상대 경로 란 현재 디렉토리(비교 대상)를 기준으로 작성된 경로를 의미합니다.


절대 경로와 상대 경로의 예시입니다.

// 상대 경로로 아바타 컴포넌트 불러오기
import Avatar from '../../../components/user/Avatar'

// 절대 경로로 아바타 컴포넌트 불러오기
import Avatar from 'components/user/Avatar'

코드를 보면 절대 경로를 사용하면 깔끔하고 가독성 높은 코드를 사용할 수 있는 것을 볼 수 있습니다.


3.2 절대 경로 설정

절대 경로 설정은 의외로 간단합니다.

tsconfig.json 파일에서 compilerOptions 아래의 baseUrl 속성을 추가해줍니다.

※ tsconfig.json 파일은 프로젝트를 컴파일하는데 필요한 루트 파일과 컴파일러 옵션을 지정하는데 사용합니다.

{
  "compilerOptions": {
    // 최상위 소스 디렉터리 경로
    "baseUrl": "src",
  }
}

3.3 Alias 경로 설정

Alias 설정을 하면 좀더 쉽게 경로를 찾을 수 있습니다.

// 상대 경로로 Header 컴포넌트 불러오기
import Header from '../../../components/layout/header'

// Alias 경로로 Header 컴포넌트 불러오기
import Header from '@Layout/header'

// Alias 경로로 Noto Sans css 불러오기
import "@Fonts/notosans.css";

webpack 설정

우선, 번들링하는 webpack이 경로를 알아야하기 때문에 설정 파일인 webpack.config.js 파일을 수정해줍니다.

resolve 옵션을 통해 아래 설정한 확장자(extension)의 경로에 이름(alias)을 지정해줍니다.

resolve: {
    extensions: [".ts", ".tsx", ".js", ".jsx", "..."],
    alias: {
        "@Components": path.resolve(__dirname, "src/components/"),
        "@Layout": path.resolve(__dirname, "src/components/layout"),
        "@Pages": path.resolve(__dirname, "src/pages/"),
        "@Images": path.resolve(__dirname, "src/assets/images"),
        "@Fonts": path.resolve(__dirname, "src/assets/fonts"),
        "@Data": path.resolve(__dirname, "src/data"),
        "@Context": path.resolve(__dirname, "src/context"),
    },
},

typescript 설정

typescript를 사용하면 타입을 검사 받기 때문에 컴파일 오류를 겪지 않으려면 tsconfig.json 파일에 타입에 관련된 설정을 추가해주어야 합니다.

paths 아래에 Alias로 설정한 것들을 같이 적어줍니다.

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": false,
    "jsx": "react-jsx",
    // 최상위 경로 설정
    "baseUrl": "src",
    // Alias 경로 설정
    "paths": {
      "~/*": ["~/*"],
      "@Components/*": ["components/*"],
      "@Layout/*": ["components/layout/*"],
      "@Pages/*": ["pages/*"],
      "@Images/*": ["assets/images/*"],
      "@Fonts/*": ["assets/fonts/*"],
      "@Data/*": ["data/*"],
      "@Context/*": ["context/*"]
    }
  },
  "include": ["src"],
  "exclude": ["node_modules"]
}


💎 마치며

매번 프로젝트 마다 설정해주니 새로 시작할 때마다 까먹어서 이리 정리하게 되었지만, 웹팩을 처음 접할 때보다는 상당히 수월하게 이해할 수 있게 되었습니다.

역시 반복 노출이 최고의 공부법인가 봅니다😁


💎 References

profile
Java, Spring 기반 풀스택 개발자의 개발 블로그입니다.

0개의 댓글