背景
在多项目中,会存在一些公共组件,这些组件是需要在多项目中进行共享的
npm 包方案
将公共组件封装,通过发布 npm包 方式实现共享,发布更新流程如下
缺点:
- 在多项目中更新是很大的问题,每次更新公共组件需要发布包,应用需要更新依赖,流程显得麻烦
- 本质上只是代码层面的共享,在多个项目中都需要打包依赖
注:这篇文章有详情介绍如何发布 npm 包 如何发布一个 npm 库,自动化管理版本号、生成 changelog 等
Git Submodule 子模块方案
在 git 中使用子模块 submodule 的方式,通过子模块可以将公共组件作为子目录包含到项目中,主工程库和子模块都有各自的 git,可以保持相对独立,主仓库只是获取到了子仓库的引用
由于子模块源码包含在主工程,这种方式方便主工程和子模块之间的调试,调试完成后,也可在主工程更新子模块
submodule 和 npm包 相似,前者所管理的依赖是子模块的源码,后者管理的是子模块的构建产物
发布流程:
添加子模块
添加完成后,会在执行目录下,发现多了两个文件,子模块目录和.gitmodules
// 路径: 子模块在工程下的路径,默认是主项目同层级下,如果要设置其他位置,需要指定路径(不能是已有的目录),或者到具体路径目录下执行命令
git submodule add 子模块远程仓库url git 路径
这个子模块目录就是需要共享的公共组件,它是一个完整的仓库,我们在项目中执行 git 操作将不会影响子模块
更新子模块
// 1.更新全部子模块(包含可能嵌套的子模块)的 branch 和 commit id 信息
git submodule update --recursive --remote
// 2.到子模块目录下
git pull
// 3.单个执行效率太慢,可以用 foreach 批量操作子模块更新
git submodule foreach git pull
删除子模块
1.删除 .gitmodules 子模块配置
2.删除 .git/config 子模块配置
3.删除 .git/modules 子模块
推送子模块更新
// 在推送到主项目前检查所有子模块是否已推送,如果子模块未提交,将报错
git push --recurse-submodules=check
注意: 如果本地子模块文件变动未 push,主项目推送到 git 后,会导致其他成员 pull 失败,因为无法获取到子模块的变动
缺点:子模块的更新、多人协作等操作比较复杂,对于开发人员的来说,增加了学习的成本
服务器公共组件方案
我们可以把公共组件放到服务器上,通过加载服务器组件文件,动态注册组件,通过加载服务器的组件文件,更新包只需要重新打包放到服务器
创建组件
在公共组件文件夹 components 下建一个公共组件 btn-button 作为示例
新建btnButton/src/index.vue文件
<template>
<div class="btn-button-warp">
<el-button type="primary" round>示例按钮</el-button>
</div>
</template>
<script>
export default {
name: 'btnButton',
data() {
return {}
},
mounted() {
this.init()
},
methods: {
init() {
console.log('btn 组件初始化完成')
}
}
}
</script>
<style lang="less">
.btn-button-warp {
height: 200px;
line-height: 200px;
text-align: center;
background: red;
}
</style>
新建 btnButton/index.js 导出文件
// 导入组件,组件必须声明 name
import btnButton from './src'
// 为组件提供 install 安装方法,供按需引入
btnButton.install = (Vue) => Vue.component(btnButton.name, btnButton)
// 导出组件
export default btnButton
在公共组件 components 下新建 index.js 统一导出文件
// 导入组件
import btnButton from './btnButton'
// 组件列表
const components = [btnButton]
// 定义 install 方法,接收 Vue 作为参数。如果使用 use 注册插件,那么所有的组件都会被注册
const install = function (Vue) {
// 判断是否安装
if (install.installed) return
// 遍历注册全局组件
components.map((component) => Vue.component(component.name, component))
}
// 判断是否是直接引入文件
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
export default {
// 导出的对象必须具有 install,才能被 Vue.use() 方法安装
install,
// 以下是具体的组件列表
btnButton
}
打包组件
通过 vue-cli-service 打包公共组件
{
"scripts": {
"build-component": "vue-cli-service build --target lib --name btn-button --dest dist src/components/index.js"
}
}
打包后文件
加载组件
之前通过 npm包 的方式加载组件是:
import btnButton from '@component-plugin/btn-button'
import '@component-plugin/btn-button/btn-button.css'
Vue.use(btnButton)
现在是把组件放到服务器上,需要加载服务器上的组件,上面的方式就不能使用了
加载服务器组件
const httpRequest = (src) => {
return new Promise((resolve) => {
var xhr = new XMLHttpRequest()
xhr.open('GET', src, true)
xhr.setRequestHeader('Content-Type', 'application;charset=utf-8')
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
resolve(xhr.responseText)
}
}
xhr.send()
})
}
const resolveComponent = (src, componentName) => {
return httpRequest(src).then((res) => {
// new Function 用于字符串执行代码
new Function(res)()
// 组件执行完毕后挂载到windown下面
const component = window[componentName].default
component.install(Vue)
})
}
const createCssLink = (href) => {
const link = document.createElement('link')
link.type = 'text/css'
link.rel = 'stvlesheet'
link.href = href
document.head.appendChild(link)
}
router.beforeEach(async (to, from, next) => {
createCssLink('/api/btn-button.css')
await resolveComponent('/api/btn-button.umd.js', 'btn-button')
next()
})
到这里后,就可以正常使用组件了
缺点:
- 动态加载组件时候,需要注意执行顺序问题,先加载完组件后再使用
- 体积问题,因为是全量引入,会导致性能下降
补充,我们在本地验证的时候,可以使用 live-server 命令,本地模拟线上服务器环境
由于运行起来的域名是 127.0.0.1:59112,跟我们的本地 localhost 产生跨域问题,我们可以在 vue.config.js 配置一下代理
module.exports = defineConfig({
devServer: {
proxy: {
//配置跨域
'/api': {
target: 'http://127.0.0.1:59112',
changOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
})
总结
通过对比 npm包、submodule 子模块、服务器组件方案,可以看出各种都有优缺点,并无好坏之分,能抓老鼠就是好猫。