使用 ReactJS构建微前端源码


我们对微服务的了解是:它有助于单独开发、部署和维护应用程序。就像 Uber 一样,预订和支付服务是单独开发和部署的。同样,我们可以单独开发、部署 React 应用程序。这意味着,您的应用程序被划分为各种单独的应用程序并且易于维护,这个概念被称为微前端。
我们将看到一个使用微前端的 React 网站,我们在其中创建了三个应用程序,博客应用程序、标题应用程序和容器应用程序(它们粘合博客和标题并代表一个 Web 应用程序)。
请从我们的 GitHub 下载完整的源代码。
 
让我们创建三个react应用程序,

  • 博客(网站博客应用)
  • 标题(网站标题)
  • 容器(实际网站,我们合并博客和标题的地方)

npx create-react-app container
npx create-react-app blogs
npx create-react-app header

 
1. 博客应用程序
让我们创建常量添加博客数组,

export const arrBlogs = [
    {
      "blogID": 1,
     
"blogName": "React Navigation",
     
"blogDetail": "Hello Developers! Let's see what's new in React Navigation 6.x.",
     
"blogURL": "https://www.kpiteng.com/blogs/react-nativagation-6.x"
    },
    {
     
"blogID": 2,
     
"blogName": "Securing React Native Application",
     
"blogDetail": "Discover a ways to develop secure react native application.",
     
"blogURL": "https://www.kpiteng.com/blogs/securing-react-native-application"
    },
    {
     
"blogID": 3,
     
"blogName": "Top 10 React Tricks Every Developer Should Use",
     
"blogDetail": "Discover a set of React best coding practices, tips and tricks that should be known by all developers.",
     
"blogURL": "https://www.kpiteng.com/blogs/top-10-react-tricks-every-developer-should-use"
    }
  ] 


为博客列表编写代码,创建一个文件Blog.js:

import React, { useState, useEffect } from "react";
import {arrBlogs} from './Constant';
import {
 Link
} from
"react-router-dom";
import
"./App.css";

function App() {
 return (
   <div className=
"container mt-5">
     <div className=
"row">
     {
       arrBlogs.map((blog, index) => {
         return (
           <div className=
"col-xs-12 col-sm-12 col-md-6 col-lg-4 col-xl-4 mb-5">
             <div className=
"card">
               <Link to={{pathname: `/blogdetail/${blog.blogID}`, id: blog.blogID, item: blog}} >
                 <div class=
"card-body">
                   <h5 class=
"card-title">{`#${blog.blogID}`}</h5>
                   <p class=
"card-text">{blog.blogName}</p>
                   <p class=
"card-text">{blog.blogDetail}</p>
                 </div>
               </Link>
             </div>
           </div>
         )
       })
     }
     </div>
   </div>
 );
}

export default App;

博客位于 url.com/blogs,所以我们需要设置react-router-dom 和 history。
yarn add react-router-dom history
 
要查看博客详细信息,我们需要为 BlogDetail 编写代码,创建文件BlogDetail.js
import React, { useState, useEffect } from "react";
import {arrBlogs} from './Constant';
import
"./App.css";

function BlogDetail(props) {

 const [blogDetail, setBlogDetail] = useState({});

 useEffect(() => {
   const blogID = parseInt(props.match.params.blogid);
   const index = arrBlogs.findIndex((blog) => blog.blogID === blogID);
   if (index !== -1){
     setBlogDetail(arrBlogs[index])
   }
 }, []);

  return (
   <div className=
"container mt-5">
     <div className=
"row">
       <div className=
"card">
         {
           Object.keys(blogDetail).length > 0 && <>
           <p>{`#${blogDetail.blogID}`}</p>
           <p>{blogDetail.blogName}</p>
           <p>{blogDetail.blogDetail}</p>
           <p>{blogDetail.blogURL}</p>
           </>
         }
         {
           Object.keys(blogDetail).length === 0 &&
           <p>We're sorry, Cound't find Blog</p>
         }
       </div>
     </div>
   </div>
 );
}

export default BlogDetail;

 
最后,我们有了 Constant、Blogs 和 BlogDetail。现在让我们为博客编写代码,BlogDetail 路由。将 App.js 代码替换为以下内容,

import React, { useState, useEffect } from "react";
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import { createBrowserHistory } from
"history";
import Blogs from './Blogs';
import BlogDetail from './BlogDetail';
import
"./App.css";

const defaultHistory = createBrowserHistory();

function App({ history = defaultHistory }) {
 return (
   <Router>
     <Switch>
       <Route exact path=
"/" component={Blogs} />
       <Route exact path=
"/blogdetail/:blogid" component={BlogDetail} />
     </Switch>
   </Router>
 );
}

export default App;

现在,是时候运行应用程序了。我们可以看到博客列表,点击博客后,它会将用户重定向到博客详细信息。
 
2. 标题应用
在这里,我们简单地添加 header div 来演示。因此,让我们添加所有必需的依赖项。
yarn add react-router-dom history
我们修改 App.js 的代码:
import React from "react";
import { createBrowserHistory } from
"history";
import
"./App.css";

const defaultHistory = createBrowserHistory();

function App({ history = defaultHistory }) {
 return (
   <div>
     <p>KPITENG (Header Application)</p>
   </div>
 );
}

export default App;

现在,让我们运行应用程序,它将显示一个简单的标题。
 
