[React 정복기] 바벨과 웹팩 자세히 들여다보기 4 (실전 리액트 프로그래밍) - 7

예흠·2020년 12월 18일
0

React 정복기!

목록 보기
7/8
post-thumbnail

리액트를 정복해 보자 💪

실전 리액트 프로그래밍 개정판 - 이재승 지음 (참고자료)

저번에 이어 웹팩의 고급편을 알아보자.

* 웹팩 (고급편)

지금까지는 웹팩의 기본적인 사용법을 알아본 것이고 이제 웹팩을 이용해서 애플리케이션의 번들 파일을 최적화하는 몇 가지 방법과 로더와 플러그인을 직접 제작해 보면서 웹팩이 내부적으로 어떻게 동작하는지 알아보자.

1. 나무 흔들기

나무 흔들기(tree shaking)는 불필요한 코드를 제거해 주는 기능이다. 나무를 흔들어서 말라 죽은 잎을 떨어뜨리는 것을 비유해서 지은 이름이다.

웹팩은 기본적으로 나무 흔들기 기능을 제공한다. 단, 제대로 동작하지 않는 경우가 있다.
=> 나무 흔들기를 잘 이해하고 있어야 번들 파일 크기를 최소로 유지할 수 있다.

프로젝트를 생성해 보자.

mkdir webpack-tree-shaking
cd webpack-tree-shaking
npm init -y
npm install webpack webpack-cli

프로젝트 루트에 src 폴더를 만들고 그 밑에 util_esm.js 파일을 만든다. 그리고 다음과 같이 두 개의 함수를 내보내는 코드를 입력한다.

ESM 문법을 사용하는 코드다.(ESM은 자바스크립트 표준 모듈 시스템)

src 폴더 밑에 util_common.js 파일을 만들고 다음 코드를 입력한다.

src 폴더 밑에 index.js 파일을 만들고 util_esm.js 모듈로부터 함수를 가져오는 코드를 입력해 보자.

웹팩 실행 후 번들 파일을 열어 보면 func2 함수가 보이지 않는다.
=> 나무 흔들기 덕분에 func2 함수가 제거된 것을 확인할 수 있다.

- 나무 흔들기가 실패하는 경우

index.js 파일에서 util_common 모듈을 사용하도록 수정해 보자.

import { func1 } from './util_common';
func1();

웹팩 실행 후 번들 파일을 열어 보면 func2 함수가 보인다.

나무 흔들기는 다음과 같은 경우에 동작하지 않는다.

  • 사용되는 모듈이 ESM이 아닌 경우
  • 사용하는 쪽에서 ESM이 아닌 다른 모듈 시스템을 사용하는 경우
  • 동적 임포트(dynamic import)를 사용하는 경우

common은 ESM이 아니기 때문에 나무 흔들기가 동작하지 않았다.
=> 사용되는 쪽과 사용하는 쪽 모두 ESM 문법을 사용하면 나무 흔들기가 제대로 동작한다.

이번에는 동적 임포트를 사용하도록 index.js 파일을 수정해 보자.

import('./util_esm').then(util => util.func1());

이렇게 동적 임포트를 사용하면 동적으로 모듈을 가져올 수 있지만 나무 흔들기가 동작하지 않는다.

util_esm.js 모듈의 func2 함수를 사용하지 않는다고 무조건 코드를 제거하면 문제가 될 수 있다.

//모듈 내부에서 자신의 함수를 호출하는 코드(웹팩이 함수를 제거하지 않음)
const arr = [];
export function func1() {
  console.log('func1', arr.length);
}
export function func2() {
  arr.push(10);
  console.log('func2');
}
func2();

모듈이 평가(evaluation)될 때 func2 함수가 실행된다. 모듈은 최초로 사용될 때 한번 평가되는데, 이때 전역 변수 arr이 변경된다. 만약 나무 흔들기 단계에서 func2 함수가 제거되면 func1 함수는 의도한 대로 작동하지 않는다.
=> 다행히 웹팩은 모듈이 평가되는 시점에 호출되는 함수를 제거하지 않는다.

- 외부 패키지의 나무 흔들기

외부 패키지에 대해서도 나무 흔들기가 적용된다. 하지만 외부 패키지는 다양한 방식의 모듈 시스템을 사용하기 때문에 나무 흔들기가 제대로 동작하지 않을 수 있다.

//ex) 로다시 패키지
import { fill } from 'lodash';
const arr = [1, 2, 3];
fill(arr, 'a');
  • 로다시 패키지는 ESM으로 되어 있지 않기 때문에 나무 흔들기로 코드가 제거되지 않음
  • 여기서는 로다시의 fill 함수만 사용하지만 웹팩으로 만들어진 번들 파일에는 로다시의 모든 코드가 포함되어 있다.

로다시는 각 함수를 별도의 파일로 만들어서 제공해 준다.
=> import fill from 'lodash/fill' 이런 식으로 특정 함수의 모듈을 가져올 수 있다.

다음은 lodash-es 패키지를 사용하는 코드다.

import { fill } from 'lodash-ex;
//...

나무 흔들기가 제대로 작용한다.
=> 이처럼 본인이 사용하는 패키지에 적용된 모듈 시스템이 무엇인지, ESM이 아니라면 각 기능을 별도의 파일로 제공하는지 여부를 파악해야 번들 크기를 줄일 수 있다.

