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

Refactoring and Enhancements of Node Federation Plugin and Filesystem Strategy, Including Addition of New Features and Tests #1312

Merged
merged 7 commits into from
Sep 7, 2023
2 changes: 2 additions & 0 deletions packages/nextjs-mf/src/default-delegate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ const loadScript = (keyOrRuntimeRemoteItem: string | RuntimeRemote) => {
(__webpack_require__ as any).l(
reference.url,
function (event: Event) {

console.log('to resolve',globalScope)
if (typeof globalScope[remoteGlobal] !== 'undefined') {
return resolveRemoteGlobal();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,9 @@ class DynamicFilesystemChunkLoadingRuntimeModule extends RuntimeModule {
}, {} as Record<string, string>)
)};`,
`Object.keys(remotes).forEach(function(remote) {
${RuntimeGlobals.require}.federation.remotes[remote] = remotes[remote];
globalThis.__remote_scope__[remote] = remotes[remote];
});`,
"console.log('remotes writter', globalThis.__remote_scope__);",
]);

return Template.asString([
Expand Down
34 changes: 32 additions & 2 deletions packages/node/src/plugins/FederationModuleInfoRuntimeModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,44 @@ class FederationModuleInfoRuntimeModule extends RuntimeModule {
/**
* @returns {string} runtime code
*/
// eslint-disable-next-line max-lines-per-function
generate() {
return Template.asString([
`${RuntimeGlobals.require}.federation = {`,
Template.indent([`cache: {},`, `remotes: {},`, `moduleInfo: {},`]),
`}`,
`};`,
// `if (!globalThis.__remote_scopes__) {`,
Template.indent([
'// backward compatible global proxy',
`globalThis.__remote_scope__ = new Proxy(globalThis.__remote_scope__ || __webpack_require__.federation, {`,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The globalThis.remote_scope proxy in FederationModuleInfoRuntimeModule.ts is used to provide backward compatibility for global proxy. It's a JavaScript Proxy object that intercepts and defines custom behavior for fundamental operations (e.g., property lookup, assignment, enumeration, function invocation, etc.) on the globalThis.remote_scope object.

Here's what it does:

  • It's created if globalThis.remote_scope doesn't already exist.
  • It proxies the federation object of RuntimeGlobals.require.
  • It has two handler functions: get and set.

The get function:

  • If the property being accessed is config, it returns the remotes object from the federation object.
  • For any other property, it returns the corresponding property from the cache object of the federation object.

The set function:

  • If the property being set is _config, it assigns the value to the remotes object of the federation object.
  • For any other property, it assigns the value to the corresponding property in the cache object of the federation object.

This proxy allows the federation object to be accessed and manipulated via globalThis.remote_scope, providing a consistent interface for managing federation module information.

Refer to the code here:

Template.indent([
`get: function(target, prop, receiver) {`,
Template.indent([
// `console.log('Reading property:', prop);`,
`if (prop === '_config') {`,
Template.indent([` return ${RuntimeGlobals.require}.federation.remotes;`]),
`} else {`,
Template.indent([` return ${RuntimeGlobals.require}.federation.cache[prop];`]),
`}`,
]),
`},`,
`set: function(target, prop, value) {`,
Template.indent([
`console.log('Writing to property:', prop);`,
`if (prop === '_config') {`,
Template.indent([`console.log('Writing to remotes', {prop,target,value}); ${RuntimeGlobals.require}.federation.remotes = value;`]),
`} else {`,
Template.indent([` ${RuntimeGlobals.require}.federation.cache[prop] = value;`]),
`}`,
`return true;`,
]),
`}`,
]),
`});`,
]),
// `}`,
]);
}
}

export default FederationModuleInfoRuntimeModule;

14 changes: 7 additions & 7 deletions packages/node/src/plugins/NodeFederationPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,23 @@ interface Context {
* This function iterates over all remotes and checks if they
* are internal or not. If it's an internal remote then we add it to our new object
* with a key of the name of the remote and value as internal. If it's not an internal
* remote then we check if there is a '@' in that string which likely means it is a global @ url
* remote then we check if there is a '@' in that string which likely means it is a global @ url
*
* @param {Record<string, any>} remotes - The remotes to parse.
* @returns {Record<string, string>} - The parsed remotes.
* */

export const parseRemotes = (remotes: Record<string, any>): Record<string, string> => {
if (!remotes || typeof remotes !== 'object') {
throw new Error('remotes must be an object');
}

return Object.entries(remotes).reduce((acc: Record<string, string>, [key, value]) => {
const isInternal = value.startsWith('internal ');
const isGlobal = value.includes('@') && !['window.', 'global.', 'globalThis.','self.'].some(prefix => value.startsWith(prefix));

acc[key] = isInternal || !isGlobal ? value : parseRemoteSyntax(value);

return acc;
}, {});
}
Expand All @@ -59,11 +59,11 @@ export const parseRemoteSyntax = (remote: any): string => {
if (typeof remote !== 'string') {
throw new Error('remote must be a string');
}

if (remote.includes('@')) {
const [url, global] = extractUrlAndGlobal(remote);
if (!['window.', 'global.', 'globalThis.'].some(prefix => global.startsWith(prefix))) {
return `globalThis.__remote_scope__.cache.${global}@${url}`;
return `globalThis.__remote_scope__.${global}@${url}`;
}
}
return remote;
Expand Down
36 changes: 19 additions & 17 deletions packages/node/src/plugins/RemotePublicPathRuntimeModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,28 @@ class AutoPublicPathRuntimeModule extends RuntimeModule {

return Template.asString([
"var scriptUrl;",
'var remoteContainerRegistry = globalThis.__remote_scope__.remotes;',
`console.log('remoteContainerRegistry',remoteContainerRegistry);`,
'var remoteContainerRegistry = globalThis.__remote_scope__;',
scriptType === "module"
? `if (typeof remoteContainerRegistry[${JSON.stringify(this.options.name)}] === "string") scriptUrl = remoteContainerRegistry[${JSON.stringify(this.options.name)}]`
: Template.asString([
`if (${RuntimeGlobals.global}.importScripts) scriptUrl = ${RuntimeGlobals.global}.location + "";`,
`var document = ${RuntimeGlobals.global}.document;`,
"if (!scriptUrl && document) {",
Template.indent([
`if (document.currentScript)`,
Template.indent(`scriptUrl = document.currentScript.src`),
"if (!scriptUrl) {",
Template.indent([
'var scripts = document.getElementsByTagName("script");',
"if(scripts.length) scriptUrl = scripts[scripts.length - 1].src"
]),
"}"
]),
"}"
: Template.asString([
'scriptUrl = __dirname',
]),
// Template.asString([
// `if (${RuntimeGlobals.global}.importScripts) scriptUrl = ${RuntimeGlobals.global}.location + "";`,
// `var document = ${RuntimeGlobals.global}.document;`,
// "if (!scriptUrl && document) {",
// Template.indent([
// `if (document.currentScript)`,
// Template.indent(`scriptUrl = document.currentScript.src`),
// "if (!scriptUrl) {",
// Template.indent([
// 'var scripts = document.getElementsByTagName("script");',
// "if(scripts.length) scriptUrl = scripts[scripts.length - 1].src"
// ]),
// "}"
// ]),
// "}"
// ]),
"// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration",
'// or pass an empty string ("") and set the __webpack_public_path__ variable from your code to use your own logic.',
'if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");',
Expand Down
13 changes: 10 additions & 3 deletions packages/node/src/plugins/loadScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ export default `
//language=JS
export const executeLoadTemplate = `
function executeLoad(url, callback, name) {

console.log('running execute load template')
if (!name) {
throw new Error('__webpack_require__.l name is required for ' + url);
}
Expand All @@ -85,11 +87,16 @@ export const executeLoadTemplate = `
const exp = {};
let remote = {exports:{}};
remoteCapsule(exp,require,remote,'node-federation-loader-' + name + '.vm',__dirname);

