Skip to content

微前端

如何设计实现微前端框架

简单来说,微前端就是把多个web应用聚合在一起,提供统一的访问入口;

给用户的观感就是一个统一的应用,但是背后的实现上可能用到不同的技术栈;

优点:

  • 技术栈无关
  • 独立性强
  • 状态隔离

缺点:

  • 子应用无法获取全局dom,割裂严重
  • 应用之间通信困难
  • 路由状态难以维护,容易丢失
  • 每个spa的白屏加载时间长

有多个方案:

  • iframe嵌入url,通信和路由管理比较困难;
  • npm

ReactRouter

一段基于ReactRouter动态导入组件的方案;

ts
export const AyncComponent: React.FC<{ hotReload?: number; } & RouteComponentProps> = ({ location, hotReload }) => {
    // 子工程资源是否加载完成
    const [ayncLoaded, setAyncLoaded] = useState(false);
    // 子工程url配置信息是否加载完成
    const [subAppMapInfoLoaded, setSubAppMapInfoLoaded] = useState(false);
    const [ayncComponent, setAyncComponent] = useState(null);
    const { pathname } = location;
    // 取路径中标识子工程前缀的部分, 例如 '/subapp/xxx/index' 其中xxx即路由唯一前缀
    const id = pathname.split('/')[2];
    useEffect(() => {
        // 如果没有子工程配置信息, 则请求
        if (!subAppMapInfoLoaded) {
            fetchSubappUrlPath(id).then((data) => {
                subAppMapInfo = data;
                setSubAppMapInfoLoaded(true);
            }).catch((url: any) => {
                // 失败处理
                goBackToIndex();
            });
            return;
        }
        const subappModule = (subAppMapInfo as any)[id];
        if (subappModule) {
            if (subappRoutes[id]) {
                // 如果已经加载过该子工程的模块,则不再加载,直接取缓存的routes
                setAyncLoaded(true);
                setAyncComponent(subappRoutes[id]);
                return;
            }
            // 如果能匹配上前缀则加载相应子工程模块
            // 如果请求成功,则触发JSONP钩子window.wmadSubapp
            currentPrefix = id;
            setAyncLoaded(false);
            const jsUrl = subappModule.js;
            loadAsyncSubapp(jsUrl)
                .then(() => {
                    // 加载子工程完成
                    setAyncComponent(subappRoutes[id]);
                    setAyncLoaded(true);
                })
                .catch((urlList) => {
                    // 如果加载失败
                    setAyncLoaded(false);
                    console.log('loading failed...'); 
                });
        } else {
            // 可以重定向到首页去
            goBackToIndex();
        }
    }, [id, subAppMapInfoLoaded, hotReload]);
    return ayncLoaded ? ayncComponent : null;
};

架构方案

  • 构建时组合:子应用也是主应用的一部分,技术栈有些耦合, 并且打包发布比较慢;

  • 运行时组合: 加载比较慢,会引入复杂度;

大多数情况下使用运行时;

渲染入口点的问题

  • JS Entry(vite使用此入口点, 但是仍然会生成一个html文件)

但是有个问题是,主应用不能保证子应用使用某一容器的特定节点为唯一的标识符,比如这里的root

js
   // src/main.js
   import { createApp } from 'vue';
   import App from './App.vue';

   createApp(App).mount('#app');
html
<!-- 子应用 index.html -->
<script src="//unpkg/antd.min.js"></script>
<body>
  <main id="root"></main>
</body>
// 子应用入口
ReactDOM.render(<App/>, document.getElementById('root'))
  • HTML Entry

解决方案 qiankun 以及 single-spa

qiankun通过动态加载和路由管理来实现微前端架构,路由切换的时候,它会加载或者卸载应用路由;

还解决了一些single-spa的问题,比如提供了css样式隔离以及js沙箱;

  • 隔离css环境 : shadowDOM隔离
  • 隔离js环境:沙箱
  • single-spa的缺口: 加载函数,js沙箱,css样式隔离

一些名词

静态资源表

它是运行时资源加载的优化工具

指前端构建工具生成的JSON文件,记录所有静态资源,比如js和css等等,还有部署路径和依赖哈希等等 核心特点就是性能优化:

  • 扫码源码,依赖追踪,资源去重,树摇(代码层面)
  • 按需加载,合并请求(将多个小文件的http请求合并成一个),CDN加速(请求层面)
  • 文件压缩,HTTP/2推送、缓存控制(通过url的哈希变化,实现缓存更新)(后面两个是什么东西?) (http层面)

其中url包含哈希用于缓存控制,dep包含依赖的其他文件;

json
{
  "a.js": {
    "url": "/static/js/a.5f100fa.js",
    "dep": ["b.js", "a.css"]
  },
  "a.css": {
    "url": "/static/css/a.63cf374.css",
    "dep": ["button.css"]
  }
}

HTTP/2 推送

具体细节

proxySandbox流程

就是用一个fakeWindow,当你更改子应用的window对象的时候,都会更改到fakeWindow;

fakeWindow没有的属性,就会去window上找给你;

图片

本站访客数 人次 本站总访问量