본문으로 바로가기

핫 모듈 리플레이스먼트

이전에 적용했던 웹팩 개발 서버는 코드의 변화를 감지해서 브라우저의 새로고침을 만들어줘서 생산성을 높여줬다

하지만 이 새로고침이 가끔 거슬릴 때가 있다

SPA를 개발할 때는 브라우저에서 데이터를 다 들고있기 때문에 새로고침을 해버리면 데이터가 다 사라진다

이때 화면 전체를 새로고침하지 않고 코드가 변경된 부분의 모듈만 새로고침해주는 기능을 제공하는 것이 핫 모듈 리플레이스먼트이다

설정

웹팩 개발서버에서 제공하는 옵션이기 때문에 따로 패키지를 설치하지 않고 바로 설정에 들어가면된다

    devServer: {
          ...
        hot: true,
        ...
    },

그냥 핫을 true로 켜주기만하면 된다

그러면 코드를 고칠 때 그 모듈에 해당하는 부분만 새로고침이 되게 된다

최적화

개발하다보면 코드가 커지기 마련인데 이게 커지고 커지다 보면 결국 브라우저의 성능에 영향을 끼친다

빌드한 파일을 다운로드 하는데 시간이 오래 걸리기 때문이다

어떻게 하면 이 빌드된 파일의 크기를 줄일 수 있을까?

production 모드

이때까지 우리는 development 개발환경을 기준으로 개발해왔다

development에서 빌드를 하면 웹팩이 디버깅의 편의를 위해 두 가지의 플러그인, NamedChunksPlugin, NamedModulesPlugin을 사용해서 빌드한다

개발환경을 production으로 바꾸면 디버깅을 신경쓰지 않고 크기를 최소화하기위해 다음과 같은 플러그인 7가지를 사용한다

FlagDependencyUsagePlugin
FlagIncludedChunksPlugin
ModuleConcatenationPlugin
NoEmitOnErrorsPlugin
OccurrenceOrderPlugin
SideEffectsFlagPlugin
TerserPlugin

이제 개발환경을 왔다갔다 하기 위해서 웹팩의 설정을 조금만 바꿔보자

const mode = process.env.NODE_ENV || "development" // 

module.exports = {
  mode,
  ...
}

이전엔 그냥 development를 주었지만 이제는 그 전에 명령어로 주입받는 형식으로 바꾸었다

아무 값이 들어오지 않는다면 development가 기본값이며 그외엔 설정한 값을 받는다

production모드로 빌드를 해 보자

NODE_ENV=production webpack

아이콘도 바뀌고 코드도 공백문자의 크기마저 아깝다는듯이 한 줄로 쭉 나열되어있다

optimazation

production모드로 바꿈으로써 웹팩이 플러그인을 추가해서 빌드된 파일의 용량을 줄였다

이것 외에도 개발자가 플러그인을 추가해서 내 입맛대로 빌드과정에 영향을 끼칠 수도 있는데

이때 플러그인을 추가할 수 있는 옵션을 제공하는 것이 optimazation 속성이다

이 속성에 css의 빌드 결과물의 용량을 줄여주는 플러그인을 추가해보도록하자

const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");

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

optimize-css-assets-webpack-plugin 플러그인은 css파일도 빈칸을 없애서 압축하는 역할을 한다

또 하나 TerserWebpackPlugin은 개발자가 미처 지우지 못한 conosole.log구문을 삭제해주고 자바스크립트 코드를 난독화해준다

마저 추가해보자

    optimization: {
        minimizer:
            mode === 'production'
                ? [new OptimizeCSSAssetsPlugin(),
                    new TerserPlugin({
                        terserOptions: {
                            compress: {
                                drop_console: true
                            }
                        }
                    })
                ]
                : []
    },

코드 스플리팅

코드를 압축하는 것에도 결국 한계가 있는데 그렇기 때문에 압축하는 것에 그치지 않고 파일들을 분할한다

파일들을 분할해서 빌드하면 브라우저가 큰 파일 하나를 다운 받을 때 보다 작은 파일 여러 개를 받는 것이 속도가 더 빠르기 때문

