前言
我们在使用脚手架工具的时候一定是离不开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
字符呢?答案是有,然后再试试搜一连串的b
和c
字符呢?答案是搜不到。
结论一:按需引入,真的可以减小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文件按需加载方面。
全局引入的优势:
- 一次引入,全局使用,无需任何局部引入的语句。
局部引入的优势:
最大优势就是拆分js文件带来的优势,也就是说,一些组件只在某些.vue里使用,结合webpack的路由懒加载,那么这些Vant组件所在的js文件将会在即将被使用的时候才会网络请求它,这样会提高首屏打开速度。
默认不加载一些Vant组件,也会降低JS的内存占用。
这时候结论差不多可以总结一下了:
结论八:如果一个Vant组件在80%的页面都会用到,尤其是首页会用到,比如一个移动项目,NavBar、Icon和Button在95%的页面都有用到,那么一定要全局引入它。如果一个Vant组件在20%或更少的页面用到,比如Uploader组件,应当局部引入它。