• vue 第三方组件按需引入,最后项目的包体积真的变小了吗?


    一、一个困扰前端的问题

    前端打包体积的优化,减少打包体积,也算是前端老生常谈的问题了。
    我们最常用的方法之一就是按需引入组件!
    各大组件库,如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"
    
    • 1

    这个时候兴高采烈的对着项目就是一顿改,一顿操作猛如虎,结果发现,这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
    
    • 1

    然后在main.js全局引入ant-design-vue

    import Vue from "vue"
    import antd from "ant-design-vue"
    Vue.use(antd)
    
    • 1
    • 2
    • 3

    然后执行打包命令,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')
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147

    然后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>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81

    这时候在项目中假设我们引用了组件,然后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
    
    • 1
    • 2
    • 3

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    echarts页面渲染的代码太长了,就不贴了,下面是测试结果

    在这里插入图片描述

    dist.zip为全局引入没有使用标签,dist(2).zip是按需引入没有使用标签,dist(3)是全局引入且使用 了标签,dist(4)是按需引入且使用了标签

    这里我们可以看到,按需引入的包体积更小。这也许是和这次测试本身按需引入的echarts组件较少有关,鄙人项目中,echarts按需引入后体积少了1.5M,这个优化还是可以的。至于按需的情况下,如果把所有的echarts组件都引用上会是啥情况,这个就没得去测了,主要是ecahrts页面渲染代码弄起来比较麻烦,不过这几个测试可以说明一点,那就是

    想要用按需引入减少包体积,只有在你只用到和全部相比很少组件的情况下有效

    如果项目中用了超一半以上的组件,通常全局引入可能会更好,因为可以更好地共用公有部分代码,体积会更小,或者不确定的情况下,都分别使用下,打包看看具体的结果。对于只用了很少组件的第三方库,比如本测试的echarts,按需引入会有很明显的优化效果

  • 相关阅读:
    红黑树以及JAVA实现
    【Linux】kill 命令使用
    用 VS Code 搞 Qt6:让信号和槽自动建立连接
    【JavaEE进阶系列 | 从小白到工程师】Collections工具类的常用方法
    频频刷屏朋友圈,白酒如何越来越年轻化?来聊聊白酒企业数字化
    软件质量评估的通用模型
    【Rust日报】2022-06-26 lnx 0.9,像 Elasticsearch 和 Algolia 这样的快速搜索引擎
    HttpRunner 如何基于 Prometheus + Grafana 实现性能监控
    libopenssl 实现私钥加密公钥解密
    Guava Cache介绍-面试用
  • 原文地址:https://blog.csdn.net/qq_41000974/article/details/126917331