简体   繁体   中英

How to create micro frontends based on different technologies using a common shell with module federation and NX?

I'm trying to create micro frontends with React and Angular (the remotes) that are used within a shell application based on React (the host/shell) using Webpacks Module Federation .

Therefore I used this official documentation provided by NX --> Advanced Angular Micro Frontends with Dynamic Module Federation

Here is what I did:

1. npx create-nx-workspace pace-microfrontends
2. npm install @nrwl/react --save-dev
3. npm install @nrwl/angular --save-dev
4. npx nx g @nrwl/react:host shell
5. npx nx g @nrwl/angular:remote angular-microfrontend --host=shell
6. npx nx g @nrwl/react:remote react-microfrontend --host=shell

So I created a fresh NX workspace, added the missing dependencies, generated the shell/host application based on React and added two micro frontends (one based on React, one based on Angular) that should be hosted within the shell.

After that it was needed to add the missing routing to the Angular micro frontend within the shell, because it was not generated by NX like for the React micro frontend, like this:

apps/shell/src/app/app.tsx

import * as React from 'react';
import NxWelcome from './nx-welcome';
import { Link, Route, Routes } from 'react-router-dom';

const ReactMicrofrontend = React.lazy(
  () => import('react-microfrontend/Module')
);

const AngularMicrofrontend = React.lazy(
  () => import('angular-microfrontend/Module')
);

export function App() {
  return (
    <React.Suspense fallback={null}>
      <ul>
        <li>
          <Link to="/">Home</Link>
        </li>
        <li>
          <Link to="/react-microfrontend">ReactMicrofrontend</Link>
        </li>
        <li>
          <Link to="/angular-microfrontend">AngularMicrofrontend</Link>
        </li>
      </ul>
      <Routes>
        <Route path="/" element={<NxWelcome title="shell" />} />
        <Route path="/react-microfrontend" element={<ReactMicrofrontend />} />
        <Route
          path="/angular-microfrontend"
          element={<AngularMicrofrontend />}
        />
      </Routes>
    </React.Suspense>
  );
}

export default App;

