背景:

此前接手了一个老项目(3、4年前开始的一个项目),面临的一个最大的问题是老旧代码的维护和迭代。维护老旧代码的可选方式并不多:

  1. 推倒重来,全部进行重构 => 工作量繁重,成本极高
  2. 依照旧有技术选型进行维护和迭代 => emmmm,一言难尽
  3. 对于迭代需求采用新的技术架构和选型,与已有功能解耦,尽量减少对旧代码的依赖和旧逻辑的修改

出于工作量和项目后续的可维护性和迭代性相对而言第3种方案比较可行。因此调研了一下微前端方案来进行改造。

调研:

微前端入门
每日优鲜微前端改造

Alibaba-console-os VS Single-Spa

Alibaba-console-os

接入方法

  1. main.js 导出Vue做如下改造
import App from './App.vue'  
import { mount } from '@alicloud/console-os-vue-portal'  
export default mount({  
    el: '#app',  
    render: h => h(App)  
})  
  1. vue.config.js 做如下配置,增加 consoleOs插件,并设定相应id
module.exports = {  
    publicPath: 'http://localhost:8080/',  
    "pluginOptions": {  
        "consoleOs": {  
            "id": "os-example-vue"  
        }  
    }  
} 
  1. 在 Host App中通过manifest.json文件引入相应子应用
import { start } from "@alicloud/console-os-kernal";  
import Application from "@alicloud/console-os-react-app";  
function App() {  
    return ( 
        <div className="App">
            <div className="vue"> 
                <Application
                    id="os-example-vue"
                    manifest="http://localhost:8080/os-example-vue.manifest.json"/>
            </div>
        </div>  );  
}  
Single-Spa

Ref: https://www.jianshu.com/p/c0f4b837dbea
Demo

接入方法

  1. main.js vue导出文件做如下改造
import Vue from 'vue';  
import singleSpaVue from 'single-spa-vue';  
import Hello from './main.vue'  
const vueLifecycles = singleSpaVue({  
    Vue,  
    appOptions: {  
        el: '#vue',  
        render: r => r(Hello)  
    }  
});  
export const bootstrap = [  
    vueLifecycles.bootstrap,  
];  
export const mount = [  
    vueLifecycles.mount,  
];  
export const unmount = [  
    vueLifecycles.unmount,  
];
  1. single-spa.config.js 注册应用
import { registerApplication, start } from 'single-spa'  
registerApplication(  
    'vue',  
    () => import('./src/vue/main.js'),  
    () => location.pathname === "/react" ? false : true
);  
start();
  1. index.html 配置
<html>  
    <head></head>  
    <body>  
        <div id="react"></div>  
        <div id="vue"></div>  
        <script src="/dist/single-spa.config.js"></script>  
    </body>  
</html> 
  1. webpack配置
module.exports = {  
    entry: {  
        'single-spa.config': './single-spa.config.js',  
    },  
}
Alibaba-console-osSingleSpa
子应用技术栈React、Vue、AngularReact、Vue、Angular
主应用技术栈React、Angular不限
子应用资源配置模式manifest.jsonsingle-spa.config.js

选择SingleSpa的原因

  1. Alibaba-console-os 的Host App不支持Vue,我们团队的主技术栈是Vue
  2. Alibaba-console-os子应用资源配置通过manifest.json引入,不适用我们公司的资源部署方式

Single-Spa实践:

架构

  1. 加载器:用来调度子应用,决定何时展示哪个子应用。
  2. 包装器:对现有的应用包装,使得加载器可以使用它们
  3. 主应用:一般是包含所有子应用公共部分的项目,
  4. 子应用:各个展示在主应用内容区的应用

大概是这样一个流程:用户访问页面,浏览器运行加载器的js文件,加载器去获取配置文件,并注册配置文件中的各个子应用,首先加载主应用(比如菜单导航等),然后通过路由判定去动态加载子应用。

接入流程

  1. 创建子应用,将admin工程拆分为edu-nav(菜单导航工程)、-old(原有业务功能工程)、edu-mis(新业务功能工程)
  2. 改造子应用,通过singleSpa对子应用进行包装。需要引入single-spa-vue以及systemjs-webpack-interop。

    • single-spa-vue是针对vue项目的包装器,包导出4个生命周期函数:bootstrap、mount、unmount、update。
    • systemjs-webpack-interop可以使webpack和systemjs一起正常工作

image

  1. 创建主应用,充当加载器的功能,有以下几个任务。

    1. 配置资源map,用于后续资源SystemJs引入
      image
    2. 通过SystemJs引入资源文件
      image
    3. 挂载Publisher,用于子应用之间的通信。
    4. registerApplication注册子应用,参数依次为:注册的子应用名称、子应用入口文件(需要是一个函数)、控制应用是否激活的函数。
    5. singleSpa.start。启动函数。

经过上述步骤,整个工程singleSpa改造完成。

实践中发现的一些问题

  1. 需要考虑隔离js,避免css冲突,并考虑按需加载资源
  2. 公用数据信息的同步:菜单或业务线的切换发生时会通过Publisher进行通知,但是在切换业务线后再从子应用A切换到子应用B时会出现子应用B未曾接收到Publisher业务线切换消息,导致业务线不准确。因此,需要在菜单工程中特殊处理,对路由进行拦截判断,并再次进行消息通知。
  3. 要格外注意在原型上挂载一些全局变量或全局函数的行为,容易被后者覆盖。

spoonysnail
80 声望1 粉丝