-
최적화 - 웹팩 심화편 | 김정환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' } ] }) ] }
반응형'Front-end > 개발환경' 카테고리의 다른 글
Babel과 Webpack을 이용한 ES6 환경 구축 | PoiemaWeb (0) 2021.03.05 마무리 - 프론트엔드 개발환경의 이해 | 김정환 (0) 2020.06.18 핫 로딩 - 웹팩 심화편 | 김정환 (0) 2020.06.17 API 서버 연동 - 웹팩 심화편 | 김정환 (0) 2020.06.16 웹팩 개발 서버 - 웹팩 심화편 | 김정환 (0) 2020.06.15