简述

按需加载就是需要什么,就只要什么,其他的东西不要。这样做的目的是为了缩小打包体积。

示例

比如当前流行的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-importbabel-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的按需引入功能,看看有哪些特性

  1. 创建一个vuecli3工程:
    vue create test-load-on-demend
    
    配置随意,我这里选手动选择特性,只要Babel以及Css Pre-preocessor
    在这里插入图片描述
    然后选择Sass/SCSS,剩下的随意。
  2. 启动测试
    npm run serve
    
    在这里插入图片描述
    一切正常
  3. 安装element-ui
    npm i element-ui -S
    
  4. 在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')
    
  5. 在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>
    
  6. 启动,勾上这个Disabled cache,在刷新一下页面。
    在这里插入图片描述
    可以看到,app.js的大小为6.7M
  7. 在根目录下创建vue.config.js,配置publicPath./
    /*vue.config.js*/
    module.exports = {
        publicPath: './'
    }
    
    这么做可以使得打包出来的文件,可以通过file协议打打开,查看效果。
  8. 打包页面
    npm run build
    
    在生成的dist目录中,直接打开index.html
    在这里插入图片描述
    同理,打开之后,调出控制台,勾上Disabled cache,刷新:
    在这里插入图片描述
    可以看到,chunk-vendor文件的大小为700多k。
  9. 现在配置按需引入,只要button以及input组件,首先删除根目录下的babel.config.js,新建一个.babelrc文件:
    /*.babelrc*/
    {
      "presets": ["@vue/app", ["@babel/preset-env", { "modules": false }]],
      "plugins": [
        [
          "component",
          {
            "libraryName": "element-ui",
            "styleLibraryName": "theme-chalk"
          }
        ]
      ]
    }
    
    
  10. 安装babel-plugin-component
    npm i babel-plugin-component -S
    
  11. 修改main.js,调整为按需引入组件,只要Button以及Input
    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)*/
    
    import {Button,Input} from 'element-ui'
    Vue.use(Button)
    Vue.use(Input)
    
    
    Vue.config.productionTip = false
    new Vue({
        render: h => h(App),
    }).$mount('#app')
    
  12. 启动,同样的,查看app.js大小
    在这里插入图片描述
    可以看到,app.js的大小变为2m左右了;
  13. 接下来打包
    npm run build
    
    会稍微感觉到,打包要比上次快了很多,因为只打包了两个组件。打包完之后,打开index.html:
    在这里插入图片描述
    可以看到,chunk-vendor只剩下33k左右,性能上有了巨大的优化。

组件库按需引入实现

好了,现在已经准备好了测试工程,准备开始实现我们自己的组件库工程,使得自己开发的组件库工程也有按需引入的功能,下面介绍两种方式创建组件库工程,实现按需引入功能

  • 基于vuecli2创建的webpack工程,基于这个工程开发组件库,并实现按需引入功能
  • 基于vuecli3创建的webpack工程,基于这个工程开发组件,并且实现按需引入功能

这里为什么要分为两种方式,一个是vuecli2创建的webpack工程,配置可以自主定义,较为灵活,因为webpack的配置文件都暴露出来了;另一个vuecli3创建的工程使用方便,简化了配置文件,同时很方便配置多单页面应用,以及其他打包配置。但是,两个工程实现按需引入功能的方式,却是有很大的差别。

test-load-on-demand工程地址

vuecli2创建的组件库工程实现按需引入

文章地址

vuecli3创建的组件库工程实现按需引入

文章地址

Logo

前往低代码交流专区

更多推荐