关于微前端:基于-qiankun-的微前端应用实践

31次阅读

共计 6286 个字符,预计需要花费 16 分钟才能阅读完成。

本文作者:张延卿

业务背景

云音乐广告 Dsp(需求方平台)平台分为合约平台(Vue 框架)和竞价平台(React 框架),因历史起因框架选型未能对立,最近来了新需要,须要同时在两个平台减少一样的模块,因为都是 Dsp 平台,前期这样的需要可能会很多,所以思考到组件复用以及升高保护老本,在想怎么对立技术栈,把 React 零碎塞到 Vue 我的项目中进行出现。

零碎是传统的左右布局,左侧侧边栏展现菜单栏,头部导航展现根底信息,利用内容全副填充到蓝色的内容区。
说实话,第一反馈我间接想嵌套 iframe,然而利用过 iframe 技术的,大家都晓得它的痛:

  • 浏览器历史栈问题后退 / 后退 <div style=”margin-top:5px” />
    无论你在 iframe 里潜行了多深,你退一步就是一万步,这个体验真的很好受
    <div style=”margin-top:5px” />
  • 利用通信 <div style=”margin-top:5px” >
    有时候主利用可能只想晓得子系统的 URL 参数,然而 iframe 利用跟它不同源,你就得想点其余方法去获取参数了,咱们最罕用的就是 postMessage
    <div style=”margin-top:5px”>
  • 缓存<div style=”margin-top:5px” />
    iframe 利用更新上线后,关上零碎会发现零碎命中缓存显示旧内容,须要用工夫戳计划解决或强制刷新

另外就是应用 MPA + 路由散发,当用户拜访页面时,由 Nginx 等负责依据路由散发到不同的业务利用,由各个业务利用实现资源的组装后返回给浏览器,这种形式就须要把界面、导航都做成相似的样子。
<div style=”margin-top:5px” />

  • 长处

    • 多框架开发;
    • 独立部署运行;
    • 利用之间齐全隔离。
  • 毛病

    • 体验差,每个独立利用加载工夫较长;
    • 因为齐全隔离,导致在导航、顶部这些通用的中央改变大,复用性变的很差。

还有就是目前比拟支流的几种微前端计划:<div style=”margin-top:5px” />

  • 基座模式:次要基于路由散发,由一个基座利用监听路由,依照路由规定去加载不同的利用,以实现利用间解耦
  • EMP:Webpack5 Module Federation,去中心化的微前端计划,能够在实现利用隔离的根底上,轻松实现利用间的资源共享和通信;

总的来说,iframe 次要用于简略并且性能要求不高的第三方零碎;MPA 无论在实现老本和体验下面都不能满足以后业务需要;基座模式和 EMP 都是不错的抉择,因 qiankun 在业内应用比拟广,较为成熟,最初还是抉择了 qiankun

乾坤(qiankun)

qiankun(乾坤)是由蚂蚁金服推出的基于 Single-Spa 实现的前端微服务框架,实质上还是 路由散发式 的服务框架,不同于本来 Single-Spa 采纳 JS Entry 加载子利用的计划,qiankun 采纳 HTML Entry 形式进行了代替优化。
JS Entry的应用限度要求

  • 限度一个 JS 入口文件
  • 图片、CSS 等动态资源须要打包到 JS 里
  • Code Splitting 无奈利用

<br/> 比照 JS Entry,HTML Entry 应用就不便太多了,我的项目配置给定入口文件后,qiankun 会自行 Fetch 申请资源,解析出 JS 和 CSS 文件资源后,插入到给定的容器中,完满~

JS Entry 的形式通常是子利用将资源打成一个 Entry Script,相似 Single-Spa 的 例子;<div style=”margin-top:5px” />
HTML Entry 则是应用 HTML 格局进行子利用资源的组织,主利用通过 Fetch html 的形式获取子利用的动态资源,同时将 HTML Document 作为子节点塞到主利用的容器中。可读性和维护性更高,更靠近最初页面挂载后的成果,也不存在须要双向本义的问题。

计划实际

因为 Vue 我的项目曾经开发实现,咱们须要在原始我的项目中进行革新,很显著选定 Vue 我的项目作为基座利用,新需要开发采纳 Create React App 搭建 React 子利用,接下来咱们看一下具体实现

基座利用革新

基座(main)采纳是的 vue-cli 搭建的,咱们放弃其本来的代码构造和逻辑不变,在此基础上独自为子利用提供一个挂载的容器 DIV,同样填充在雷同的内容展现区域。
qiankun 只须要在基座利用中引入,为了方便管理,咱们新增目录,命名为 micro,标识目录外面是微前端革新代码,进行全局配置初始化,革新如下:
路由配置文件 app.js

