vue 公共组件多项目共享方案

1,265 阅读5分钟

背景

在多项目中,会存在一些公共组件,这些组件是需要在多项目中进行共享的

npm 包方案

将公共组件封装,通过发布 npm包 方式实现共享,发布更新流程如下

image.png

缺点:

  1. 在多项目中更新是很大的问题,每次更新公共组件需要发布包,应用需要更新依赖,流程显得麻烦
  2. 本质上只是代码层面的共享,在多个项目中都需要打包依赖

注:这篇文章有详情介绍如何发布 npm 包 如何发布一个 npm 库,自动化管理版本号、生成 changelog 等

Git Submodule 子模块方案

在 git 中使用子模块 submodule 的方式,通过子模块可以将公共组件作为子目录包含到项目中,主工程库和子模块都有各自的 git,可以保持相对独立,主仓库只是获取到了子仓库的引用

由于子模块源码包含在主工程,这种方式方便主工程和子模块之间的调试,调试完成后,也可在主工程更新子模块

submodulenpm包 相似,前者所管理的依赖是子模块的源码,后者管理的是子模块的构建产物

发布流程:

image.png

添加子模块

添加完成后,会在执行目录下,发现多了两个文件,子模块目录和.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"
  }
}

打包后文件

image.png

加载组件

之前通过 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()
})

到这里后,就可以正常使用组件了

image.png

缺点:

  1. 动态加载组件时候,需要注意执行顺序问题,先加载完组件后再使用
  2. 体积问题,因为是全量引入,会导致性能下降

补充,我们在本地验证的时候,可以使用 live-server 命令,本地模拟线上服务器环境

image.png

由于运行起来的域名是 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 子模块、服务器组件方案,可以看出各种都有优缺点,并无好坏之分,能抓老鼠就是好猫。