Write By CS逍遥剑仙
我的主页: www.csxiaoyao.com
GitHub: github.com/csxiaoyaojianxian
Email: [email protected]
1. 背景
前端模块化开发模式已成主流,但随着前端项目规模的不断扩大,开发者可能会遇到以下一些问题:
- 不仅打包的效率越来越低下,而且打包后的文件体积也不断增加;
- 首屏加载文件过大,白屏时间过长;
- 有时,加载的组件名称不确定,需要动态确定需要加载的组件;
- 整体打包导致大型项目若需要扩展组件,开发者必须下载完整工程,被迫开放源码,且易冲突
本文将选用 vue 框架,使用三种方式实现前端模块的动态加载,分别解决上述的一个或多个问题。
2. vue 动态 & 异步组件
在大型应用中,我们常常需要将应用切分,在客户端请求时按需加载,减少首次请求的文件体积,并缓存供下次使用。vue 框架提供了动态组件 & 异步组件功能 ( 文档地址 )。
2.1 动态组件实现组件动态切换
动态组件即通过 is
属性来动态地切换不同的组件:
<component v-bind:is="currentComponent"></component>
2.2 异步组件实现懒加载
而异步组件则允许以工厂函数的方式定义组件并异步解析,只有当该组件需要被渲染时才会触发该工厂函数,并缓存组件供下次使用:
Vue.component(
'async-example',
() => import('./async-component') // 返回一个 Promise 对象
)
异步组件同样适用于组件的局部注册方式:
new Vue({
components: {
'async-component': () => import('./async-component')
}
})
2.3 动态 & 异步组件实现组件动态加载
结合动态组件和异步组件的特性,即可轻松实现动态加载,即修改动态组件的 is
标签,触发异步组件的加载。
3. webpack – require.context
使用 webpack 打包,模块需要通过 es6-import
或是 require
的方式导入。当导入的组件较多时,一个个导入,会比较繁琐。【方式2】使用 vue 的动态&异步组件实现了懒加载,但需要显式地指定所有需要加载的组件,幸运的是,webpack 提供了 require.context
的 api 供开发者动态导入模块,这样开发者甚至可以根据接口返回动态地加载组件,下面是一个 demo:
<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为匹配组件文件名的正则表达式:
const requireComponent = require.context(
'./comps', // 其组件目录的相对路径
false, // 是否查询其子目录
/comp[0-9]+\.(vue|js)$/ // 匹配组件文件名的正则表达式
)
遍历 require.context
返回值的 key,并注册,若这个组件选项是通过 export default
导出的会优先使用 .default
。
requireComponent.keys().forEach(fileName => {
const componentConfig = requireComponent(fileName) // 获取组件配置
const componentName = fileName.split('/').pop().replace(/\.\w+$/, '')
// 全局注册组件
Vue.component(
componentName,
componentConfig.default || componentConfig
)
})
4. vue 子组件独立打包
上面的【方式3】解决了【方式2】显式指定全部组件的不便,但动态组件仍需要和主项目一起打包,在一些场景下则显得不便,最理想的状态应该是:主程序和子组件独立打包,能够根据异步接口的返回结果动态地加载组件。
4.1 webpack + vue-loader
webpack 中 vue 子组件独立打包,需要使用对应的 vue-loader
识别 vue 文件,见 01-webpack
,可以参考 vue-loader
文档,vue-loader文档地址,需要配置 webpack.config.js
{
test: /\.vue$/,
loader: 'vue-loader'
}
构建的组件的使用
<!-- 动态组件 -->
<div :is="compName"></div>
动态载入 build 后的 bundle
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 文档地址:
# 将一个单独的入口构建为一个库
$ vue-cli-service build --target lib --name myLib [entry]
4.3 导入动态组件脚本文件
经过打包的组件可以生成 js 脚本文件或 lib 库文件,可以根据接口等方式的返回结果,通过 script
或 import
的方式引入,见 03-vue-lib
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. 参考资料
- 动态组件 & 异步组件 (https://cn.vuejs.org/v2/guide/components-dynamic-async.html)
- Webpack-require.context (https://webpack.js.org/guides/dependency-management/#requirecontext)
- Vue-loader手动设置 (https://vue-loader.vuejs.org/zh/guide/#手动设置)
- Vue-cli 应用 (https://cli.vuejs.org/zh/guide/build-targets.html#应用)