背景:
此前接手了一个老项目(3、4年前开始的一个项目),面临的一个最大的问题是老旧代码的维护和迭代。维护老旧代码的可选方式并不多:
- 推倒重来,全部进行重构 => 工作量繁重,成本极高
- 依照旧有技术选型进行维护和迭代 => emmmm,一言难尽
- 对于迭代需求采用新的技术架构和选型,与已有功能解耦,尽量减少对旧代码的依赖和旧逻辑的修改
出于工作量和项目后续的可维护性和迭代性相对而言第3种方案比较可行。因此调研了一下微前端方案来进行改造。
调研:
Alibaba-console-os VS Single-Spa
Alibaba-console-os
接入方法
- 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)
})
- vue.config.js 做如下配置,增加 consoleOs插件,并设定相应id
module.exports = {
publicPath: 'http://localhost:8080/',
"pluginOptions": {
"consoleOs": {
"id": "os-example-vue"
}
}
}
- 在 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
接入方法
- 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,
];
- single-spa.config.js 注册应用
import { registerApplication, start } from 'single-spa'
registerApplication(
'vue',
() => import('./src/vue/main.js'),
() => location.pathname === "/react" ? false : true
);
start();
- index.html 配置
<html>
<head></head>
<body>
<div id="react"></div>
<div id="vue"></div>
<script src="/dist/single-spa.config.js"></script>
</body>
</html>
- webpack配置
module.exports = {
entry: {
'single-spa.config': './single-spa.config.js',
},
}
Alibaba-console-os | SingleSpa | |
---|---|---|
子应用技术栈 | React、Vue、Angular | React、Vue、Angular |
主应用技术栈 | React、Angular | 不限 |
子应用资源配置模式 | manifest.json | single-spa.config.js |
选择SingleSpa的原因
- Alibaba-console-os 的Host App不支持Vue,我们团队的主技术栈是Vue
- Alibaba-console-os子应用资源配置通过manifest.json引入,不适用我们公司的资源部署方式
Single-Spa实践:
架构
- 加载器:用来调度子应用,决定何时展示哪个子应用。
- 包装器:对现有的应用包装,使得加载器可以使用它们
- 主应用:一般是包含所有子应用公共部分的项目,
- 子应用:各个展示在主应用内容区的应用
大概是这样一个流程:用户访问页面,浏览器运行加载器的js文件,加载器去获取配置文件,并注册配置文件中的各个子应用,首先加载主应用(比如菜单导航等),然后通过路由判定去动态加载子应用。
接入流程
- 创建子应用,将admin工程拆分为edu-nav(菜单导航工程)、-old(原有业务功能工程)、edu-mis(新业务功能工程)
改造子应用,通过singleSpa对子应用进行包装。需要引入single-spa-vue以及systemjs-webpack-interop。
- single-spa-vue是针对vue项目的包装器,包导出4个生命周期函数:bootstrap、mount、unmount、update。
- systemjs-webpack-interop可以使webpack和systemjs一起正常工作
创建主应用,充当加载器的功能,有以下几个任务。
- 配置资源map,用于后续资源SystemJs引入
- 通过SystemJs引入资源文件
- 挂载Publisher,用于子应用之间的通信。
- registerApplication注册子应用,参数依次为:注册的子应用名称、子应用入口文件(需要是一个函数)、控制应用是否激活的函数。
- singleSpa.start。启动函数。
- 配置资源map,用于后续资源SystemJs引入
经过上述步骤,整个工程singleSpa改造完成。
实践中发现的一些问题
- 需要考虑隔离js,避免css冲突,并考虑按需加载资源
- 公用数据信息的同步:菜单或业务线的切换发生时会通过Publisher进行通知,但是在切换业务线后再从子应用A切换到子应用B时会出现子应用B未曾接收到Publisher业务线切换消息,导致业务线不准确。因此,需要在菜单工程中特殊处理,对路由进行拦截判断,并再次进行消息通知。
- 要格外注意在原型上挂载一些全局变量或全局函数的行为,容易被后者覆盖。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。