我本地写的文档,大部分内容我直截了当的截图了。
我根据报错提示进行了调整:
import React from 'react';
import ReactDOM from 'react-dom/client';
import { isInIcestark, setLibraryName } from '@ice/stark-app';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
let w_root;
export function mount(props) {
w_root = ReactDOM.createRoot(props.container);
w_root.render(<App {...props.customProps} />);
}
export function unmount(props) {
w_root.unmount();
}
setLibraryName('microApp');
const root = ReactDOM.createRoot(document.getElementById('root'));
if (!isInIcestark()) {
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
}
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
import { useEffect } from 'react';
import './App.css';
function App() {
useEffect(() => {
console.log('useEffect');
return () => {
console.log('unmout');
}
})
return (
<div className="App">
我是create-react-app的独立项目
</div>
);
}
export default App;
子应用的生命周期感知:顺利在进入的时候打印到“useEffect”, 离开的时候打印了"unmout", 完成将普通的create-react-app 项目成为子应用。
性能优化 性能优化 | icestark
第二步:主应用接入
yarn add qiankun
-
index.js 文件 (配置路由)
import React from 'react'; import ReactDOM from 'react-dom/client'; import { BrowserRouter } from "react-router-dom"; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; const root = ReactDOM.createRoot(document.getElementById('root_main')); root.render( <React.StrictMode> <BrowserRouter> <App /> </BrowserRouter> </React.StrictMode> ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals();
-
主应用App.js文件(入口文件):
import { registerMicroApps, start } from 'qiankun'; import { Routes, Route, Link } from "react-router-dom"; import { useEffect } from 'react'; import './App.css'; function App() { useEffect(() => { registerMicroApps([ { name: 'child', // app name registered entry: '//localhost:7001', container: '#Child', activeRule: 'child' }, { name: 'about', // app name registered entry: '//localhost:7001', container: '#about', activeRule: 'about' }, ]); start(); }, []) return ( <div className="App"> <div style={{ background: '#577dc3', padding: 10, color: '#fff' }}> headers </div> <div style={{ display: 'flex' }}> <div style={{ padding: 10, borderRight: '1px solid #eee' }}> <Link to="/main">main</Link> <br /><br /> <Link to="/child">child</Link> <br /><br /> <Link to="/about">about</Link> <br /><br /> <Link to="/">/</Link> </div> <div style={{ flex: 1 }}> <Routes> <Route path="/" element={<div>enter</div>} /> <Route path="main" element={<div>main</div>} /> <Route path="child" element={<div id="Child"></div>} /> <Route path="about" element={<div id="about"></div>} /> </Routes> </div> </div> </div> ); } export default App; App.css 样式文件
.App { width: 100vw; height: 100vh; overflow: hidden; }
上一步中,只有1,3是跟逻辑相关的,2. 4都是强调了第一步的改动,到此主应用就完成了。
第三步:子应用接入
yarn eject
-
修改配置:webpack.config.js文件
image.png
image.png
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from "react-router-dom";
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
console.log('react app bootstraped');
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
let root_child
export async function mount(props) {
root_child = ReactDOM.createRoot(props.container ? props.container.querySelector('#root') : document.getElementById('root'));
root_child.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount(props) {
console.log(props.container.querySelector('#root'))
// unmountComponentAtNode会报错,注释掉后不影响使用
// ReactDOM.unmountComponentAtNode(
// props.container ? props.container.querySelector('#root') : document.getElementById('root'),
// );
}
/**
* 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
*/
export async function update(props) {
console.log('update props', props);
}
if (!window.__POWERED_BY_QIANKUN__) {
// 不是qiankun过来的就正常展示
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
}
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
-
这里重点注意:官网给的是
export async function mount(props) { ReactDOM.render(<App />, props.container ? props.container.querySelector('#root') : document.getElementById('root')); }
这个是旧版本的写法,新版本的react需要ReactDOM.createRoo,然后render来作为渲染方式,不然会报错。
这里我只是简单实现了在主应用中插入子应用,效果如上图。
思路
- 请求子应用url地址,得到入口js文件,这里也需要配置跨域哦
- 子应用向window注册wei_mount事件(我目前没有想到其他更好的办法)
- 主应用修改window._IS_WS_WEI,表示当前为微服务
- 主应用写上根模块id="child"
主应用实现代码
这里注意bundle.js的入口路径不同项目也许有差别,可以做个微调。
app.js文件
import { useState } from 'react';
import { useEffect } from 'react';
import './App.css';
window._IS_WS_WEI_ = 'wangshi的demo';
let a;
const runScript = async (url) => {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = url;
script.onload = resolve;
script.onerror = reject;
const firstScript = document.getElementsByTagName('script')[0];
firstScript.parentNode.insertBefore(script, firstScript);
});
};
function App() {
useEffect(() => {
asyncCall();
}, [])
async function asyncCall() {
if (a) {
return;
}
a = true;
await runScript('http://localhost:3001/static/js/bundle.js')
window.wei_mount({ container: document.getElementById('child') })
}
return (
<div className="main_App">
我是主
<div id="child"></div>
</div>
);
}
export default App;
入口文件路径查找: 我是这样确认我的入口位置的
Why Not Iframe
为什么不用 iframe,这几乎是所有微前端方案第一个会被 challenge 的问题。但是大部分微前端方案又不约而同放弃了 iframe 方案,自然是有原因的,并不是为了 "炫技" 或者刻意追求 "特立独行"。
如果不考虑体验问题,iframe 几乎是最完美的微前端解决方案了。
iframe 最大的特性就是提供了浏览器原生的硬隔离方案,不论是样式隔离、js 隔离这类问题统统都能被完美解决。但他的最大问题也在于他的隔离性无法被突破,导致应用间上下文无法被共享,随之带来的开发体验、产品体验的问题。
1url 不同步。浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。
2UI 不同步,DOM 结构不共享。想象一下屏幕右下角 1/4 的 iframe 里来一个带遮罩层的弹框,同时我们要求这个弹框要浏览器居中显示,还要浏览器 resize 时自动居中..
3全局上下文完全隔离,内存变量不共享。iframe 内外系统的通信、数据同步等需求,主应用的 cookie 要透传到根域名都不同的子应用中实现免登效果。
4慢。每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程。
其中有的问题比较好解决(问题1),有的问题我们可以睁一只眼闭一只眼(问题4),但有的问题我们则很难解决(问题3)甚至无法解决(问题2),而这些无法解决的问题恰恰又会给产品带来非常严重的体验问题, 最终导致我们舍弃了 iframe 方案。
-----摘抄于qiankun