Since the module federation configuration was not right as well (it couldn't find the remoteEntry.js file), I also had to change it from this:

apps/shell/module-federation.config.js

// @ts-check

/**
 * @type {import('@nrwl/devkit').ModuleFederationConfig}
 **/
const moduleFederationConfig = {
  name: 'shell',
  remotes: [react-microfrontend'],
};

module.exports = moduleFederationConfig;

..to this:

apps/shell/module-federation.config.js

// @ts-check

/**
 * @type {import('@nrwl/devkit').ModuleFederationConfig}
 **/
const moduleFederationConfig = {
  name: 'shell',
  remotes: [
    ['angular-microfrontend', 'http://localhost:4201/remoteEntry.mjs'],
    ['react-microfrontend', 'http://localhost:4202/remoteEntry.js'],
  ],
};

module.exports = moduleFederationConfig;

After these adjustments it was possible to start the shell application with npm start .

The routing for Home and ReactMicrofrontend works fine but as soon as I click on AngularMicrofrontend I get the following errors:

react_devtools_backend.js:4012 Warning: lazy: Expected the result of a dynamic import() call. Instead received: [object Module]

Your code should look like: const MyComponent = lazy(() => import('./MyComponent')) at RenderedRoute (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:4840:5) at Routes (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:5272:5) at Suspense at App at Router (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:5204:15) at BrowserRouter (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:3464:5) overrideMethod @ react_devtools_backend.js:4012 printWarning @ react.development.js:209 error @ react.development.js:183 lazyInitializer @ react.development.js:1400 resolveLazy @ react-dom.development.js:14907 reconcileSingleElement @ react-dom.development.js:15718 reconcileChildFibers @ react-dom.development.js:15808 reconcileChildren @ react-dom.development.js:19174 updateContextProvider @ react-dom.development.js:21154 beginWork @ react-dom.development.js:21649 beginWork$1 @ react-dom.devel opment.js:27426 performUnitOfWork @ react-dom.development.js:26557 workLoopConcurrent @ react-dom.development.js:26543 renderRootConcurrent @ react-dom.development.js:26505 performConcurrentWorkOnRoot @ react-dom.development.js:25738 workLoop @ scheduler.development.js:266 flushWork @ scheduler.development.js:239 performWorkUntilDeadline @ scheduler.development.js:533 queue. @ task.js:61 run @ task.js:35 listener @ task.js:46 react_devtools_backend.js:4012 Warning: lazy: Expected the result of a dynamic import() call. Instead received: [object Module]

Your code should look like: const MyComponent = lazy(() => import('./MyComponent')) at Lazy at RenderedRoute (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:4840:5) at Routes (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:5272:5) at Suspense at App at Router (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:5204:15) at BrowserRouter (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:3464:5) overrideMethod @ react_devtools_backend.js:4012 printWarning @ react.development.js:209 error @ react.development.js:183 lazyInitializer @ react.development.js:1400 mountLazyComponent @ react-dom.development.js:19944 beginWork @ react-dom.development.js:21593 beginWork$1 @ react-dom.development.js:27426 performUnitOfWork @ react-dom.development.js:26557 workLoopConcurrent @ react-dom.development.js:26543 renderRootConcurrent @ react-dom.development.js:26505 performConcurrentWorkOnRoot @ react-dom.development.js:25738 workLoop @ scheduler.development.js:266 flushWork @ scheduler.development.js:239 performWorkUntilDeadline @ scheduler.development.js:533 queue. @ task.js:61 run @ task.js:35 listener @ task.js:46 react_devtools_backend.js:4012 Warning: lazy: Expected the result of a dynamic import() call. Instead received: [object Module]

Your code should look like: const MyComponent = lazy(() => import('./MyComponent')) at Lazy at RenderedRoute (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:4840:5) at Routes (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:5272:5) at Suspense at App at Router (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:5204:15) at BrowserRouter (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:3464:5) overrideMethod @ react_devtools_backend.js:4012 printWarning @ react.development.js:209 error @ react.development.js:183 lazyInitializer @ react.development.js:1400 mountLazyComponent @ react-dom.development.js:19944 beginWork @ react-dom.development.js:21593 callCallback @ react-dom.development.js:4164 invokeGuardedCallbackDev @ react-dom.development.js:4213 invokeGuardedCallback @ react-dom.development.js:4277 beginWork$1 @ react-dom.development.js:27451 performUnitOfWork @ react-dom .development.js:26557 workLoopConcurrent @ react-dom.development.js:26543 renderRootConcurrent @ react-dom.development.js:26505 performConcurrentWorkOnRoot @ react-dom.development.js:25738 workLoop @ scheduler.development.js:266 flushWork @ scheduler.development.js:239 performWorkUntilDeadline @ scheduler.development.js:533 queue. @ task.js:61 run @ task.js:35 listener @ task.js:46 react-dom.development.js:20013 Uncaught Error: Element type is invalid. Received a promise that resolves to: undefined. Lazy element type must resolve to a class or function. at mountLazyComponent (react-dom.development.js:20013:1) at beginWork (react-dom.development.js:21593:1) at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1) at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:1) at invokeGuardedCallback (react-dom.development.js:4277:1) at beginWork$1 (react-dom.development.js:27451:1) at performUnitOfWork (react-dom.development.js:26557:1) at workLoopConcurrent (react-dom.development.js:26543:1) at renderRootConcurrent (react-dom.development.js:26505:1) at performConcurrentWorkOnRoot (react-dom.development.js:25738:1) mountLazyComponent @ react-dom.development.js:20013 beginWork @ react-dom.development.js:21593 callCallback @ react-dom.development.js:4164 invokeGuardedCallbackDev @ react-dom.development.js:4213 invokeGuardedCallback @ react-dom.development.js:4277 beginWork$1 @ react-dom.development.js:27451 performUnitOfWork @ react-dom.development.js:26557 workLoopConcurrent @ react-dom.development.js:26543 renderRootC oncurrent @ react-dom.development.js:26505 performConcurrentWorkOnRoot @ react-dom.development.js:25738 workLoop @ scheduler.development.js:266 flushWork @ scheduler.development.js:239 performWorkUntilDeadline @ scheduler.development.js:533 queue. @ task.js:61 run @ task.js:35 listener @ task.js:46 react_devtools_backend.js:4012 Warning: lazy: Expected the result of a dynamic import() call. Instead received: [object Module]