가장 단순한 분할법은 엔트리를 여러개로 만드는 것이다

    entry: {
        main : './src/app.js',
        result: './src/result.js'
    },

이렇게 빌드 후 파일 이름 : 경로 로 작성해주기만 하면 엔트리가 분할되어서 빌드된다

이때 HtmlWebpackPlugin에 의해서 html은 분할된 js파일 두개 모두다 불러들이고 있다

...
<script type="text/javascript" src="main.js"></script>
<script type="text/javascript" src="result.js"></script>
...

한 가지 신경써주어야하는 부분이 있는데 코드를 분할하다보면 각각의 파일에 중복되는 부분이 생기기 마련이다

이 경우에는 optization.splitChucks 옵션을 설정해주어야한다

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

이렇게 splitChunks 옵션을 설정해주면 각각 파일에서 중복되는 부분은 사라지고, 중복된 부분은 새로운 파일을 만들어서 관리하게된다

그림에 보이는 vendors~main~result.js에 main과 result 에서 중복된 코드들이 모두 들어가있다

다이나믹 임포트

위에서 처럼 코드를 분할하고 중복을 빼주는 건 간단하지만 규모가 커져서 분할해야할 엔트리가 많아진다면 꽤나 귀찮아 질 것 같다

그래서 분할하는 과정도 자동화가 필요하다

여태까지 임포트할 때는 최상단에서 import 구문으로 불러온뒤 본문 어딘가에서 사용했지만 동적 임포트는 살짝 다르다

import("./result.js").then(m=>{
    const result = m.default;

}

이 코드에서의 result는 아래와 같다

import result from "./result";

저렇게 사용하는 것을 동적 임포트라고 부른다

저런 형태로 모듈을 사용하게 되면 웹팩에서 저 구문을 만났을 때 스스로 파일을 분할해서 빌드하는데 이때 빌드될 파일의 이름을 설정해주어야한다

import(/* webpackChunkName: "result" */"./result.js").then(m=>{
    const result = m.default;
}

이렇게만 해주면 기존에 설정해 주었던 엔트리, 스플릿청크 옵션은 전부 다 지워도 알아서 엔트리를 분할하고 중복되는 코드는 뽑아내서 제거한다


이런식으로 스플리팅하고 코드의 용량을 줄이는 것은 개발 초반에는 불필요한 부분이다

개발을 하고 빌드를 한 후에 파일의 크기가 1메가 이상이 넘어간다면 위의 단계를 적용하는 것을 고려해보자

externals

최적화할 부분이 하나 더 남아있다..

바로 외부 라이브러리 부분인데 이 라이브러리들은 이미 빌드되어서 제공되기 때문에 프로젝트내에서 빌드를 또 해줄 필요가 없다

그렇기 때문에 빌드 단계에서 제외하고, 그 대신 라이브러리의 이미 빌드되어있는 부분을 복사해서 output에 같이 넣어어주게끔 하면된다

빌드 단계에서 제외시키는 것은 웹팩 자체 설정으로 가능하고 복사하는 부분은 copy-webpack-plugin을 받아서 플러그인에 적용시켜주면된다

예를 들것은 axios 라이브러리이다

    externals: {
        axios : "axios"
    },

이렇게 설정해주면 웹팩은 마주치는 axios를 전역 변수로 취급해서 따로 빌드하지 않는다

이제 axios를 제외하고 빌드시켰으니 output에는 axios의 정보가 없다

그래서 node_modules에 있는 axios폴더에 이미 빌드되어있는 axios.min.js를 output에 복사해서 옮겨야한다

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

설정값에 있는 from 경로에 있는 파일을 to 경로로 복사해서 옮기는 일을 해준다

한 가지가 더 남았는데 빌드시에 axios가 빠져있으니 html에 axios를 불러오는 코드는 없다

그래서 html에 직접 추가해주어야한다

(CopyPlugin을 쓰는 경우 대부분이 externals설정을 적용하는 상황일텐데 어째서 빌드시에 html에 적용하는 옵션이 없는걸까)

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