번들링한 결과물의 코드가 커지면, 브라우저에서 해당 파일을 로드하는데 너무 많은 시간을 소모할 수 있다. 결과적으로 사용자가 기다려야 할 로딩 시간이 너무 길어진다. 그렇다면 번들링한 결과를 어떻게 최적화할 수 있을지 몇가지 방법에 대해 알아보자.
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "SET NODE_ENV=production&webpack --progress",
"start": "webpack-dev-server --progress"
},
위와 같이 SET NODE_ENV=production&webpack --progress
와 같은 방식으로 실행하면 된다.
설치
npm install -D cross-env
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "cross-env NODE_ENV=production webpack --progress",
"start": "webpack-dev-server --progress"
},
cross-env
는 리눅스에서 사용하는 명령어들을 윈도우에서도 똑같이 지원할 수 있게 만든 의존성이다.
설치
npm install -g win-node-env
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "NODE_ENV=production webpack --progress",
"start": "webpack-dev-server --progress"
},
이건 새로운 명령어를 생성해서 실행하는 방법인데, 환경이 바뀌었을 때 글로벌에 대한 의존성 추가를 명시하지 않으면 에러날 때 조금 헤맬 것 같아서 조금 걱정이 되는 방법이다.
그래도 이 방법을 쓰면 리눅스 환경에서 윈도우즈로 가져왔을 때, 아무런 명령어 변경도 안하고 그대로 실행 가능하다.
Webpack Configuration Mode 공식문서
웹팩 설정파일에서 mode
값을 설정하는 것이 최적화에서 생각보다 큰 의미를 갖는다. mode
에는 총 3가지 값이 올 수 있는데,
development
는 디버깅에 최적화된 모드이다.
지금까지 설정했던 development
는 디버깅 편의를 위해 아래 두개의 플러그인을 사용하고 있었다.
NamedChunksPlugin
(웹팩 v4)NamedModulesPlugin
(웹팩 v4)development
모드에서 DefinePlugin
을 사용한다면, process.env.NODE_ENV
의 값이 development
로 설정되어 애플리케이션에 전역변수로 주입된다.
production
은 배포에 최적화된 모드이다.
mode
를 production
으로 설정하면, 번들링한 결과물을 최소화하기 위해서 다음 7개 플러그인을 사용한다.
FlagDependencyUsagePlugin
FlagIncludedChunksPlugin
ModuleConcatenationPlugin
NoEmitOnErrorsPlugin
TerserPlugin
OccurrenceOrderPlugin
(웹팩 v4)SideEffectsFlagPlugin
(웹팩 v4)production
모드에서 DefinePlugin
을 사용한다면, process.env.NODE_ENV
의 값이 production
으로 설정되어 애플리케이션에 전역변수로 주입된다.
const mode = process.env.NODE_ENV || 'development'
module.exports = {
mode,
}
webpack.config.js
에 위와 같이 코드를 작성하면, process.env.NODE_ENV
로 받은 값이 있을 때는 해당 mode
로 실행시키고, 만일 없다면 기본 값인 development
를 이용하게 된다.
코드를 알아볼 수 없게 난독화되어있다.
빌드 과정을 커스터마이징할 수 있는 속성이 optimization
속성이다.
HtmlWebpackPlugin
이 html 파일을 압축했던것처럼 css 파일도 빈칸을 없애고 압축하고 싶다면 어떻게 해야 할까? css-minimizer-webpack-plugin
을 사용하면 된다.
웹팩 버전 4 에서는
optimize-css-assets-webpack-plugin
을 사용한다.
TerserWebpackPlugin
은 자바스크립트 코드를 난독화하고, debugger
구문을 제거한다. 이 외에도 콘솔로그를 제거하는 옵션도 있다. 배포 버전에는 굳이 console.log()
가 필요하지 않기 때문이다.
npm i -D css-minimizer-webpack-plugin terser-webpack-plugin
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [new TerserPlugin(), new CssMinimizerPlugin()],
},
}
terser-webpack-plugin
은 자바스크립트를 minify하는데 사용되는 플러그인인데, 커스터마이즈된 minimize를 하려면 저렇게 다시 직접 설치해서 입력해주어야 한다.
css-minimizer-webpack-plugin
을 사용하기 위함이다.
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
optimization: {
minimize: mode === "production",
minimizer: [new TerserPlugin(), new CssMinimizerPlugin()],
},
}
위와 같이 간단하게 mode
를 이용해 설정해주면 된다.
optimization: {
minimize: mode === "production",
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true, // 콘솔 로그를 제거
},
},
}),
new CssMinimizerPlugin(),
],
},
위와 같이 TerserPlugin
에 옵션을 주면 콘솔로그를 제거할 수 있다.
css
가 압축되었다.
실행 결과에서 console.log
도 물론 뜨지 않는다.
module.exports = {
mode,
entry: {
app: "./src/app.js",
math: "./src/math.js",
},
...
만일 웹팩의 설정파일에서 위와 같이 entry가 2개이고, 두개에서 동일한 패키지를 사용한다고 가정해보자. 이를테면 현재 두 파일에서 공통으로 axios
패키지를 사용한다고 가정해보자.
app.js
result.js
axios
라는 동일한 코드들이 중복되고 있다.
웹팩은 기본적으로 아래와 같은 조건 하에 chunks를 나눈다.
node_modules
폴더에 있는 경우마지막 2개의 조건을 만족하려하면, 청크의 크기는 더 큰것이 선호된다.
기본 설정은 아래와 같다.
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async',
minSize: 20000,
minRemainingSize: 0,
minChunks: 1,
maxAsyncRequests: 30,
maxInitialRequests: 30,
enforceSizeThreshold: 50000,
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
};
내가 이번에 해준 설정은 아래와 같다.
optimization: {
minimize: mode === "production",
minimizer: [
new CssMinimizerPlugin(),
],
splitChunks: {
chunks: "all",
},
},
그냥 단순히 optimization.splitChunks.chunks: "all"
을 주었을 뿐이다. 나머지는 기본 설정으로 적용됐을 것이다.
기존에 axios
에 createInstance()
하던 부분들은 사라지고, 단순히 require()
로 불러오는 부분만 남아있다.
그리고 배포 디렉토리에 정체를 알 수 없는 669.js
와 850.js
가 있는데, 해당 파일 내부에 Axios
코드가 들어있다.
용량도 669.js
와 850.js
가 가장 크다.
result.js
파일을 따로 번들링하여 다이나믹하게 임포트해보자.
import "./app.css";
import form from "./form.js";
// import result from "./result.js";
console.log("hello world");
document.addEventListener("DOMContentLoaded", async () => {
const formEl = document.createElement("div");
formEl.innerHTML = form.render();
document.body.appendChild(formEl);
import(/* webpackChunkName: "result" */ "./result.js").then(async (m) => {
const result = m.default;
const resultEl = document.createElement("div");
resultEl.innerHTML = await result.render();
document.body.appendChild(resultEl);
});
});
기존의 import
구문을 주석처리하고 아래쪽에 import()
메소드를 통해 "./result.js"
파일을 가져왔다. 그리고 파일명 문자열 앞에 /* webpackChunkName: "result" */
주석도 의미가 있다. 반드시 적어주어야 한다.
위와 같이 코드를 변경하고, 기존에 optimizer.splitChunks
부분은 주석처리해두었다.
app.js
안에 붙어있던, result.js
파일이 분리되었다.
axios
같은 서드파티 라이브러리는 패키지로 제공될 때 이미 빌드 과정을 거쳤기 때문에, 빌드 프로세스가 필요 없다. 웹팩 설정 중 externals
가 이런 기능을 제공한다.
// webpack.config.js
module.exports = {
externals: {
axios: 'axios'
}
}
externals
에 추가하면 웹팩은 코드에서 axios
를 사용하더라도 번들에 포함하지 않고 빌드한다. 대신에 이를 전역 변수로 접근하도록 키로 설정한 axios
가 그 이름이다.
...
externals: {
axios: "axios",
},
optimization: {
minimize: mode === "production",
minimizer: [
new CssMinimizerPlugin(),
],
},
};
위와 같이 externals.axios
는 axios
라는 모듈을 쓸 때, 전역변수 axios
가 있는 것처럼 빌드하라는 뜻이다. axios
는 우리가 설치한 것이기 때문에 node_modules
내부에 존재한다.
plugins: [
new CopyPlugin({
patterns: [
{
from: "./node_modules/axios/dist/axios.min.js",
to: "./axios.min.js",
},
],
}),
],
위와 같이 node_modules
에 있는 패키지를 dist/axios.min.js
라는 파일로 불러온다고 적어주면 된다.
axios.min.js가 따로 빠졌다. 이렇게 하면 웹팩에서 쓸데없는 빌드 시간도 줄일 수 있다.