console.log('after capsule');
remote = remote.exports || remote;
globalThis.__remote_scope__.cache[remoteName] = remote[name] || remote;
globalThis.__remote_scope__.remotes[remoteName] = url;
console.log('got exports',remote)
globalThis.__remote_scope__[remoteName] = remote[name] || remote;
console.log('globalThis.__remote_scope__[remoteName]',globalThis.__remote_scope__[remoteName]);

globalThis.__remote_scope__._config[remoteName] = url;
console.log(globalThis.__remote_scope__);
callback(globalThis.__remote_scope__.cache[name])
callback(globalThis.__remote_scope__[name])
} catch (e) {
console.error('executeLoad hit catch block', e);
e.target = {src: url};
Expand Down
4 changes: 1 addition & 3 deletions packages/node/src/plugins/parts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,7 @@ export function generateLoadingCode(
}, {} as Record<string, string>)
)};`,

`var requestedRemote = ${
RuntimeGlobals.require
}.federation.remotes[${JSON.stringify(name)}]`,
`var requestedRemote = globalThis.__remote_scope__[${JSON.stringify(name)}]`,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

local federation instance doesnt sync global remotes, this is a little backwards and needs refactoring. Relying on backward compatiable convention to read and write globally to hosts federation scope


"if(typeof requestedRemote === 'function'){",
Template.indent(
Expand Down
Loading