共计 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
办法加载子利用,跳转到子利用定义的路由,同时应用addGlobalUncaughtErrorHandler
和removeGlobalUncaughtErrorHandler
监听并解决异常情况(例如子利用加载失败),当子利用监听到跳转路由时,加载子利用(上述<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,如果子利用和主利用同时定义了雷同事件,会相互影响,如,用
onClick
或addEventListener
给<body>
增加了一个点击事件,JS 沙箱并不能打消它的影响,还得靠平时的代码标准 - 部署上有点繁琐,须要手动解决跨域问题
5、将来可能须要思考一些问题
- 专用组件依赖复用:我的项目中防止不了的比方申请库的封装,我可能并不想在子利用中再写一套同样的申请封装代码
- 自动化注入:每一个子利用革新的过程其实也是挺麻烦的事件,然而其实大多的工作都是标准化流程,在思考通过脚本主动注册子利用,实现自动化
总结
其实写下来整个我的项目,最大的感触 qiankun 的开箱可用性十分强,须要更改的我的项目配置根本很少,当然遇到的一些坑点也必定是踩过能力更清晰。
如果文章有什么问题或者谬误,欢送斧正交换,谢谢!
本文公布自 网易云音乐大前端团队,文章未经受权禁止任何模式的转载。咱们长年招收前端、iOS、Android,如果你筹备换工作,又恰好喜爱云音乐,那就退出咱们 grp.music-fe (at) corp.netease.com!