// 路由配置
const apps = [
 {
 name: 'ReactMicroApp',
 entry: '//localhost:10100',
 container: '#frame',
 activeRule: '/react'
 }
];

利用配置注册函数

import {registerMicroApps, start} from "qiankun";
import apps from "./apps";
// 注册子利用函数,包装成高阶函数,不便前期如果有参数注入批改 app 配置
export const registerApp = () => registerMicroApps(apps);
// 导出 qiankun 的启动函数
export default start;

Layout 组件

<section class="app-main">
 <transition v-show="$route.name" name="fade-transform" mode="out-in">
 <!-- 主利用渲染区,用于挂载主利用路由触发的组件 -->
 <router-view />
 </transition>
 <!-- 子利用渲染区,用于挂载子利用节点 -->
 <div id="frame" />
</section>
import startQiankun, {registerApp} from "../../../micro";
export default {
 name: "AppMain",
 mounted() {
 // 初始化配置
 registerApp();
 startQiankun();},
};

这里会用到 qiankun 的两个重要的 API:

  • registerMicroApps
  • start

留神点:咱们抉择在 mounted 生命周期中进行初始化配置,是为了保障挂载容器肯定存在
咱们来通过图示具体了解一下 qiankun 注册子利用的过程:

  • 依赖注入后,会先初始化标识变量参数 xx_QIANKUN__,用于子利用判断所处环境等
  • 当 qiankun 会通过 activeRule 的规定来判断是否激活子利用

    • activeRule 为字符串时,以路由拦挡形式进行自主拦挡
    • activeRule 为函数时,依据函数返回值判断是否激活

<!—->

  • 当激活子利用时,会通过 HTML-Entry 来解析子利用动态资源地址,挂载到对应容器上
  • 创立沙箱环境,查找子利用生命周期函数,初始化子利用

打造 qiankun 子利用

咱们基于 Create React App 创立一个 React 我的项目利用,由上述的流程形容,咱们晓得子利用得向外裸露一系列生命周期函数供 qiankun 调用,在 index.js 文件中进行革新:
减少 public-path.js 文件
目录外层增加 public-path.js 文件,当子利用挂载在主利用下时,如果咱们的一些动态资源沿用了 publicPath=/ 的配置,咱们拿到的域名将会是主利用域名,这个时候就会造成资源加载出错,好在 Webpack 提供了批改办法,如下:

if (window.__POWERED_BY_QIANKUN__) {__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;}

路由 base 设置
因为通常来说,主利用会拦挡浏览器路由变动以激活加载子利用。比方,上述的代码里咱们的路由配置,激活规定写了 activeRule: /react,这是什么意思呢?这意味着,当浏览器 pathname 匹配到 /react 时,会激活子利用,然而如果咱们的子利用路由配置是上面这样的:

<Router> 
 <Route exact path="/" component={Home} />
 <Route path="/list" component={List} /> 
</Router> 

咱们怎么实现域名 /react 能正确加载对应的组件呢?大家肯定经验过用域名二级目录拜访的需要,这里是一样的,咱们判断是否在 qiankun 环境下,调整下 base 即可,如下:

const BASE_NAME = window.__POWERED_BY_QIANKUN__ ? "/react" : "";
...
<Router base={BASE_NAME}>
...
</Router> 

减少生命周期函数
子利用的入口文件退出生命周期函数初始化,不便主利用调用资源实现后按利用名称调用子利用的生命周期

/**
 * bootstrap 只会在微利用初始化的时候调用一次,下次微利用从新进入时会间接调用 mount 钩子,不会再反复触发 bootstrap。* 通常咱们能够在这里做一些全局变量的初始化,比方不会在 unmount 阶段被销毁的利用级别的缓存等。*/
export async function bootstrap() {console.log("bootstraped");
}
/**
 * 利用每次进入都会调用 mount 办法,通常咱们在这里触发利用的渲染办法
 */
export async function mount(props) {console.log("mount", props);
 render(props);
}
/**
 * 利用每次切出 / 卸载 会调用的办法,通常在这里咱们会卸载微利用的利用实例
 */
export async function unmount() {console.log("unmount");
 ReactDOM.unmountComponentAtNode(document.getElementById("root"));
}

留神:所有的生明周期函数都必须是 Promise
批改打包配置

