micro-app
micro-app 一款轻量、高效、功能强大的微前端框架 真的超简单~
前言
本文章使用 micro-app 以及 react (create-react-app 生成的项目) 实现文章中的内容。
具体会实现两个项目之间通过基座实现跳转,跳转后两个页面中的状态不会丢失。
名词解释
基座应用:用来放置子应用(业务项目)的容器,可以和各个子应用进行通信,主要是负责展示子应用。
子应用:就是前端项目,可以是 react 项目、vue 项目、ng 项目等
项目结构
-- root #项目根目录
-- main #基座应用
-- react 项目目录
-- one #子应用1
-- react 项目目录
-- two #子应用2
-- react 项目目录
子应用代码编写
简单实现页面中有一个输入框,并且有一个按钮可以用来跳转到另一个项目。
直接在项目入口文件编写如下代码
// 第一个项目 one/src/index.js
import './public-path'
import React from "react";
import { render } from "react-dom";
import { HashRouter, Switch, Route } from "react-router-dom";
const App = () => {
return (
<HashRouter>
<Switch>
<Route path="/Test001">
<input type="text" />
<button
onClick={() => {
const data = window.microApp?.getData();
data?.pushState("/two/#/Test002");
}}
> 到 two 项目 </button>
</Route>
</Switch>
</HashRouter>
);
};
const rootDom = document.getElementById("root");
render(<App />, rootDom);
ps: react-router-dom@6.x 版本的写法和上面是不一样的,Switch 变成 Routes 即可。
// 第二个项目 two/src/index.js
import './public-path'
import React from "react";
import { render } from "react-dom";
import { HashRouter, Switch, Route } from "react-router-dom";
const App = () => {
return (
<HashRouter>
<Switch>
<Route path="/Test002">
<input type="text" />
<button
onClick={() => {
const data = window.microApp?.getData();
data?.pushState("/one/#/Test001");
}}
> 到 one 项目 </button>
</Route>
</Switch>
</HashRouter>
);
};
const rootDom = document.getElementById("root");
render(<App />, rootDom);
上面两个子项目几乎是一样的,只是按钮中的文字不同和页面的路由不同。
需要注意的是按钮点击后执行的 getData 函数这时候是没有的,它是由基座注入的一个方法。
运行两个项目可以看到页面如下(我分别运行的 81和82端口)
public-path.js
// __MICRO_APP_ENVIRONMENT__和__MICRO_APP_PUBLIC_PATH__是由micro-app注入的全局变量
if (window.__MICRO_APP_ENVIRONMENT__) {
// eslint-disable-next-line
__webpack_public_path__ = window.__MICRO_APP_PUBLIC_PATH__
}
子应用设置跨域
本地开发时需要给 webpack 服务器设置跨域,线上生产环境需要给 nginx 设置跨域。
webpack 配置跨域
使用create-react-app脚手架创建的项目,在 config/webpackDevServer.config.js 文件中添加 headers(这里是使用 yarn rejest 将 webpack 暴露出来才会有的文件,也可以用其他方法来解决这个问题)。
其它项目在webpack-dev-server中添加headers。
headers: {
'Access-Control-Allow-Origin': '*',
}
nginx 配置跨域
location / {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
if ($request_method = 'OPTIONS') {
return 204;
}
}
基座应用代码编写
为了更方便的阅读各个代码段的关系,所以把所有代码都放到了一个代码文件中
// 基座项目 main/src/index.js
import React from "react";
import { render } from "react-dom";
import { BrowserRouter, Switch, Route, Link, useHistory } from "react-router-dom";
// 因为React不支持自定义事件,所以需要引入一个polyfill。
/** @jsxRuntime classic */
/** @jsx jsxCustomEvent */
import jsxCustomEvent from "@micro-zoe/micro-app/polyfill/jsx-custom-event";
// entry
import microApp from "@micro-zoe/micro-app";
// 定义好两个子应用的数据
const apps = [
{ name: "appOne", url: "http://localhost:81/" },
{ name: "appTwo", url: "http://localhost:82/" },
];
// 预加载子应用
microApp.preFetch(apps);
microApp.start();
// 子应用1
const Page = () => {
// 子应用之间的跳转必须由基座实现
// 如果在页面中使用 location.href = 'xxx' 跳转,将会导致 keep-alive 无法缓存
const history = useHistory();
function pushState(path) {
history.push(path);
}
return (
<>
<span style={{ fontSize: 15, paddingRight: 6 }}>当前在应用1</span>
<micro-app
// name(必传):应用名称
name={apps[0].name}
// url(必传):应用地址,会被自动补全为http://localhost:3000/index.html
url={apps[0].url}
// 切换应用后页面状态不会丢失
keep-alive
// 传入到子应用的数据
data={{
pushState,
}}
></micro-app>
</>
);
};
// 子应用2
const PageTwo = () => {
const history = useHistory();
function pushState(path) {
history.push(path);
}
return (
<>
<span style={{ fontSize: 15, paddingRight: 6 }}>当前在应用2</span>
<micro-app
name={apps[1].name}
url={apps[1].url}
keep-alive
data={{
pushState,
}}
></micro-app>
</>
);
};
// 路由
const RouterLay = () => {
return (
<BrowserRouter basename="/main">
<Switch>
<Route path="/one">
<Page />
</Route>
<Route path="/two">
<PageTwo />
</Route>
</Switch>
</BrowserRouter>
);
};
const App = () => {
return <RouterLay />;
};
const rootDom = document.getElementById("root");
render(<App />, rootDom);
简单分析一波上面的代码
首先项目有两个路由 /one 和 /two
两个路由分别对应了两个函数组件,两个函数组件中的内容几乎一致,都是调用micro-app组件,然后传入一个 data 给子应用,提供子应用跳转到另一个子应用的能力。
打开基座项目
show 一波代码
😑 是不是超简单(感谢京东提供的车轮)~
虽然用起来简单,但是要用到生产环境中还是需要花一些时间的,主要的时间还是花在路由处理、各个子应用直接数据的共享...。 子应用需要调用基座提供的方法跳转(如果不考虑缓存页面的话就不用考虑这么多了~)。
如下面这种,如果系统顶部有页签功能,各个子系统之间切换的话,同步这个页签...🤔