create-react-app 없이 리액트 앱 빌드하기 2 - Webpack

베이시스·2022년 7월 8일
0
post-thumbnail

들어서며

정말 오랜만에 글을 쓰게 되었습니다. 이번에는 Babel과 세트로 등장하곤 하는 Webpack에 대해 알아보도록 하겠습니다.

웹팩은 의존성을 가진 여러 자바스크립트 모듈을 사전 지정된 규칙으로 합쳐 주는 번들러입니다. 그런데 모듈은 또 뭐고, 의존성은 또 뭐고, 번들링은 또 뭐란 말인가요? 대체 그런 게 왜 필요한 걸까요?

그냥 자바스크립트 쓰면 되는 거 아니야?

언제나 그렇듯 가능 합니다. 그러나 자바스크립트를 HTML에서 바로 가져올 경우 필연적으로 스크립트는 전역 스코프를 건드리게 되어 전역 스코프의 오염을 피할 수 없습니다.

let a = 100;

const setNumber = function() {
  a = 50;
  console.log(a);
}

setNumber();
console.log(a);

이 때 setNumber()console.log()50을 출력하며 이는 자명합니다. 그런데 마지막 줄의 console.log() 는 무엇을 출력할까요?

출력은 100이 아닌 50입니다. 함수 스코프 내에서 a = 50 을 수행함으로써 전역 스코프의 a 의 값이 바뀌어 버린 것입니다.

미봉책, IIFE

전역 스코프가 오염되는 문제를 해결하기 위해 즉시실행 함수 표현식(IIFE, Immediately invoked function expression)가 도입되었습니다. 별도의 스코프를 사용함으로써 전역 스코프의 오염을 피하려는 시도였죠.

(() => {
  // execution
})();

하지만 이 방법도 한계는 있습니다. 스코프의 분리라는 목적에는 충실했지만 코드 상의 해당 위치에서 바로 실행되는 만큼 항상 우리의 의도대로 작동하게 하기가 어려웠습니다.

모듈화만 된다면 이 문제가 깔끔하게 해결될 텐데..

모듈이 나왔습니다. 그래서?

자바스크립트 파일을 모듈화하려는 시도는 여러 구현 명세를 만들어 냈습니다. 대표적으로 AMD, CommonJS, UMD가 있습니다.

여러 커뮤니티에서 각자의 구현 스펙을 제안하고 있었으나 ES6에서 모듈 시스템을 표준화함으로써 모듈을 사용할 수 있는 일관된 환경이 갖추어졌습니다.


하지만 현실은 항상 장밋빛이지는 않습니다.

ES module이 나오고부터도 몇 년이 지나서야 일부 브라우저가 지원하기 시작했고 현재는 거의 모든 현역 브라우저가 모듈을 지원하지만 브라우저가 모듈 여러 개를 나누어 요청하고 의존성이 있는 스크립트가 평가가 완료되기 전까지 해당 모듈의 평가가 중단(block)됩니다. 퍼포먼스가 중요한 웹 환경에선 정말 난감한 일입니다.

이 문제를 해결할 구세주가 등장했으니, 그 이름하야 웹팩입니다.


두 번째, 웹팩 (Webpack)

상술한 의존 관계에 있는 어려 모듈을 합쳐 주는(번들링이라고 합니다)하는 장치가 웹팩입니다.
즉 ES6을 지원하지 않는 구형 브라우저에 대응하기 위해 의존성을 갖는 모듈을 번들링하고, 번들링된 결과물을 Babel이 ES5 스펙으로 트랜스파일링(babel-loader)해 브라우저 호환성이 보장되는 스크립트로 만드는 것이죠. 번들링 조건에 따라 HTTP 요청 횟수가 줄어든다는 이점도 챙길 수 있습니다.

웹팩의 기본

웹팩은 앞서 설명했듯 의존성을 가진 여러 모듈을 번들링해 주는 장치입니다. 여기서의 설정을 조금씩 고쳐 자신의 입맛대로 번들링을 할 수 있습니다.

웹팩 설치

초기화를 해 준 뒤 웹팩 필수 의존성을 설치해 줍시다.

yarn init -y
yarn add webpack webpack-cli -D

src 폴더를 만든 뒤 웹팩이 처리할 진입점 파일 index.js과 합을 구하는 모듈 sum.js를 만들어 주세요.

index.js

import sum from "./sum";

console.log(sum(1, 2));

sum.js

const sum = (a, b) => a + b;

export default sum;

그리고 프로젝트 루트에 webpack.config.js 파일을 만들어 주세요. 이 파일이 웹팩이 어떻게 작동할지를 결정합니다.

모두 만들고 나면 위와 같은 구조가 됩니다.
webpack.config.js 파일에는 아래와 같은 빈 객체를 export하도록 설정해 주세요. 이 객체에 내용을 채워서 웹팩의 작동 규칙을 정하게 됩니다.

module.exports = {}

웹팩 설정 객체에 들어가는 내용은 아래와 같습니다.


mode

모듈을 번들링하는 방법을 정의합니다.

  • development
    개발 모드입니다. dependencies와 함께 devDependencies까지 번들링됩니다.
  • production
    프로덕션 모드입니다. devDependencies는 번들링되지 않습니다.
