0

I am trying to implement a importing a react app, built using CRACO, into a nextjs app. I have tried multiple different implementations, but cannot get the import to work as a module import. I have pulled in https://www.npmjs.com/package/@module-federation/nextjs-mf and https://www.npmjs.com/package/@module-federation/utilities. Trying all example implementations. I am able to import my component dynamically using either next/dynamic or importRemote, however loading dynamically prevents me from using the full suite of functionality.

All of this aside, the biggest issue is that when I import dynamically the next/router back functionality breaks.

I pull in the component and it loads. Underneath the module federated component I am rendering a link, upon click the link the nextjs app correctly updates the browser history and updates the DOM, directing me to a new page. If i click the back button, or add a button on my new page that mimics the back functionality, I can see the browser history update but the DOM does not update.

I feel like this has something to do with how I am importing but I cannot figure out how to resolve this issue.

I am running an nextJS host which is pulling in a react component via module federation.

Next v12.0.7 React v17.0.2

I am stuck on these dependencies due to downstream internal projects which have not updated to latest react.

Remote: The exposed remote function builds a div and renders the root of the micro-frontend header:

initialize.tsx

import * as ReactDOM from 'react-dom';
import './index.scss';
import TopNav from './TopNav';
const initialize = () => { 
  var headerContainer = document.createElement('div');
  headerContainer.id = 'header';
  document.body.prepend(headerContainer);
  const root = document.getElementById('header');
  ReactDOM.render(<TopNav />, root); 
  return null; 
}; 
export default initialize; 

modulefederation.config.js

const deps = require('./package.json').dependencies;
const federationConfig = require('./federation.config.json');

module.exports = {
 ...federationConfig, 
 name: 'test', 
 filename: 'remoteEntry.js', 
 shared: { 
  ...deps,
  react: { 
   singleton: true, 
   requiredVersion: false, 
  },
  'react-dom': { 
   singleton: true, 
   requiredVersion: false, 
  }, 
 }, 
}; 

Host: I have tried multiple ways of using module federation within Nextjs. From dynamic remotes to delegate remotes. These implementations all throw errors when I try to use a normal import.

different ways I tried to get remotes

~Delegates~

remote-delegate.js:

import { importDelegatedModule } from '@module-federation/utilities';

module.exports = new Promise((resolve, reject) => { 
 console.log('Delegate being called for', __resourceQuery);
 const currentRequest = new URLSearchParams(__resourceQuery).get('remote');
 const [global, url] = currentRequest.split('@'); 
 importDelegatedModule({ global, url, })
  .then((container) => { resolve(container); })
  .catch((err) => reject(err)); 
}); 

next.config.js:

const remotes = { 
 header: 'promise "./remote-delegate.js?remote=consoleui_header@https://localhost:8080/remoteEntry.js"',
};
 
webpack: (config, options) => { 
  return { 
   plugins: [ 
    ...config?.plugins,
    new NextFederationPlugin({ 
     name: 'host',
     remotes,
     filename: `static/${location}/remoteEntry.js`, 
    }), 
   ], 
  }; 
 }, 
};

~Dynamic Promise~

next.config.js:

const remotes = { 
 header: 'promise "./remote-delegate.js?remote=consoleui_header@https://localhost:8080/remoteEntry.js"',
};
 
webpack: (config, options) => { 
  return { 
   plugins: [ 
    ...config?.plugins,
    new NextFederationPlugin({ 
     name: 'host',
         remotes: {
            header: `promise new Promise(resolve => {
              const remoteUrlWithVersion = 'https://localhost:8080/remoteEntry.js'
              const script = document.createElement('script')
              script.src = remoteUrlWithVersion
              script.onload = () => {
                // the injected script has loaded and is available on window
                // we can now resolve this Promise
                const proxy = {
                  get: (request) => window.app1.get(request),
                  init: (arg) => {
                    try {
                      return window.app1.init(arg)
                    } catch(e) {
                      console.log('remote container already initialized')
                    }
                  }
                }
                resolve(proxy)
              }
              // inject this script with the src set to the versioned remoteEntry.js
              document.head.appendChild(script);
            })
            `,
          },
     filename: `static/${location}/remoteEntry.js`, 
    }), 
   ], 
  }; 
 }, 
};

Neither of these implementations worked as I'd like. I couldn't import like a normal module and had to fall back to dynamic importing.

~Dynamic Imports~ This is the best implementation that allows me to access the most from my module, however I still can only access it dynamically and this is where the back button shows up. However I have tested the back button with both Dynamic Promise and Delegates and ran into the same issue.

next.config.js

  webpack: (config, options) => {
    return {
      plugins: [
        ...config?.plugins,
        new NextFederationPlugin({
          name: 'host',
          remotes: {
            header: `consoleui_header@https://localhost:8080/remoteEntry.js`,
          },
          filename: `static/${location}/remoteEntry.js`,
        }),
      ],
    };
  },

_app.tsx

import dynamic from "next/dynamic";

const Initialize = dynamic(() => import ('header/initialize').then(mod => mod.default()),{
  ssr: false,
}) 

function MyApp({Component, pageProps }: any {
  return <>
      <Initialize />
    {!isLoading && 

      <Component {...pageProps} />
    }
  </>
}

/test/index.tsx

import Link from 'next/link'
import React from 'react'

const list = () => (
    <Link href={"test/detail?id=123"} > list page</Link>
)

export default list

/test/detail.tsx

import Link from 'next/link'
import React from 'react'

const detail = () => (
    <Link href={"test/detail?id=123"} > details page</Link>
)

export default detail

In this example if I remove <Initialize /> from the return of my app, the back button works as expected.

0 Answers0