Your code should look like: const MyComponent = lazy(() => import('./MyComponent')) at RenderedRoute (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:4840:5) at Routes (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:5272:5) at Suspense at App at Router (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:5204:15) at BrowserRouter (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:3464:5) overrideMethod @ react_devtools_backend.js:4012 printWarning @ react.development.js:209 error @ react.development.js:183 lazyInitializer @ react.development.js:1400 resolveLazy @ react-dom.development.js:14907 reconcileSingleElement @ react-dom.development.js:15718 reconcileChildFibers @ react-dom.development.js:15808 reconcileChildren @ react-dom.development.js:19174 updateContextProvider @ react-dom.development.js:21154 beginWork @ react-dom.development.js:21649 beginWork$1 @ react-dom.devel opment.js:27426 performUnitOfWork @ react-dom.development.js:26557 workLoopSync @ react-dom.development.js:26466 renderRootSync @ react-dom.development.js:26434 recoverFromConcurrentError @ react-dom.development.js:25850 performConcurrentWorkOnRoot @ react-dom.development.js:25750 workLoop @ scheduler.development.js:266 flushWork @ scheduler.development.js:239 performWorkUntilDeadline @ scheduler.development.js:533 queue. @ task.js:61 run @ task.js:35 listener @ task.js:46 react_devtools_backend.js:4012 Warning: lazy: Expected the result of a dynamic import() call. Instead received: [object Module]

Your code should look like: const MyComponent = lazy(() => import('./MyComponent')) at Lazy at RenderedRoute (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:4840:5) at Routes (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:5272:5) at Suspense at App at Router (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:5204:15) at BrowserRouter (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:3464:5) overrideMethod @ react_devtools_backend.js:4012 printWarning @ react.development.js:209 error @ react.development.js:183 lazyInitializer @ react.development.js:1400 mountLazyComponent @ react-dom.development.js:19944 beginWork @ react-dom.development.js:21593 beginWork$1 @ react-dom.development.js:27426 performUnitOfWork @ react-dom.development.js:26557 workLoopSync @ react-dom.development.js:26466 renderRootSync @ react-dom.development.js:26434 recoverFromConcurrentError @ react-dom.d evelopment.js:25850 performConcurrentWorkOnRoot @ react-dom.development.js:25750 workLoop @ scheduler.development.js:266 flushWork @ scheduler.development.js:239 performWorkUntilDeadline @ scheduler.development.js:533 queue. @ task.js:61 run @ task.js:35 listener @ task.js:46 react_devtools_backend.js:4012 Warning: lazy: Expected the result of a dynamic import() call. Instead received: [object Module]

