什么是微前端
微前端是一种将一个大型前端应用拆分为多个较小的、松耦合的应用的架构和技术方案。它的主要特征和优势包括:
- 独立开发、独立部署 - 每个微前端应用可以由不同的团队独立开发,无需协调其他应用的开发进度,并可以独立部署上线。
- 技术栈无关 - 每个微前端可以选用不同的编程语言、框架、构建工具等技术栈。
- 运行时动态组合 - 在浏览器端动态加载和组合多个微前端,呈现一个完整的应用页面。
- 高内聚,松耦合 - 每个应用高内聚只关注自己的功能,应用之间松耦合通过预定义接口进行通信。
- 增量升级 - 可以只升级更新其中几个微前端应用,无需整体重新部署。
- 隔离状态 - 每个微前端有自己独立的状态,不会互相影响。
- 定制化的技术栈 - 根据不同应用场景自由选择技术栈。
为什么用微前端
在微前端框架之前,我们要解决多个前端应用的整合,只能通过iframe的方式处理,这样虽然也能解决大部分问题,但是由于iframe中的内容是独立渲染的,所以,最难解决的一个问题,就是子应用中,如果我要弹出一个模态框,遮挡层,就只能覆盖子应用,而父应用是完全暴露的。这样的视觉感受就会很差,而微前端的方案,就可以解决这样的问题。
微前端的选择
微前端就目前来说 ,虽然很多大厂都有投入应用,但是总的来说,还是远不如服务端的微服务这样大规模的使用。主要原因还是绝大多数项目的体量完全不需要这样的技术方案。但是如果解耦的角度看,虽然使用微前端会增加一些成本,如果项目的体量或者性质比较合适的话,收益也是很划算的。
目前主流的微前端框架有 ,阿里的qiankun,腾讯的wujie,京东的MicroApp 等其他的微前端框架,在参考相关资料以及相关人员的使用咨询后,我们选择了wujie来作为我们的微前端框架
wujie
官方是这么介绍wujie的
无界微前端方案基于 WebComponent 容器 + iframe 沙箱 能够完善的解决适配成本、样式隔离、运行性能、页面白屏、子应用通信、子应用保活、多应用激活、vite 框架支持、应用共享等
环境
- react
- antd
wujie主应用引入
官方的示例中,所有的子应用都是预先规划好的,所以,只需要按照路由地址,在对应的组件中引入wujie组件即可。与官网示例不同,我们的应用是一个偏管理系统的模式,希望是具体的功能应用都能独立部署。这就意味着我们的子应用是不固定的,考虑到整体的布局结构,这里将所有的功能应用,都放在一个路由页面下,用app 作为前缀,后面跟着应用标识。
这样一来,所有的子应用,其实都在一个组件中,这里,我们通过应用的标识来确定到底展示哪一个子应用。
添加依赖
"wujie-react": "^1.0.18"
子应用组件,保留下所有加载的子应用,根据应用标识进行切换展示
const degrade = window.localStorage.getItem("degrade") === "true" || !window.Proxy || !window.CustomElementRegistry;
const WuJieView = () => {
const {state} = useLocation();
const navigate = useNavigate()
const {identifier} = useParams()
const [loading, setLoading] = useState(false)
const [app, setApp] = useState<any>()
const [apps, setApps] = useState<{ name: string, url: string }[]>([])
const [appMiss, setAppMiss] = useState(false)
useEffect(() => {
if (state) {
if (!_.find(apps, (item) => {
return item.name === state.identifier
})) {
setApps(_.concat(apps, {name: state.identifier, url: state.url}))
}
setApp({name: state.identifier, url: state.url})
} else {
if (identifier) {
appstoreApi.queryAppByIdentifier(identifier).then((app: any) => {
setApps(_.concat(apps, {name: app.identifier, url: app.url}))
setApp({name: app.identifier, url: app.url})
}).catch(async (ex) => {
message.error(ex.message)
})
}
}
}, [state]);
return <div style={{width: '100%', height: '100%'}}>
{apps.map((appItem) => <div key={appItem.name}
style={{
width: '100%',
height: '100%',
display: (_.isEqual(appItem.name, app.name) ? 'block' : 'none')
}}>
{_.isEqual(appItem.name, app.name) && <WuJieReact
height={"100%"}
width={"100%"}
name={appItem.name}
url={appItem.url}
sync={true}
alive={true}
degrade={degrade}
props={{"DubheToken": getTenantUserToken()}} //传递属性给子应用,这里传递的是Token
activated={() => setLoading(false)}
afterMount={() => {
}}
/>}
</div>)}
{appMiss && <div>应用不存在</div>}
</div>
}
export default WuJieView
这样主应用就集成完成了。
wujie子应用引入
由于在ts 下有诸多限制,这里借用lodash 进行相关的属性获取设置。这里主要就是通过全局变量的判断来做一些相应的处理 ,官网资料地址 https://wujie-micro.github.io/doc/guide/variable.html ,
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
if (_.get(window, '__POWERED_BY_WUJIE__')) {
const props = _.get(window, ['$wujie', 'props'])
setTenantUserToken(props?.DubheToken) //获取主应用传递的token,设置给当前应用
_.set(window, '__WUJIE_MOUNT', () => {
root.render(
<ConfigProvider locale={zhCN}>
<RouterProvider router={router}/>
</ConfigProvider>
);
})
_.set(window, '__WUJIE_UNMOUNT', () => {
root.unmount()
})
} else {
root.render(
<ConfigProvider locale={zhCN}>
<RouterProvider router={router}/>
</ConfigProvider>
);
}
效果如下
到这里,wujie 的基本集成就完成了,后面,再根据自己的需求,参考官方文档进行调整即可。
wujie跨域问题的解决
由于主应用和子应用是分别部署的,所以跨域问题在所难免,这里贴出跨域的解决方案
开发环境,devServer中proxy的配置
devServer: {
historyApiFallback: true,
port:3104,
proxy: {
"/api/*": {
target: "http://localhost:8094/",
changeOrigin: true,
secure: false,
onProxyRes: function (proxyRes, req, res) {
if (req.method === 'OPTIONS') {
proxyRes.headers['Access-Control-Allow-Origin'] = req.headers.origin || '*'
proxyRes.headers['Access-Control-Allow-Credentials'] = true
proxyRes.headers['Access-Control-Allow-Methods'] = 'GET,POST,OPTIONS,PUT,DELETE,FETCH'
// 这里的参数,根据自己项目增删
proxyRes.headers['Access-Control-Allow-Headers'] = 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,token,source'
proxyRes.statusCode = 204
} else {
proxyRes.headers['Access-Control-Allow-Origin'] = req.headers.origin || '*'
proxyRes.headers['Access-Control-Allow-Credentials'] = true
}
}
},
}
},
nginx 配置,这里需要同时处理静态文件和接口反向代理的跨域
server{
listen 80;
server_name localhost;
client_max_body_size 50M;
location /{
add_header "Access-Control-Allow-Credentials" true;
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, FETCH, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
root /workspace;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass $SERVER_PROXY_PASS;
add_header "Access-Control-Allow-Credentials" true;
add_header 'Access-Control-Allow-Origin' $http_origin;
if ($request_method = 'OPTIONS') {
add_header "Access-Control-Allow-Credentials" true;
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, FETCH, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,token,source';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
}
}
以上,就是基于wujie的微前端方案的完整应用。 总体来说,wujie的使用成本很低,基本没什么需要改造的,这一点比qiankun 是要强出不少的。