前端微应用深入了解

背景

最近,工作中遇到一个需求:接着上一次微应用开发的项目,产品设计了另一个模块,这个模块的要求是可以单独运行,也可以部署至父应用中作为一个模块使用。有了上一次微应用开发的经验,这次我决定在项目中使用微应用技术接入,该模块功能单独立一个项目。

技术方案

  • 父应用使用qiankun进行微应用的注册和启动;
  • 子应用使用@umi-js/plugin-qiankun进行微应用所需的一些配置进行自动化生成。

    需要解决的问题

描述: 父应用依旧使用vue技术栈,子应用使用umi创建的react项目(react v4.x),采用history路由模式。
上一次遇到的问题主要集中在开发时子应用所需要的配置项,并且子应用不需要安装任何第三方包就可以接入;而本次子应用使用的是umi的plugin,配置方面不需要多做处理,只需要在umi生成的子应用项目的配置文件中加上一个配置项就可以了,点击查看详情

本次开发完成后与父应用对接时产生了下面的问题:
1.history模式路由问题:
问题描述:由于子应用是父应用的一个模块,所以当父应用进入某一个路由时,才加载该子应用。所以我在父应用中增加了路由 /microApp,在对应的组件里加入放置子应用的容器。子应用base和publicPath均为 /microAppRoute ,子应用页面为/page。但是当我配置好子应用时,在NGINX里做转发,规则为 /microAppRoute 转发至子应用,这时候访问 /microApp/page 无法进入父应用相应的组件,只访问 /microApp 可以进入父应用的组件并加载子应用,但是无法加载子应用的路由。如果将父应用的路由匹配规则改为 /microApp/*,则访问 /microApp/page 只能被父应用匹配,无法加载子应用页面。
解决方案:由于子应用使用history模式,所以子应用的路由base和父应用中子应用模块入口路由必须保持一致,也就是说父应用中子应用的入口路由规则为 /microApp,那么子应用的base也必须配置为 /microApp,这样在访问 /microApp/page 时不仅匹配到了父应用的 /microApp 路由,并且加载了子应用,子应用也识别了base为 /microApp 并且会加载 /page 的页面组件。点击查看官方文档,第2条解答了该问题。
思考:如果父应用中子应用的entry和base一致,会有啥问题呢,估计各位应该是清楚的!代码如下:

1
2
3
4
5
6
7
8
9
registerMicroApps([
{
name: 'microApp2',
entry: '//microApp.xxx.com/microApp/', // 该子应用和父应用同地址同端口
activeRule: '/microApp',
props: msg,
container: '#microAppContainer',
},
]);

思考解答:因为需要在nginx中配置子应用的入口,所以如果子应用的entry的pathname和父应用中的子应用入口路由一致的话,那么父应用的子应用入口路由将不会进入,因为被nginx匹配到了,直接就访问了子应用的单独项目了。
2.地图资源问题
问题描述:由于子应用中需要在地图上进行操作,所以引入了地图的js API,但是qiankun是将所有script标签里的资源通过fetch获取,所以出现了百度地图的js资源不支持跨域。我在网上搜索,看到在启动微应用时,可以传入配置项参数 excludeAssetFilter,可以避免一些资源通过fetch获取,代码如下:
1
2
3
start({
excludeAssetFilter: assetUrl => assetUrl.indexOf('api.map.baidu.com') !== -1,
});

但是此时我开发中发现,如果是直接通过script标签写在子应用中的资源该配置是不会生效的,只有动态插入的资源这个配置才会生效,这样的话相当于只解决了地图动态插入的资源的跨域问题。
解决方案:我在网上看到两个解决方案
* 1.1 将子应用中初始就会加载的资源放在父应用中进行加载;
* 1.2 在start的配置项里重写fetch函数,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const excludeAsserts = ['api.map.baidu.com', ...];
start({
fetch: (url, args) => {
if (excludeAsserts.some(item => url.includes(item))) {
// 匹配到地图资源时,手动建立script标签插入head中 ...
const $script = document.createElement('script');
$script.src = url;
global.document.getElementsByTagName('head')[0].appendChild($script);
// 如果需要清除掉动态的script也可以自己写后续代码
return Promise.resolve();
}
return window.fetch(url, ...args)
}
});