Skip to content

Dynamic Remote Modules #7

@nvonbenken

Description

@nvonbenken

How would you go about using this solution with dynamic remote modules?

I need to configure dynamic remote modules to handle deploying to different environments. Most implementations I've seen pull the remote URLs from either a static manifest or an API and import them using React.lazy. That doesn't play nicely with the use of the mount function here.

I've tried something like this but it doesn't seem to work properly. Any suggestions?

Remote app bootstrap.tsx:

import { useRef, useEffect } from 'react';
import { createRoot } from 'react-dom/client';
import { RouterProvider, useLocation, useNavigate } from 'react-router-dom';

import { createRouter } from './app/routing/router-factory';
import { RoutingStrategy } from './app/routing/types';

const mount = ({
  mountPoint,
  initialPathname,
  routingStrategy,
}: {
  mountPoint: HTMLDivElement;
  initialPathname?: string;
  routingStrategy?: RoutingStrategy;
}) => {
  const router = createRouter({ strategy: routingStrategy, initialPathname });
  const root = createRoot(mountPoint);
  root.render(<RouterProvider router={router} />);

  return () => queueMicrotask(() => root.unmount());
};

const RemoteApp1 = ({ initialPathname }: any) => {
  const wrapperRef = useRef<HTMLDivElement>(null);
  const navigate = useNavigate();
  const location = useLocation();

  useEffect(() => {
    const remoteAppNavigationEventHandler = (event: Event) => {
      const pathname = (event as CustomEvent<string>).detail;
      const newPathname = `${initialPathname}${pathname}`;
      if (newPathname === location.pathname) {
        return;
      }
      navigate(newPathname);
    };
    window.addEventListener(
      '[RA1] navigated',
      remoteAppNavigationEventHandler 
    );

    return () => {
      window.removeEventListener(
        '[RA1] navigated',
        remoteAppNavigationEventHandler 
      );
    };
  }, [location]);

  useEffect(() => {
    if (location.pathname.startsWith(initialPathname)) {
      window.dispatchEvent(
        new CustomEvent('[host] navigated', {
          detail: location.pathname.replace(initialPathname, ''),
        })
      );
    }
  }, [location]);

  const isFirstRunRef = useRef(true);
  const unmountRef = useRef(() => {});

  useEffect(() => {
    if (!isFirstRunRef.current) {
      return;
    }
    unmountRef.current = mount({
      mountPoint: wrapperRef.current!,
      initialPathname: location.pathname.replace(initialPathname, ''),
    });
    isFirstRunRef.current = false;
  }, [location]);

  useEffect(() => unmountRef.current, []);

  return <div ref={wrapperRef} id="remote-app-1" />;
};

export default RemoteApp1;

Host component RemoteApp1.tsx:

import { loadRemoteModule } from './load-remote-module';
import { lazy, Suspense } from 'react';

import { REMOTE_APP_1_ROUTING_PREFIX } from '../routing/constants';

const remoteApp1Basename= `/${REMOTE_APP_1_ROUTING_PREFIX}`;

const RemoteApp1Module = lazy(() =>
  loadRemoteModule('remote-app-1', './Module')
);

const RemoteApp1 = () => {
  return (
    <Suspense>
      <RemoteApp1Module initialPathname={remoteApp1Basename} />
    </Suspense>
  );
};

export default RemoteApp1 ;

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions