ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 최적화 - 웹팩 심화편 | 김정환
    Front-end/개발환경 2020. 6. 17. 22:37
    반응형

    [출처 : https://jeonghwan-kim.github.io]

    4. 최적화

    코드가 많아지면 번들링된 결과물도 커지기 마련이다. 거의 메가바이트 단위로 커질수도 있는데 브라우져 성능에 영향을 줄 수 있다. 파일을 다운로드하는데 시간이 많이 걸리기 때문이다. 이번 섹션에서는 번들링한 결과물을 어떻게 최적화 할수 있는지 몇가지 방법에 대해 알아보겠다.

     

    4-1. production 모드

    웹팩에 내장되어 있는 최적화 방법중 mode 값을 설정하는 방식이 가장 기본이다. 세 가지 값이 올 수 있는데 지금까지 설정한 “development”는 디버깅 편의를 위해 아래 두 개 플러그인을 사용한다.

     

    · NamedChunksPlugin

    · NamedModulesPlugin

     

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

     

    반면 mode를 “production”으로 설정하면 자바스크립트 결과물을 최소화 하기 위해 다음 일곱 개 플러그인을 사용한다.

     

    · FlagDependencyUsagePlugin

    · FlagIncludedChunksPlugin

    · ModuleConcatenationPlugin

    · NoEmitOnErrorsPlugin

    · OccurrenceOrderPlugin

    · SideEffectsFlagPlugin

    · TerserPlugin

     

    DefinePlugin을 사용한다면 process.env.NODE_ENV 값이 “production” 으로 설정되어 어플리케이션 전역변수로 들어간다.

    그럼 환경변수 NODE_ENV 값에 따라 모드를 설정하도록 웹팩 설정 코드를 다음과 같이 추가할 수 있겠다.

    webpack.config.js :

    const mode = process.env.NODE_ENV || 'development'; // 기본값을 development로 설정
    
    module.exports = {
      mode,
    }

    * key와 value의 값이 같으면 key만 입력해도 된다. (ES6 문법)

     

    빌드 시에 이를 운영 모드로 설정하여 실행하도록 npm 스크립트를 추가한다.

    package.json :

    {
      "scripts": {
        "start": "webpack-dev-server --progress",
        "build": "NODE_ENV=production webpack --progress"
      }
    }

    * "'NODE_ENV'은(는) 내부 또는 외부 명령, 실행할 수 있는 프로그램, 또는 배치 파일이 아닙니다." 에러 발생 시 

    $ npm install -g win-node-env node-env를 설치

     

    start는 개발 서버를 구동하기 때문에 환경변수를 설정하지 않고 기본값 development를 사용할 것이다. 배포용으로 만들 build는 환경변수를 production으로 설정했고 웹팩 mode에 설정된다.

     

    빌드한 뒤 결과물을 확인해 보자.

    $ npm run build

     

    왼쪽에 development로 설정해서 빌드한 결과물과 비교해 보면 오른쪽에 production으로 빌드한 결과물의 확연한 차이를 볼 수 있다.

     

    4-2. optimazation 속성으로 최적화

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

    HtmlWebpackPlugin이 html 파일을 압축한것 처럼 css 파일도 빈칸을 없애는 압축을 하려면 어떻게 해야할까? optimize-css-assets-webpack-plugin이 바로 그것이다.

     

    플러그인을 다운로드 하고,

    $ npm i -D optimize-css-assets-webpack-plugin

     

    웹팩 설정을 추가한다.

    webpack.config.js :

    const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
    
    module.exports = {
      optimization: {
        minimizer: mode === 'production' ? [
          new OptimizeCSSAssetsPlugin(),
        ] : [],
      },
    }

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

     

    빌드하뒤 확인하면 css 파일도 압축되었다.

    mode=production일 경우 사용되는 TerserWebpackPlugin은 자바스크립트 코드를 난독화하고 debugger 구문을 제거한다. 기본 설정 외에도 콘솔 로그를 제거하는 옵션도 있는데 배포 버전에는 로그를 감추는 것이 좋을 수도 있기 때문이다.

     

    이 플러그인을 설치한 뒤,

    $ npm i -D terser-webpack-plugin

     

    optionmization.minimizer 배열에 추가한다.

    webpack.config.js :

    const TerserPlugin = require('terser-webpack-plugin');
    
    module.exports = {
      optimization: {
        minimizer: mode === 'production' ? [
          new TerserPlugin({
            terserOptions: {
              compress: {
                drop_console: true, // 콘솔 로그를 제거한다 
              }
            }
          }),
        ] : [],
      },
    }

     

    4-3. 코드 스플리팅

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

     

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

    webpack.config.js : 

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

    빌드하면 엔트리가 두 개 생성되고 물론 하나의 엔트이일 때보다 용량이 조금 줄었다.

     

    dist 폴더 내의 파일 목록을 보면,

    $ ls -lh dist 

    모듈을 어떻게 분리하는냐에 따라 이 결과물의 크기를 조절할 수 있는데 지금은 거의 변화가 없다. HtmlWebpackPlugin에 의해 html 코드에소 두 파일을 로딩하는 코드도 추가된다.

     

    하지만 두 파일을 비교해 보면 중복코드가 있다.

    axios 모듈인데 main, form 둘 다 axios를 사용하기 때문이다.

     

    SplitChunksPlugin은 코드를 분리할때 중복을 예방하는 플러그인이다.

    optization.splitChucks 속성을 설정하는 방식이다.

    webpack.config.js :

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

    다시 빌드해보자.

    $ npm run build

    main.js, form.js외에도 vendors~form~main.js 파일도 생겼다. 마지막 파일은 두 엔트리의 중복 코드를 담은 파일이다. axios로 검색하면 main.js와 form.js에서는 없고 vendors~form~main.js에만 있다.

     

    이런 방식은 엔트리 포인트를 적절히 분리해야기 때문에 손이 많이 가는 편이다. 반면 자동으로 변경해 주는 방식이 있는데 이를 다이나믹 임포트라고 부른다.

     

    기존 컨트롤러 코드를 보면 이렇다.

    /src/app.js :

    import form from './form';
    
    document.addEventListener('DOMContentLoaded', () => {
      formEl = document.createElement("div");
      formEl.innerHTML = form.render();
      document.body.appendChild(formEl);
    })
    

    import/from으로 컨트롤러 모듈을 가져와서 사용했다.

     

    이를 동적으로 임포트하려면 다음처럼 변경한다.

    //import form from "./form";
    
    document.addEventListener("DOMContentLoaded", async () => {
      import(/* webpackChunkName: "form" */ "./form.js").then(m => {
        const form = m.default;
        formEl = document.createElement("div");
        formEl.innerHTML = form.render();
        document.body.appendChild(formEl);
      })
    })

    import() 함수로 가져올 컨트롤러 모듈 경로를 전달하는데 주석으로 webpackHunkName: “form”를 전달했다. 이것은 웹펙이 이 파일을 처리할때 청크로 분리하는데 그 청그 이름을 설정한 것이다.

     

    그리고 나서 프라미스를 상수 form에 담아서 모듈을 가져와 사용하였다.

     

    변경한 웹팩 설정 파일도 다시 복구해야 한다. 엔트리 포인트를 다시 main만 남겨두고 optimization에 설정한 SplitChunksPlugin 옵션도 제거한다.

    webpack.config.js : 

    module.exports = {
      entry: {
        main: "./src/app.js",
        // controller: "./src/controller.js",  // 분리한 엔트리 포인트 제거
      },  
      optimization: {
        // splitChunks: {  // SplitChunksPlugin 옵션 제거
        //  chunks: 'all',
        // },
      },
    }

     

    빌드하면 자동으로 파일이 분리되었다.

    다이나믹 임포트로 모듈을 가져오면 단일 엔트리를 유지하면서 코드를 분리할 수 있다.

    엔트리를 분리하지 않아도 form와 app의 중복코드를 vendors~form.js 파일로 분리한다.

     

    4-4. externals

    조금만 더 생각해 보면 최적화해 볼 수 있는 부분이 있다. 바로 axios같은 써드파티 라이브러리다. 패키지로 제공될때 이미 빌드 과정을 거쳤기 때문에 빌드 프로세스에서 제외하는 것이 좋다.

     

    웹팩 설정중 externals가 바로 이러한 기능을 제공한다.

    webpack.config.js :

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

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

     

    axios는 이미 node_modules에 위치해 있기 때문에 이를 웹팩 아웃풋 폴더에 옮기고 index.html에서 로딩해야한다.

     

    파일을 복사하는 CopyWebpackPlugin을 설치한다.

    $ npm i -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' // 목적지 파일에 들어간다
            }
          ]
        })
      ]
    }

    마지막으로 index.html에서는 axios를 로딩하는 코드를 추가한다.

    src/index.html :

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

    axios는 이렇게 직접 추가했지만 번들링한 결과물은 htmlwebpacPlugin이 주입해 주는 것을 잊지말자.

     

    다시 빌드해 보면,

    axios는 빌드하지 않고 복사만 한다. form와 main이 분리되었다. 이전에는 공통의 코드인 axios가 vender~.js로 분리되었는데 지금은 파일조차 없다. 만약 써드파티 라이브러리 외에 공통의 코드가 있다면 이 파일로 분리되었을 것이다.

     

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


    SUMMARY

    웹팩 사용방법에 대해 좀더 알아 보았다.

     

    개발 서버를 띄워 파일 감지, api 서버 연동 등 개발 환경을 좀 더 편리하게 구성할 수 있었다. 특히 핫 모듈 리플레이스먼트는 일부 모듈의 변경만 감지하여 페이지 갱신 없이 변경사항을 브라우져에 렌더링할 수 있다.

     

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

     


     

    TODO 1. 

    환경변수 NODE_ENV에 따라 development나 production 값을 설정하세요 

    더보기

    webpack.config.js : 

    const mode = process.env.NODE_ENV || "development";
    
    module.exports = {
      mode,
    }

    TODO 2.

    최적화 설정을 구성하세요 

    더보기

    $ npm i optimize-css-assets-webpack-plugin

    $ npm i terser-webpack-plugin

     

    webpack.config.js :

    const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
    const TerserPlugin = require('terser-webpack-plugin');
    
    module.exports = {
      optimization: {
        minimizer: mode === 'production'
        ? [
          new OptimizeCssAssetsPlugin(),
          new TerserPlugin({
            terserOptions: {
              compress: {
           	  	drop_console: true, // console.log()를 제거한다.
              },
            },
          }),
        ]
        : [],
      },
    }

    pakage.json :

    {
      "scripts": {
        "build": "NODE_ENV=production webpack --progress",
      },
    }

    TODO 3.

    외부 라이브러리 axios를 로딩할수 있도록 웹팩에서 파일을 복사하세요

    더보기

    $ npm i copy-webpack-plugin

     

    webpack.config.js :

    const CopyPlugin = require('copy-webpack-plugin');
    
    module.exports = {
      externals: {
        axios: "axios",
      },
      plugins: [
        new CopyPlugin({
          patterns: [
            {
              from: './node_modules/axios/dist/axios.min.js',
              to: './axios.min.js'
            }
          ]
        })
      ]
    }
    반응형

    댓글

Luster Sun