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

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"]
}

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 7
  • Comments: 50 (24 by maintainers)

Most upvoted comments

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.

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

@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.

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

https://github.com/module-federation/universe/pull/1268 resolves or should resolve any and all module consumption related problems. Thanks to https://github.com/module-federation/universe/pull/1224 - I now have absolute control

Specifically this: https://github.com/module-federation/universe/pull/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

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?)

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?

@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.

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 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

okay i think i have found a solution to this: https://github.com/module-federation/universe/pull/1149

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.

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 */
  /******/

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",
    ```

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 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.

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

https://github.com/module-federation/universe/issues/1102#issuecomment-1642794271

FYI I did use suggest from https://github.com/module-federation/universe/issues/1102#issuecomment-1632909792 and used internal delegated module and seems build fixed, all work

thanks, ill take a peek soon as possible