Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[nextjs-mf v7] SWC error: Cannot read properties of undefined (reading 'call') #1102

Closed
yspychala opened this issue Jul 3, 2023 · 50 comments
Assignees
Labels
help wanted Extra attention is needed

Comments

@yspychala
Copy link
Contributor

yspychala commented Jul 3, 2023

As discussed a few days ago on Twitter with @ScriptedAlchemy, an error happens when using the nextjs-mf plugin.

Sometimes it works when accessing directly the host (http://localhost:3001), but then if I access the remote in the browser (http://localhost:3000), error appears in host. It’s really erratic and I don’t understand what’s going on here.

One thing is sure, the SWC compiler is doing some weird stuff because if I switch to Babel in the remote it works correctly.

SWC may transform either the remoteEntry.js or the exposed module (or both?). And there’s no way to tell Next to not doing this right now.

Error

TypeError: Cannot read properties of undefined (reading 'call')

at options.factory (host/.next/static/chunks/remoteEntry.js (780:31))

Context

  • nextjs-mf v7.0.0 without delegated modules
  • react v18.2.0
  • next v13.4.8 with SWC compiler (enabled by default today in Next.js) and /pages dir
  • uses browserslist queries in remote ("cover 90% in FR", "last 3 versions", "not dead", "maintained node versions")
  • either with import from or React Lazy + React Suspense
  • mac OS (M1) / node.js v18.16.0

How to reproduce

Apps were generated with Next CLI, so it’s really a basic config.

  1. Clone this repo: https://github.com/yspychala/nextjsmf-v7-swc-debug
  2. npm i in /host and /remote directories
  3. npm run dev in /host and /remote
  4. Go to http://localhost:3000 (remote) and http://localhost:3001 (host)
  5. See error in host

If we turn off SWC if favor of Babel in remote, it works again:

// Create .babelrc in /remote and restart servers
{
  "presets": ["next/babel"]
}
@RexGalicie
Copy link

RexGalicie commented Jul 3, 2023

hey @ScriptedAlchemy, faced similar issue
Basically at v6.7.1 all work as expected without any errors, soon when version updated to v7.0.0 this issues upper.

Cannot read properties of undefined (reading 'call')
TypeError: Cannot read properties of undefined (reading 'call') at options.factory (http://localhost/_next/static/chunks/remoteEntry.js?1688420058928:799:31) at __webpack_require__ (http://localhost/_next/static/chunks/remoteEntry.js?1688420058928:106:33) at fn (http://localhost/_next/static/chunks/remoteEntry.js?1688420058928:439:21)

Builded code

/******/ 	/* webpack/runtime/react refresh */
  /******/ 	!function() {
  /******/ 		if (__webpack_require__.i) {
  /******/ 		__webpack_require__.i.push(function(options) {
  /******/ 			var originalFactory = options.factory;
  /******/ 			options.factory = function(moduleObject, moduleExports, webpackRequire) {
  /******/ 				var hasRefresh = typeof self !== "undefined" && !!self.$RefreshInterceptModuleExecution$;
  /******/ 				var cleanup = hasRefresh ? self.$RefreshInterceptModuleExecution$(moduleObject.id) : function() {};
  /******/ 				try {
  /******/ 				->	originalFactory.call(this, moduleObject, moduleExports, webpackRequire);    <-
  /******/ 				} finally {
  /******/ 					cleanup();
  /******/ 				}
  /******/ 			}
  /******/ 		})
  /******/ 		}
  /******/ 	}();
  /******/
  /******/ 	/* webpack/runtime/compat */
  /******/

@yspychala
Copy link
Contributor Author

@RexGalicie Is your app using SWC as well?

@RexGalicie
Copy link

@yspychala in my case seems both, but it is on some big project, I did create demo test case, and can not replicate it
let me do more test later today, btw with v6.7.1 all good with or without SWC

@ScriptedAlchemy
Copy link
Member

does anyone have a repo that can somewhat reproduce this that i can work against? Otherwise we need a zoom call or something, i need to be able to see the error happen in realtime and poke around to see what module id it wants thats not there

@ScriptedAlchemy ScriptedAlchemy added the help wanted Extra attention is needed label Jul 5, 2023
@ScriptedAlchemy
Copy link
Member

to confirm, the hosts own remote is not being loaded in this application right?

like 3000's remoteEntry file is not loaded, on http:localhost:3000, only 3001 is loaded?

@MarcusNotheis
Copy link
Contributor

Unfortunately I haven't figured out a minimal reproducible sample yet, but my setup looks like the following:

  1. localhost:3000 NextJS with Module Federation, delegated modules to :3001
  2. localhost:3001: NextJS with Module Federation, delegated modules to :3002
  3. localhost:3002: create-react-app with standard module federation plugin

In this scenario, the remoteEntry.js of :3001 is crashing while trying to resolve these two modules:

  • "./node_modules/@swc/helpers/esm/_class_call_check.js"
  • "./node_modules/@swc/helpers/esm/_async_to_generator.js"

Screenshot 2023-07-05 at 08 33 31

As soon as I have a reproducible repo I'll post it here.

@yspychala
Copy link
Contributor Author

@ScriptedAlchemy here’s a repo 👉 https://github.com/yspychala/nextjsmf-v7-swc-debug (check out the “How to reproduce” section of my original post)

And I can confirm that only the remote’s remoteEntry is loaded on host, not the host one.

@ScriptedAlchemy
Copy link
Member

thanks, ill take a peek soon as possible

@ScriptedAlchemy ScriptedAlchemy self-assigned this Jul 6, 2023
@beratbayram
Copy link

beratbayram commented Jul 6, 2023

I don't know whether it is related but I am getting this after the update to nextjs-mf-7.0.2:

error ../../node_modules/@module-federation/nextjs-mf/src/default-delegate.js?remote=banka@http://localhost:3003/_next/static/chunks/remoteEntry.js
Cannot read properties of undefined (reading 'updateHash')
warn Fast Refresh had to perform a full reload due to a runtime error.

@RexGalicie
Copy link

RexGalicie commented Jul 6, 2023

hey @ScriptedAlchemy
FYI
I've added logs just to see what provide that error
../../node_modules/.pnpm/@swc+helpers@0.5.1/node_m…ules/@swc/helpers/esm/_interop_require_default.js

Open image to see full trace

Basically with catch I manage to see remote seems all okay, only errors at console, but functionality fine same as with v6.7.1

/******/ 	/* webpack/runtime/react refresh */
/******/ 	!function() {
/******/ 		if (__webpack_require__.i) {
/******/ 		__webpack_require__.i.push(function(options) {
/******/ 			var originalFactory = options.factory;
/******/ 			options.factory = function(moduleObject, moduleExports, webpackRequire) {
/******/ 				var hasRefresh = typeof self !== "undefined" && !!self.$RefreshInterceptModuleExecution$;
/******/ 				var cleanup = hasRefresh ? self.$RefreshInterceptModuleExecution$(moduleObject.id) : function() {};
/******/ 				try {
/******/ 					originalFactory.call(this, moduleObject, moduleExports, webpackRequire);
/******/ 				}catch(e) {
                  console.error(e)
                  console.log(originalFactory, options)
                }finally {
/******/ 					cleanup();
/******/ 				}
/******/ 			}
/******/ 		})
/******/ 		}
/******/ 	}();
/******/
/******/ 	/* webpack/runtime/compat */

@prakashmallow
Copy link

@ScriptedAlchemy I'm also facing the same issue in the child app remote with the production domain to build Nextjs app in mf v6.2.2

@yspychala
Copy link
Contributor Author

yspychala commented Jul 7, 2023

Ok so I worked on the most straightforward way to reproduce the issue, besides the example I gave before.

It’s easy to see the bug directly in the nextjs-ssr Module Federation example: https://github.com/module-federation/module-federation-examples/tree/master/nextjs-ssr

  • Update each app’s package.json to add a browserslist query: "browserslist": ["last 3 versions"] (also works with a .browserslistrc file)
  • yarn start
  • 💥

Please note this doesn’t mean necessarily the bug appears only with this, but it’s a way to reproduce it.

image

@ScriptedAlchemy
Copy link
Member

@yspychala excellent, thank you - ill take a look hopefully this evening.
@beratbayram i was helping a user debug this morning and we were able to incrementally get updateHash error. Not sure if related either, but they also have call of undefined - but remoteEntry never loaded and webpack crash immediately. Ill investigate if i can reproduce on a public repo.

If you have issues - stick on v6 so you are not blocked. This was a major rewrite and sadly testing can only flush out so many use cases, few users use beta but we had a few weeks of no issues reported. So the only way to flush out additional cases was to publish v7 and get more user feedback.

@benmarch
Copy link

benmarch commented Jul 12, 2023

@yspychala try adding next/image to your shared dependencies in both host and remote (it's referenced in the full stack trace you posted):

{
  // ...,
  shared: {
    'next/image': {
      eager: false,
      requiredVersion: false,
      singleton: false,
      import: undefined,
    },
    // ...
  } 
}

@yspychala
Copy link
Contributor Author

@benmarch By the time you posted your message, I found out on my side next/image was missing in the default shared scope 😅
It solves part of the issue actually (maybe we can open a PR?). But I still have the error for a remote module where the classnames dependency seems to be an issue.

And I was able to reproduce the error in two different projects where none of those imports are used in a remote module (next/image and classnames).

@benmarch
Copy link

Awesome, @yspychala, I'm glad you were able to fix it (at least that part of it)!

Yeah I worked with Zack earlier this week on it, he's working on an update that should fix this along with other related issues that trigger the same error message. I can provide a temporary workaround in a few minutes; it was enough to unblock me, but it's a bit messy at the moment.

@benmarch
Copy link

@yspychala here's the workaround:

Create a file called delegate-module.js somewhere near your next/webpack config and paste the following (it's just copied from the plugin source code but removes imports):

`delegate-module.js`
const extractUrlAndGlobal = (urlAndGlobal) => {
  const index = urlAndGlobal.indexOf('@');
  if (index <= 0 || index === urlAndGlobal.length - 1) {
    throw new Error(`Invalid request "${urlAndGlobal}"`);
  }
  return [urlAndGlobal.substring(index + 1), urlAndGlobal.substring(0, index)];
};

const remoteVars = process.env['REMOTES'] || {}

const getRuntimeRemotes = () => {
  try {
    const runtimeRemotes = Object.entries(remoteVars).reduce(function (
      acc,
      item
    ) {
      const [key, value] = item;
      // if its an object with a thenable (eagerly executing function)
      if (typeof value === 'object' && typeof value.then === 'function') {
        acc[key] = { asyncContainer: value };
      }
      // if its a function that must be called (lazily executing function)
      else if (typeof value === 'function') {
        // @ts-ignore
        acc[key] = { asyncContainer: value };
      }
      // if its a delegate module, skip it
      else if (typeof value === 'string' && value.startsWith('internal ')) {
        const [request, query] = value.replace('internal ', '').split('?');
        if (query) {
          const remoteSyntax = new URLSearchParams(query).get('remote');
          if (remoteSyntax) {
            const [url, global] = extractUrlAndGlobal(remoteSyntax);
            acc[key] = { global, url };
          }
        }
      }
      // if its just a string (global@url)
      else if (typeof value === 'string') {
        const [url, global] = extractUrlAndGlobal(value);
        acc[key] = { global, url };
      }
      // we dont know or currently support this type
      else {
        //@ts-ignore
        console.warn('remotes process', process.env.REMOTES);
        throw new Error(
          `[mf] Invalid value received for runtime_remote "${key}"`
        );
      }
      return acc;
    },
    {});

    return runtimeRemotes;
  } catch (err) {
    console.warn('Unable to retrieve runtime remotes: ', err);
  }

  return {};
};


const loadScript = (keyOrRuntimeRemoteItem) => {
  const runtimeRemotes = getRuntimeRemotes();

  // 1) Load remote container if needed
  let asyncContainer;
  const reference =
    typeof keyOrRuntimeRemoteItem === 'string'
      ? runtimeRemotes[keyOrRuntimeRemoteItem]
      : keyOrRuntimeRemoteItem;

  if (reference.asyncContainer) {
    asyncContainer =
      typeof reference.asyncContainer.then === 'function'
        ? reference.asyncContainer
        : // @ts-ignore
          reference.asyncContainer();
  } else {
    // This casting is just to satisfy typescript,
    // In reality remoteGlobal will always be a string;
    const remoteGlobal = reference.global

    // Check if theres an override for container key if not use remote global
    const containerKey = reference.uniqueKey
      ? (reference.uniqueKey)
      : remoteGlobal;

    const __webpack_error__ = new Error()

    // @ts-ignore
    if (!globalThis.__remote_scope__) {
      // create a global scope for container, similar to how remotes are set on window in the browser
      // @ts-ignore
      globalThis.__remote_scope__ = {
        // @ts-ignore
        _config: {},
      };
    }
    // @ts-ignore
    const globalScope =
      // @ts-ignore
      typeof window !== 'undefined' ? window : globalThis.__remote_scope__;

    if (typeof window === 'undefined') {
      globalScope['_config'][containerKey] = reference.url;
    } else {
      // to match promise template system, can be removed once promise template is gone
      if (!globalScope['remoteLoading']) {
        globalScope['remoteLoading'] = {};
      }
      if (globalScope['remoteLoading'][containerKey]) {
        return globalScope['remoteLoading'][containerKey];
      }
    }
    // @ts-ignore
    asyncContainer = new Promise(function (resolve, reject) {
      function resolveRemoteGlobal() {
        const asyncContainer = globalScope[
          remoteGlobal
        ];
        return resolve(asyncContainer);
      }

      if (typeof globalScope[remoteGlobal] !== 'undefined') {
        return resolveRemoteGlobal();
      }

      (__webpack_require__).l(
        reference.url,
        function (event) {
          if (typeof globalScope[remoteGlobal] !== 'undefined') {
            return resolveRemoteGlobal();
          }

          const errorType =
            event && (event.type === 'load' ? 'missing' : event.type);
          const realSrc =
            event && event.target && (event.target).src;

          __webpack_error__.message =
            'Loading script failed.\n(' +
            errorType +
            ': ' +
            realSrc +
            ' or global var ' +
            remoteGlobal +
            ')';

          __webpack_error__.name = 'ScriptExternalLoadError';
          __webpack_error__.type = errorType;
          __webpack_error__.request = realSrc;

          reject(__webpack_error__);
        },
        containerKey
      );
    }).catch(function (err) {
      console.error('container is offline, returning fake remote');
      console.error(err);

      return {
        fake: true,
        // @ts-ignore
        get: (arg) => {
          console.warn('faking', arg, 'module on, its offline');

          return Promise.resolve(() => {
            return {
              __esModule: true,
              default: () => {
                return null;
              },
            };
          });
        },
        //eslint-disable-next-line
        init: () => {},
      };
    });
    if (typeof window !== 'undefined') {
      globalScope['remoteLoading'][containerKey] = asyncContainer;
    }
  }

  return asyncContainer;
};


const importDelegatedModule = async (
  keyOrRuntimeRemoteItem
) => {
  // @ts-ignore
  return loadScript(keyOrRuntimeRemoteItem)
    .then((asyncContainer) => {
      return asyncContainer;
    })
    .then((asyncContainer) => {
      // most of this is only needed because of legacy promise based implementation
      // can remove proxies once we remove promise based implementations
      if (typeof window === 'undefined') {
        if (!Object.hasOwnProperty.call(keyOrRuntimeRemoteItem, 'global')) {
          return asyncContainer;
        }

        // return asyncContainer;

        //TODO: need to solve chunk flushing with delegated modules
        return {
          get: function (arg) {
            //@ts-ignore
            return asyncContainer.get(arg).then((f) => {
              const m = f();
              const result = {
                __esModule: m.__esModule,
              };
              for (const prop in m) {
                if (typeof m[prop] === 'function') {
                  Object.defineProperty(result, prop, {
                    get: function () {
                      return function () {
                        //@ts-ignore
                        if (globalThis.usedChunks)
                          //@ts-ignore
                          globalThis.usedChunks.add(
                            //@ts-ignore
                            `${keyOrRuntimeRemoteItem.global}->${arg}`
                          );
                        //eslint-disable-next-line prefer-rest-params
                        return m[prop](...arguments);
                      };
                    },
                    enumerable: true,
                  });
                } else {
                  Object.defineProperty(result, prop, {
                    get: () => {
                      //@ts-ignore
                      if (globalThis.usedChunks)
                        //@ts-ignore
                        globalThis.usedChunks.add(
                          //@ts-ignore
                          `${keyOrRuntimeRemoteItem.global}->${arg}`
                        );

                      return m[prop];
                    },
                    enumerable: true,
                  });
                }
              }

              if (m.then) {
                return Promise.resolve(() => result);
              }

              return () => result;
            });
          },
          init: asyncContainer.init,
        };
      } else {
        return asyncContainer;
      }
    });
};



// eslint-disable-next-line no-async-promise-executor
module.exports = new Promise(async (resolve, reject) => {
  // eslint-disable-next-line no-undef
  const currentRequest = new URLSearchParams(__resourceQuery).get('remote');
  // @ts-ignore
  const [global, url] = currentRequest.split('@');
  importDelegatedModule({
    global,
    url: url + '?' + Date.now(),
  })
    // @ts-ignore
    .then((remote) => {
      resolve(remote);
    })
    // @ts-ignore
    .catch((err) => reject(err));
});

Then update your remotes config like this:

// next.config.js

const federationConfig = {
  // ...
  remotes: {
    remoteApp: `internal ${require.resolve('./relative/path/to/delegate-module.js')}?remote=remoteApp@http://localhost:3001/_next/static/${isServer ? 'ssr' : 'chunks'}/remoteEntry.js`
  }
}

This should fix the remainder of the errors, but let me know if it still pops up, i might have some other ideas.

@beratbayram
Copy link

beratbayram commented Jul 13, 2023

@ScriptedAlchemy first of all thank you for your huge efforts for this project. Idk if it helps but I have managed to clear all errors simply by not using SSR (which is not critical for my project). My current workaround using useEffect + importRemote:

useMFE.ts
import { importRemote } from '@module-federation/utilities';
import React from 'react';
import { useState, useEffect } from 'react';

function calcUrlRoute() {
  const isServer = typeof window === 'undefined';
  return isServer ? 'ssr' : 'chunks';
}

function Loading() {
  return <div>Loading...</div>;
}

type MFE = {
  default: React.JSX.Element;
}

export function useMFE(name: string, port: number) {
  const [mfe, setMfe] = useState(() => Loading);

  useEffect(() => {
    (async () => {
      const remoteMfe: MFE = await importRemote({
        url: `http://localhost:${port}/_next/static/${calcUrlRoute()}`,
        scope: name,
        module: '.',
      });
      setMfe(() => remoteMfe.default);
    })();
  });

  return mfe;
}
root > index.tsx
import { Suspense } from 'react';
import { useMFE } from '../../useMFE';

const Loading = ({ name }) => <div>Loading {name}...</div>;

export function Index() {
  const Banka = useMFE('banka', 3003);
  const Anasayfa = useMFE('anasayfa', 3001);
  const UstMenu = useMFE('ustMenu', 3002);

  return (
    <div className={styles.root}>
        <Suspense fallback={<Loading name={'Banka'} />}>
            <Banka />
        </Suspense>
        <Suspense fallback={<Loading name={'Üst Menü'} />}>
            <UstMenu />
        </Suspense>
        <Suspense fallback={<Loading name={'Anasayfa'} />}>
            <Anasayfa />
        </Suspense>
    </div>
  );
}

export default Index;

Now I am getting "Shared module next/router doesn't exist in shared scope default" error when I try to access a route in an MFE like http://localhost:3002/foo. And I havent tried that from root

Note: I feel like my issue might not be related to current thread, so I can open a new one if you want.

@yspychala
Copy link
Contributor Author

yspychala commented Jul 13, 2023

@benmarch thank you, I just tried but unfortunately it doesn’t fix the issue. It fails just by importing a component that uses rest props, like this:

const Component = ({ prop1, prop2, ...rest }) => {
   return <div {...rest}>hey</div>
}

Without the rest props it’s ok.

@bflopez
Copy link

bflopez commented Jul 13, 2023

Not sure if this was mentioned anywhere in this thread but for me this only happens when I go directly to a route with a federated module. If I load into the host app where there is no remoteEntry and then navigate to a page with module federation, the issue doesn't happen. Once it crashes though, nothing loads.

EDIT: Looks like once a federated module has been loaded, any type of hard refreshing crashes the app.

@ScriptedAlchemy
Copy link
Member

okay i think i have found a solution to this: #1149

@ScriptedAlchemy
Copy link
Member

next/image resolved in #1149 as well.

@simeon-raykov
Copy link

@ScriptedAlchemy The new release fixed the problem for me but if I try to switch imports with React.lazy, in order to get rid of the hydration error, it fails with the previous error "Cannot read properties of undefined (reading 'call')"

@yspychala
Copy link
Contributor Author

I’m sorry but I’m still facing this issue with the latest release. I’m able to reproduce it in the nextjs-ssr example with the latest release and with a browserslist query as described above, for example:

// .browserslistrc
last 2 versions, not dead, > 0.2%

or

// .browserslistrc
> 0.5%, last 2 versions, Firefox ESR, not dead

Also I can confirm what @simeon-raykov said about React.lazy.

@RexGalicie
Copy link

RexGalicie commented Jul 19, 2023

@ScriptedAlchemy
Thank you for update.
I did update MF to v7.0.6, seems this error "Cannot read properties of undefined (reading 'call')" fixed
but faced other issue with next build, dev mode works perfectly fine.

       - info Skipping linting
       - info Checking validity of types...
       - info Creating an optimized production build...
       (node:65053) [DEP_WEBPACK_MODULE_ID] DeprecationWarning: Module.id: Use new ChunkGraph API
       (Use `node --trace-deprecation ...` to show where the warning was created)
       Failed to compile.
       
       ../../node_modules/.pnpm/@module-federation+utilities@2.0.4_next@13.4.10_react-dom@18.2.0_react@18.2.0_webpack@5.88.1/node_modules/@module-federation/utilities/src/utils/importDelegatedModule.js
       Self-reference dependency has unused export name: This should not happen
       
       Import trace for requested module:
       ../../node_modules/.pnpm/@module-federation+utilities@2.0.4_next@13.4.10_react-dom@18.2.0_react@18.2.0_webpack@5.88.1/node_modules/@module-federation/utilities/src/utils/importDelegatedModule.js
       ../../node_modules/.pnpm/@module-federation+nextjs-mf@7.0.6_next@13.4.10_react-dom@18.2.0_react@18.2.0_webpack@5.88.1/node_modules/@module-federation/nextjs-mf/src/default-delegate.js?remote=simulate@http://localhost:4202/_next/static/chunks/remoteEntry.js

@benmarch
Copy link

Thanks, @ScriptedAlchemy! The latest update (7.0.6) is working as expected for me. I was able to create my own delegate module and import importDelegatedModule from "utilities" so that I can load my remote URLs at runtime. Everything appears to be working as expected without any errors on either the server or client. Thank you for your support!

@RexGalicie
Copy link

RexGalicie commented Jul 29, 2023

hey @ScriptedAlchemy, any news in progress ?
mainly node_modules/@module-federation/utilities/src/utils/importDelegatedModule.js
Self-reference dependency has unused export name: This should not happen

#1102 (comment)

FYI
I did use suggest from #1102 (comment)
and used internal delegated module and seems build fixed, all work

@simonbergstrom
Copy link

hey @ScriptedAlchemy, any news in progress ? mainly node_modules/@module-federation/utilities/src/utils/importDelegatedModule.js Self-reference dependency has unused export name: This should not happen

#1102 (comment)

FYI I did use suggest from #1102 (comment) and used internal delegated module and seems build fixed, all work

Experiencing the same issue. Works fine when running locally but the build failed after start using delegated modules. (v7.0.6)

@RexGalicie
Copy link

@simonbergstrom i did managed via custom delegate
Basically as this comment
#1102 (comment)

@wassgha
Copy link
Contributor

wassgha commented Aug 7, 2023

I don't know whether it is related but I am getting this after the update to nextjs-mf-7.0.2:

error ../../node_modules/@module-federation/nextjs-mf/src/default-delegate.js?remote=banka@http://localhost:3003/_next/static/chunks/remoteEntry.js
Cannot read properties of undefined (reading 'updateHash')
warn Fast Refresh had to perform a full reload due to a runtime error.

Still getting this updateHash error occasionally, no clear way to reproduce it consistently. A server reboot usually makes it go away. Did anyone manage to fix it?

@simonbergstrom
Copy link

@simonbergstrom i did managed via custom delegate Basically as this comment #1102 (comment)

Worked for me as well. My problem is that I believe my colleagues will be scared if I add this scary piece of code into our next setup so I would really like to simplify my solution before proceeding with my solution. Feels like this should be a part of the module federation module.

The only thing Im using the remote delegate is to support relative remote path on clientside. (I use only CSR in my next.js host app for the federated module pages which consumes federated modules from pure react apps.).

Any updates in the near future regarding this errors? I tried to update to 7.0.8 but then I could not even start the app due to crash.

Error: error - unhandledRejection: TypeError: Cannot read properties of undefined (reading 'call')

If you suggest any other solution for my issue in all open to hear it.

My Setup:

// in next.config.js

remotes: {
          mfe: createDelegatedModule(
            require.resolve('./remote-delegate.js'),
            {
              remote: `myMfe@/mfe-route/remoteEntry.js`,
            }
          ),
...

In remote-delegate.js

module.exports = new Promise(async (resolve, reject) => {
  const { importDelegatedModule } = await import(
    '@module-federation/utilities'
  );

  //Getting the current request by getting the 'remote' query parameter using URLSearchParams
  const currentRequest =
    // eslint-disable-next-line no-undef
    new URLSearchParams(__resourceQuery).get('remote') || '';
  //Splitting the currentRequest using "@" as the separator and assigning the values to "global" and "url"
  const [global, path] = currentRequest.split('@');

  const baseUrl =
    typeof window !== 'undefined'
      ? window.location.origin
      : 'http://www.fallback-url.com';

  importDelegatedModule({
    global,
    url: baseUrl + path,
  })
    .then((remote) => {
      if (remote.fake) {
        reject(remote);
      } else {
        resolve(remote);
      }
    })
    .catch((err) => {
      reject(err);
    });
});

// React code:

 const [MFE, setMFE] =
    useState<LazyExoticComponent<ComponentType<MFEProps>>>();
...
useEffect(() => {
    // Ensure only clientside render
    const mfe = lazy(() => import('mfe/myMFE'));
    setMFE(mfe);
  }, []);
  ...
  
  return (
...
  <MFE />  
  )

@digitalhank
Copy link

digitalhank commented Aug 29, 2023

Was experiencing Cannot read properties of undefined (reading 'call') issue on latest (7.0.8) on local. The remote would crash on every render but host would load fine. Removing any reference to next/error on the both remote and host fixed the issue, remotes are loading fine now. Although deployment was successful the server is failing to start now.

edit - the server failing to start was unrelated to this. nextjs was setting the hostname to an ipv6 address.

@ScriptedAlchemy
Copy link
Member

@digitalhank can you share configs or a repo that can replicate this.

@digitalhank
Copy link

digitalhank commented Sep 1, 2023

if i import Error from 'next/error' and invoke it in a remote app, the host page where the dynamic module is loaded will crash. The remote is running fine independently though.
FYI i have replaced some proprietary lines.

TypeError: Cannot read properties of undefined (reading 'call') at options.factory
//host next.config
const path = require('node:path')
const { withNx } = require('@nrwl/next/plugins/with-nx')
const { NextFederationPlugin } = require('@module-federation/nextjs-mf')
const formatSharedDependencies = require('../../tools/webpack/formatSharedDependencies')

/**
 * @type {import('@nrwl/next/plugins/with-nx').WithNxOptions}
 **/
const nextConfig = {
  nx: {
    svgr: true,
  },
  reactStrictMode: true,
  output: 'standalone',
  outputFileTracing: true,
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: '**.internal.com',
      },
    ],
  },
  transpilePackages: ['internal-ui-lib', 'internal-ui-lib'],
  experimental: {
    outputFileTracingRoot: path.join(__dirname, '../../'),
  },
  webpack(config, options) {
    const location = options.isServer ? 'ssr' : 'chunks'
    config.plugins.push(
      new NextFederationPlugin({
        name: 'host',
        filename: 'static/chunks/remoteEntry.js',
        remotes: {
          remote: `remote@${process.env.REMOTE_URL}/_next/static/${location}/remoteEntry.js`,
        },
        shared: formatSharedDependencies({ isHost: true }),
      }),
    )

    return config
  },
}

module.exports = withNx(nextConfig)
//host page file 

import { memo } from 'react'
import dynamic from 'next/dynamic'
import { ErrorBoundary } from '@shared/utilities'
import { AppProps } from 'next/app'

export function Remote() {
  const RemotePage = dynamic(() => import('remote/app').then((m) => m.default), {
    ssr: false,
  })

  return (
    <ErrorBoundary>
      <RemotePage {...props} />
    </ErrorBoundary>
  )
}

export default memo(Remote)

@ScriptedAlchemy
Copy link
Member

Next/error isn't in share scope. Likely why it's failing. I can issue patch

@digitalhank
Copy link

digitalhank commented Sep 13, 2023

Next/error isn't in share scope. Likely why it's failing. I can issue patch

@ScriptedAlchemy Thanks, i tried adding next/error to the host 'shared' but this didn't change anything. Perhaps it needs to be done at library level so that it is available at initialisation.

I am getting Cannot read properties of undefined (reading 'updateHash') at times though but only on local development. Issue seems to arise when a remote referenced on host fails to load on navigation / page load. I don't think a fake remote is successfully replacing it. In my project's case, host has several remotes (all referenced in next.config) owned by different teams, all living in a nx monorepo.

If i am a developer on team A, i will only run host and remote A. This seems to be fine except when a remote which is not running is requested (like if you accidentally access a page which references the offline remote), the updateHash issue will arise and persist despite navigating away or even restarting the server. Nuking dist and cache helps.

I realise we should move the remote reference out of next.config into a fully dynamic solution using injectScript but i never got injectScript to work properly. Best result i got was a remote successfully rendering for a second before crashing, i can't recall the error message.

edit - I tried cloning and running @module-federation/nextjs-mf in order to npm link the package. I wanted a better grasp on the issues we are facing and support the library development but was never able to successfully run the library.

@digitalhank
Copy link

digitalhank commented Sep 14, 2023

@ScriptedAlchemy i just ran 8.0.0 locally and the "Cannot read properties of undefined (reading 'updateHash')" issue seems to be resolved. The library is also handling unavailable remotes better with a log in console instead of complete failure. Will report back if anything unexpected comes up. next/error import is still not possible.

@ScriptedAlchemy
Copy link
Member

My CI is broken, released everything as a major version and also destroyed the dependency linking so everything on the "next" tag on npm is currently broken and I am blocked till it's fixed. Sorry! I the latest code does however resolve this issue I think, I'm just not able to release

@ScriptedAlchemy
Copy link
Member

If you install node utilities and nextmf seperately. It will work though. I'm glad it looks to be headed in the right direction, thanks for the feedback

@ScriptedAlchemy
Copy link
Member

It's also not a major release. So should not be breaking change.

@stevebrowndotco
Copy link

Hi @ScriptedAlchemy can I confirm that this is fixed in the following versions please?

    "@module-federation/nextjs-mf": "^8.0.0",
    "@module-federation/node": "^2.0.2-beta.2",
    "@module-federation/utilities": "^3.0.2",
    ```

@liunate
Copy link

liunate commented Sep 19, 2023

I have similar problem on both latest v7 and v8, showing the error stack at browser console in a simple host React app consuming remote Next.js module. Any idea or advice to approach the error would be really appreciated 🙏

The error stack is:

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'call')
    at options.factory (remoteEntry.js:892:31)
    at __webpack_require__ (remoteEntry.js:92:33)
    at remoteEntry.js:430:83
    at __webpack_require__.m.<computed> [as webpack/sharing/consume/default/react/react] (consumes:157:1)
    at __webpack_require__ (bootstrap:19:1)
    at ./src/dummy.jsx (bootstrap.jsx:6:23)
    at __webpack_require__ (bootstrap:19:1)
    at ./src/bootstrap.jsx (src_bootstrap_jsx.js:11:64)
    at Function.__webpack_require__ (bootstrap:19:1)

I just pushed a minimum reproduce at my fork of module-federation-examples at https://github.com/liunate/module-federation-examples/tree/reproduce-call-error with same results from plugin v7 latest or v8

The reproduce steps are:

  1. Go to react-nextjs/react-host-nextjs-remote
  2. Run yarn
  3. Run yarn start
  4. Go to http://127.0.0.1:8080 (React app) it perfectly renders remote of its own expose
  5. Go to http://127.0.0.1:8081/dummy (Next.js app) it renders remote from both React app and its own expose
  6. Now uncomment react-nextjs/react-host-nextjs-remote/host/src/dummy.jsx:4 to import the remote module from Next.js app
  7. Run yarn start and http://127.0.0.1:8080 logs the error stack I just mentioned.

Interestingly in my another reproduce repo in which which both sides are Next.js app, similar error stack but it throws at @swc/helper.

Some other observation is that I am sure at the page error is generated:

  1. I checked all remote bundle files and I am sure they do properly expose their modules like I can see in their respective bundle file, their expose are generated in the var moduleMap.
  2. In browser console, the global keeps those loadable modules e.g. (await window.remote.get('./nextjs-remote-component')), can be fetched. It's when I further invoke the resolved function to get the actual module instance, the error I mentioned above pops out.

So it seems whenever consuming a remote module generated by another Next.js app with MF Next plugin, the error just throws out at unexpected places.

@ScriptedAlchemy
Copy link
Member

@liunate avoid next at all costs of possible.

@liunate
Copy link

liunate commented Sep 19, 2023

@ScriptedAlchemy do you mean the skipSharingNextInternals extra option, or try out the other framework modern.js if I’m doing the module federation stuff?

@ScriptedAlchemy
Copy link
Member

skipSharingNextInternals - im not sure thats still implemented.

If microfrontend or module federation is important to you, then next.js is absolutely not the framework to use.
modernjs however is built for federation and is what i primarily focus on.

Its next release includes SSR + (HMR, TS) support too, so no need to use next.

Next.js isnt built for this

@stevebrowndotco
Copy link

stevebrowndotco commented Sep 20, 2023

My team had this issue for about a week for production builds in our nextjs-mf + nx monorepo, and we discovered that there were two offending es6 imports from a single local nx library (NOT federated modules) that were kicking off the error. Commenting out the imports made the error going away.

These libs were async loading further modules down the line.

We found that we had to add the lib via its path in the tsconfig.base.json file to the shared modules array, to get around this error. But we dont have to do this for our other libs.

We aren't keen on adding shared libs liberally, because we think we lose tree shaking support? That's one for another day I suppose.

We did not need to upgrade to version 8.0.0, nor add the node or utilities packages

@liunate
Copy link

liunate commented Sep 23, 2023

Just some further info. I tried some configs and from the error call stack, it seems Webpack runtime script is trying to load the shared module react from the remote scope regardless that I have already declared a compatible shared deps in host webpack config. The actual react module is not supposed to be loaded from remote and the actual react files should not exist in remote because the @module-federation/nextjs-mf internally has default shared :

react: {
    singleton: true,
    requiredVersion: false,
    eager: false,
    import: false,  // <---------- Does this mean do not include and generate the shared module `react` alongside the generated remote entry file? https://webpack.js.org/plugins/module-federation-plugin/#import
},

image

@ScriptedAlchemy
Copy link
Member

Okay long standing issue, through the unification process with bytedance, bore some fruits for next.js

#1268 resolves or should resolve any and all module consumption related problems.
Thanks to #1224 - I now have absolute control

Specifically this: #1433

Should resolve "call of undefined" and "eager consumption" errors, particularly eager consumption, which has been the root cause of most of the Problems with Next.js - import() is no longer required for module federation to work

@wassgha
Copy link
Contributor

wassgha commented Sep 30, 2023

Is there a version that we can test these changes on? @ScriptedAlchemy if possible please confirm which import path is now recommended for next.js (script injection? dynamic? react.lazy?)

@ScriptedAlchemy
Copy link
Member

use next tag on nom for the v8 prerelease. #1530

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests