一、一个困扰前端的问题

前端打包体积的优化,减少打包体积,也算是前端老生常谈的问题了。
我们最常用的方法之一就是按需引入组件!
各大组件库,如ant-design,element-ui,都有按需引入的栗子,有些会告诉你,按需引入,可以减少包体积!

百度搜索“组件按需引入”,会有一大堆文章告诉你组件如何按需引入,并且大部分都不约而同的复制了这句话:按需引入可以减少打包体积!

事实果真如此?

No!

二、不信任起源

对于这事,开始时鄙人也深信不疑。项目开发里面,能按需引入的都选择按需引入了,毕竟优化的意识深深刻在脑海里。

最近在写组件库,却让我对这个问题产生了怀疑。

本来嘛,因为历史原因,还有为了项目进度原因,我们的组件库开始是写在项目里面的,只是把他大概按照组件库的模式,单独存放于文件夹里面,后来有空了,就把之前备用好的组件抽了出来,组成公司组件库。一切搞好后,替换了之前项目里面的内置组件。然后打包,当我把打包好的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 ant-design-vue@1.7.8 --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="/">Home</router-link> |
      <router-link to="/about">About</router-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的情况
在这里插入图片描述

这是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,按需引入会有很明显的优化效果

Logo

前往低代码交流专区

更多推荐