[Webpack5] 번들링 최적화

상민·2022년 10월 23일
0
post-thumbnail

코드가 많아지면 번들링한 결과물도 커진다.
이는 파일을 다운로드 하는데 시간이 많이 걸리기 때문에 브라우저 성능에 영향을 줄 수 있다.
어떻게 하면 번들링한 결과물을 최적화 할 수 있는지 알아보자.


production 모드

가장 쉬운 방법은 웹팩에 내장되어 있는 방법인 mode값을 설정하는 방식이다.
지금까지는 mode를 development로 설정하여 개발했는데 mode값을 production으로 주면 운영환경에 적합한 모듈을 만들어 낼 수 있다.

development모드는 디버깅 편의를 위해 아래 두 개의 플러그인을 사용한다.

  • NamedChunksPlugin
  • NamedModulesPlugin

DefinePlugin을 사용한다면 process.env.NODE_ENV 값이 development로 설정되어 어플리케이션에 전역변수로 주입된다.

반면 mode를 production으로 설정하면 번들링 결과를 최소화 하기 위해 아래 일곱 개의 플러그인을 사용한다.

  • FlagDependencyUsagePlugin
  • FlagIncludedChunkPlugin
  • ModuleConcatenationPlugin
  • NoEmitOnErrorsPlugin
  • OccurrenceOrderPlugin
  • SideEffectsFlagPlugin
  • TerserPlugin

DefinePlugin을 사용한다면 process.env.NODE_ENV 값이 production로 설정되어 어플리케이션에 전역변수로 주입된다.

그럼 환경변수 NODE_ENV 값에 따라 mode값을 설정하도록 웹팩 설정 코드를 아래와 같이 추가하면 된다.

  • webpack.config.js

const mode = process.env.NODE_ENV || 'development'; // 기본값을 development로 설정

module.exports = {
	mode,
}

build 시에는 mode를 production으로 실행하기 위해 packgage.json에는 아래와 같이 설정한다.

  • package.json

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "NODE_ENV=production webpack --progress",
    "prepare": "husky install",
    "start": "webpack serve --open --mode=development --progress" 
  },

build 스크립트 앞에 NODE_ENV=production이라고 명시해준다.
start 스크립트에는 따로 값을 전달해주지 않으므로 기본값이 development를 사용할 것이다.

npm run build 명령어를 입력하고 빌드된 파일의 main.js를 확인해보면 알아볼 수 없게 코드가 난독화 되어있다.

반대로 build 스크립트에 NODE_ENV를 development로 주고 빌드한 후 main.js를 확인하면 코드가 알아볼 수 있도록 되어있을 것이다.

mode가 production이면 위에 번들링 결과를 최소화하기 위해 위 7가지 플러그인들이 동작했기 때문이다.

따라서 mode를 production으로 주는 것만으로도 번들링을 최적화 할 수 있다.


optimization 설정

빌드 과정을 커스터마이징할 수 있는 여지를 제공하는데 그것이 바로 optimization 속성이다.

이전 포스팅에서 HtmlWebpackPlugin을 사용했는데 이 플러그인이 html파일을 압축한 것 처럼 css 파일도 빈칸을 없애는 압축을 할 수 있다.
optimize-css-assets-webpack-plugin이 바로 그것이다.

webpack5부터는 optimize-css-assets-webpack-plugin 대신에 css-minimizer-webpack-plugin을 사용한다.
저는 webpack5로 실습을 하고 있으므로 css-minimizer-webpack-plugin를 사용해보겠다.

우선 패키지를 설치한다.

$ npm install -D css-minimizer-webpack-plugin

일반적인 플러그인들은 webpack 설정에서 plugins에 설정을 해주었는데, 해당 플러그인은 조금 다르게optimization에 설정을 해주어야 한다.

  • webpack.config.js

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
  optimization: {
    minimizer: mode === "production" ? [new CssMinimizerPlugin()] : [],
  },
}

optimization.minimizer는 웹팩이 결과물을 압축할 때 사용할 플러그인을 넣는 배열이다.
설치한 CssOptimizerPlugin을 전달해서 빌드 결과물 중 css 파일을 압축하도록 한 것이다.

이렇게 설정 후 다시 빌드해보면 이전과 다르게 css도 압축이 되어서 번들링된 것을 확인할 수 있다.

production 모드일 경우 사용되는 TerserWebpackPlugin은 자바스크립트 코드를 난독화하고 디버거 구문을 제거한다. 이 외에도 콘솔 로그를 제거해주는 옵션도 있다.

해당 플러그인도 설치해보자

$ npm install -D terser-webpack-plugin
  • webpack.config.js

const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
  optimization: {
    minimizer:
      mode === "production"
        ? [
            new CssMinimizerPlugin(),
            new TerserPlugin({
              terserOptions: {
                compress: {
                  drop_console: true, // 콘솔 로그를 제거한다.
                },
              },
            }),
          ]
        : [],
  },
}

위에 적용한CssMinimizerPlugin 뒤에 TerserPlugin도 생성자로 넣었다.
옵션을 인자로 전달할 수 있는데 terserOptions.compress.drop_consoletrue로 주면 자바스크립트 코드를 압축할 때 콘솔 로그를 제거할 수 있다.


