文章目录
简述
按需加载就是需要什么,就只要什么,其他的东西不要。这样做的目的是为了缩小打包体积。
示例
比如当前流行的web端组件库ElementUI,就有这个按需加载功能;一个系统的登录页,需要的组件是非常少的,大多数都是只用到Input输入框以及Button按钮。所以为了能够提高登录页面的加载速度,需要使用ElementUI的按需加载功能,只引入输入框以及按钮,这样用户在打开登录页的时候,就不需要等待太久,会有一个比较好的用户体验;
原理
本文的原理参考ElementUI,只不过要做的东西比element要简单很多。element在使用按需引入的时候,需要开发者在自己的项目上使用插件babel-plugin-component,这个插件可以将代码中的引入代码进行转换
babel-plugin-component
import { Button } from 'components'
这句代码会被转换成:
var button = require('components/lib/button')
require('components/lib/button/style.css')
这个components
就是依赖的名称,比如element-ui
,那它怎么知道引入的是lib目录下的button,而不是其他目录下的button,这个是需要配置.babelrc
这个配置文件,接下来以element-ui的按需引入配置进行示例说明。
element-ui按需引入
配置.babelrc
文件
{
"presets": [["es2015", { "modules": false }]],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
以下代码,会被转换:
import {Button,Icon} fron 'element-ui'
会被转换成以下代码
const Button = require('element-ui/lib/button.js`)
require('element-ui/theme-chalk/button.css')
const Icon = require('element-ui/lib/icon.js')
require('element-ui/theme-chalk/icon.css')
其中在引入css的时候,其中的theme-chalk
路径是通过.babelrc文件中的styleLibraryName
确定的,
引入js的时候,其中的lib
是通过.babelrc文件中的libDir
属性决定的,只不过这个属性的默认值是lib;
接下来看一下,在node_modules中的element-ui的结构,先看js
再看css:
看package.json中的main:
看完这个结构之后,大家应该明白了,当全引入的时候:
import ELEMENT from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
这两句代码,就相当于引入截图中的element-ui.common.js
以及theme-chalk/index.css
;当按需引入组件的时候,就是分别引入对应组件的js以及css文件,而babel-plugin-component
只是帮忙转换了一下语法而已,所以说,按需引入,是需要组件库打包出来的文件支持的,如果element-ui没有分别打包这些组件,那么调用者是无法实现按需引入功能的;
babel-plugin-import
因为现在使用vue-cli3脚手架创建的vue工程是使用babel7编译的,babel-plugin-component
已经不再适用于babel7,文本将会使用babel-plugin-import实现按需引入的功能。其实babel-plugin-import
与babel-plugin-component
差异不大,就是做一下语法转换。
组件分开打包以及全部打包
刚刚在分析element-ui的打包目录可以知道,当需要按需引入的时候,会分别引入对应组件的js以及css,所以不同的组件都需要打包,所以在webpack配置中,每个组件都需要有一个打包入口。同时还需要有一个总的打包入口,这个入口文件引入所有的组件并且注册
组件分开打包
组件库中的组件是非常多的,如果每一个入口文件都是手动在webpack配置文件中维护的话,会非常麻烦,这里设定一个规则,所有的组件都放在某个目录下,这里为components
,打包的时候,自动扫描这个目录。自动生成入口配置信息。示例:
- components
- button
- pl-button.vue
- index.js
- ele
- index.js
- icon
- pl-icon.vue
- index.js
- input
- pl-input.vue
- index.js
里面的index.js就是每一个组件的入口文件,组件的入口文件暴露一个install方法,用来注册组件,比如button中的入口文件:
import Button from './pl-button'
Button.install = (Vue) => Vue.component(Button.name, Button)
export default Button
当需要按需引入button的时候,可以通过以下方式使用这个组件:
const Button = require('[button打包之后得到的js地址]')
Vue.use(Button)
扫描组件入口信息:
/*build/utils.js*/
const fs = require('fs')
const path = require('path')
const join = path.join
const resolve = (dir) => path.join(__dirname, '../', dir)
function getComponentEntries(path) {
let files = fs.readdirSync(resolve(path));
const componentEntries = files.reduce((ret, item) => {
const itemPath = join(path, item)
const isDir = fs.statSync(itemPath).isDirectory();
if (isDir) {
ret[item] = resolve(join(itemPath, 'index.js'))
} else {
const [name] = item.split('.')
ret[name] = resolve(`${itemPath}`)
}
return ret
}, {})
console.dir(componentEntries)
return componentEntries
}
getComponentEntries('components')
得到的结果:
这样就得到的所有组件的入口配置信息;
组件全部打包入口
当不需要按需引入的时候,开发者引入的是总的打包文件,这个总的打包文件是通过总的打包入口文件打包而来的,根据上面的组件信息,总的打包入口文件:
/*src/index.js*/
import Button from 'components/button'
import Icon from 'components/icon'
import Ele from 'components/ele'
const PlainApp = {
install(Vue) {
Vue.use(Button)
Vue.use(Icon)
Vue.use(Ele)
}
}
export default PlainApp
同时,还需要在打包入口中增加总的打包入口文件地址:
entry:{
index: resolve('src/index.js'),
},
所以,最后的入口信息为:
entry: {
...getComponentEntries('components'),
index: resolve('src/index.js'),
},
测试按需引入
为了测试我们自己创建的组件库能够支持按需引入,我们需要创建一个示例工程,用来测试按需引入是否成功,这里我们使用vuecli3脚手架创建的工程来测试,不过在测试我们自己创建的组件库之前,先测试element-ui的按需引入功能,看看有哪些特性
- 创建一个vuecli3工程:
配置随意,我这里选手动选择特性,只要vue create test-load-on-demend
Babel
以及Css Pre-preocessor
然后选择Sass/SCSS
,剩下的随意。 - 启动测试
npm run serve
一切正常 - 安装element-ui
npm i element-ui -S
- 在main.js中全部安装element-ui
import Vue from 'vue' import App from './App.vue' import ELEMENT from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' Vue.use(ELEMENT) Vue.config.productionTip = false new Vue({ render: h => h(App), }).$mount('#app')
- 在App.vue中使用element的button组件以及input组件,同时去掉App.vue中自带的HellWorld组件,以及删除HelloWorld.vue这个文件,因为不需要这个。
/*src/App.Vue*/ <template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png"> <el-button>hello world</el-button> <el-input/> </div> </template> <script> export default { name: 'app', } </script> <style lang="scss"> </style>
- 启动,勾上这个
Disabled cache
,在刷新一下页面。
可以看到,app.js的大小为6.7M - 在根目录下创建
vue.config.js
,配置publicPath
为./
;
这么做可以使得打包出来的文件,可以通过file协议打打开,查看效果。/*vue.config.js*/ module.exports = { publicPath: './' }
- 打包页面
在生成的dist目录中,直接打开index.htmlnpm run build
同理,打开之后,调出控制台,勾上Disabled cache
,刷新:
可以看到,chunk-vendor文件的大小为700多k。 - 现在配置按需引入,只要button以及input组件,首先删除根目录下的babel.config.js,新建一个.babelrc文件:
/*.babelrc*/ { "presets": ["@vue/app", ["@babel/preset-env", { "modules": false }]], "plugins": [ [ "component", { "libraryName": "element-ui", "styleLibraryName": "theme-chalk" } ] ] }
- 安装
babel-plugin-component
npm i babel-plugin-component -S
- 修改
main.js
,调整为按需引入组件,只要Button以及Inputimport Vue from 'vue' import App from './App.vue' /*import ELEMENT from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' Vue.use(ELEMENT)*/ import {Button,Input} from 'element-ui' Vue.use(Button) Vue.use(Input) Vue.config.productionTip = false new Vue({ render: h => h(App), }).$mount('#app')
- 启动,同样的,查看app.js大小
可以看到,app.js的大小变为2m左右了; - 接下来打包
会稍微感觉到,打包要比上次快了很多,因为只打包了两个组件。打包完之后,打开index.html:npm run build
可以看到,chunk-vendor只剩下33k左右,性能上有了巨大的优化。
组件库按需引入实现
好了,现在已经准备好了测试工程,准备开始实现我们自己的组件库工程,使得自己开发的组件库工程也有按需引入的功能,下面介绍两种方式创建组件库工程,实现按需引入功能
- 基于vuecli2创建的webpack工程,基于这个工程开发组件库,并实现按需引入功能
- 基于vuecli3创建的webpack工程,基于这个工程开发组件,并且实现按需引入功能
这里为什么要分为两种方式,一个是vuecli2创建的webpack工程,配置可以自主定义,较为灵活,因为webpack的配置文件都暴露出来了;另一个vuecli3创建的工程使用方便,简化了配置文件,同时很方便配置多单页面应用,以及其他打包配置。但是,两个工程实现按需引入功能的方式,却是有很大的差别。