背景

一个产品线中会有多个项目,每个项目都存在一些公共组件。这些组件是需要共享的

解决方案

我们一开始最先想到的是使用npm包进行共享的。但是最大的问题就是npm包的更新问题。由于在开发初期,共享组件的改动非常频繁,一天下来可能发布三四个版本。这样子下来就会导致每个项目都需要重新安装依赖,然后重新打包,显得非常麻烦。

无独有偶,我在无意间看见一篇文章:滴滴 webapp 5.0 Vue 2.0 重构经验分享,里面提及到了异步加载的业务线组件,如何动态注册?,使用到了 Vue 提供了动态注册组件的 api,通过Vue.component('async-example',function(resolve){ //... })的方式可以去加载远程的组件。基于这个解决方案,我做了一个简单的 demo 来尝试一下,发现是可行的。

公共组件处理

我们以一个自定义demo-button组件为例:

编写组件

新建button/demo-button.vue文件

<template>
  <button class="demo-button" :class="`demo-button-${type}`">按钮</button>
</template>

<script>
export default {
  props: {
    type: {
      type: String,
      default: "primary",
    },
  },
};
</script>

<style scope>
.demo-button {
  appearance: none;
  box-sizing: border-box;
  display: inline-block;
  margin: 0;
  padding: 12px 20px;
  user-select: none;
  background: #fff;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  outline: none;
  transition: 0.1s;
}
.demo-button-primary {
  color: #fff;
  background-color: #409eff;
  border-color: #409eff;
}
.demo-button-success {
  color: #fff;
  background-color: #67c23a;
  border-color: #67c23a;
}
</style>

新建button/index.js文件导出组件

import DemoButton from "./demo-button.vue";

export default DemoButton;

打包组件

关于组件的打包,我们其实并不需要自己搭建一个环境出来,可以直接借助vue-cli-servicevue-cli-service不仅可以打包项目,还可以单独打包组件,详情可以点击这里。我们在package.json中添加如下script脚本:

{
  "scripts": {
    "build-component": "vue-cli-service build --target lib --name demo-button ./src/component/button/index.js"
  }
}

--target lib:打包出来的文件格式。指定lib格式会打包umdcommonjs格式的文件格式。我们这里需要的是umd格式的文件

--name demo-button:两个作用。第一个作用是声明打包出来的文件名。第二个作用就是umd格式文件暴露在全局的变量名,即可以通过windown['demo-button']获取该自定义组件

./src/component/button/index.js:组件的入口,入口文件也可以直接指向.vue文件

打包出来的文件包括demo-button.umd.jsdemo-button.css,我们只需要把这 2 个文件丢到服务器上面即可

加载公共组件

以往加载异步组件的方式是:

Vue.component(
  "demo-button",
  // 这个动态导入会返回一个 `Promise` 对象。
  () => import("xxx/xxx.vue")
);

这种方式是结合了webpack的功能,并且xxx/xxx.vue文件是在项目中的,但是我们此时的公共组件是在服务器上面的,需要跑到服务器上面加载,因此上面这种方式是不行的。

非常庆幸的是Vue.component第二个参数是一个工厂函数,并且需要返回一个promise实例。因此我们需要自己封装一个函数,并把组件通过resolve的方式返回出去。

加载服务器文件

function 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();
  });
}

执行文件代码并获取组件

/**
 * 
 * @param {*} src 文件路径
 * @param {*} componentName 跟打包时传入的`--name demo-button`有关
 * @returns 
 */
function resolveComponent(src, componentName) {
  return httpRequest(src).then((res) => {
    // 使用eval或者new Function执行代码
    // eval(res);
    new Function(res)();
    // 代码执行完毕之后会把组件挂载到windown下面,这个跟打包的格式有关
    // 为了防止全局环境变量的污染,打包的时候可以使用命名空间
    const component = window[componentName].default;
    return component;
  });
}

注意点:

1、获取得到的文件内容是字符串,需要使用eval或者new Function执行代码,这里强烈推荐使用new Function这种方式使用

2、字符串代码执行完毕之后,windown对象上面会存在一个demo-button的全局对象,此时一定要通过.default获取,因为我们是通过export default导出的

3、打包的文件格式一定要用umd格式,因为可以在全局对象windown中找到,其他格式的找不到在哪里

4、为了防止全局变量的污染,打包的时候可以使用命名空间,获取组件的时候也需要把命名空间添加上

注册组件

Vue.component(
  'demo-button',
  // 这个动态导入会返回一个 `Promise` 对象。
  ()=>resolveComponent('http://127.0.0.1:8081/demo-button.umd.js','demo-button')
)

到这里为止,我们就可以使用demo-button这个组件了

关于样式的处理方式

因为我们是使用vue-cli-service进行打包,所以css文件和js文件是分离的。主要自行引入服务端的样式文件。可以在html文件中使用style标签引入样式,也可以在css文件中通过原生css@import进行引入

当然,如果是自己搭建的环境,可以考虑把样式也打包进js文件中,这样子就可以不用考虑样式引入的问题了

总结

到此,我们就已经可以加载远程服务器的文件,并使用组件了,这样子做的最大好处就是组件的频繁更新不用重新发包,项目的开发人员也不用重新安装依赖。组件修改完成之后,只需要重新打包放到远程服务上即可。

Logo

前往低代码交流专区

更多推荐