module.exports = {webpack: (config) => {
 // 微利用的包名,这里与主利用中注册的微利用名称统一
 config.output.library = `ReactMicroApp`;
 // 将你的 library 裸露为所有的模块定义下都可运行的形式
 config.output.libraryTarget = "umd";
 // 按需加载相干,设置为 webpackJsonp_ReactMicroApp 即可
 config.output.jsonpFunction = `webpackJsonp_ReactMicroApp`;
 config.resolve.alias = {
 ...config.resolve.alias,
 "@": path.resolve(__dirname, "src"),
 };
 return config;
 },
 devServer: function (configFunction) {return function (proxy, allowedHost) {const config = configFunction(proxy, allowedHost);
 // 敞开主机查看,使微利用能够被 fetch
 config.disableHostCheck = true;
 // 配置跨域申请头,解决开发环境的跨域问题
 config.headers = {"Access-Control-Allow-Origin": "*",};
 // 配置 history 模式
 config.historyApiFallback = true;
 return config;
 };
 },
};

留神:配置的批改为了达到两个目标,一个是裸露生命周期函数给主利用调用,第二点是容许跨域拜访,批改的留神点能够参考代码的正文。

  • 裸露生命周期:UMD 能够让 qiankun 按利用名称匹配到生命周期函数
  • 跨域配置:主利用是通过 Fetch 获取资源,所以为了解决跨域问题,必须设置容许跨域拜访

小结:跳转流程梳理,在主利用 router 中定义子利用跳转 path,如下图,在调用组件 mounted 生命周期中应用 qiankun 裸露的 loadMicroApp 办法加载子利用,跳转到子利用定义的路由,同时应用 addGlobalUncaughtErrorHandlerremoveGlobalUncaughtErrorHandler 监听并解决异常情况(例如子利用加载失败),当子利用监听到跳转路由时,加载子利用(上述 <Router> 组件中)定义的 component,实现主利用到子利用的跳转。

{
 path: '/xxx',
 component: Layout,
 children: [
 {
 path: '/xxx',
 component: () => import('@/micro/app/react'),
 meta: {title: 'xxx', icon: 'user'}
 }
 ]
 },

我的项目中遇到的问题

1、子利用未胜利加载
如果我的项目启动实现后,发现子利用零碎没有加载,咱们应该关上控制台剖析起因:

  • 控制台无报错:子利用未激活,查看激活规定配置是否正确
  • 挂载容器未找到:查看容器 DIV 是否在 qiankun start 时肯定存在,如不能保障需设法在 DOM 挂载后执行。

<br/>2、基座利用路由模式
基座利用我的项目是 hash 模式路由,这种状况下子利用的路由模式必须跟主利用保持一致,否则会加载异样。起因很简略,假如子利用采纳 history 模式,每次切换路由都会扭转 pathname,这个时候很难再通过激活规定去匹配到子利用,造成子利用 unmount
3、CSS 款式错乱
因为默认状况下 qiankun 并不会开启 CSS 沙箱进行款式隔离,当主利用和子利用产生款式错乱时,咱们能够启用 {strictStyleIsolation: true} 配置开启严格隔离款式,这个时候会用 Shadow Dom 节点包裹子利用,置信大家看到这个也很相熟,和微信小程序中页面和自定义组件的款式隔离计划统一。
4、 另外,在接入过程中,总结了几个须要留神的点

  • 尽管 qiankun 反对 jQuery,但对多页利用的老我的项目接入不是很敌对,须要每个页面都批改,老本也很高,这类老我的项目接入还是比拟举荐 iframe;
  • 因为 qiankun 的形式,是通过 HTML-Entry 抽取 JS 文件和 DOM 构造的,实际上和主利用共用的是同一个 Document,如果子利用和主利用同时定义了雷同事件,会相互影响,如,用 onClickaddEventListener<body>增加了一个点击事件,JS 沙箱并不能打消它的影响,还得靠平时的代码标准
  • 部署上有点繁琐,须要手动解决跨域问题

5、将来可能须要思考一些问题

  • 专用组件依赖复用:我的项目中防止不了的比方申请库的封装,我可能并不想在子利用中再写一套同样的申请封装代码
  • 自动化注入:每一个子利用革新的过程其实也是挺麻烦的事件,然而其实大多的工作都是标准化流程,在思考通过脚本主动注册子利用,实现自动化

总结

其实写下来整个我的项目,最大的感触 qiankun 的开箱可用性十分强,须要更改的我的项目配置根本很少,当然遇到的一些坑点也必定是踩过能力更清晰。
如果文章有什么问题或者谬误,欢送斧正交换,谢谢!

本文公布自 网易云音乐大前端团队,文章未经受权禁止任何模式的转载。咱们长年招收前端、iOS、Android,如果你筹备换工作,又恰好喜爱云音乐,那就退出咱们 grp.music-fe (at) corp.netease.com!

正文完
 0