当前位置: 首页 > news >正文

MFE: React + Angular 混合demo

前言

技术栈选择

  • React:使用 create-react-app 快速搭建应用
  • Angular:使用 Angular CLI 初始化项目
  • 集成工具:选择 module-federation(Webpack 5 原生支持)

使用 Module Federation 实现混合

Angular Remote 和 React Host 示例:

Angular Main code:(作为远程模块)

  • Main Component:
<div class="angular-mfe"><p>这是会显示在React host项目的页面内容<p></p>
</div>
@Component({standalone: true,selector: 'angular-remote-to-react',templateUrl: './angular-remote-to-react.component.html',styleUrls: ['./angular-remote-to-react.component.scss'],imports: [BrowserAnimationsModule],encapsulation: ViewEncapsulation.None,
})export class AngularRemoteToReactComponent {}
<!--全局样式: node_module里面的,或者自己写的-->
.angular-mfe {@import "../../../node_modules/XXXX/main-crimson.scss";@import "../../../node_modules/XXXX/_icons_direct_url.scss";@import "../../../node_modules/XXXX/_font_face_direct_url_10px.scss";@import "../../assets/widgets.style.scss";
}

特别注意,因为@angular/cdk这个包的元素独立在了主component外面,像下面这样,所以它的样式要单独处理,插入到html里面让其生效。

        单独处理代码:

import { DOCUMENT } from '@angular/common';
import { Inject, Injectable } from '@angular/core';@Injectable({providedIn: 'root'
})
export class ThemeSwitchingService {//for Toggle StyleSheetsobserver:MutationObserver//for Toggle Stylesheets end//   siteUrl=environment.assetsUrl()//'http://localhost:4211/','UAT env'siteUrl= '/assets'; //相对路径//null, "test"public styleSheets=()=>[{name:"test",link:`${this.siteUrl}/assets/style-dbs-DLS-3-1-dialog-only.css`,label:"test",value:"test"},]constructor(@Inject(DOCUMENT) private _document: Document,) { }set assetUrl(siteUrl){this.styleSheets()}initialiseObserver(selectedStyle:string="test"){//Step 1: append font-facethis.appendAdditionalAssets()//for Toggle Stylesheets//Step 2: Create a MutationObserver to detect changes in childList of <head> and move <link id="overall_style"> to bottomlet targetEl = this._document.getElementsByTagName("head")[0];let target_id='overall_style'this.moveStyleSheet(target_id)this.selectStyleSheet(selectedStyle)}disconnectObserver(){}selectStyleSheet(selectedName:string){let target_id='overall_style'//Move style sheets to the bottomthis.moveStyleSheet(target_id);let selectedStyles=this.styleSheets().find(obj=>obj.value===selectedName)if(selectedStyles==undefined){selectedStyles=this.styleSheets()[0];}this._document.getElementById(target_id).setAttribute('href',selectedStyles.link)}moveStyleSheet(target_id:string){let head_element = document.getElementsByTagName("head")[0];let link_element:Element;if(this._document.getElementById(target_id)!=null){link_element=this._document.getElementById(target_id)if(head_element!=undefined && head_element.children[head_element.children.length-1].id!==target_id){head_element.removeChild(link_element)head_element.appendChild(link_element);}}else{let new_link=this._document.createElement("link")new_link.id=target_idnew_link.rel="stylesheet"new_link.href=`${this.siteUrl}/assets/style-dbs-DLS-3-1-dialog-only.css`head_element.appendChild(new_link);}}appendAdditionalAssets(){this,this.additionAdditionalAssets.forEach((assetObject)=>{console.log("check assets", assetObject, assetObject?.id)this.appendAsset(assetObject)})}appendAsset(assetObject:any){let target_id=assetObject?.idif(document.getElementById(target_id)==null){let head_element = document.getElementsByTagName("head")[0];let new_link=this.createAssetEl(assetObject)head_element.appendChild(new_link);}}createAssetEl(assetObject:any){let new_link=this._document.createElement(assetObject?.el)new_link.id=assetObject?.idnew_link.rel=assetObject?.relnew_link.href=assetObject?.hrefreturn new_link}public additionAdditionalAssets:any[]=[{//font-face assets: required for typography assets to be injected into hostid:"mfe_font-face",el:"link",rel:"stylesheet",href:`https://fonts.googleapis.com/css2?family=Public+Sans:wght@100;300;400;500;600;700&family=Open+Sans:wght@300;400;500;600;700;800&display=swap`},{//@angular/cdk: required for @angular/cdk assets to be injected into host. library package removed prebuild cssid:"mfe_angular-cdk",el:"link",rel:"stylesheet",href:`${this.siteUrl}/angular/overlay-prebuilt.css`}]
}
  • App Component:
