# Reduce Initial Bundle Size for CMS
As sprints go on, our CMS project becomes a huge project. But this application responses very slow due to very large **app chunk** and **vendor chunk**, and it might irritate clients because of the long time waiting for static bundle responding.
There are two problems in our CMS:
1. Too many **unused pages** are included in **app chunk** - In fact, there are only few of them might be invited for the first time. **Lazy route** can split page routes into multiple async chunks to minimize app chunk, only when user actually needs the page, web server serve the page js bundle for him.
2. Too many **unused third party dependencies** included in **vendor chunk** - Not all the dependencies are important. For example: `vue-code-diff` is a large and rarely used dependency only for event change history page, then it's a worst strategy to add it in vendor chunk, since only user who invites this page requires this package.
There are few strategies to resolve the struggle below.
### 1. Dynamic import page component
Not all routes are required in the initial app chunk, we should minimize bundle size for client as possible.
```javascript
[
// ...
{
path: '/page/foo/editor',
name: 'Foo Editor',
component: () => import('@/pages/Foo/FooEditor'),
}
]
```
### 2. Replace `underscore` with `ramda` and `lodash-es`
Our project is based on `underscore` and `ramda` as util functions. But in fact, only little `underscore` functions are used. I tried monolitic import `underscore` to gain small bundle size but failed. Finally, I replace almost 95% of `unserscore` functions with `ramda` functions, as for some functions which are not pure-function such as`throttle` and `uniqueId`, I used `lodash-es`.
#### AS-IS

#### TO-BE

### 3. Split vendor chunk into multiple chunks
To decrease vendor size, I drop large and rarely used third party dependencies, and then split vendor to ui and base chunk to make sure all chunks are as tiny as possible.
```javascript
const webpackConfig = {
// ...
optimization: {
splitChunks: {
cacheGroups: {
ui: {
name: 'ui',
chunks: 'initial',
test: /[\\/]node_modules[\\/](bootstrap|bootstrap-vue)[\\/]/,
priority: -5,
enforce: true,
reuseExistingChunk: true
},
base: {
name: 'base',
chunks: 'initial',
// exclude all large or rarely used third party dependencies
test: /[\\/]node_modules[\\/]((?!(highlights|vue-code-diff|vue-carousel|vue-datepicker)).+)[\\/]/,
priority: -10,
enforce: true,
reuseExistingChunk: true
},
default: {
chunks: 'initial',
minChunks: 2,
priority: -20,
enforce: true
}
}
}
}
}
```
### 4. Tree Shake BootstrapVue components
Add two cutom plugins `BoostrapVue` and `BootstrapVueIcons` to import only used components.
#### AS-IS

#### TO-BE
```javascript
// BootstrapVue
import {
AlertPlugin,
BForm,
BFormText,
BFormInvalidFeedback,
} from 'bootstrap-vue'
export default {
install (vm) {
// Alert
vm.use(AlertPlugin)
// Form
vm.component('BForm', BForm)
vm.component('BFormText', BFormText)
vm.component('BFormInvalidFeedback', BFormInvalidFeedback)
}
}
```
```javascript
// BootstrapVueIcons
import {
BIcon,
BIconPersonFill
} from 'bootstrap-vue'
export default {
install (vm) {
// Icons
vm.component('BIcon', BIcon)
// Icon Reference: https://github.com/bootstrap-vue/bootstrap-vue/blob/dev/src/icons/icons.js
vm.component('BIconPersonFill', BIconPersonFill)
// ...
}
}
```

### 5. Ignore MomentJS locale
Since we don't use any locale format date string for usage. Drop all locales in `momentjs`.
```javascript
const webpack = require('webpack')
const webpackConfig = {
// ...
plugins: [
// exclude node_modules/moment/locale/*.js
new webpack.IgnorePlugin({
resourceRegExp: /^\.\/locale$/,
contextRegExp: /moment$/,
})
]
}
```
### 6. Final result
#### AS-IS
##### Bundle analyze
Max chunk size was 7.69 MB (uncompressed).
Many unused pages were involved in app chunk.
Many unused third party dependencies were involved in vendor chunk.

##### Network
User paid about 7~11 seconds for the page response.

#### TO-BE
##### Bundle analyze
Max chunk size is 2.54 MB (uncompressed).
Only core business logic are invloved in app chunk, and not all pages included in the initial bundle.
Only core third party dependencies are involved in vendor chunk, as for user requires a rarely dependency, he needs an additional network request for it.
Besides, chunks seems more average after code splitting.

##### Network
User pays about 1~4 seconds for the page response.

### 6. What's next for CMS?
Here are some tips for refactoring our CMS after all these changes:
1. Replace `momentjs` with `dayjs` for better bundle size.
2. Setup `gzip` for `js`, `css`, `html` resource for web server, we have the follow up solution in this [article](https://hackmd.io/@lilybon/add0compress-response-middleware-for-services).
3. Load `ckeditor4` only for required page.
4. Stub `ec-lib` and `bootstrap-vue` for component unit test as possible to reduce unit test time.
5. Extract `/pages` from `/components` to improve readability of project.
###### tags: `Work` `JavaScript` `Vue` `Vue2`