前端打包体积的优化,减少打包体积,也算是前端老生常谈的问题了。
我们最常用的方法之一就是按需引入组件!
各大组件库,如ant-design,element-ui,都有按需引入的栗子,有些会告诉你,按需引入,可以减少包体积!
百度搜索“组件按需引入”,会有一大堆文章告诉你组件如何按需引入,并且大部分都不约而同的复制了这句话:按需引入可以减少打包体积!
事实果真如此?
对于这事,开始时鄙人也深信不疑。项目开发里面,能按需引入的都选择按需引入了,毕竟优化的意识深深刻在脑海里。
最近在写组件库,却让我对这个问题产生了怀疑。
本来嘛,因为历史原因,还有为了项目进度原因,我们的组件库开始是写在项目里面的,只是把他大概按照组件库的模式,单独存放于文件夹里面,后来有空了,就把之前备用好的组件抽了出来,组成公司组件库。一切搞好后,替换了之前项目里面的内置组件。然后打包,当我把打包好的zip文件发给后端小伙伴的时候,赫然发现,使用了组件库后的项目,包体积比之前增加了约1.5M!!
瞬间就感觉不能接受了!为什么会这样?这不是我想要的结果!
但是,因为组件库要在后面的新项目推广使用,抽出来是必需!那就着手优化解决呗!
开始写组件库,打包为了方便快捷,并没有自己去构建打包方式,直接用的vue的库模式打包,打出来的包是全部打成一块的。所以一开始的优化思路就是,做成可以按需引入的打包方式。怎么做?找个参考的吧,虽然我们的库是基于ant-design-vue的,但是参考的还是选择了element-ui,没有别的,就是element-ui的代码看着比ant-design-vue顺眼,可阅读性也较好。element-ui库打包后的文件是这样的,把每个组件单独打包成一个js文件
经过一番折腾,终于搞好了可以像这样的按需引入
import Button from "element-ui/lib/button"
这个时候兴高采烈的对着项目就是一顿改,一顿操作猛如虎,结果发现,这nm优化了个寂寞,因为使用按需引入后,打包后的代码体积比没有使用按需引入的还要多出1M
受挫后,又重新换了一种方式打包,结果相差无几!
然后冷静下来,对这种按需打包模式打包后的文件进行分析。首先选中所有按组件单独打包得到的js文件,看了一下总体积,确实比统一打包要大1M多。
为什么?
想了一下其实也很好理解。我们封装组件库的时候,总有公用的函数吧?有公用的组件吧?有公用的依赖吧?这些公用的东西,如果我们统一打包,那么他们就只有一份,每个组件都去引用就好了。
但是,如果我们要将一个单独的组件打包成一个可以独立用的js模块,那他就不得不把这些公共的东西包进去。这下子好了,假设我有A、B、C三个组件,共同依赖了D模块,A、B、C分别为10kb大小,D是5kb大小,统一打包的情况下,总共就35kb,分开打包呢?A、B、C都会变成15kb,三个就成了45kb了。
体积就是这么被增大的!!
为了验证这想法,我们新建一个size-test项目来看看
这里按鄙人的项目习惯,建了一个vue2+less+vuex+router的项目,然后我们先安装ant-design-vue
npm i [email protected] --save
然后在main.js全局引入ant-design-vue
import Vue from "vue"
import antd from "ant-design-vue"
Vue.use(antd)
然后执行打包命令,npm run build
打包后,将dist压缩为zip
如上图,此时的包体积压缩后是2.05M
接下来我们把项目改成按需引入的形式,将main.js改成如下
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
// import antd from "ant-design-vue"
Vue.config.productionTip = false
// Vue.use(antd)
import Affix from 'ant-design-vue/lib/affix';
import Anchor from 'ant-design-vue/lib/anchor';
import AutoComplete from 'ant-design-vue/lib/auto-complete';
import Alert from 'ant-design-vue/lib/alert';
import Avatar from 'ant-design-vue/lib/avatar';
import BackTop from 'ant-design-vue/lib/back-top';
import Badge from 'ant-design-vue/lib/badge';
import Base from 'ant-design-vue/lib/base';
import Breadcrumb from 'ant-design-vue/lib/breadcrumb';
import Button from 'ant-design-vue/lib/button';
import Calendar from 'ant-design-vue/lib/calendar';
import Card from 'ant-design-vue/lib/card';
import Collapse from 'ant-design-vue/lib/collapse';
import Carousel from 'ant-design-vue/lib/carousel';
import Cascader from 'ant-design-vue/lib/cascader';
import Checkbox from 'ant-design-vue/lib/checkbox';
import Col from 'ant-design-vue/lib/col';
import DatePicker from 'ant-design-vue/lib/date-picker';
import Divider from 'ant-design-vue/lib/divider';
import Dropdown from 'ant-design-vue/lib/dropdown';
import Form from 'ant-design-vue/lib/form';
import FormModel from 'ant-design-vue/lib/form-model';
import Icon from 'ant-design-vue/lib/icon';
import Input from 'ant-design-vue/lib/input';
import InputNumber from 'ant-design-vue/lib/input-number';
import Layout from 'ant-design-vue/lib/layout';
import List from 'ant-design-vue/lib/list';
import LocaleProvider from 'ant-design-vue/lib/locale-provider';
import Menu from 'ant-design-vue/lib/menu';
import Mentions from 'ant-design-vue/lib/mentions';
import Modal from 'ant-design-vue/lib/modal';
import Pagination from 'ant-design-vue/lib/pagination';
import Popconfirm from 'ant-design-vue/lib/popconfirm';
import Popover from 'ant-design-vue/lib/popover';
import Progress from 'ant-design-vue/lib/progress';
import Radio from 'ant-design-vue/lib/radio';
import Rate from 'ant-design-vue/lib/rate';
import Row from 'ant-design-vue/lib/row';
import Select from 'ant-design-vue/lib/select';
import Slider from 'ant-design-vue/lib/slider';
import Spin from 'ant-design-vue/lib/spin';
import Statistic from 'ant-design-vue/lib/statistic';
import Steps from 'ant-design-vue/lib/steps';
import Switch from 'ant-design-vue/lib/switch';
import Table from 'ant-design-vue/lib/table';
import Transfer from 'ant-design-vue/lib/transfer';
import Tree from 'ant-design-vue/lib/tree';
import TreeSelect from 'ant-design-vue/lib/tree-select';
import Tabs from 'ant-design-vue/lib/tabs';
import Tag from 'ant-design-vue/lib/tag';
import TimePicker from 'ant-design-vue/lib/time-picker';
import Timeline from 'ant-design-vue/lib/timeline';
import Tooltip from 'ant-design-vue/lib/tooltip';
import Upload from 'ant-design-vue/lib/upload';
import Drawer from 'ant-design-vue/lib/drawer';
import Skeleton from 'ant-design-vue/lib/skeleton';
import Comment from 'ant-design-vue/lib/comment';
import ConfigProvider from 'ant-design-vue/lib/config-provider';
import Empty from 'ant-design-vue/lib/empty';
import Result from 'ant-design-vue/lib/result';
import Descriptions from 'ant-design-vue/lib/descriptions';
import PageHeader from 'ant-design-vue/lib/page-header';
import Space from 'ant-design-vue/lib/space';
const components = [
Base,
Affix,
Anchor,
AutoComplete,
Alert,
Avatar,
BackTop,
Badge,
Breadcrumb,
Button,
Calendar,
Card,
Collapse,
Carousel,
Cascader,
Checkbox,
Col,
DatePicker,
Divider,
Dropdown,
Form,
FormModel,
Icon,
Input,
InputNumber,
Layout,
List,
LocaleProvider,
Menu,
Mentions,
Modal,
Pagination,
Popconfirm,
Popover,
Progress,
Radio,
Rate,
Row,
Select,
Slider,
Spin,
Statistic,
Steps,
Switch,
Table,
Transfer,
Tree,
TreeSelect,
Tabs,
Tag,
TimePicker,
Timeline,
Tooltip,
Upload,
Drawer,
Skeleton,
Comment,
// ColorPicker,
ConfigProvider,
Empty,
Result,
Descriptions,
PageHeader,
Space,
]
components.forEach(item => {
Vue.use(item)
})
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
然后npm run build,打包看看结果,如下图
可以看得出来,比全局引入要多100kb,而且这里,你如果仔细去数的话,发现按需引入的,我还漏掉了个别组件,所以实际应该比这个还要大点点。
但是,这个差别也不大嘛。比起实际项目中的情况,虽说是要大点,但是没大多少。
别急,我们再来改下,将app.vue修改如下:
<template>
<div id="app">
<nav>
<router-link to="/">Homerouter-link> |
<router-link to="/about">Aboutrouter-link>
nav>
<a-table>a-table>
<a-select>a-select>
<a-input>a-input>
<a-input-group>a-input-group>
<a-input-number>a-input-number>
<a-input-search>a-input-search>
<a-alert>a-alert>
<a-auto-complete>a-auto-complete>
<a-avatar>a-avatar>
<a-button>a-button>
<a-back-top>a-back-top>
<a-badge>a-badge>
<a-breadcrumb>a-breadcrumb>
<a-checkbox>a-checkbox>
<a-calendar>a-calendar>
<a-card>a-card>
<a-col>a-col>
<a-collapse>a-collapse>
<a-cascader>a-cascader>
<a-divider>a-divider>
<a-descriptions>a-descriptions>
<a-drawer>a-drawer>
<a-dropdown>a-dropdown>
<a-empty>a-empty>
<a-form-model>
<a-form-model-item>a-form-model-item>
a-form-model>
<a-list>
<a-list-item>a-list-item>
a-list>
<a-menu>a-menu>
<a-modal>a-modal>
<a-page-header>a-page-header>
<a-pagination>a-pagination>
<a-popconfirm>a-popconfirm>
<a-progress>a-progress>
<a-radio-button>a-radio-button>
<a-row>a-row>
<a-range-picker>a-range-picker>
<a-rate>a-rate>
<a-slider>a-slider>
<a-tabs>a-tabs>
<a-textarea>a-textarea>
<a-time-picker>a-time-picker>
<a-timeline>a-timeline>
<a-tooltip>a-tooltip>
<a-tree>a-tree>
<a-upload>a-upload>
<a-week-picker>a-week-picker>
div>
template>
<style lang="less">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
nav {
padding: 30px;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
}
style>
这时候在项目中假设我们引用了组件,然后main.js还是先改成全局引入,打包,结果如下:
我们可以看到,app.vue引入组件后,打包没太多变化。
然后我们改成按需引入,再打包
结果很明显,按需打包后,立马增加了10kb!!
那么在复杂的项目中,组件的大量使用,会不会增加的更多?答案是肯定的!鄙人的项目里面,按需和全局引入,按需引入要多出300kb,这是在按需引入时至少有十个组件没有引入的情况下。
这是element-ui的测试结果,dist.zip为全局引入没有使用标签,dist(2).zip是按需引入没有使用标签,dist(3)是全局引入且使用 了标签,dist(4)是按需引入且使用了标签
我的天,虽然趋势没有变,就是总体上按需引入的包会更大,但是element-ui和ant-design-vue有1M左右的差距。从这里也许也能看出一些两个组件库的一些优劣(ps:个人更喜欢element-ui,在项目开发中如果使用了table组件的树形结构渲染,两个组件的性能有很明显区别,element-ui更快哦!!)
然后,我想看看既没有引入ant-design-vue,也没有引入element-ui的情况,如下
274kb。这也能解释了鄙人的项目里面,为什么将组件抽出去成组件库后,体积会马上增加1.5M的原因,因为组件库是依赖ant-design-vue的,组件库里面塞了ant-design-vue本身的代码啊!!
求助:看到的大佬能教下怎么破嘛?如果组件库只用了js,其实也还好办,组件库可以直接使用项目里面的ant-design-vue,但是组件库用了ts以后,项目注册的ant-design-vue没法使用了,会报找不到标签的错误!所以不得不在库里面单独引入ant-design-vue,这是导致包体积增加1.5M的重要原因
最后,再来试试echarts
echarts全局引入代码
import Vue from "vue"
import * as echarts from "echarts"
Vue.prototype.$echarts = echarts
echarts按需引入代码
import Vue from "vue"
import * as echarts from "echarts/core"
import {
TitleComponent,
ToolboxComponent,
TooltipComponent,
GridComponent,
DataZoomComponent,
LegendComponent
} from "echarts/components"
import { LineChart, PieChart } from "echarts/charts"
import { UniversalTransition, LabelLayout } from "echarts/features"
import { CanvasRenderer } from "echarts/renderers"
echarts.use([
TitleComponent,
ToolboxComponent,
TooltipComponent,
GridComponent,
DataZoomComponent,
LineChart,
CanvasRenderer,
UniversalTransition,
LegendComponent,
PieChart,
LabelLayout
])
Vue.prototype.$echarts = echarts
echarts页面渲染的代码太长了,就不贴了,下面是测试结果
dist.zip为全局引入没有使用标签,dist(2).zip是按需引入没有使用标签,dist(3)是全局引入且使用 了标签,dist(4)是按需引入且使用了标签
这里我们可以看到,按需引入的包体积更小。这也许是和这次测试本身按需引入的echarts组件较少有关,鄙人项目中,echarts按需引入后体积少了1.5M,这个优化还是可以的。至于按需的情况下,如果把所有的echarts组件都引用上会是啥情况,这个就没得去测了,主要是ecahrts页面渲染代码弄起来比较麻烦,不过这几个测试可以说明一点,那就是
想要用按需引入减少包体积,只有在你只用到和全部相比很少组件的情况下有效
如果项目中用了超一半以上的组件,通常全局引入可能会更好,因为可以更好地共用公有部分代码,体积会更小,或者不确定的情况下,都分别使用下,打包看看具体的结果。对于只用了很少组件的第三方库,比如本测试的echarts,按需引入会有很明显的优化效果