图片起源:https://zhuanlan.zhihu.com/p/…
本文作者:史志鹏
前言
LOOK 直播经营后盾工程是一个迭代了 2+ 年,累计超过 10+ 位开发者参加业务开发,页面数量多达 250+ 的“巨石利用”。代码量的宏大,带来了构建、部署的低效,此外该工程依赖外部的一套 Regularjs 技术栈也曾经实现了历史使命,相应的 UI 组件库、工程脚手架也被举荐停止使用,走向了少保护或者不保护的阶段。因而,LOOK 直播经营后盾基于 React 新建工程、做工程拆分被提上了工作日程。一句话形容指标就是:新的页面将在基于 React 的新工程开发,React 工程能够独立部署,而 LOOK 直播经营后盾对外输入的拜访地址冀望维持不变。
本文基于 LOOK 直播经营后盾的微前端落地实际总结而成。次要介绍在既有“巨石利用”、Regularjs 和 React 技术栈共存的场景下,应用微前端框架 qiankun,实现 CMS 利用的微前端落地历程。
对于 qiankun 的介绍,请移步至官网查阅,本文不会侧重于介绍无关微前端的概念。
一. 背景
1.1 现状
- 如上所述,存在一个如下图所示的 CMS 利用,这个利用的工程咱们称之为 liveadmin,拜访地址为:
https://example.com/liveadmin
,拜访如下图所示。
- 咱们心愿不再在 liveadmin 旧工程新增新业务页面,因而咱们基于外部的一个 React 脚手架新建了一个称为 increase 的新工程,新的业务页面都举荐应用这个工程开发,这个利用能够独立部署独立拜访,拜访地址为:
https://example.com/lookadmin
,拜访如下图所示:
1.2 指标
咱们心愿应用微前端的形式,集成这两个利用的所有菜单,让用户无感知这个变动,仍旧依照原有的拜访形式 https://example.com/liveadmin
,能够拜访到 liveadmin 和 increase 工程的所有页面。
针对这样一个指标,咱们须要解决以下两个外围问题:
- 两个零碎的菜单合成展现;
- 应用原有拜访地址拜访两个利用的页面。
对于第 2 个问题,置信对 qiankun 理解的同学能够和咱们一样达成共识,至于第 1 个问题,咱们在实际的过程中,通过外部的一些计划失去解决。下文在实现的过程会加以形容。这里咱们先给出整个我的项目落地的效果图:
能够看到,increase 新工程的一级菜单被追加到了 liveadmin 工程的一级菜单前面,原始地址能够拜访到两个工程的所有的菜单。
1.3 权限治理
说到 CMS,还须要说一下权限管理系统的实现,下文简称 PMS。
- 权限:目前在咱们的 PMS 里定义了两种类型的权限:页面权限(决定用户是否能够看到某个页面)、性能权限(决定用户是否能够拜访某个性能的 API)。前端负责页面权限的实现,性能权限则由服务端进行管控。
- 权限治理:本文仅论述页面权限的治理。首先每个前端利用都关联一个 PMS 的权限利用,比方 liveadmin 关联的是 appCode = live_backend 这个权限利用。在前端利用工程部署胜利后,通过后门的形式推送前端工程的页面和页面关联的权限码数据到 PMS。风控经营在 PMS 零碎中找到对应的权限利用,依照角色粒度调配页面权限,领有该角色的用户即可拜访该角色被调配的页面。
- 权限管制:在前端利用被拜访时,最外层的模块负责申请以后用户的页面权限码列表,而后依据此权限码列表过滤出能够拜访的无效菜单,并注册无效菜单的路由,最初生成一个以后用户权限下的非法菜单利用。
二. 实现
2.1 lookcms 主利用
- 首先,新建一个 CMS 根底工程,定义它为主利用 lookcms,具备根本的申请权限和菜单数据、渲染菜单的性能。
入口文件执行以下申请权限和菜单数据、渲染菜单的性能。
// 应用 Redux Store 解决数据
const store = createAppStore();
// 查看登录状态
store.dispatch(checkLogin());
// 监听异步登录状态数据
const unlistener = store.subscribe(() => {unlistener();
const {auth: { account: { login, name: userName} } } = store.getState();
if (login) { // 如果已登录,依据以后用户信息申请以后用户的权限和菜单数据
store.dispatch(getAllMenusAndPrivileges({ userName}));
subScribeMenusAndPrivileges();} else {injectView(); // 未登录则渲染登录页面
}
});
// 监听异步权限和菜单数据
const subScribeMenusAndPrivileges = () => {const unlistener = store.subscribe(() => {unlistener();
const {auth: { privileges, menus, allMenus, account} } = store.getState();
store.dispatch(setMenus(menus)); // 设置主利用的菜单,据此渲染主利用 lookcms 的菜单
injectView(); // 挂载登录态的视图
// 启动 qiankun,并将菜单、权限、用户信息等传递,用于后续传递给子利用,拦挡子利用的申请
startQiankun(allMenus, privileges, account, store);
});
};
// 依据登录状态渲染页面
const injectView = () => {const { auth: { account: { login} } } = store.getState();
if (login) {new App().$inject('#j-main');
} else {new Auth().$inject('#j-main');
window.history.pushState({}, '', `${$config.rootPath}/auth?redirect=${window.location.pathname}`);
}
};
- 引入 qiankun,注册 liveadmin 和 increase 这两个子利用。
定义好子利用,依照 qiankun 官网的文档,确定 name、entry、container 和 activeRule 字段,其中 entry 配置留神辨别环境,并接管上一步的 menus,privileges 等数据,根本代码如下:
// 定义子利用汇合
const subApps = [{ // liveadmin 旧工程
name: 'music-live-admin', // 取子利用的 package.json 的 name 字段
entrys: { // entry 辨别环境
dev: '//localhost:3001',
// liveadmin 这里定义 rootPath 为 liveadminlegacy,便于将原有的 liveadmin 开释给主利用应用,以达到应用原始拜访地址拜访页面的目标。test: `//${window.location.host}/liveadminlegacy/`,
online: `//${window.location.host}/liveadminlegacy/`,
},
pmsAppCode: 'live_legacy_backend', // 权限解决相干
pmsCodePrefix: 'module_livelegacyadmin', // 权限解决相干
defaultMenus: ['welcome', 'activity']
}, { // increase 新工程
name: 'music-live-admin-react',
entrys: {
dev: '//localhost:4444',
test: `//${window.location.host}/lookadmin/`,
online: `//${window.location.host}/lookadmin/`,
},
pmsAppCode: 'look_backend',
pmsCodePrefix: 'module_lookadmin',
defaultMenus: []}];
// 注册子利用
registerMicroApps(subApps.map(app => ({
name: app.name,
entry: app.entrys[$config.env], // 子利用的拜访入口
container: '#j-subapp', // 子利用在主利用的挂载点
activeRule: ({pathname}) => { // 定义加载以后子利用的路由匹配策略,此处是依据 pathname 和以后子利用的菜单 key 比拟来做的判断
const curAppMenus = allMenus.find(m => m.appCode === app.pmsAppCode).subMenus.map(({name}) => name);
const isInCurApp = !!app.defaultMenus.concat(curAppMenus).find(headKey => pathname.indexOf(`${$config.rootPath}/${headKey}`) > -1);
return isInCurApp;
},
// 传递给子利用的数据:菜单、权限、账户,能够使得子利用不再申请相干数据,当然子利用须要做好判断
props: {menus: allMenus.find(m => m.appCode === app.pmsAppCode).subMenus, privileges, account }
})));
// ...
start({prefetch: false});
- 主利用菜单逻辑
咱们基于已有的 menus 菜单数据,应用外部的 UI 组件实现了菜单的渲染,对每一个菜单绑定了点击事件,点击后通过 pushState 的形式,变更窗口的门路。比方点击 a-b 菜单,对应的路由便是 http://example.com/liveadmin/a/b
,qiankun 会响应路由的变动,依据定义的 activeRule 匹配到对应的的子利用,接着子利用接管路由,加载子利用对应的页面资源。具体的实现过程能够参考 qiankun 源码,根本的思维是荡涤子利用入口返回的 html 中的 <script>
标签,fetch 模块的 Javascript 资源,而后通过 eval 执行对应的 Javascript。
2.2 liveadmin 子利用
- 依照 qiankun 官网文档的做法,在子利用的入口文件中导出相应的生命周期钩子函数。
if (window.__POWERED_BY_QIANKUN__) { // 注入 Webpack publicPath, 使得主利用正确加载子利用的资源
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
if (!window.__POWERED_BY_QIANKUN__) { // 独立拜访启动逻辑
bootstrapApp({});
}
export const bootstrap = async () => { // 启动前钩子
await Promise.resolve(1);
};
export const mount = async (props) => { // 集成拜访启动逻辑,接手主利用传递的数据
bootstrapApp(props);
};
export const unmount = async (props) => { // 卸载子利用的钩子
props.container.querySelector('#j-look').remove();};
- 批改 Webpack 打包配置。
output: {
path: DIST_PATH,
publicPath: ROOTPATH,
filename: '[name].js',
chunkFilename: '[name].js',
library: `${packageName}-[name]`,
libraryTarget: 'umd', // 指定打包的 Javascript UMD 格局
jsonpFunction: `webpackJsonp_${packageName}`,
},
- 解决集成拜访时,暗藏子利用的头部和侧边栏元素。
const App = Regular.extend({
template: window.__POWERED_BY_QIANKUN__
? `
<div class="g-wrapper" r-view></div>
`
: `
<div class="g-bd">
<div class="g-hd mui-row">
<AppHead menus={headMenus}
moreMenus={moreMenus}
selected={selectedHeadMenuKey}
open={showSideMenu}
on-select={actions.selectHeadMenu($event)}
on-toggle={actions.toggleSideMenu()}
on-logout={actions.logoutAuth}></AppHead>
</div>
<div class="g-main mui-row">
<div class="g-sd mui-col-4" r-hide={!showSideMenu}>
<AppSide menus={sideMenus}
selected={selectedSideMenuKey}
show={showSideMenu}
on-select={actions.selectSideMenu($event)}></AppSide>
</div>
<div class="g-cnt" r-class={cntClass}>
<div class="g-wrapper" r-view></div>
</div>
</div>
</div>
`,
name: 'App',
// ...
})
- 解决集成拜访时,屏蔽权限数据和登录信息的申请,改为接管主利用传递的权限和菜单数据,防止冗余的 HTTP 申请和数据设置。
if (props.container) { // 集成拜访时,间接设置权限和菜单
store.dispatch(setMenus(props.menus))
store.dispatch({
type: 'GET_PRIVILEGES_SUCCESS',
payload: {
privileges: props.privileges,
menus: props.menus
}
});
} else { // 独立拜访时,申请用户权限,菜单间接读取本地的配置
MixInMenus(props.container);
store.dispatch(getPrivileges({ userName: name}));
}
if (props.container) { // 集成拜访时,设置用户登录账户
store.dispatch({
type: 'LOGIN_STATUS_SUCCESS',
payload: {
user: props.account,
loginType: 'OPENID'
}
});
} else { // 独立拜访时,申请和设置用户登录信息
store.dispatch(loginStatus());
}
- 解决集成拜访时,路由 base 更改
因为集成拜访时要对立 rootPath 为 liveadmin,所以集成拜访时注册的路由要批改成主利用的 rootPath 以及新的挂载点。
const start = (container) => {
router.start({
root: config.base,
html5: true,
view: container ? container.querySelector('#j-look') : Regular.dom.find('#j-look')
});
};
2.3 increase 子利用
同 liveadmin 子利用做的事相似。
- 导出相应的生命周期钩子。
if (window.__POWERED_BY_QIANKUN__) {__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;}
const CONTAINER = document.getElementById('container');
if (!window.__POWERED_BY_QIANKUN__) {const history = createBrowserHistory({ basename: Config.base});
ReactDOM.render(<Provider store={store()}>
<Symbol />
<Router path="/" history={history}>
{routeChildren()}
</Router>
</Provider>,
CONTAINER
);
}
export const bootstrap = async () => {await Promise.resolve(1);
};
export const mount = async (props) => {const history = createBrowserHistory({ basename: Config.qiankun.base});
ReactDOM.render(<Provider store={store()}>
<Symbol />
<Router path='/' history={history}>
{routeChildren(props)}
</Router>
</Provider>,
props.container.querySelector('#container') || CONTAINER
);
};
export const unmount = async (props) => {ReactDOM.unmountComponentAtNode(props.container.querySelector('#container') || CONTAINER);
};
- Webpack 打包配置。
output: {
path: DIST_PATH,
publicPath: ROOTPATH,
filename: '[name].js',
chunkFilename: '[name].js',
library: `${packageName}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packageName}`,
},
- 集成拜访时,去掉头部和侧边栏。
if (window.__POWERED_BY_QIANKUN__) { // eslint-disable-line
return (<BaseLayout location={location} history={history} pms={pms}>
<Fragment>
{
curMenuItem && curMenuItem.block
? blockPage
: children
}
</Fragment>
</BaseLayout>
);
}
- 集成拜访时,屏蔽权限和登录申请,接管主利用传递的权限和菜单数据。
useEffect(() => {if (login.status === 1) {history.push(redirectUrl);
} else if (pms.account) { // 集成拜访,间接设置数据
dispatch('Login/success', pms.account);
dispatch('Login/setPrivileges', pms.privileges);
} else { // 独立拜访,申请数据
loginAction.getLoginStatus().subscribe({next: () => {history.push(redirectUrl);
},
error: (res) => {if (res.code === 301) {
history.push('/login', {
redirectUrl,
host
});
}
}
});
}
});
- 集成拜访时,更改 react-router base。
export const mount = async (props) => {const history = createBrowserHistory({ basename: Config.qiankun.base});
ReactDOM.render(<Provider store={store()}>
<Symbol />
<Router path='/' history={history}>
{routeChildren(props)}
</Router>
</Provider>,
props.container.querySelector('#container') || CONTAINER
);
};
2.4 权限集成(可选步骤)
- 上文提到,一个前端利用关联一个 PMS 权限利用,那么如果通过微前端的形式组合了每个前端利用,而每个前端子利用如果还仍然对应本人的 PMS 权限利用的权限,那么站在权限管理人员的角度而言,就须要关注多个 PMS 权限利用,进行调配权限、治理角色,操作起来都很麻烦,比方两个子利用的页面辨别,两个子利用同一权限的角色治理等。因而,须要思考将子利用对应的 PMS 权限利用也对立起来,这里仅形容咱们的解决形式,仅供参考。
-
要尽量维持原有的权限治理形式(权限管理人员通过前端利用后门推送页面权限码到 PMS,而后到 PMS 进行页面权限调配),则微前端场景下,权限集成须要做的事件能够形容为:
- 各个子利用先推送本工程的菜单和权限码数据到到各自的 PMS 权限利用。
- 主利用加载各子利用的菜单和权限码数据,批改每个菜单和权限码的数据为主利用对应的 PMS 权限利用数据,而后对立推送到主利用对应的 PMS 权限利用,权限管理人员能够在主利用对应的 PMS 权限利用内进行权限的统一分配治理。
- 在咱们的实际中,为了使权限管理人员仍旧不感知这种拆分利用带来的变动,仍旧应用原 liveadmin 利用对应的 appCode = live_backend PMS 权限利用进行权限调配,咱们须要把 liveadmin 对应的 PMS 权限利用更改为 lookcms 主利用对应的 PMS 权限利用,而为 liveadmin 子利用新建一个 appCode = live_legacy_backend 的 PMS 权限利用,新的 increase 子利用则持续对应 appCode = look_backend 这个 PMS 权限利用。以上两个子利用的菜单和权限码数据依照上一步形容的第 2 点各自上报给对应的 PMS 权限利用。最初 lookcms 主利用同时获取 appCode = live_legacy_backend 和 appCode = look_backend 这两个 PMS 权限利用的前端子利用菜单和权限码数据,批改为 appCode = live_backend 的 PMS 权限利用数据,推送到 PMS,整体的流程如下图所示,右边是原有的零碎设计,左边是革新的零碎设计。
2.5 部署
- liveadmin 和 increase 各自应用云音乐的前端动态部署零碎进行独立部署,主利用 lookcms 也是独立部署。
- 解决好主利用拜访子利用资源跨域的问题。在咱们的实际过程中,因为都部署在同一个域下,资源打包遵循了同域规定。
2.6 小结
自此,咱们曾经实现了基于 qiankun LOOK 直播经营后盾的微前端的实现,次要是新建了主工程,划分了主利用的职责,同时批改了子工程,使得子利用能够被集成到主利用被拜访,也能够放弃原有独立拜访性能。整体的流程,能够用下图形容:
三. 依赖共享
qiankun 官网并没有举荐具体的依赖共享解决方案,咱们对此也进行了一些摸索,论断能够总结为:对于 Regularjs,React 等 Javascript 公共库的依赖的能够通过 Webpack 的 externals 和 qiankun 加载子利用生命周期函数以及 import-html-entry 插件来解决,而对于组件等须要代码共享的场景,则能够应用 Webapck 5 的 module federation plugin 来解决。具体计划如下:
3.1. 咱们整顿出的公共依赖分为两类
3.1.1. 一类是根底库,比方 Regularjs,Regular-state,MUI,React,React Router 等冀望在整个拜访周期中不要反复加载的资源。
3.1.2. 另一类是公共组件,比方 React 组件须要在各子利用之间相互共享,不须要进行工程间的代码拷贝。
3.2. 对于以上两类依赖,咱们做了一些本地的实际,因为还没有迫切的业务需要以及 Webpack 5 暂为公布稳定版(截至本文公布时,Webpack 5 曾经公布了 release 版本,后续看具体的业务需要是否上线此局部 feature),因而还没有在生产环境验证,但在这里能够分享下解决形式和后果。
3.2.1. 对于第一类公共依赖,咱们实现共享的冀望的是:在集成拜访时,主利用能够动静加载子利用强依赖的库,子利用本身不再加载,独立拜访时,子利用自身又能够自主加载本身须要的依赖。这里就要解决好两个问题:a. 主利用怎么收集和动静加载子利用的依赖 b. 子利用怎么做到集成和独立拜访时对资源加载的不同体现。
3.2.1.1. 第一个问题,咱们须要保护一个公共依赖的定义,即在主利用中定义每个子利用所依赖的公共资源,在 qiankun 的全局微利用生命周期钩子 beforeLoad 中通过插入 <script>
标签的形式,加载以后子利用所需的 Javascript 资源,参考代码如下。
// 定义子利用的公共依赖
const dependencies = {live_backend: ['regular', 'restate'],
look_backend: ['react', 'react-dom']
};
// 返回依赖名称
const getDependencies = appName => dependencies[appName];
// 构建 script 标签
const loadScript = (url) => {const script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
script.setAttribute('ignore', 'true'); // 防止反复加载
script.onerror = () => {Message.error(` 加载失败 ${url},请刷新重试 `);
};
document.head.appendChild(script);
};
// 加载某个子利用前加载以后子利用的所需资源
beforeLoad: [(app) => {console.log('[LifeCycle] before load %c%s', 'color: green;', app.name);
getDependencies(app.name).forEach((dependency) => {loadScript(`${window.location.origin}/${$config.rootPath}${dependency}.js`);
});
}
],
这里还要留神通过 Webpack 来生产好相应的依赖资源,咱们应用的是 copy-webpack-plugin 插件将 node_modules 下的 release 资源转换成包成能够通过独立 URL 拜访的资源。
// 开发
plugins: [
new webpack.DefinePlugin({
'process.env': {NODE_ENV: JSON.stringify('development')
}
}),
new webpack.NoEmitOnErrorsPlugin(),
new CopyWebpackPlugin({
patterns: [{ from: path.join(__dirname, '../node_modules/regularjs/dist/regular.js'), to: '../s/regular.js' },
{from: path.join(__dirname, '../node_modules/regular-state/restate.pack.js'), to: '../s/restate.js' },
{from: path.join(__dirname, '../node_modules/react/umd/react.development.js'), to: '../s/react.js' },
{from: path.join(__dirname, '../node_modules/react-dom/umd/react-dom.development.js'), to: '../s/react-dom.js' }
]
})
],
// 生产
new CopyWebpackPlugin({
patterns: [{ from: path.join(__dirname, '../node_modules/regularjs/dist/regular.min.js'), to: '../s/regular.js' },
{from: path.join(__dirname, '../node_modules/regular-state/restate.pack.js'), to: '../s/restate.js' },
{from: path.join(__dirname, '../node_modules/react/umd/react.production.js'), to: '../s/react.js' },
{from: path.join(__dirname, '../node_modules/react-dom/umd/react-dom.production.js'), to: '../s/react-dom.js' }
]
})
3.2.1.2. 对于子利用集成和独立拜访时,对公共依赖的二次加载问题,咱们采纳的办法是,首先子利用将主利用曾经定义的公共依赖通过 copy-webpack-plugin 和 html-webpack-externals-plugin 这两个插件应用 external 的形式独立进去,不打包到 Webpack bundle 中,同时通过插件的配置,给 <script>
标签加上 ignore 属性,那么在 qiankun 加载这个子利用时应用,qiankun 依赖的 import-html-entry 插件剖析到 <script>
标签时,会疏忽加载有 ignore 属性的 <script>
标签,而独立拜访时子利用自身能够失常加载这个 Javascript 资源。
plugins: [
new CopyWebpackPlugin({
patterns: [{ from: path.join(__dirname, '../node_modules/regularjs/dist/regular.js'), to: '../s/regular.js' },
{from: path.join(__dirname, '../node_modules/regular-state/restate.pack.js'), to: '../s/restate.js' },
]
}),
new HtmlWebpackExternalsPlugin({
externals: [{
module: 'remoteEntry',
entry: 'http://localhost:3000/remoteEntry.js'
}, {
module: 'regularjs',
entry: {
path: 'http://localhost:3001/regular.js',
attributes: {ignore: 'true'}
},
global: 'Regular'
}, {
module: 'regular-state',
entry: {
path: 'http://localhost:3001/restate.js',
attributes: {ignore: 'true'}
},
global: 'restate'
}],
})
],
3.2.2. 针对第二类共享代码的场景,咱们调研了 Webpack 5 的 module federation plugin,通过利用之间援用对方导入导出的 Webpack 编译公共资源信息,来异步加载公共代码,从而实现代码共享。
3.2.2.1. 首先,咱们实际所定义的场景是:lookcms 主利用同时提供基于 Regularjs 的 RButton 组件和基于 React 的 TButton 组件别离共享给 liveadmin 子利用和 increase 子利用。
3.2.2.2. 对于 lookcms 主利用,咱们定义 Webpack5 module federation plugin 如下:
plugins: [// new BundleAnalyzerPlugin(),
new ModuleFederationPlugin({
name: 'lookcms',
library: {type: 'var', name: 'lookcms'},
filename: 'remoteEntry.js',
exposes: {TButton: path.join(__dirname, '../client/exports/rgbtn.js'),
RButton: path.join(__dirname, '../client/exports/rcbtn.js'),
},
shared: ['react', 'regularjs']
}),
],
定义的共享代码组件如下图所示:
3.2.2.3. 对于 liveadmin 子利用,咱们定义 Webpack5 module federation plugin 如下:
plugins: [new BundleAnalyzerPlugin(),
new ModuleFederationPlugin({
name: 'liveadmin_remote',
library: {type: 'var', name: 'liveadmin_remote'},
remotes: {lookcms: 'lookcms',},
shared: ['regularjs']
}),
],
应用形式上,子利用首先要在 html 中插入源为 http://localhost:3000/remoteEntry.js
的主利用共享资源的入口,能够通过 html-webpack-externals-plugin 插入,见上文子利用的公共依赖 external 解决。
对于内部共享资源的加载,子利用都是通过 Webpack 的 import 办法异步加载而来,而后插入到虚构 DOM 中,咱们冀望参考 Webapck 给出的 React 计划做 Regularjs 的实现,很遗憾的是 Regularjs 并没有相应的根底性能帮咱们实现 Lazy 和 Suspense。
通过一番调研,咱们抉择基于 Regularjs 提供的 r-component API 来条件渲染异步加载的组件。
根本的思维是定义一个 Regularjs 组件,这个 Regularjs 组件在初始化阶段从 props 中获取要加载的异步组件 name,在构建阶段通过 Webpack import 办法加载 lookcms 共享的组件 name,并依照 props 中定义的 name 增加到 RSuspense 组件中,同时批改 RSuspense 组件 r-component 的展现逻辑,展现 name 绑定的组件。
因为 Regularjs 的语法书写受限,咱们不便将上述 RSuspense 组件逻辑形象进去,因而采纳了 Babel 转换的形式,通过开发人员定义一个组件的加载模式语句,应用 Babel AST 转换为 RSuspense 组件。最初在 Regularjs 的模版中应用这个 RSuspense
组件即可。
// 反对定义一个 fallback
const Loading = Regular.extend({template: '<div>Loading...{content}</div>',
name: 'Loading'
});
// 写成一个 lazy 加载的模式语句
const TButton = Regular.lazy(() => import('lookcms/TButton'), Loading);
// 模版中应用 Babel AST 转换好的 RSuspense 组件
`<RSuspense origin='lookcms/TButton' fallback='Loading' />`
通过 Babel AST 做的语法转换如下图所示:
理论运行成果如下图所示:
3.2.2.4. 对于 increase 子利用,咱们定义 Webpack 5 module federation plugin 如下:
plugins: [
new ModuleFederationPlugin({
name: 'lookadmin_remote',
library: {type: 'var', name: 'lookadmin_remote'},
remotes: {lookcms: 'lookcms',},
shared: ['react']
}),
],
应用形式上,参考 Webpack 5 的官网文档即可,代码如下:
const RemoteButton = React.lazy(() => import('lookcms/RButton'));
const Home = () => (
<div className="m-home">
欢送
<React.Suspense fallback="Loading Button">
<RemoteButton />
</React.Suspense>
</div>
);
理论运行成果如下图所示:
- 总结
四. 注意事项
- 跨域资源
如果你的利用内通过其余形式实现了跨域资源的加载,请留神 qiankun 是通过 fetch 的形式加载所有子利用资源的,因而跨域的资源须要通过 CORS 实现跨域拜访。 - 子利用的 html 标签
可能你的某个子利用的 html 标签上设置了某些属性或者附带了某些性能,要留神 qiankun 理论解决中剥离掉了子利用的 html 标签,因而如果由设置 rem 的需要,请留神应用其余形式适配。
五. 将来
- 自动化
子利用的接入通过平台的形式接入,当然这须要子利用恪守的标准行程。 - 依赖共享
Webpack 5 曾经公布了其正式版本,因而对于 module federation plugin 的应用能够提上工作日程。
六. 总结
LOOK 直播经营后盾基于理论的业务场景,应用 qiankun 进行了微前端形式的工程拆分,目前在生产环境安稳运行了近 4 个月,在实际的过程中,的确在需要确立和接入 qiankun 的实现以及部署利用几个阶段碰到了一些难点,比方开始的需要确立,咱们对要实现的主菜单性能有过斟酌,在接入 qiankun 的过程中常常碰到报错,在部署的过程中也遇到外部部署零碎的抉择和妨碍,好在共事们给力,我的项目能顺利的上线和运行。
参考资料
- qiankun
- Regularjs
- Module Federation Plugin
本文公布自 网易云音乐大前端团队,文章未经受权禁止任何模式的转载。咱们长年招收前端、iOS、Android,如果你筹备换工作,又恰好喜爱云音乐,那就退出咱们 grp.music-fe(at)corp.netease.com!