Intro.
( 방법만 궁금하시면 아래로 쭉 내려주셔요!)
아무런 설정을 안할 시, 웹팩은 모든 소스파일들을 하나의 파일로 번들링합니다.
하나의 파일로 번들링한다는게 나쁜건 아닙니다만, 예를 들어, 특정 피쳐 A가 있고, A의 경우, 특정 상황이 아니면 사용하지 않는다 하면 어떨까요? 가장 대표적으로 node_modules가 있겠습니다.
실제로 node_modules를 열어보면 굉장히 많은 모듈들이 있으나, 실제로 주로 사용하는 것은 몇개 안된다는걸 깨달으실 겁니다.
그리고 렌더링 관점에서 따지자면, 더욱 그 수는 줄어들것이구요.
만약 하나의 파일로 번들링된다면, 굉~장히 거대한 번들이 될테고, 브라우저 캐싱 관점에서도 굉장한 비효율이 야기되겠죠?
게다가 번들 하나의 사이즈가 크기 때문에, 피쳐 A와 같은 코드들이 많으면 많을 수록, 첫 렌더링을 위한 로딩이 상당히 오래 걸릴 것입니다. 이러한 경우 코드 스플리팅(code spliting)기법을 통해 최적화가 가능합니다.
말 그대로, 번들을 여러개로 나눔으로써, 최적화를 진행하는 겁니다.
React 사용자들의 경우, lazy() 함수를 통해 자동적으로 코드 스플리팅이 가능합니다만, 해당 기능 아래에서 일어나는 일이라 생각하셔도 됩니다.
Background
먼저, 코드 스플릿팅은 웹팩에 플러그인 중 split-chunk-plugin을 통해 진행 가능합니다. 기본적으로 webpack설치 시, 같이 설치 되기에, 따로 추가는 않하셔도 됩니다.
webpack config 파일의 optimization.splitChunks 프로퍼티를 통해 구성이 가능합니다.
그전에 먼저 'chunks' 와 'cache group'에 대해 짚고 넘어가야 할 것 같습니다.
chunks
- all - 동적이든, 정적이든 상관하지 않고 각 모듈별로 하나의 청크로 분리합니다. (비동기 , 동기 청크간 공유 가능, 단 청크당 번들 사이즈는 클 수 있음)
- initial - 정적으로 import된 모듈만 최적화 합니다. 즉, 정적으로 import된 모듈들을 무조건 chunk로 분리한단 뜻입니다. 또한 동적 import 파일 또한 분리합니다. 그렇게 해야, 동적으로 import시, 불러올 수 있습니다. (해당 과정에서 동일한 패키지가 들어있는 파일이 여러개 생성 될 수 있습니다) (정말 쉽게 말하자면, 엔트리 레벨, 렌더링 시, 필요한 모듈에 대해 설정한다 생각하면 편합니다)
- async (default) - 동적으로 import된 모듈만 최적화 합니다. 해당 모듈들을 chunk로 분리합니다.
import mod from "moduleA";
const num = Math.round(Math.random()*10)
if( num % 2 === 0){
import("moduleB")
}
console.log("Monkey")
async : chunk0.js(index.js + moduelA) + chunk1.js(moduleB)
initial : chunk0.js(index.js) + chunk1.js(moduleA) + chunk2.js(moduleB)
all : chunk0.js(index.js) + chunk1.js(moduleA + moduleB)
Webpack: What is the difference between "all" and "initial" options in optimization.splitChunks.chunks
I'm looking for a clear explanation of the difference between these two options in webpack. I've read the description here but the difference is not clear. Quoting the description: Setting the
stackoverflow.com
cache groups
splitChunks.cacheGroups.{chunk name}하위 프로퍼티에 따라 청크를 생성하는 규칙을 선언한다 생각하시면 됩니다.
- priority : 특정 모듈의 경우 여러 cacheGroups의 조건을 만족하는 경우가 있습니다. 이 때, priority가 높은 규칙에 의해 청크로 변환됩니다.
- reuseExistingChunk : 특정 청크의 경우 여러번 재생성 됩니다. 하나의 모듈을 여러 파일에서 정적 혹은 동적으로 import할 때, 코드 스플릿팅 규칙을 어떻게 구성하느냐에 여러번 생성되기도 합니다. 해당 프로퍼티를 true로 할 시, 해당 청크를 다른 청크에서도 재활용합니다.
- test : 해당 그룹에서 선택될 모듈 규칙입니다. (해당 그룹으로 포함시킬 모듈을 선택하는것이라 생각해도 될듯 합니다.)
- filename : 청크 이름을 지정합니다. 디렉토리 형태로 작성함으로써, 폴더형태로 분리하는것도 가능합니다.
- enforce : 사이즈 제약 혹은 개수 제약조건을 무시하고 청크를 생성합니다. 해당 링크 참조
SplitChunksPlugin | 웹팩
웹팩은 모듈 번들러입니다. 주요 목적은 브라우저에서 사용할 수 있도록 JavaScript 파일을 번들로 묶는 것이지만, 리소스나 애셋을 변환하고 번들링 또는 패키징할 수도 있습니다.
webpack.kr
Method
어떻게 코드 스플리팅을 진행할 것이냐고 묻는다면, 상황에 따라 달라집니다.
예를 들어, http2를 지원하는 경우, 특정 사이즈로 번들을 나눠 최적화를 진행할 수도 있고, 본인이 특정 모듈 혹은 파일에 대해 특별히 스플릿팅을 진행해야 할 것 같다 판단이 들면 진행합니다.
저 같은 경우, 번들 분석을 손쉽게 하기 위해, "webpack-bundle-analyzer"라는 모듈을 통해 분석을 진행합니다.
먼저 분석용 스크립트 시행을 위해, 모듈 설치 후 , 웹팩 설정 파일을 만듭니다.
Webpack-bundle-analyzer 설정하기
yarn add -D webpack-bundle-analyzer
//webpack.analyze.config.js
const production = require('./webpack.prod.config');
const { merge } = require('webpack-merge');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = merge(production, {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'server',
analyzerPort: '9001',
openAnalyzer: true
})
]
});
개발 환경에서 번들을 분석하는 것은 의미가 없다 생각하기에... 게다가 개발 환경에서 각종 번들링이나 압축 기법 추가하면 오히려 빌드 타임이 증가하여 개발단에선 더 손해라 생각하기에 저는 production 환경 설정파일을 분석 대상으로 설정했습니다만,
혹시 개발 환경이랑 프로덕션 환경 둘다 분석하고 싶으시다면, 위 파일을 하나 더 만들어서 production 대신 dev 설정파일 가져와서 진행하시면 될듯 합니다.
이제 package.json에 실행 스크립트만 만들면 analyzer용 설정은 끝입니다.
//package.json
...
"scripts": {
"analyze": "webpack --config webpack.analyze.config.js"
},
혹시 이전 글들을 참고하신 분들이 계시다면 아래처럼 구성하면됩니다.
"scripts": {
"analyze": "cross-env NODE_ENV=production webpack --config webpack/webpack.analyze.config.js"
},
Optimization
먼저 현재 번들 구성이 어떻게 되어있는지 확인해보겠습니다.
메인 번들하나에 jquery와 lodash모듈이 매우 큰 점유율을 가지고 있고, 그외의 각종 모듈들이 우측 한켠에 모여져있고, 제 실질적인 코드는 정말 맨 우측 보이지 않을정도로 찌부려뜨려져있습니다.
jquery의 경우 정적 + 동적 import (굉장히 import가 많을 경우), lodash의 경우, 정적 import일 경우입니다.
jquery는 굳이 메인 번들에 넣지 말고 따로 분리하고 lodash는 분리할 필요가 없을 듯 합니다.
splitChunks: {
cacheGroups: {
jquery: {
test: /[\\/]node_modules[\\/]jquery/,
chunks: 'all',
name: 'jquery'
}
}
}
이번엔 HTTP/2 사용을 가정해보겠습니다.
한번의 TCP연결을 통해 여러 데이터 스트림을 보내는게 가능함으로, 청크를 특정 크기로 잘라 보낼면 될 듯 합니다.
저같은 경우, 그냥 러프하게 20KB ~ 0의 크기로 청크를 분할하겠습니다.
splitChunks: {
chunks: 'all',
maxSize: 20000,
minSize: 0,
name(module, chunks, cacheGroupKey) {
const moduleFileName = module
.identifier()
.split('/')
.reduceRight((item) => item);
const allChunksNames = chunks.map((item) => item.name).join('~');
return `${cacheGroupKey}/${allChunksNames}-${moduleFileName}`;
}
}
웹팩 기본설정들 때문에, 덩치 큰 모듈은 defaultVendor로 빠졋지만, 나머지들은 설정에 맞게 잘 분할된것을 볼 수 있습니다.
이번엔 node_modules자체를 메인 번들에서 따로 빼보겠습니다.
node_modules를 하나의 청크로 뺄 예정이니, maxSize를 무한으로 설정합니다.
splitChunks: {
chunks: 'all',
maxSize: Infinity,
minSize: 0,
cacheGroups: {
node_modules: {
test: /[\\/]node_moduels[\\/]/,
name: 'node_modules'
}
}
}
단, 현재 제 코드에 jquery를 동적 import 중이므로, jquery만 따로 다른 청크로 다시 분할됩니다.
이번엔 node_modules내의 모든 모듈들을 하나의 청크로 분리시키겠습니다.
이 방법 또한 HTTP/2 방식에 적합한 방법일듯합니다.
splitChunks: {
chunks: 'all',
maxSize: Infinity,
minSize: 0,
cacheGroups: {
node_modules: {
test: /[\\/]node_modules[\\/]/,
name(module) {
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
return packageName;
}
}
}
}
}
마지막으로 처음으로 돌아가서 가장 큰 lodash, jquery, 나머지 node_modules 그리고 src 파일들로 분할해보겠습니다.
먼저 하나의 페이지에 여러 청크들이 import될 수 있으므로 , runtimeChunk : 'single'을 통해, 모든 청크들이 동일한 런타임을 공유할 수 있도록 설정하겠습니다. runtimeChunk에 대해 궁금하시면 해당 글을 참조하세요.
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
maxSize: Infinity,
minSize: 2000,
cacheGroups: {
lodash: {
test: /[\\/]node_modules[\\/]lodash[\\/]/,
name: 'lodash',
priority: 10
},
node_modules: {
test: /[\\/]node_modules[\\/]/,
name: 'node_modules',
chunks: 'initial'
}
}
}
jquery는 현재 동적 import 중이므로, 어짜피 자동으로 다른 청크로 분리될 것이기에, 제외했습니다.
저는 lodash는 node_modules에 대해 분리된 청크로 있기 원하기에, lodash용 규칙을 따로 작성했습니다. 그리고 해당 규칙이 나머지 node_modules 규칙에 대해 높은 우선순위를 갖도록 priority =10으로 설정하였습니다.
나머지 node_modules에 대해서, 동적으로 import 되지 않을 것이 자명하기에 chunks = initial로 설정하였습니다.
'optimization' 카테고리의 다른 글
[Webpack + Opt] 이미지 애셋 압축하기 (image-minimizer-webpack-plugin) (0) | 2023.04.27 |
---|---|
[Webpack + opt] css 최적화 (0) | 2023.04.25 |