코드 스플리팅

코드를 압축하는 것 외에도 아예 결과물을 여러개로 쪼개면 좀 더 브라우저에서 다운로드 속도를 높일 수 있다.
큰 파일을 하나 다운로드 하는 것보다 작은 파일 여러개를 동시에 다운로드 하는 것이 더 빠르기 때문이다.

가장 단순한 방법은 엔트리를 여러개로 분리하는 것이다.

  • webpack.config.js

module.exports = {
  entry: {
    main: "./src/app.js",
    result: "./src/result.js",
  },
}

이렇게 엔트리포인트를 두 개로 나누면 번들 결과에서도 main.jsresult.js 두개가 생성이 된다.
index.html에서도 두 개의 자바스크립트 파일을 불러온다.

하지만 중복되는 코드가 각각 엔트리포인트 파일에 있을 수 있다.

이를 해결하기 위해 optimization.splitChunks.chunks"all"로 설정하면 중복되는 코드를 제거하고 엔트리 포인트를 다시 빌드한다.

  • webpack.config.js

module.exports = {
  optimization: {
    splitChunks: {
      chunks: "all",
    },
  },
}

하지만 개발자가 엔트리포인트를 분리하는 것은 손이 많이가므로 자동화 시키는 방법이 필요하다.
다이나믹 임포트 방식을 사용해서 동적으로 임포트를 사용해보자.

  • src/app.js

import "./app.css";
import form from "./form";

let resultEl;
let formEl;

document.addEventListener("DOMContentLoaded", async () => {

  formEl = document.createElement("div");
  formEl.innerHTML = form.render();
  document.body.appendChild(formEl);

  import(/* webpackChunkName: "result"*/ "./result.js").then(async (m) => {
    const result = m.default;
    resultEl = document.createElement("div");
    resultEl.innerHTML = await result.render();
    document.body.appendChild(resultEl);
  });
});

result 모듈을 사용하는 부분에서 import 구문을 사용해 result.js를 가져오고 result 모듈을 모듈이 default로 빼내고 있는 모듈로 설정해 주면 된다.
그리고 특별한 주석을 넣어주어야 한다.
webpackChunkName: "result"를 주석으로 넣으면 result라는 번들을 따로 만들어준다.

이렇게 설정하면 위 webpack.config.js에서 optimization에 설정한 코드는 전부 사용하지 않아도 된다.

optimization의 splitChunk 부분과 entry 부분을 따로 작성해주지 않아도 되므로 주석처리하고 npm run build 명령어로 번들링해보자.

번들링 결과 main.jsresult.js 두 개의 번들링 파일이 나오고 중복되는 코드도 따로 번들이 추가되는 것을 확인할 수 있다.


externals

마지막 방법은 애초에 번들 대상에서 제외되는 대상은 빌드 범위에서 제외시키는 방법이다.
axios같은 써드파티 라이브러리들은 패키지로 제공될 때 이미 빌드 과정을 거쳤기 때문에 빌드 프로세스에서 제외하는 것이 좋다.
웹팩 설정 중 externals가 바로 이러한 기능을 제공한다.

  • webpack.config.js

module.exports = {
  externals: {
    axios: "axios",
  }
}

externals에 axios를 추가하면 웹팩은 코드에서 axios를 사용하더라도 번들에 포함하지 않고 빌드한다.
대신 이를 전역 변수로 접근하도록하는데 키로 설정한 axios가 그 이름이다.

axios는 이미 node_modulesdp 위치해 있기 때문에 이를 웹팩 아웃풋 폴더에 옮기고 index.html에서 로딩해야 한다.
파일을 복사하는 CopyWebpackPlugin을 설치한다.

$ npm install -D copy-webpack-plugin

해당 플러그인을 웹팩 설정파일에 추가한다.

  • webpack.config.js

const CopyPlugin = require("copy-webpack-plugin");

module.exports = {
  plugins: [
    new CopyPlugin({
      patterns: [
        {
          from: "./node_modules/axios/dist/axios.min.js",
          to: "./axios.min.js",
        },
      ],
    }),
  ],
}

./node_modules/axios/dist/axios.min.js파일을 빌드 결과에 axios.min.js 파일로 복사하겠다는 의미이다.

마지막으로 index.html에 해당 axios파일을 로딩하는 코드를 추가한다.

  • src/index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document<%= env %>
  </title>
  <!-- 이것은 주석입니다 -->
</head>

<body>
  <script type="text/javascript" src="axios.min.js"></script>
</body>

</html>

이렇게 서드파티 라이브러리를 externals로 분리하면 용량이 감소할 뿐만 아니라 빌드시간도 줄어들고 개발 환경도 가벼워질 수 있다.


정리

mode 옵션을 production으로 설정하면 웹팩 내장 플러그인이 프로덕션 모드로 동작한다. 번들링 결과물 크기가 커지면 브라우져에서 다운로딩하는 성능이 떨어질수 있는데 코드 스플리트 기법을 사용해서 해결할 수 있다. 써드파티 라이브러리는 externals로 옮겨 빌드 과정에서 제외할수 있다.


참고자료: https://www.inflearn.com/course/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD/dashboard

profile
FE Developer

0개의 댓글