likes
comments
collection
share

解决模块共享问题之加载远程组件

作者站长头像
站长
· 阅读数 11

需求场景描述

在B端的多个业务系统中,模块A 存在于存在业务系统甲中,强依赖该业务系统;模块A的体积随着需求的不断更新迭代也在一天天变大,加载的速度也在一天一天的变慢。

加载慢的原因有多个方面,本身模块A的功能很大很复杂,大量组件,以及网络请求,以及强制的接口依赖顺序等等各方面原因;

项目的脚手架依旧使用的是 vue2.x + element ui ,这熟悉的配方,熟悉的味道; 但是因为是内网项目且对应的客户群体尤为特殊,我们自己封装了统一的业务系统脚手架以及魔改的它妈都认不出的Element UI,作为我们的业务统一的基座;

就在这样的项目情况下,项目乙 也需要集成 模块A, 而且整个的业务流程模块A在乙项目中作为前置流程,有了这个前置才能流转到项目甲;

是的没错,就这么扯,模块A既满足了项目乙的业务,还满足了系统甲的业务,然后构成了整个业务的闭环;

因为只是流程的不同和部分内容的不同(很多都是共用的),我们决定将业务进行耦合;决定不拆分。

目前的解决方案

经过了第一版的 iframe 方案;

又经过了第二版的 独立组件按需集成 方案;

在运行了很长一段时间后(半年至一年)一切看起来都很完美;因为毕竟有了对比就能凸显出来优势对吧;

如果不出意外,这个方案将继续沿用,既然这样说那么意外就马上要来了.....

没错 问题已经凸显出来了,这次的问题不是客户反馈的,是测试同学反馈的;

因为 项目A,项目B 都依赖了公共组件包,所以在每次提测需求的时候,项目A,项目B,都要单独构建,但是因为项目A,和项目B是不同的测试同学负责,导致测试A项目的同学经常性的遗忘构建项目B,就直接进行开始测试了。导致报了很多不复存在的bug, 最关键是有一两次都要进行发版了,最后发现项目B根本没有正确的构建,所以整个测试过程测了个寂寞;

测试同学的工作方式这些我们不予置评,他们就提出了能不能不每次都要去构建两个项目;

这其实咋心里也明白,这问题远远不止构建麻烦,真正的问题在于 公共组件包打了补丁或者迭代了需求后,就必须 A,B两个项目同时走发布流程,部署到生产环境,因为又是不同的部门负责A, B项目,导致 A在加班发版的时候,经常给B负责的同学打电话让别人提测提发布....

那其实正常的迭代 这样是没有问题的,但是遇到紧急的或者高频率的提测或发布,就显得不那么友好。

本着生产环境跑的好好的,能不动就不动的原则,我们理应回绝这合理的要求;

但是这毕竟不是咋的做事风格啊 ,问题都凸显了,那就去解决啊

那就整起来啊;必须得整起来,加班也得整起来;

经过一番思考 我们畅想,如果B项目能够实时加载A项目的模块,那问题不就得到了完美解决了吗, 那首选方案就是 iframe 啊

但是 iframe 存在的问题,我们已经很明了啊,难道一切又要回到最初? 那中间的过程不是白瞎了吗?

这时候就想到了 公共js 的问题;也就是下面我们要讲到的 远程组件;

加载远程组件

既然想到了,那就去实践啊,毕竟实践了就能知道行不行,为什么不行,哪儿不行。

首先我们坚信这个方案是可行的;

公共组件 我们首先需要打包成UMD 或者。IIFE 格式

参考打包构建脚本:

//rollup.config.js

import vue from 'rollup-plugin-vue' 
import commonjs from 'rollup-plugin-commonjs' 
import builtins from 'rollup-plugin-node-builtins'
import globals from 'rollup-plugin-node-globals'
import { ugligy } form 'rollup-plugin-node-ugligy'



export default { 
    input: './src/index.js', 
    output: { 
        format: 'iife', 
        file: './dist/index.js', 
        name: 'ComononUI' 
    }, 
    plugins: [
        globals(),
        builtins(),
        commonjs(), 
        vue(),
        ugligy()
    ] 
}

通过rollup.js 我们 js打包成了 iife 的js文件;

那么我们接下来就需要想办法把这个文件运行起来;

我们这里分三步

第一步 把文件拷贝到A项目的public目录下,然后通过浏览器直接输入地址确定能够访问到该文件

这个操作比较简单,不做过多的叙述

在使用jenkins 构建的时候,我们配合 nodejs 脚本,

在 npm ci 之后

把node_modules/commonUi/dist/index.js 文件 拷贝到 public 下

第二部 我们在项目B去加载这个文件;


// loadConponent.js

export function loadComponent(url){
   
   const clearScript = (script)=>{
        if (script.parentNode) {
            script.parentNode.removeChild(script)
        }
        script.onload = null
        script.onerror = null
        script = null
    }

    return new Promise((resolve, reject) => {
        const script = document.createElement('script')
        document.body.append(script)
        script.src=url+'?t='+new Date().getTime()
        script.onload=()=>{
            resolve()
            clearScript(script)
        }
        script.onerror=()=>{
            reject(new Error('fetch script failed'))
            clearScript(script)
        }
    })
}

第三部 使用组件

我们通过动态组件

<conponent :is="xxx" :props="props" />
<template>
  <component v-if="com" :is="com"></component>
</template>

<script>
import { loadComponent } from './loadConponent.js'
import * as Vue from 'vue'
export default {
  name: 'Test',
  data(){
    return {
      com:null
    }
  },
  mounted() {
    window.Vue=Vue
     loadComponent('http://127.0.0.1/projectA/commonUI/dist/index.js').then(()=>{
      console.log("window['commonUI']>>> ",window['commonUI'])
      window['commonUI'] && window['commonUI'].install(window.Vue)
      this.com = window['commonUI']
      window['commonUI'] = null
    })
  },
}
</script>

这样我们就完成了远程组件的加载应用

关于组件中的 peerDependencies 部分的处理

把所以peerDependencies 中依赖的包 都通过全局的方式挂载在winodw 上

在组件 install 完成后,即可卸载

例如:

import * as Vue from 'vue'
improt * as utils form 'utils'

window.Vue = Vue
window.utuls=utils

需要注意的点是:需要在远程脚本加载之前挂载,不然脚本加载就会报错,peerDependencies 的包的变量找不到, 当然我们也可以一开始在 rollup.config.js 中 申明好 globals

如果不声明的话,在运行 rollup 的时候 也会报警告:

以 axioa 为例:

(!) Missing global variable name Use output.globals to specify browser global variable names corresponding to external modules

axios (guessing 'axios')

你不配置的话,只能根据 guessing 的提示信息进行包名的挂载了

目前该方案已经调试通过,但是不会应用集成在项目中,如你所想它确实存在或多或少的问题。

接下来,我们将使用 模块联邦的形式在进行一次实践,

模块联邦无疑是现阶段比较优雅的方案;此方案在实践的过程中也是遇到了许多问题,截止目前已经逐步彻底解决,并且集成至项目中;已经在测试环境中正式开跑。毕竟它更适合模块共享这类业务场景,比如微前端 对吧。

当然在这过程中也浅浅的尝试了 vue3-sfc-loader 这个方案,它也是支持vue2 的效果也是不错的。

最后的最后

下面请上我们今天的主角:有请小趴菜

解决模块共享问题之加载远程组件