코드가 많아지면 번들링한 결과물도 커진다.
이는 파일을 다운로드 하는데 시간이 많이 걸리기 때문에 브라우저 성능에 영향을 줄 수 있다.
어떻게 하면 번들링한 결과물을 최적화 할 수 있는지 알아보자.
가장 쉬운 방법은 웹팩에 내장되어 있는 방법인 mode
값을 설정하는 방식이다.
지금까지는 mode를 development
로 설정하여 개발했는데 mode값을 production
으로 주면 운영환경에 적합한 모듈을 만들어 낼 수 있다.
development
모드는 디버깅 편의를 위해 아래 두 개의 플러그인을 사용한다.
DefinePlugin
을 사용한다면 process.env.NODE_ENV
값이 development
로 설정되어 어플리케이션에 전역변수로 주입된다.
반면 mode를 production
으로 설정하면 번들링 결과를 최소화 하기 위해 아래 일곱 개의 플러그인을 사용한다.
DefinePlugin
을 사용한다면 process.env.NODE_ENV
값이 production
로 설정되어 어플리케이션에 전역변수로 주입된다.
그럼 환경변수 NODE_ENV
값에 따라 mode값을 설정하도록 웹팩 설정 코드를 아래와 같이 추가하면 된다.
const mode = process.env.NODE_ENV || 'development'; // 기본값을 development로 설정
module.exports = {
mode,
}
build 시에는 mode를 production
으로 실행하기 위해 packgage.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
속성이다.
이전 포스팅에서 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
에 설정을 해주어야 한다.
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
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_console
을 true
로 주면 자바스크립트 코드를 압축할 때 콘솔 로그를 제거할 수 있다.
코드를 압축하는 것 외에도 아예 결과물을 여러개로 쪼개면
좀 더 브라우저에서 다운로드 속도를 높일 수 있다.
큰 파일을 하나 다운로드 하는 것보다 작은 파일 여러개를 동시에 다운로드 하는 것이 더 빠르기 때문이다.
가장 단순한 방법은 엔트리를 여러개로 분리
하는 것이다.
module.exports = {
entry: {
main: "./src/app.js",
result: "./src/result.js",
},
}
이렇게 엔트리포인트를 두 개로 나누면 번들 결과에서도 main.js
와 result.js
두개가 생성이 된다.
index.html에서도 두 개의 자바스크립트 파일을 불러온다.
하지만 중복되는 코드가 각각 엔트리포인트 파일에 있을 수 있다.
이를 해결하기 위해 optimization.splitChunks.chunks
를 "all"
로 설정하면 중복되는 코드를 제거하고 엔트리 포인트를 다시 빌드한다.
module.exports = {
optimization: {
splitChunks: {
chunks: "all",
},
},
}
하지만 개발자가 엔트리포인트를 분리하는 것은 손이 많이가므로 자동화 시키는 방법이 필요하다.
다이나믹 임포트
방식을 사용해서 동적으로 임포트를 사용해보자.
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.js
와 result.js
두 개의 번들링 파일이 나오고 중복되는 코드도 따로 번들이 추가
되는 것을 확인할 수 있다.
마지막 방법은 애초에 번들 대상에서 제외되는 대상은 빌드 범위에서 제외시키는 방법
이다.
axios같은 써드파티 라이브러리들은 패키지로 제공될 때 이미 빌드 과정을 거쳤기 때문에 빌드 프로세스에서 제외하는 것이 좋다.
웹팩 설정 중 externals
가 바로 이러한 기능을 제공한다.
module.exports = {
externals: {
axios: "axios",
}
}
externals
에 axios를 추가하면 웹팩은 코드에서 axios를 사용하더라도 번들에 포함하지 않고 빌드
한다.
대신 이를 전역 변수로 접근하도록하는데 키로 설정한 axios가 그 이름이다.
axios는 이미 node_modulesdp 위치해 있기 때문에 이를 웹팩 아웃풋 폴더에 옮기고 index.html에서 로딩해야 한다.
파일을 복사하는 CopyWebpackPlugin을 설치한다.
$ npm install -D copy-webpack-plugin
해당 플러그인을 웹팩 설정파일에 추가한다.
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파일을 로딩하는 코드를 추가한다.
<!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
로 옮겨 빌드 과정에서 제외할수 있다.