3. 容器应用
现在,是时候设置我们的容器应用程序,它实际使用/合并标题和博客应用程序到我们的容器应用程序(我们的主网站)中
让我们将 react-router-dom, history 添加到容器应用程序中。之后让我们更新 App.js 的代码
import React, { useState } from "react";
import { BrowserRouter, Switch, Route } from
"react-router-dom";
import { createBrowserHistory } from
"history";
import MicroFrontend from
"./MicroFrontend";

import
"./App.css";

const defaultHistory = createBrowserHistory();

const {
 REACT_APP_HEADER_HOST: headerHost,
 REACT_APP_BLOGS_HOST: blogHost,
} = process.env;

function Header({ history }) {
 return <MicroFrontend history={history} host={headerHost} name=
"Header" />;
}

function Blogs({ history }) {
 return <MicroFrontend history={history} host={blogHost} name=
"Blogs" />;
}

function BlogDetail({history}) {
 return (
   <div>
     <MicroFrontend history={history} host={blogHost} name=
"Blogs" />
   </div>
 );
}


function Home({ history }) {

 return (
   <div className=
"container">
      <Header />
      <Blogs />
   </div>
 );
}

function App() {
 return (
   <BrowserRouter>
     <React.Fragment>
       <Switch>
         <Route exact path=
"/" component={Home} />
         <Route exact path=
"/blogdetail/:blogid" component={BlogDetail} />
       </Switch>
     </React.Fragment>
   </BrowserRouter>
 );
}

export default App;

 
设置微前端 -
想一想,我的容器应用程序是如何知道标题应用程序和博客应用程序的。让我们一一设置。
* 设置Web 应用程序端口 - *容器应用程序 - 端口 3000标题应用程序 - 端口 3001博客应用程序 - 端口 3002
为此,请更新 package.json,

容器应用:

"scripts": {
   
"start": "PORT=3000 react-app-rewired start",
 },

标题应用:
"scripts": {
   
"start": "PORT=3001 react-app-rewired start",
 },

博客应用:
"scripts": {
   
"start": "PORT=3002 react-app-rewired start",
 },

现在,在容器应用程序的根目录中创建 .env 文件,
REACT_APP_HEADER_HOST= http://localhost:3001
REACT_APP_BLOGS_HOST= http://localhost:3002

React App 将整个应用程序捆绑到 main.js,我们有渲染、挂载、卸载组件的功能。

Render Function Name: render{ApplicationName}
UnMount Function Name: unmount{ApplicationName}

所以,你的博客应用程序看起来像:
renderBlogs
unmountBlogs
同样的方式,标题应用 看起来像,
renderHeader
unmountHeader
我们在Container App中创建一个MicroFrontend.js文件,里面有挂载、卸载组件的业务逻辑。

import React, { useEffect } from "react";

function MicroFrontend({ name, host, history }) {
 useEffect(() => {
   const scriptId = `micro-frontend-script-${name}`;

   const renderMicroFrontend = () => {

     window[`render${name}`](`${name}-container`, history);
   };

   if (document.getElementById(scriptId)) {
     renderMicroFrontend();
     return;
   }

   fetch(`${host}/asset-manifest.json`)
     .then((res) => res.json())
     .then((manifest) => {
       const script = document.createElement(
"script");
       script.id = scriptId;
       script.crossOrigin =
"";
       script.src = `${host}${manifest.files[
"main.js"]}`;
       script.onload = () => {
         renderMicroFrontend();
       };
       document.head.appendChild(script);
     });

   return () => {
     window[`unmount${name}`] && window[`unmount${name}`](`${name}-container`);
   };
 });

 return <main id={`${name}-container`} />;
}

MicroFrontend.defaultProps = {
 document,
 window,
};

export default MicroFrontend;


如您所见,MicroFrontend 组件将名称、主机和历史记录作为参数。查看从主机获取资产清单.json 并使用 main.js 创建脚本对象的 fetch 函数,它将使用渲染函数来挂载组件。
 
为博客应用程序设置微前端 
让我们安装 react-app-rewired 包,它会在不弹出应用程序的情况下覆盖构建配置。
yarn add react-app-rewired

在博客应用的根目录下创建 config.overrides.js 并添加以下代码。

module.exports = {
   webpack: (config, env) => {
     config.optimization.runtimeChunk = false;
     config.optimization.splitChunks = {
       cacheGroups: {
         default: false,
       },
     };
      config.output.filename = "static/js/[name].js";
      config.plugins[5].options.filename =
"static/css/[name].css";
     config.plugins[5].options.moduleFilename = () =>
"static/css/main.css";
     return config;
   },
 };

现在,让我们更新 package.json 文件的脚本部分:

"scripts": {
   
"start": "PORT=3002 react-app-rewired start",
   
"build": "react-app-rewired build",
   
"test": "react-app-rewired test",
   
"eject": "react-app-rewired eject"
 },

以及博客应用程序中更新 index.js 的最后一步,
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

window.renderBlogs = (containerId, history) => {
 ReactDOM.render(
   <App history={history} />,
   document.getElementById(containerId),
 );
};

window.unmountBlogs = containerId => {
 ReactDOM.unmountComponentAtNode(document.getElementById(containerId));
};

if (!document.getElementById('Blogs-container')) {
 ReactDOM.render(<App />, document.getElementById('root'));
}

 
为标题应用程序设置微前端 -

  • 安装 react-app-rewired
  • 更新 package.json
  • 更新 index.js 文件

最后,运行容器应用程序(我们的主 Web 应用程序)