​<angular-remote-to-react></angular-remote-to-react>
import { Component} from '@angular/core';@Component({selector: 'angular-mfe',templateUrl: './app.component.html',styleUrl: './app.component.scss'
})
export class AppComponent {}
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { HttpClient,provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { CommonModule, DatePipe } from '@angular/common';
import { AngularRemoteToReactComponent } from './angular-remote-to-react.component';export function HttpLoaderFactory(http: HttpClient) {return new TranslateHttpLoader(http);
}@NgModule({declarations: [AppComponent],bootstrap: [AppComponent],imports: [BrowserModule,BrowserAnimationsModule,CommonModule,AngularRemoteToReactComponent,TranslateModule.forRoot({loader: {provide: TranslateLoader,useFactory: HttpLoaderFactory,deps: [HttpClient]}})], providers: [DatePipe,provideHttpClient(withInterceptorsFromDi())]
})export class AppModule {}
  • 导出给React挂载的Component: loadApp.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import "zone.js";
import { AppModule } from "./app/app.module";let appRef: any = null;const mount = async () => {appRef = await platformBrowserDynamic().bootstrapModule(AppModule).catch(err => console.error(err));
};const unmount = () => {if (appRef) {appRef.destroy();appRef = null;}
};export { mount, unmount };
  • webpack.config.js:
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');module.exports = {output: {publicPath: '/',uniqueName: 'remoteApp'},plugins: [new ModuleFederationPlugin({name: 'remoteApp',filename: 'remoteEntry.js',exposes: {'./LoadAngularApp': './src/loadApp.ts'},shared: {'@angular/core': { singleton: true, strictVersion: true },'@angular/common': { singleton: true, strictVersion: true },'@angular/router': { singleton: true, strictVersion: true }}})]
};

React Main code:(作为主机)

  • webpack.config.js:
new ModuleFederationPlugin({name: "app",remotes: {},shared:{...deps,'react-dom': {singleton: true,eager:true},react: {singleton: true,eager:true},}}),
  • 加载Angular组件:
import { useEffect, useState } from 'react';
import { BrowserRouter, Routes, Route, useNavigate } from 'react-router-dom';
import { loadRemoteModule } from '@angular-architects/module-federation';
import styled from 'styled-components';// Styled AngularContainer component
export const AngularContainer = styled.div`/* Apply scaling to the entire content */// transform: scale(0.625);// transform-origin: top left;// width: 160%;/* Font size normalization - primary requirement */// * {//   font-size: 62.5%;// zoom: 0.625; /* 使用zoom属性代替transform:scale */// -moz-transform: scale(0.625); /* Firefox不支持zoom,使用transform */// -moz-transform-origin: top left;// display: block;// }`;//HOME
function Home() {const navigate = useNavigate();return (<div><button onClick={() => navigate('/angular-mfe')}>Go to Angular MFE</button><br /></div>);
}//Angular MFE
function AngularMfe() {const [AngularComponent, setAngularComponent] = useState(null);let unmountFunction = null;useEffect(() => {const loadModule = async () => {const { mount, unmount } = await loadRemoteModule({type: 'module',remoteEntry: '/remoteEntry.js',remoteName: 'remoteApp',exposedModule: './LoadAngularApp'});unmountFunction = unmount;setTimeout(() => {setAngularComponent(mount);setTimeout(() => {dispatchData();}, 1000)}, 200);};loadModule();return () => {if (unmountFunction) {unmountFunction();}};}, []);/*** Dispatches initialization data to the Angular micro-frontend** Required parameters:*/const dispatchData = () => {try {// Prepare event data with required parametersconst eventData = {// User and context informationtest:'test'};// Create and dispatch the eventconst passDataEvent = new CustomEvent('PassDataEvent', { detail: eventData });window.dispatchEvent(passDataEvent);} catch (error) {console.error('Failed to dispatch data:', error);}};if (!AngularComponent) {console.log(AngularComponent);return <div>Loading...</div>;}return (<div><h4 className='test'>React Host Container</h4><hr/><AngularContainer><angular-mfe /></AngularContainer></div>);
}//APP
const MyReactComponent = () => {return (<BrowserRouter><Routes><Route path="/" element={<Home />} />         {/* Home */}<Route path="/angular-mfe" element={<AngularMfe />} />   {/* Angular MFE 路由*/}</Routes></BrowserRouter>);
};export default MyReactComponent;

Angular Host 和 React Remote 示例:

React 项目配置(作为远程模块)

  • 创建可导出的 React 组件:
// src/ReactComponent.jsx
import React from 'react';export default function ReactComponent() {return <h1>This is a React component in Angular</h1>;
}
  • 修改 webpack.config.js:在 Webpack 5 的配置中暴露组件
new ModuleFederationPlugin({name: 'reactApp',filename: 'remoteEntry.js',exposes: {'./ReactComponent': './src/ReactComponent'}
});

Angular 项目配置(作为主机)

  • webpack.config.js中添加:
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
module.exports = {plugins: [new ModuleFederationPlugin({remotes: {reactApp: "reactApp@http://localhost:3001/remoteEntry.js",},}),],
};
  • 动态加载组件,在 Angular 中动态加载 React 组件:

确保项目已安装 reactreact-dom 和 @types/react

npm install react react-dom @types/react
  • Angular 组件逻辑
// react-wrapper.component.ts
@Component({selector: 'app-react-wrapper',template: '<div id="react-container"></div>'
})
export class ReactWrapperComponent implements OnInit {async ngOnInit() {const module = await import('reactApp/ReactComponent');const ReactDOM = await import('react-dom');ReactDOM.render(module.default(), document.getElementById('react-container'));}
}

通信机制

通过自定义事件实现跨框架通信:

React 发布事件

window.dispatchEvent(new CustomEvent('reactEvent', { detail: data }));

Angular 监听事件

@HostListener('window:reactEvent', ['$event'])
onReactEvent(event: CustomEvent) {console.log(event.detail);
}
详情可以参考这篇博客:https://blog.csdn.net/qq_44327851/article/details/148713528?spm=1011.2124.3001.6209

样式隔离

  • 为根组件添加框架特定的 CSS 命名空间(如 angular-appreact-app
  • 使用 Shadow DOM 或 CSS-in-JS 库(如 styled-components)
http://www.dtcms.com/a/511045.html

相关文章:

  • CR0 控制位解释
  • 半成品网站周村网站制作哪家好
  • 自然语言处理NLP的数据预处理:从原始文本到模型输入(MindSpore版)
  • 清空显存占用
  • UNTER++模型简介
  • PHP Error 处理指南
  • Linux学习笔记(十)--进程替换与创建一个自己的简易版shell
  • go语言实现 基于 Session 和 Redis 实现短信验证码登录
  • 福建网站建设制作阿里巴巴旗下跨境电商平台有哪些
  • 潇洒郎:最佳完美——Windows防火墙与端口管理工具——支持ipv6、ipv4端口转发管理
  • Elastic MCP 服务器:向任何 AI agent 暴露 Agent Builder 工具
  • 小说网站建设详细流程游戏开发有前途吗
  • echarts tooltip数据太长导致显示不全
  • 用户选剧情,AI写故事:Trae Solo+GLM-4.6实现沉浸式小说创作体验
  • 【Linux】初始Linux和Linux下基本指令:ls pwd cd touch mkdir rmdir rm 指令
  • 《Linux系统编程之入门基础》【Linux基础 理论+命令】(下)
  • 农业网站建设招标书网站导航条内容
  • LLAMA Factory 微调Qwen2.0-VL-2B视觉大模型
  • 婚纱网站建设案例wordpress默认主题twenty
  • 网站访问者qq企业网站备案名称窍门
  • 个人网站源码下载有口皆碑的域名备案加急
  • 农用地转建设用地结果查询网站苏州品牌网站设计
  • 做外贸的网站怎么建立wordpress建网站主页
  • 建设部网站中天人建筑工程有限公司网站设计是怎么做的
  • 网站建设管理工作经验介绍建美食网站有哪些原因
  • 电子商务网站后台功能wordpress thesis 开发
  • 自已做的网站怎么做域名解析沐众科技网站建设
  • 一款app开发需要多少钱郑州seo网络营销技术
  • 做动漫的网站合肥seo公司
  • 免费商城网站制作网站建设制度