- 바벨 사용 시 주의할 점

또 하나 주의할 점은 우리가 작성한 코드를 바벨로 컴파일한 이후에도 ESM 문법으로 남아 있어야 한다는 것이다.
=> @babel/preset-env 플러그인을 사용한다면 babel.config.js 파일에서 다음과 같이 설정해야 한다.

const presets = [
  [
    '@babel/preset-env',
    {
      //...
      modules: false,
    },
  ],
  //...
];
//...

2. 코드 분할

애플리케이션의 전체 코드를 하나의 번들 파일로 만드는 것은 좋은 생각이 아닐 수 있다.
=> 불필요한 코드까지 전송되어 사용자의 요청으로부터 페이지가 렌더링 되기까지 오래 걸릴 수 있기 때문(번들 파일을 하나만 만들면 관리 부담이 적어지므로 회사 내부 직원용 애플리케이션을 만들 때는 좋은 선택이 될 수 있다.)

많은 수의 사용자를 대상으로 하는 서비스라면 응답 시간을 최소화하기 위해 코드를 분할 하는게 좋다.

프로젝트 하나를 만들어보자.

mkdir webpack-split
cd webpack-split
npm init -y
npm install webpack webpack-cli react lodash

코드를 분할하는 가장 직관적인 방법은 웹팩의 entry 설정값에 페이지별로 파일을 입력하는 것이다.
두 개의 페이지를 가진 간단한 프로그램을 만들기 위해 src 폴더를 만들고 index1.js, index2.js 파일을 만들고 코드를 입력해보자.

src 폴더 밑에 util.js 파일을 만들고 add 함수의 코드를 입력하자.

프로젝트 루트에 webpack.config.js 파일을 만들고 페이지별로 entry를 설정해 보자.

npm install clean-webpack-plugin

  • 각 페이지의 자바스크립트 파일을 entry로 입력한다.
  • dist 폴더를 정리하기 위해 clean-webpack-plugin을 사용한다.

웹팩을 실행해 보면 page1.js, page2.js 두 파일이 생성된다.
=> 두 파일 모두 같은 모듈의 내용을 포함하고 있기 때문에 비효율적이다.

- SplitChunksPlugin

웹팩에서는 코드 분할을 위해 기본적으로 SplitChunksPlugin을 내장하고 있다. 별도의 패키지를 설치하지 않고 설정 파일을 조금 수정하는 것만으로 코드 분할을 할 수 있다.

webpack.config.js 파일을 다음처럼 수정하자.

  • 이해를 돕기 위해 페이지 하나만 생성
  • optimization의 splitChunks 속성을 이용하면 코드를 분할할 수 있다.
  • chunks 속성의 기본값은 동적 임포트만 분할하는 async다.
    => 동적 임포트가 아니더라도 코드가 분할되도록 all로 설정

이 상태로 웹팩을 빌드하면 로다시와 리액트 모듈은 vendor.js 파일로 만들어 진다. util.js 모듈은 파일의 크기가 작기 때문에 page1.js 파일에 포함된다.

splitChunks 속성을 제대로 이해하기 위해서는 먼저 기본값의 형태를 이해해야 한다.

module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async', // 1)
      minSize: 30000, // 2)
      minChunks: 1, // 3)
      //...
      cacheGroups: { // 4)
        default: {
          minChunks: 2, // 5)
          priority: -20,
          reuseExistingChunk: true,
        },
        defaultVendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          //...(모든 괄호 닫기)
  • 1) 동적 임포트만 코드를 분할하도록 설정 (async)
  • 2) 파일 크기가 30kb 이상인 모듈만 분할 대상으로 한다.
  • 3) 한 개 이상의 청크(chunk)에 포함되어 있어야 한다. (청크는 웹팩에서 내부적으로 사용되는 용어 => 번들 파일이라고 이해)
  • 4) 파일 분할은 그룹별로 이뤄진다. 기본적으로 외부 모듈(vendors)과 내부 모듈(default) 두 그룹으로 설정되어 있다.
    • 외부 모듈은 내부 모듈보다 비교적 낮은 비율로 코드가 변경되기 때문에 브라우저에 오래 캐싱될 수 있다는 장점이 있다.
  • 5) 내부 모듈은 두 개 이상의 번들 파일에 포함되어야 분할된다.

util.js 모듈을 내부 모듈 그룹으로 분할하기 위해 다음과 같이 설정한다.

파일 크기 제한에 걸리지 않도록 낮은 값을 설정하고 청크 개수 제한을 최소 한 개로 설정했다.

이 상태로 웹팩을 실행하면 page1.js, vendors.js, default.js 세 개의 번들 파일이 생성된다.

새로운 그룹을 추가해서 리액트 패키지만 별도의 번들 파일로 분할해 보자.

다음과 같이 설정하면 리액트 패키지는 react.bundle.js 파일로 분할된다.

  • reactBundle의 priority : 이 그룹의 우선순위가 높아야 리택트 모듈이 vendors 그룹에 들어가지 않는다.

작성중...

profile
노래하는 개발자입니다.

0개의 댓글