Your code should look like: const MyComponent = lazy(() => import('./MyComponent')) at Lazy at RenderedRoute (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:4840:5) at Routes (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:5272:5) at Suspense at App at Router (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:5204:15) at BrowserRouter (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:3464:5) overrideMethod @ react_devtools_backend.js:4012 printWarning @ react.development.js:209 error @ react.development.js:183 lazyInitializer @ react.development.js:1400 mountLazyComponent @ react-dom.development.js:19944 beginWork @ react-dom.development.js:21593 callCallback @ react-dom.development.js:4164 invokeGuardedCallbackDev @ react-dom.development.js:4213 invokeGuardedCallback @ react-dom.development.js:4277 beginWork$1 @ react-dom.development.js:27451 performUnitOfWork @ react-dom .development.js:26557 workLoopSync @ react-dom.development.js:26466 renderRootSync @ react-dom.development.js:26434 recoverFromConcurrentError @ react-dom.development.js:25850 performConcurrentWorkOnRoot @ react-dom.development.js:25750 workLoop @ scheduler.development.js:266 flushWork @ scheduler.development.js:239 performWorkUntilDeadline @ scheduler.development.js:533 queue. @ task.js:61 run @ task.js:35 listener @ task.js:46 react-dom.development.js:20013 Uncaught Error: Element type is invalid. Received a promise that resolves to: undefined. Lazy element type must resolve to a class or function. at mountLazyComponent (react-dom.development.js:20013:1) at beginWork (react-dom.development.js:21593:1) at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1) at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:1) at invokeGuardedCallback (react-dom.development.js:4277:1) at beginWork$1 (react-dom.development.js:27451:1) at performUnitOfWork (react-dom.development.js:26557:1) at workLoopSync (react-dom.development.js:26466:1) at renderRootSync (react-dom.development.js:26434:1) at recoverFromConcurrentError (react-dom.development.js:25850:1) mountLazyComponent @ react-dom.development.js:20013 beginWork @ react-dom.development.js:21593 callCallback @ react-dom.development.js:4164 invokeGuardedCallbackDev @ react-dom.development.js:4213 invokeGuardedCallback @ react-dom.development.js:4277 beginWork$1 @ react-dom.development.js:27451 performUnitOfWork @ react-dom.development.js:26557 workLoopSync @ react-dom.development.js:26466 renderRootSync @ react-dom.development.js:26434 recoverFromConcurrentError @ react-dom.development.js:25850 performConcurrentWorkOnRoot @ react-dom.development.js:25750 workLoop @ scheduler.development.js:266 flushWork @ scheduler.development.js:239 performWorkUntilDeadline @ scheduler.development.js:533 queue. @ task.js:61 run @ task.js:35 listener @ task.js:46 react_devtools_backend.js:4012 The above error occurred in the <Route.Provider> component:

 at RenderedRoute (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:4840:5) at Routes (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:5272:5) at Suspense at App at Router (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:5204:15) at BrowserRouter (http://localhost:4200/vendors-node_modules_react-router-dom_dist_index_js.js:3464:5)

Consider adding an error boundary to your tree to customize error handling behavior. Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries. overrideMethod @ react_devtools_backend.js:4012 logCapturedError @ react-dom.development.js:18687 update.callback @ react-dom.development.js:18720 callCallback @ react-dom.development.js:13923 commitUpdateQueue @ react-dom.development.js:13944 commitLayoutEffectOnFiber @ react-dom.development.js:23391 commitLayoutMountEffects_complete @ react-dom.development.js:24688 commitLayoutEffects_begin @ react-dom.development.js:24674 commitLayoutEffects @ react-dom.development.js:24612 commitRootImpl @ react-dom.development.js:26823 commitRoot @ react-dom.development.js:26682 finishConcurrentRender @ react-dom.development.js:25892 performConcurrentWorkOnRoot @ react-dom.development.js:25809 workLoop @ scheduler.development.js:266 flushWork @ scheduler.development.js:239 performWorkUntilDeadline @ scheduler.development.js:533 queue. @ task.js:61 run @ task.js:35 listener @ task.js:46 react-dom.development.js:26923 Uncaught TypeError: Cannot read properties of undefined (reading 'displayName') at getDisplayName (react_devtools_backend.js:261:19) at getDisplayNameForFiber (react_devtools_backend.js:6381:55) at Object.markComponentErrored (react_devtools_backend.js:5615:29) at markComponentErrored (react-dom.development.js:5053:1) at handleError (react-dom.development.js:26307:1) at renderRootSync (react-dom.development.js:26437:1) at recoverFromConcurrentError (react-dom.development.js:25850:1) at performConcurrentWorkOnRoot (react-dom.development.js:25750:1) at workLoop (scheduler.development.js:266:1) at flushWork (scheduler.development.js:239:1)

What am I doing wrong or what's missing here? Is there an example how to use this documentation to combine React and Angular? If you do the same steps with only React or only Angular it works fine..

Hope someone can help..

Angular Micro Frontend: 在此处输入图像描述

Shell Application: 在此处输入图像描述

React Micro Frontend在此处输入图像描述

I do not exactly know what is wrong on your configuration, but to use different technologies together with module-federation please take a look here:

https://www.angulararchitects.io/aktuelles/multi-framework-and-version-micro-frontends-with-module-federation-your-4-steps-guide/

There is a very good example how to combine different technologies together. I hope that helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM