一定要避免全文件模块引入、全局引入吗?实验告诉你答案

前言

我们在使用脚手架工具的时候一定是离不开webpack,但是很多人对于是不是一定要避免全局引入、全模块引入还很模糊,今天我做一系列实验来测试一下。

全文件模块引入VS文件内部分模块按需引入

全文件模块引入也就是:

import abc from '@/modules/abc.js'
abc.a()

文件内部分模块按需引入也就是:

import {a} from '@/modules/abc.js'
a()

我们搭建一个vue-cli 4,注意关掉eslint。然后测试这两种写法带来的dist文件大小差距。先说上面代码的对比,导致的大小差距应该就是大约4个字节的差别,差别不明显,我们的abc.js要写的大一点,比如:

全文件模块引入的abc.js:

export default {
  a() {
    console.log('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
    console.log('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
    console.log('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
    console.log('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
    console.log('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
    console.log('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
    console.log('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
    console.log('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
    console.log('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
    console.log('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
  },
  b() {
    console.log('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')
    console.log('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')
    console.log('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')
    console.log('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')
    console.log('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')
    console.log('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')
    console.log('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')
    console.log('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')
    console.log('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')
    console.log('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')
  },
  c() {
    console.log('ccccccccccccccccccccccccccccccccccccccccccccc')
    console.log('ccccccccccccccccccccccccccccccccccccccccccccc')
    console.log('ccccccccccccccccccccccccccccccccccccccccccccc')
    console.log('ccccccccccccccccccccccccccccccccccccccccccccc')
    console.log('ccccccccccccccccccccccccccccccccccccccccccccc')
    console.log('ccccccccccccccccccccccccccccccccccccccccccccc')
    console.log('ccccccccccccccccccccccccccccccccccccccccccccc')
    console.log('ccccccccccccccccccccccccccccccccccccccccccccc')
    console.log('ccccccccccccccccccccccccccccccccccccccccccccc')
    console.log('ccccccccccccccccccccccccccccccccccccccccccccc')
  },
}

文件内部分模块按需引入的abc.js:

  export function a() {
    console.log('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
    console.log('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
    console.log('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
    console.log('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
    console.log('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
    console.log('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
    console.log('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
    console.log('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
    console.log('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
    console.log('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
  }
  export function b() {
    console.log('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')
    console.log('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')
    console.log('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')
    console.log('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')
    console.log('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')
    console.log('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')
    console.log('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')
    console.log('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')
    console.log('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')
    console.log('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')
  }
  export function c() {
    console.log('ccccccccccccccccccccccccccccccccccccccccccccc')
    console.log('ccccccccccccccccccccccccccccccccccccccccccccc')
    console.log('ccccccccccccccccccccccccccccccccccccccccccccc')
    console.log('ccccccccccccccccccccccccccccccccccccccccccccc')
    console.log('ccccccccccccccccccccccccccccccccccccccccccccc')
    console.log('ccccccccccccccccccccccccccccccccccccccccccccc')
    console.log('ccccccccccccccccccccccccccccccccccccccccccccc')
    console.log('ccccccccccccccccccccccccccccccccccccccccccccc')
    console.log('ccccccccccccccccccccccccccccccccccccccccccccc')
    console.log('ccccccccccccccccccccccccccccccccccccccccccccc')
  }

再分别写上对应的import写法,然后yarn build,之后查看js文件夹三个js文件的大小之和:

全文件模块引入:151,657 字节
文件内部分模块按需引入:150,402 字节

相差1200多字节!那么我们再搜搜看,按需引入的js文件里面有没有一连串的a字符呢?答案是有,然后再试试搜一连串的bc字符呢?答案是搜不到。

结论一:按需引入,真的可以减小dist体积。

第二个问题:按需引入前提下,export之外的代码,会被打包吗?

在abc.js顶部加上const x = 'xxxxxxxxxxxxxxx',然后在a模块console.log(x)。yarn build之后,搜索dist的js,搜一连串x,结果能搜到。

然后,把a模块的console.log(x)挪到b模块,重新build。搜索dist的js,搜一连串x,结果搜不到。

结论二:webpack会判断export之外的代码是否需要引入,需要的话,也会打包。不需要就不打包。

有人说,我在export之外敲一个console.log('yyyyyyyyyyyyyy'),结果被打包了,怎么解释?

很好解释啊,这行代码,以webpack的智慧,不可能判断出它是对项目有用的还是没用的,所以只好给打包上了。而且,在export之外写跟模块无关的代码,是不是脑抽?

结论三:export之外的代码,凡是webpack无法判断有没有用的,它会按照有用来处理。请不要写与模块无关的代码,也不要为难webpack,错在你,不在于webpack。

有人问,一个vue文件使用了a模块,另一个vue文件使用了b和c模块,按需引入还有意义吗?

继续做实验。

全文件引入写法,dist的js文件之和是151,752 字节
按需引入写法,dist的js文件之和是151,860 字节

相差108个字节。差距应该是体现在了按需引入方案webpack打包之后的一些边边角角的引入语句上,毕竟拆开引入要重复写若干遍引入语句。

结论四:文件的所有模块都用得到的前提下,还是全文件引入的写法节省字节(尽管节省的很有限),而且,全文件写法还有一个更大的优点就是,它的执行是abc.a(),而按需引入是a(),无疑,abc.a()会让你知道a方法是从哪来的,书写更为清晰。

结论五:开发者自己编的模块,要分成两类:
1、针对服务全局的模块;
2、针对服务某个页面或组件的模块。
对于针对服务全局的模块,应当按需引入,理由在本文后半部分有讨论。
对于针对服务某个页面或组件的模块,应在页面或组件里全文件引入,这样思路更简单,代码清晰度更高。
同理,对于开源组件库的引入,如果你能确定所有组件全用得到(这一点很难做到,比如一个组件库提供50个组件,你敢说你50个你会全用遍?),就一定要全文件引入,否则,还是按需引入的好。

本文附赠一个实验,拿Vant组件库的几种引入方法,做实验测试一下。

现在打开Vant的官网看它的手册,它介绍了几种引入方法,我们一一测试。

Vant两种引入模式测试

yarn add vant安装是没啥说的。

方式一. 自动按需引入组件(Vant官方推荐)

官方要求安装babel-plugin-import,由于它只作为dev依赖,不影响打包体积,所以咱们放心装上:yarn add babel-plugin-import --dev。然后在babel.config.js中配置,也不多说。然后使用import { Button } from 'vant';来引用组件即可,并不需要Vue.use(Button);

然后我们在Home.vue写一个button。

<van-button type="default">这是个按钮</van-button>
import {Button} from 'vant'
  components: {
    VanButton: Button
  }

dist得到146,791字节。

方式二、手动按需引入组件

去babel.config.js删除掉配置,改用方式二,也就是引入方式有区别。

import Button from 'vant/lib/button';
import 'vant/lib/button/style';

dist得到150,926字节。

结论六:Vant官方推荐的引入方式确实能节省体积。

到此,全文件引入和按需引入的对比就结束了,然后我们实验一下某个组件全局引入和局部引入的区别。

单一组件全局引入VS局部引入

拿Vant的Icon组件做一个测试。

局部引入

首先我们在2个.vue文件内局部引入Icon组件。

<van-icon name="close" /><van-icon name="chat" info="99+" />
import {Icon} from 'vant';
  components: {
    VanIcon: Icon
  }

dist得到144,325字节。

全局引入

main.js:

const globalComponents = {
  install: () => {
    Vue.component('VanIcon', Icon)
  }
}
Vue.use(globalComponents)

组件内去掉import和components变量。

dist得到144,332 字节。

相比之下,两种方式相差7字节,局部引入方式体积小。但是!因为全局引入写了一个install方法占了100多字节,而局部引入多写的那些代码加起来也没有90字节。所以结论是:

结论七:全局引入节省字节,其实这很好理解,一次引入,全局使用,当然省字节。而且项目越大,全局引入就越节省字节。

其实,这两种方式对于代码量的差别已经是次要矛盾,因为两种引入方式打包进来的组件体积是差不多的,并不是说局部多次引入就会多次打包,这个常识我就不做实验了。主要矛盾集中在内存占用和js文件按需加载方面。

全局引入的优势:

  1. 一次引入,全局使用,无需任何局部引入的语句。

局部引入的优势:

  1. 最大优势就是拆分js文件带来的优势,也就是说,一些组件只在某些.vue里使用,结合webpack的路由懒加载,那么这些Vant组件所在的js文件将会在即将被使用的时候才会网络请求它,这样会提高首屏打开速度。

  2. 默认不加载一些Vant组件,也会降低JS的内存占用。

这时候结论差不多可以总结一下了:

结论八:如果一个Vant组件在80%的页面都会用到,尤其是首页会用到,比如一个移动项目,NavBar、Icon和Button在95%的页面都有用到,那么一定要全局引入它。如果一个Vant组件在20%或更少的页面用到,比如Uploader组件,应当局部引入它。

推荐阅读更多精彩内容