module.exports = {
  mode: process.env.NODE_ENV === "development" ?
  	"development" : "production",
};

entry

모듈 번들링을 시작하는 진입점입니다. 여기부터 의존 관계를 파악하여 번들링한다고 생각하면 됩니다.
진입점은 배열로 여러 개를 지정할 수도 있습니다.

module.exports = {
  entry: "./src/index.js",
};

output

번들링된 결과물을 내보낼 곳입니다. 기본값은 /dist에 JS 파일이 main.js, 나머지 파일이 /dist 안에 내보내어집니다.

module.exports = {
  output: {
    path: path.resolve(__dirname, "./build"),
    publicPath: "/",
    filename: "[name].[chunkhash].js",
  },
};

path

결과물이 내보내어질 경로입니다.

publicPath

정적 파일이 로드되는 경로입니다. CDN을 따로 두고 있을 경우 유용합니다.

filename

내보내어질 파일명입니다.
내보내어질 파일명은 지정된 템플릿을 사용하거나 수동 입력할 수 있습니다. 예시는 청크 이름과 청크 해시를 파일명에 붙인 것으로, 파일명을 지정하는 템플릿은 여기의 output.filename에서 확인할 수 있습니다.


module

로더와 플러그인, 개발 서버 관련 실습은 내용이 너무 길어지기에 별도의 포스트로 분리합니다.

그런데 웹팩은 조금 특이하게도 모든 파일을 모듈로 간주합니다. 대신 이런 파일을 웹팩이 직접 로딩할 수 있지는 않기에 로더를 통해 JS 코드로 로딩합니다.

module.exports = {
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: '/node_modules/',
        loader: 'babel-loader',
      },
      {
        test: /\.svg$/,
        use: ['@svgr/webpack'],
      },
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
      {
        test: /\.(png|jpg|jpeg|gif|ico)$/,
        exclude: /node_modules/,
        use: {
          loader: 'file-loader',
          options: {
            name: '[contenthash].[ext]',
          },
        },
      },
    ],
  }, 
}

module의 rules 배열에 각 규칙이 들어가 있습니다. 그럼 각 rule을 파헤쳐 봅시다.

test

규칙을 적용할 조건(정규식)입니다. 해당 조건에 맞는 파일을 로더가 처리하도록 합니다.

exclude

조건을 만족하지만 로더가 가져올 필요가 없는 특수 조건(정규식)입니다. 보통 Node.js 모듈이 저장되는 경로인 node_modules가 지정됩니다.

loader

조건을 만족할 때 사용할 로더가 하나 뿐이고 적용할 옵션이 없다면 use 대신 쓸 수 있습니다. 로더 이름을 문자열로 입력합니다.

use

조건을 만족할 때 사용할 로더입니다. 배열이며, 맨 뒤에서부터 역순으로 처리합니다.

use: [MiniCssExtractPlugin.loader, 'css-loader']

use (with options)

로더 옵션이 필요할 경우 객체로 입력합니다.

  • loader : 사용할 로더입니다.
  • option : 로더의 옵션입니다.
use: {
  	loader: 'file-loader',
  	options: {
    		name: '[contenthash].[ext]',
  	},
}

자주 쓰이는 로더는 아래와 같습니다.

css-loader

CSS 파일을 읽어들일 수 있게 해 줍니다.

style-loader

JS 파일로 변환된 CSS 코드를 HTML에 주입해 줍니다.

file-loader

웹팩 아웃풋에 파일을 옮겨 모듈로 사용할 수 있게 합니다. 주로 이미지 등의 에셋을 가져올 때 사용합니다.

url-loader

일정 크기 미만의 작은 이미지를 Data URI Scheme으로(Base64) 인코딩해 주는 로더입니다.
작은 크기의 반복적인 네트워크 요청을 줄여 성능 개선을 도모할 수 있습니다.


플러그인

플러그인은 번들된 결과물을 처리하는 장치입니다.

plugins: [
  new CleanWebpackPlugin(),
]

자주 쓰이는 플러그인은 아래와 같습니다.

BannerPlugin (웹팩 내장)

번들 결과물 상단에 주석으로 배너를 달아 줍니다.

DefinePlugin (웹팩 내장)

환경 변수 정보를 제공합니다. 기본적으로 NODE_ENV가 들어갑니다.

HtmlWebpackPlugin

진입점으로 설정한 JS 파일을 스크립트로 포함하는 HTML 파일 생성을 자동화합니다. 일종의 템플릿을 생성해 주는 플러그인입니다.

MiniCssExtractPlugin

결과물에서 CSS 파일을 분리해서 따로 만들어 줍니다. 성능상의 이점을 위한 플러그인입니다.

CleanWebpackPlugin

이전 빌드 결과물을 삭제하여 찌꺼기가 발생하지 않게 해 줍니다.


마치며

여기서 설명한 웹팩 옵션은 일부에 불과합니다. 언급한 것 이외에도 수많은 하위 옵션이 있으며 입맛에 따라 손볼 수 있습니다.

다음 포스트에서는 웹팩과 바벨을 함께 사용해 간단한 React 웹 앱을 만들어 봄으로써 각각의 사용법을 간략하게나마 알아보도록 하겠습니다.

읽어 주셔서 감사합니다.

profile
사진찍는 주니어 프론트엔드 개발자

0개의 댓글