提交 15fd69f2 作者: yueyang.lv

refactor(模块联邦): 适配模块联邦

开启模块联邦模式,Saturn 业务代码改用远程加载
上级 1e42e811
const { ModuleFederationPlugin } = require("webpack").container;
const { dependencies } = require("./package.json");
function createShared(names) {
return names.map((item) => ({
singleton: true,
strictVersion: true,
eager: true,
requiredVersion: dependencies[item],
}));
}
function filterShared(keys) {
let newDeps = {};
for (let i in dependencies) {
if (!keys.includes(i)) {
newDeps[i] = dependencies[i];
}
}
return newDeps;
}
// 模块联邦
module.exports = function (config, env) {
config.plugins.unshift(
new ModuleFederationPlugin({
name: "minio_console",
remotes: {
saturnApp: "saturnApp@http://localhost:9000/remoteEntry.js",
},
filename: "remoteEntry.js",
exposes: {
"./PageLayout": "./src/screens/Console/Common/Layout/PageLayout.tsx",
"./PageHeader":
"./src/screens/Console/Common/PageHeader/PageHeader.tsx",
"./history": "./src/history.ts",
},
shared: {
...filterShared([
// '@codemirror/lang-json',
// '@codemirror/legacy-modes',
// '@date-io/moment',
// '@emotion/react',
// '@emotion/styled',
// '@mui/icons-material',
"@mui/lab",
"@mui/material",
// '@mui/styled-engine-sc',
// '@mui/styles',
// '@uiw/react-codemirror',
// 'history',
"js-cookie",
// 'kbar',
// 'local-storage-fallback',
// 'lodash',
// 'minio',
"moment",
// 'react-chartjs-2',
// 'react-copy-to-clipboard',
// 'react-dropzone',
// 'react-grid-layout',
// 'react-hot-loader',
// 'react-moment',
// 'react-redux',
// 'react-virtualized',
// 'react-window',
// 'react-window-infinite-loader',
// 'recharts',
// 'redux',
// 'redux-thunk',
"superagent",
// 'use-debounce',
// 'websocket',
// 'chart.js',
]),
...createShared(["react", "react-dom"]),
},
})
);
return config;
};
const rewireReactHotLoader = require("react-app-rewire-hot-loader");
// const { ModuleFederationPlugin } = require("webpack").container;
// const { dependencies } = require("./package.json");
const mfConfigPlugin = require("./config-mf");
/* config-overrides.js */
module.exports = function override(config, env) {
if (env === "development") {
config.resolve.alias["react-dom"] = "@hot-loader/react-dom";
}
// config.plugins.push(
// new ModuleFederationPlugin({
// name: "minio_console",
// remotes: {
// saturnApp: "saturnApp@http://localhost:9000/remoteEntry.js",
// },
// shared: {
// // ...dependencies,
// react: {
// import: "react", // the "react" package will be used a provided and fallback module
// shareKey: "react", // under this name the shared module will be placed in the share scope
// shareScope: "default", // share scope with this name will be used
// singleton: true, // only a single version of the shared module is allowed
// requiredVersion: dependencies.react,
// },
// },
// })
// );
config = rewireReactHotLoader(config, env);
// 模块联邦
mfConfigPlugin(config, env);
return config;
};
......@@ -14,8 +14,6 @@
"@mui/material": "^5.4.0",
"@mui/styled-engine-sc": "^5.3.0",
"@mui/styles": "^5.3.0",
"@mui/x-data-grid": "^5.14.0",
"@mui/x-date-pickers": "^5.0.0-beta.6",
"@uiw/react-codemirror": "^4.3.2",
"history": "^4.10.1",
"js-cookie": "^3.0.1",
......@@ -24,15 +22,12 @@
"lodash": "^4.17.21",
"minio": "^7.0.26",
"moment": "^2.29.1",
"qrcode": "^1.5.1",
"react": "^17.0.2",
"react-chartjs-2": "^2.9.0",
"react-copy-to-clipboard": "^5.0.2",
"react-dom": "17.0.1",
"react-dropzone": "^11.4.2",
"react-grid-layout": "^1.2.0",
"react-hook-form": "^7.34.2",
"react-hook-form-mui": "^5.5.1",
"react-hot-loader": "^4.13.0",
"react-moment": "^1.1.1",
"react-redux": "^7.1.3",
......
......@@ -21,7 +21,7 @@ import { hot } from "react-hot-loader/root";
import ProtectedRoute from "./ProtectedRoutes";
import LoadingComponent from "./common/LoadingComponent";
import AppConsole from "./screens/Console/ConsoleKBar";
import { fullRouters } from "./screens/Console/Saturn/routes";
import { SaturnAppFullscreen } from "./saturnCloud";
const Login = React.lazy(() => import("./screens/LoginPage/LoginPage"));
const LoginCallback = React.lazy(
......@@ -50,18 +50,11 @@ const Routes = () => {
</Suspense>
)}
/>
{fullRouters.map((router) => (
<Route
exact
path={router.path}
key={router.path}
children={(routerProps) => (
<Suspense fallback={<LoadingComponent />}>
<router.component />
</Suspense>
)}
/>
))}
<Route
exact
path="/saturn/fullscreen/*"
children={(routerProps) => <SaturnAppFullscreen />}
/>
<ProtectedRoute Component={AppConsole} />
</Switch>
</Router>
......
import("./bootstrap");
/// <reference types="react-scripts" />
declare module "saturnApp/*";
/* eslint-disable no-undef */
import React from "react";
import LoadingComponent from "../common/LoadingComponent";
function loadComponent(scope, module) {
return async () => {
// Initializes the share scope. This fills it with known provided modules from this build and all remotes
// @ts-ignore
await __webpack_init_sharing__("default");
const container = window[scope]; // or get the container somewhere else
// Initialize the container, it may provide shared modules
// @ts-ignore
await container.init(__webpack_share_scopes__.default);
// @ts-ignore
const factory = await window[scope].get(module);
const Module = factory();
return Module;
};
}
const urlCache = new Set();
const useDynamicScript = (url) => {
const [ready, setReady] = React.useState(false);
const [errorLoading, setErrorLoading] = React.useState(false);
React.useEffect(() => {
if (!url) return;
if (urlCache.has(url)) {
setReady(true);
setErrorLoading(false);
return;
}
setReady(false);
setErrorLoading(false);
const element = document.createElement("script");
element.src = url;
element.type = "text/javascript";
element.async = true;
element.onload = () => {
urlCache.add(url);
setReady(true);
};
element.onerror = () => {
setReady(false);
setErrorLoading(true);
};
document.head.appendChild(element);
return () => {
urlCache.delete(url);
document.head.removeChild(element);
};
}, [url]);
return {
errorLoading,
ready,
};
};
const componentCache = new Map();
export const useFederatedComponent = (remoteUrl, scope, module) => {
const key = `${remoteUrl}-${scope}-${module}`;
const [Component, setComponent] = React.useState(null);
const { ready, errorLoading } = useDynamicScript(remoteUrl);
React.useEffect(() => {
if (Component) setComponent(null);
// Only recalculate when key changes
}, [key]);
React.useEffect(() => {
if (ready && !Component) {
const Comp = React.lazy(loadComponent(scope, module));
componentCache.set(key, Comp);
setComponent(Comp);
}
// key includes all dependencies (scope/module)
}, [Component, ready, key]);
return { errorLoading, Component };
};
interface IProps {
/** ./App */
module: string;
/** saturnApp */
scope: string;
/** http://localhost:9000/remoteEntry.js */
url: string;
}
export default function RemoteComponent({ module, scope, url }: IProps) {
const { Component: FederatedComponent, errorLoading } = useFederatedComponent(
url,
scope,
module
);
return (
<React.Suspense fallback={LoadingComponent}>
{errorLoading ? (
`加载模块 "${module}" 错误`
) : (
<>{FederatedComponent && <FederatedComponent />}</>
)}
</React.Suspense>
);
}
import { Suspense, lazy, useState, useEffect } from "react";
import LoadingComponent from "../common/LoadingComponent";
import history from "../history";
// import RemoteComponent from "./RemoteComponent";
// import loadMenu from "saturnApp/menu";
// const URL = "http://localhost:9000/remoteEntry.js";
// const SCOPE = "saturnApp";
const RemoteSaturnApp = lazy(() => import("saturnApp/App"));
const RemoteSaturnAppFullscreen = lazy(() => import("saturnApp/AppFullscreen"));
// const loadMenu = () => import("saturnApp/menu");
// console.log("loadMenu", loadMenu);
// export function SaturnApp() {
// return <RemoteComponent url={URL} scope={SCOPE} module="./App" />;
// }
// export function SaturnAppFullscreen() {
// return <RemoteComponent url={URL} scope={SCOPE} module="./AppFullscreen" />;
// }
/** 在 Layout 下展示的内容 */
export function SaturnApp() {
return (
<Suspense fallback={<LoadingComponent />}>
<RemoteSaturnApp history={history} />
</Suspense>
);
}
/** 全屏展示的内容 */
export function SaturnAppFullscreen() {
return (
<Suspense fallback={<LoadingComponent />}>
<RemoteSaturnAppFullscreen history={history} />
</Suspense>
);
}
/** 加载侧栏菜单 */
export function useSaturnConsoleMenu() {
const [menu, setMenu] = useState([]);
useEffect(() => {
import("saturnApp/consoleMenu")
.then((res) => {
console.log(res.default);
setMenu(res.default);
})
.catch((e) => {
console.error(e);
});
}, []);
return menu;
}
......@@ -50,7 +50,7 @@ import {
import { hasPermission } from "../../common/SecureComponent";
import { IRouteRule } from "./Menu/types";
import LoadingComponent from "../../common/LoadingComponent";
import SaturnApp from "./Saturn";
import { SaturnApp } from "../../saturnCloud";
const Trace = React.lazy(() => import("./Trace/Trace"));
const Heal = React.lazy(() => import("./Heal/Heal"));
......@@ -606,7 +606,9 @@ const Console = ({
<IconsScreen />
</Suspense>
</Route>
<SaturnApp key="/saturnApp" history={history} />
<Route key={"/saturn"} exact path={"/saturn/*"}>
<SaturnApp />
</Route>
{allowedRoutes.length > 0 ? (
<Redirect to={allowedRoutes[0].path} />
) : null}
......
......@@ -27,14 +27,14 @@ import { setMenuOpen, userLoggedIn } from "../../../actions";
import { ErrorResponseHandler } from "../../../common/types";
import { clearSession } from "../../../common/utils";
import history from "../../../history";
import api from "../../../common/api";
import { resetSession } from "../actions";
import MenuToggle from "./MenuToggle";
import ConsoleMenuList from "./ConsoleMenuList";
import { validRoutes } from "../valid-routes";
import { toLoginPage } from '../../../utils/toLogin';
import { toLoginPage } from "../../../utils/toLogin";
import { useSaturnConsoleMenu } from "../../../saturnCloud";
const drawerWidth = 245;
......@@ -102,6 +102,8 @@ const Menu = ({
setMenuOpen,
features,
}: IMenuProps) => {
const saturnMenu = useSaturnConsoleMenu();
const logout = () => {
const deleteSession = () => {
clearSession();
......@@ -121,6 +123,7 @@ const Menu = ({
});
};
const allowedMenuItems = validRoutes(features, operatorMode);
allowedMenuItems.push(...saturnMenu);
return (
<Drawer
......
......@@ -4,14 +4,15 @@ interface ToLoginPageOptions {
}
/** 跳转到登录页 */
export const toLoginPage = (options?: ToLoginPageOptions): void => {
let { callbackUrl = true, replace = false } = options || {};
let { callbackUrl = true } = options || {};
const { replace = false } = options || {};
if (typeof callbackUrl === "boolean" && callbackUrl) {
// 如果是 true 就是用当前 url 作为登录成功后回调地址
callbackUrl = encodeURIComponent(window.location.href);
}
const queryStr = callbackUrl ? `?callbackUrl=${callbackUrl}` : "";
let url = `/saturn/login${queryStr}`;
let url = `/saturn/fullscreen/login${queryStr}`;
if (process.env.NODE_ENV === "production") {
let loginUrl = `https://xxyy.co/#/login${queryStr}`;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论