地方网站系统,观看床做视频网站,龙岩网上房地产网,高邮做网站前言 本文中会提到很多目前数栈中使用的特定名词#xff0c;统一做下解释描述 dt-common#xff1a;每个子产品都会引入的公共包(类似 NPM 包) AppMenus#xff1a;在子产品中快速进入到其他子产品的导航栏#xff0c;统一维护在 dt-common 中#xff0c;子产品从 dt-com…前言 本文中会提到很多目前数栈中使用的特定名词统一做下解释描述 dt-common每个子产品都会引入的公共包(类似 NPM 包) AppMenus在子产品中快速进入到其他子产品的导航栏统一维护在 dt-common 中子产品从 dt-common 中引入 Portal所有子产品的统一入口 APP_CONF子产品的一些配置信息存放 背景 由于迭代中我们有很多需求都是针对 AppMenus 的这些需求的生效需要各个子产品的配合进行统一变更。现在的数栈前端的项目当中 AppMenus 的相关逻辑存在于 dt-common 中dt-common 又以独立的目录存在于每个子项目中 所以当出现这种需求的时候变更的分支就包含所有的子产品这给前端以及测试同学都带来很多重复性的工作。 本文旨在通过 webpack5 Module Federation 的实践实现 AppMenus 与各个子产品的解耦即 AppMenus 作为共享资源将其从 dt-common 中分离出来保证其更新部署无需其他子产品的配合。 本地实现 Portal 项目 拆分 AppMenus 到 Portal 下的 Components 中Portal 内部引用 AppMenus 就是正常组件内部的引用 配置 Module Federation 相关配置方便其他项目进行远程依赖 const federationConfig {name: portal,filename: remoteEntry.js,// 当前组件需要暴露出去的组件exposes: {./AppMenus: ./src/views/components/app-menus,},shared: {react: { singleton: true,eager: true, requiredVersion: deps.react},react-dom: {singleton: true,eager: true,requiredVersion: deps[react-dom],},},};const plugins [...baseKoConfig.plugins,{key: WebpackPlugin,action: add,opts: {name: ModuleFederationPlugin,fn: () new ModuleFederationPlugin({ ...federationConfig }),},},].filter(Boolean); dt-common 中修改 Navigator 组件引用 AppMenus 的方式通过 props.children 实现 子产品项目 配置 Module Federation config const federationConfig {name: xxx,filename: remoteEntry.js,// 关联需要引入的其他应用remotes: {// 本地相互访问采取该方式portal: portalhttp://127.0.0.1:8081/portal/remoteEntry.js,},shared: {antd: {singleton: true,eager: true,requiredVersion: deps.antd,},react: {singleton: true,eager: true,requiredVersion: deps.react,},react-dom: {singleton: true,eager: true,requiredVersion: deps[react-dom],},},
}; 修改 AppMenus 引用方式 const AppMenus React.lazy(() import(portal/AppMenus));
Navigator{...this.props}
React.Suspense fallbackloadingAppMenus {...this.props} //React.Suspense
/Navigator// 需要 ts 定义
// typings/app.d.ts 文件
declare module portal/AppMenus {const AppMenus: React.ComponentTypeany;export default AppMenus;
} 注意本地调试的时候子产品中需要代理 Portal 的访问路径到 Portal 服务的端口下才能访问 Portal 暴露出来的组件的相关chunckjs module.exports {proxy: {/portal: {target: http://127.0.0.1:8081, // 本地//target: portal 对应的地址, 本地 -〉 devops 环境changeOrigin: true,secure: false,onProxyReq: ProxyReq,},}
} 远程部署 部署到服务器上由于 Portal 项目中的 AppMenus 相当于是远程组件即共享依赖子产品为宿主环境所以部署的时候需要对应部署 Portal 项目与子产品。而在上述配置中需要变更的是加载的地址。 Portal 项目中没有需要变更的变更的是子产品中的相关逻辑。 //remote.tsx
import React from react;function loadComponent(scope, module) {return async () {// Initializes the share scope. This fills it with known provided modules from this build and all remotesawait __webpack_init_sharing__(default);const container window[scope]; // or get the container somewhere else// Initialize the container, it may provide shared modulesawait container.init(__webpack_share_scopes__.default);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 () {console.log(onload);urlCache.add(url);setReady(true);};element.onerror () {console.log(error);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 };
};//layout header.tsx
const Header () {....const url ${window.APP_CONF?.remoteApp}/portal/remoteEntry.js;const scope portal;const module ./AppMenusconst { Component: FederatedComponent, errorLoading } useFederatedComponent(url,scope,module);return (Navigator logo{Logo /} menuItems{menuItems} licenseApps{licenseApps} {...props}{errorLoading ? (WarningOutlined /) : (FederatedComponent (React.Suspense fallback{Spin /}{FederatedComponent {...props} top{64} showBackPortal /}/React.Suspense))}/Navigator);
} 如何调试 子产品本地 → Portal 本地 Portal 与某个资产同时在不同的端口上运行Portal 无需变更子产品需要以下相关的文件在这种情况下 remoteApp 为本地启动的 portal 项目本地环境同时当我们启动项目的时候需要将 /partal 的请求代理到本地环境 // proxy - 代理修改
// header 引用的远程地址 - 修改window.APP_CONF?.remoteApp http://127.0.0.1:8081proxy: {/portal: {target: http://127.0.0.1:8081}
} 子产品本地 → Portal 的服务器环境 本地起 console 的服务服务器上部署 Portal 对应的 Module Ferderation 分支同上只不过此时 Portal 已经部署了remote 和代理地址只需要改成部署后的 Portal 地址即可 // proxy - 代理修改
// header 引用的远程地址 - 修改window.APP_CONF?.remoteApp xxxproxy: {/portal: {target: xxx}
} 子产品服务器环境 → Portal 的服务器环境 子产品 Portal 分别部署到服务器环境上修改子产品的 config 的配置 添加 window.APP_CONF.remoteApp 到 Portal 的服务器环境 异常处理 当 Portal 部署不当或者是版本不对应的时候没有 AppMenus 远程暴露出来的话 做了异常处理思路是 当请求 remoteEntry.js 出现 error 的时候是不会展示 AppMenus 相关组件的 当 Portal 已经部署其他子产品未接入 Module Federation 是不会影响到子产品的正常展示的子产品当下使用的 应是 dt-common 中的 AppMenus 如何开发 AppMenus 问题记录 依赖版本不一致 【Error】Could not find store in either the context or props of Connect(N). Either wrap the root component in a Provider, or explicitly pass store as a prop to Connect(N). 发现报错路径为 portal/xxx可以定位到是 AppMunes 发生了问题导致原因子产品 React-Redux 和 Portal React-Redux 版本不一致导致的需要在对应子产品 federationConfig 处理 react-redux 为共享 总结 本文主要从业务层面结合 webpack 5 Module Federation 实现 AppMenus 的解耦问题。主要涉及 dt-common 、Portal、子产品的变更。通过解耦能够发现我们对 AppMenus 的开发流程减少了不少有效的提高了我们的效率。 文章转载自袋鼠云数栈前端 原文链接https://www.cnblogs.com/dtux/p/17898758.html 体验地址引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构