-
-
Notifications
You must be signed in to change notification settings - Fork 266
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
Comments
hey @ScriptedAlchemy, faced similar issue
Builded code
|
@RexGalicie Is your app using SWC as well? |
@yspychala in my case seems both, but it is on some big project, I did create demo test case, and can not replicate it |
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 |
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? |
Unfortunately I haven't figured out a minimal reproducible sample yet, but my setup looks like the following:
In this scenario, the remoteEntry.js of :3001 is crashing while trying to resolve these two modules:
As soon as I have a reproducible repo I'll post it here. |
@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. |
thanks, ill take a peek soon as possible |
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. |
hey @ScriptedAlchemy Basically with catch I manage to see remote seems all okay, only errors at console, but functionality fine same as with v6.7.1
|
@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 |
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
Please note this doesn’t mean necessarily the bug appears only with this, but it’s a way to reproduce it. |
@yspychala excellent, thank you - ill take a look hopefully this evening. 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. |
@yspychala try adding
|
@benmarch By the time you posted your message, I found out on my side And I was able to reproduce the error in two different projects where none of those imports are used in a remote module ( |
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. |
@yspychala here's the workaround: Create a file called `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. |
@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.tsimport { 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.tsximport { 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. |
@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. |
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. |
okay i think i have found a solution to this: #1149 |
|
@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')" |
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:
or
Also I can confirm what @simeon-raykov said about React.lazy. |
@ScriptedAlchemy
|
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 |
hey @ScriptedAlchemy, any news in progress ? FYI |
Experiencing the same issue. Works fine when running locally but the build failed after start using delegated modules. (v7.0.6) |
@simonbergstrom i did managed via custom delegate |
Still getting this |
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: If you suggest any other solution for my issue in all open to hear it. My Setup: // in next.config.js
In remote-delegate.js
// React code:
|
Was experiencing edit - the server failing to start was unrelated to this. nextjs was setting the hostname to an ipv6 address. |
@digitalhank can you share configs or a repo that can replicate this. |
if i
|
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 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 edit - I tried cloning and running |
@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. |
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 |
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 |
It's also not a major release. So should not be breaking change. |
Hi @ScriptedAlchemy can I confirm that this is fixed in the following versions please?
|
I have similar problem on both latest v7 and v8, showing the error stack at browser console in a simple host The error stack is:
I just pushed a minimum reproduce at my fork of The reproduce steps are:
Interestingly in my another reproduce repo in which which both sides are Some other observation is that I am sure at the page error is generated:
So it seems whenever consuming a remote module generated by another |
@liunate avoid next at all costs of possible. |
@ScriptedAlchemy do you mean the |
If microfrontend or module federation is important to you, then next.js is absolutely not the framework to use. Its next release includes SSR + (HMR, TS) support too, so no need to use next. Next.js isnt built for this |
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 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 |
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: {
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
}, |
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. 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 |
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?) |
use |
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
"cover 90% in FR", "last 3 versions", "not dead", "maintained node versions"
)import from
or React Lazy + React SuspenseHow to reproduce
Apps were generated with Next CLI, so it’s really a basic config.
npm i
in /host and /remote directoriesnpm run dev
in /host and /remotehttp://localhost:3000
(remote) andhttp://localhost:3001
(host)If we turn off SWC if favor of Babel in remote, it works again:
The text was updated successfully, but these errors were encountered: