【技术创作101训练营】三种不同场景下 vue 组件动态加载的方法及实现

2020-09-23 13:18:23 浏览数 (1)

Write By CS逍遥剑仙 我的主页: www.csxiaoyao.com GitHub: github.com/csxiaoyaojianxian Email: sunjianfeng@csxiaoyao.com

1. 背景

前端模块化开发模式已成主流,但随着前端项目规模的不断扩大,开发者可能会遇到以下一些问题:

  1. 不仅打包的效率越来越低下,而且打包后的文件体积也不断增加;
  2. 首屏加载文件过大,白屏时间过长;
  3. 有时,加载的组件名称不确定,需要动态确定需要加载的组件;
  4. 整体打包导致大型项目若需要扩展组件,开发者必须下载完整工程,被迫开放源码,且易冲突

本文将选用 vue 框架,使用三种方式实现前端模块的动态加载,分别解决上述的一个或多个问题。

2. vue 动态 & 异步组件

在大型应用中,我们常常需要将应用切分,在客户端请求时按需加载,减少首次请求的文件体积,并缓存供下次使用。vue 框架提供了动态组件 & 异步组件功能 ( 文档地址 )。

2.1 动态组件实现组件动态切换

动态组件即通过 is 属性来动态地切换不同的组件:

代码语言:txt复制
<component v-bind:is="currentComponent"></component>

2.2 异步组件实现懒加载

而异步组件则允许以工厂函数的方式定义组件并异步解析,只有当该组件需要被渲染时才会触发该工厂函数,并缓存组件供下次使用:

代码语言:txt复制
Vue.component(
  'async-example',
  () => import('./async-component') // 返回一个 Promise 对象
)

异步组件同样适用于组件的局部注册方式:

代码语言:txt复制
new Vue({
  components: {
    'async-component': () => import('./async-component')
  }
})

2.3 动态 & 异步组件实现组件动态加载

结合动态组件和异步组件的特性,即可轻松实现动态加载,即修改动态组件的 is 标签,触发异步组件的加载。

3. webpack - require.context

【 demo 地址 】

使用 webpack 打包,模块需要通过 es6-import 或是 require 的方式导入。当导入的组件较多时,一个个导入,会比较繁琐。【方式2】使用 vue 的动态&异步组件实现了懒加载,但需要显式地指定所有需要加载的组件,幸运的是,webpack 提供了 require.context 的 api 供开发者动态导入模块,这样开发者甚至可以根据接口返回动态地加载组件,下面是一个 demo:

代码语言:txt复制
<template>
  <div id="app">
    <component :is="app"></component>
  </div>
</template>
<script>
import Vue from 'vue'
export default {
  data () {
    return {
      app: ''
    }
  },
  created () {
    // 模拟异步请求
    setTimeout(() => {
      const requireComponent = require.context('./comps', false, /comp[0-9] .(vue|js)$/)
      requireComponent.keys().forEach(fileName => {
        const componentConfig = requireComponent(fileName)
        const componentName = fileName.split('/').pop().replace(/.w $/, '')
        Vue.component(
          componentName,
          componentConfig.default || componentConfig
        )
      })
      // 修改动态组件 is 标签
      setTimeout(() => {
        this.app = 'comp2'
      }, 2000)
    }, 1000)
  }
}
</script>

require.context 需要传入三个参数,参数1为组件目录的相对路径,参数2为是否查询其子目录,参数3为匹配组件文件名的正则表达式:

代码语言:txt复制
const requireComponent = require.context(
	'./comps', // 其组件目录的相对路径
	false, // 是否查询其子目录
	/comp[0-9] .(vue|js)$/ // 匹配组件文件名的正则表达式
)

遍历 require.context 返回值的 key,并注册,若这个组件选项是通过 export default 导出的会优先使用 .default

代码语言:txt复制
requireComponent.keys().forEach(fileName => {
	const componentConfig = requireComponent(fileName) // 获取组件配置
	const componentName = fileName.split('/').pop().replace(/.w $/, '')
	// 全局注册组件
	Vue.component(
		componentName,
		componentConfig.default || componentConfig
	)
})

4. vue 子组件独立打包

【 demo 地址 】

上面的【方式3】解决了【方式2】显式指定全部组件的不便,但动态组件仍需要和主项目一起打包,在一些场景下则显得不便,最理想的状态应该是:主程序和子组件独立打包,能够根据异步接口的返回结果动态地加载组件。

4.1 webpack vue-loader

webpack 中 vue 子组件独立打包,需要使用对应的 vue-loader 识别 vue 文件,见 01-webpack,可以参考 vue-loader 文档,vue-loader文档地址,需要配置 webpack.config.js

代码语言:txt复制
{
	test: /.vue$/,
	loader: 'vue-loader'
}

构建的组件的使用

代码语言:txt复制
<!-- 动态组件 -->
<div :is="compName"></div>

动态载入 build 后的 bundle

代码语言:txt复制
loadScript('xxx').then(() => {
  Vue.component(compName, window[compName].default);
  this.compName = compName;
})

4.2 vue-cli

vue 官方提供的脚手架 vue-cli 集成了 vue-loader,开箱即用,可以轻松实现独立入口打包。见 02-vue-lib,参考 vue-cli 文档,vue-cli 文档地址:

代码语言:txt复制
# 将一个单独的入口构建为一个库
$ vue-cli-service build --target lib --name myLib [entry]

4.3 导入动态组件脚本文件

经过打包的组件可以生成 js 脚本文件或 lib 库文件,可以根据接口等方式的返回结果,通过 scriptimport 的方式引入,见 03-vue-lib

代码语言:txt复制
loadScript('http://xxx/xx.js').then(() => {
	Vue.component(compName, window[compName].default || window[compName]);
	this.compName = compName;
})

5. 使用场景总结

本文总结了三种组件动态加载的方式,其中:

(1) vue 动态 & 异步组件的方式最简单,能够实现组件的懒加载,可以在普通项目中直接使用,但需要显式地指定所有动态组件并和主程序一起打包,适合大部分场景;

(2) webpack 的 require.context 方式支持用正则表达式的方式异步导入组件,适合导入多个文件名满足一定规律的组件,并且支持从接口等方式,根据返回结果异步加载组件,但是仍然需要和主程序一起打包,适合多人同时在一个项目下开发,并且子组件迭代频繁,需要通过文件名的正则表达式动态载入的场景;

(3) 子组件独立打包的方式通过 vue-loader 等 webpack 插件,对子组件独立打包,并根据接口返回结果动态加载,但是实现较为复杂,适合大型系统用于扩展插件,或者在不开放源码的前提下实现项目间的组件调用。

独立打包不仅能够缩短项目的打包时间,减少打包文件体积,加快加载速度,还能实现项目间的组件调用。在实践中,我们需要根据不同场景选择适合的方式。

6. 参考资料

  1. 动态组件 & 异步组件 (https://cn.vuejs.org/v2/guide/components-dynamic-async.html)
  2. Webpack-require.context (https://webpack.js.org/guides/dependency-management/#requirecontext)
  3. Vue-loader手动设置 (https://vue-loader.vuejs.org/zh/guide/#手动设置)
  4. Vue-cli 应用 (https://cli.vuejs.org/zh/guide/build-targets.html#应用)

0 人点赞