From 3959637d203bd7154b43a6fbae85b765589106dd Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Mon, 31 Mar 2025 15:34:46 -0700 Subject: [PATCH 1/2] fix: add embed runtime back --- .changeset/slow-cows-wait.md | 6 +++++ .../HoistContainerReferencesPlugin.ts | 27 +++++++++++++++++++ .../runtime/FederationRuntimePlugin.ts | 13 +++++++++ packages/runtime/package.json | 2 +- 4 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 .changeset/slow-cows-wait.md diff --git a/.changeset/slow-cows-wait.md b/.changeset/slow-cows-wait.md new file mode 100644 index 00000000000..388bd7d37a3 --- /dev/null +++ b/.changeset/slow-cows-wait.md @@ -0,0 +1,6 @@ +--- +'@module-federation/enhanced': patch +'@module-federation/runtime': patch +--- + +implement embedded runtime diff --git a/packages/enhanced/src/lib/container/HoistContainerReferencesPlugin.ts b/packages/enhanced/src/lib/container/HoistContainerReferencesPlugin.ts index 92c07a95f34..93095d0f54d 100644 --- a/packages/enhanced/src/lib/container/HoistContainerReferencesPlugin.ts +++ b/packages/enhanced/src/lib/container/HoistContainerReferencesPlugin.ts @@ -41,6 +41,33 @@ export class HoistContainerReferences implements WebpackPluginInstance { }, ); + // Add modules that reference @module-federation/runtime + compilation.hooks.finishModules.tap(PLUGIN_NAME, (modules) => { + for (const module of modules) { + // Check if this is a normal module with a resource + if (!('resource' in module) || !module.resource) continue; + + // Check module dependencies for @module-federation/runtime references + const mgm = compilation.moduleGraph._getModuleGraphModule(module); + if (!mgm?.outgoingConnections) continue; + + for (const connection of mgm.outgoingConnections) { + // Safely check if dependency has a request property and if it includes our target + if ( + connection.dependency && + 'request' in connection.dependency && + typeof connection.dependency.request === 'string' && + connection.dependency.request.includes( + '@module-federation/runtime', + ) + ) { + containerEntryDependencies.add(connection.dependency); + break; + } + } + } + }); + // Hook into the optimizeChunks phase compilation.hooks.optimizeChunks.tap( { diff --git a/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts b/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts index 2ea4110b7f2..8da43e4d60d 100644 --- a/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts +++ b/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts @@ -48,6 +48,13 @@ const RuntimePath = require.resolve('@module-federation/runtime', { paths: [RuntimeToolsPath], }); +const EmbeddedRuntimePath = require.resolve( + '@module-federation/runtime/embedded', + { + paths: [RuntimeToolsPath], + }, +); + const federationGlobal = getFederationGlobalScope(RuntimeGlobals); const onceForCompiler = new WeakSet(); @@ -420,6 +427,12 @@ class FederationRuntimePlugin { if (/webpack-bundler-runtime/.test(resolveData.contextInfo.issuer)) { resolveData.request = runtimePath; + if (resolveData.createData) { + resolveData.createData.request = resolveData.request; + } + } else { + resolveData.request = EmbeddedRuntimePath; + if (resolveData.createData) { resolveData.createData.request = resolveData.request; } diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 041f7f601fd..571919293fe 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@module-federation/runtime", - "version": "0.11.2", + "version": "0.11.0", "author": "zhouxiao ", "main": "./dist/index.cjs.js", "module": "./dist/index.esm.mjs", From 9a56b7422fe7c345bf7fd919637efa5cb6359e8a Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Mon, 31 Mar 2025 15:43:21 -0700 Subject: [PATCH 2/2] fix: add embed runtime back --- packages/runtime/__tests__/sync.spec.ts | 737 ++++++++++++++++++++++++ packages/runtime/src/embedded.ts | 366 ++++++++++++ 2 files changed, 1103 insertions(+) create mode 100644 packages/runtime/__tests__/sync.spec.ts create mode 100644 packages/runtime/src/embedded.ts diff --git a/packages/runtime/__tests__/sync.spec.ts b/packages/runtime/__tests__/sync.spec.ts new file mode 100644 index 00000000000..3e263550da5 --- /dev/null +++ b/packages/runtime/__tests__/sync.spec.ts @@ -0,0 +1,737 @@ +import { describe, it, expect, beforeAll, afterAll, assert } from 'vitest'; +import { matchRemoteWithNameAndExpose } from '@module-federation/runtime-core'; +import { + addGlobalSnapshot, + getGlobalSnapshot, + Global, + setGlobalFederationConstructor, +} from '@module-federation/runtime-core'; + +import { requestList } from './mock/env'; + +// Helper function to check if a method is private +function isPrivate(methodName: string): boolean { + return methodName.startsWith('_'); +} + +describe('Embed Module Proxy', async () => { + // Dynamically import the index module + const Index = await import('../src/index'); + + beforeAll(async () => { + // Mock the global __webpack_require__ to provide the runtime + //@ts-ignore + globalThis.__webpack_require__ = { + federation: { + runtime: Index, + }, + }; + }); + + afterAll(async () => { + // Clean up the global __webpack_require__ mock + //@ts-ignore + delete globalThis.__webpack_require__; + }); + + // Dynamically import the embedded module + const Embedded = await import('../src/embedded'); + describe('Api Sync', () => { + it('should have the same exports in embedded.ts and index.ts', () => { + // Compare the exports of embedded.ts and index.ts + const embeddedExports = Object.keys(Embedded).sort(); + const indexExports = Object.keys(Index).sort(); + expect(embeddedExports).toEqual(indexExports); + }); + + it('FederationHost class should have the same methods in embedded.ts and index.ts', () => { + // Create instances of FederationHost from both embedded.ts and index.ts + const embeddedHost = new Embedded.FederationHost({ + name: '@federation/test', + remotes: [], + }); + const indexHost = new Index.FederationHost({ + name: '@federation/test', + remotes: [], + }); + + // Get the method names of FederationHost instances, excluding private methods + const embeddedMethods = Object.getOwnPropertyNames( + Object.getPrototypeOf(embeddedHost), + ) + .filter( + (prop) => + typeof embeddedHost[prop] === 'function' && !isPrivate(prop), + ) + .sort(); + const indexMethods = Object.getOwnPropertyNames( + Object.getPrototypeOf(indexHost), + ) + .filter( + (prop) => typeof indexHost[prop] === 'function' && !isPrivate(prop), + ) + .sort(); + + // Compare the method names + expect(embeddedMethods).toEqual(indexMethods); + }); + + it('Module class should have the same methods in embedded.ts and index.ts', () => { + // Create instances of Module from both embedded.ts and index.ts + const embeddedModule = new Embedded.Module({ + remoteInfo: { + name: '@federation/test', + entry: '', + type: '', + entryGlobalName: '', + shareScope: '', + }, + host: new Embedded.FederationHost({ + name: '@federation/test', + remotes: [], + }), + }); + const indexModule = new Index.Module({ + remoteInfo: { + name: '@federation/test', + entry: '', + type: '', + entryGlobalName: '', + shareScope: '', + }, + host: new Index.FederationHost({ + name: '@federation/test', + remotes: [], + }), + }); + + // Get the method names of Module instances, excluding private methods + const embeddedMethods = Object.getOwnPropertyNames( + Object.getPrototypeOf(embeddedModule), + ) + .filter( + (prop) => + typeof embeddedModule[prop] === 'function' && !isPrivate(prop), + ) + .sort(); + const indexMethods = Object.getOwnPropertyNames( + Object.getPrototypeOf(indexModule), + ) + .filter( + (prop) => typeof indexModule[prop] === 'function' && !isPrivate(prop), + ) + .sort(); + // Compare the method names + expect(embeddedMethods).toEqual(indexMethods); + }); + }); + describe('General API Tests', () => { + describe('matchRemote', () => { + it('match default export with pkgName', () => { + const matchInfo = matchRemoteWithNameAndExpose( + [ + { + name: '@federation/matchRemote', + version: '1.0.0', + }, + { + name: '@federation/matchRemote2', + version: '1.0.0', + }, + ], + '@federation/matchRemote', + ); + assert(matchInfo, 'matchRemote should return a matchInfo'); + const { expose, remote } = matchInfo; + expect(expose).toBe('.'); + expect(remote).toMatchObject({ + name: '@federation/matchRemote', + version: '1.0.0', + }); + }); + + it('match default export with alias', () => { + const matchInfo = matchRemoteWithNameAndExpose( + [ + { + name: '@federation/matchRemote', + version: '1.0.0', + alias: 'hello', + }, + { + name: '@federation/matchRemote2', + version: '1.0.0', + }, + ], + 'hello', + ); + assert(matchInfo, 'matchRemote should return a matchInfo'); + const { expose, remote } = matchInfo; + expect(expose).toBe('.'); + expect(remote).toMatchObject({ + name: '@federation/matchRemote', + version: '1.0.0', + alias: 'hello', + }); + }); + + it('match pkgName', () => { + const matchInfo = matchRemoteWithNameAndExpose( + [ + { + name: '@federation/matchRemote', + version: '1.0.0', + }, + { + name: '@federation/matchRemote2', + version: '1.0.0', + }, + ], + '@federation/matchRemote/util', + ); + assert(matchInfo, 'matchRemote should return a matchInfo'); + const { expose, remote } = matchInfo; + expect(expose).toBe('./util'); + expect(remote).toMatchObject({ + name: '@federation/matchRemote', + version: '1.0.0', + }); + }); + + it('match alias', () => { + const matchInfo = matchRemoteWithNameAndExpose( + [ + { + name: '@federation/matchRemote', + version: '1.0.0', + }, + { + name: '@federation/matchRemote2', + alias: '@matchRemote2', + version: '1.0.0', + }, + ], + '@matchRemote2/utils/add', + ); + assert(matchInfo, 'matchRemote should return a matchInfo'); + const { expose, remote } = matchInfo; + expect(expose).toBe('./utils/add'); + expect(remote).toMatchObject({ + name: '@federation/matchRemote2', + alias: '@matchRemote2', + version: '1.0.0', + }); + }); + }); + + // eslint-disable-next-line max-lines-per-function + describe('loadRemote', () => { + it('api', () => { + const FederationInstance = new Embedded.FederationHost({ + name: '@federation-test/loadRemote-api', + remotes: [], + }); + expect(FederationInstance.loadRemote).toBeInstanceOf(Function); + }); + + it('loadRemote from global', async () => { + const reset = addGlobalSnapshot({ + '@federation-test/globalinfo': { + globalName: '', + buildVersion: '', + publicPath: '', + remoteTypes: '', + shared: [], + remoteEntry: '', + remoteEntryType: 'global', + modules: [], + version: '0.0.1', + remotesInfo: { + '@federation-test/app2': { + matchedVersion: '0.0.1', + }, + }, + }, + '@federation-test/app2:0.0.1': { + globalName: '', + publicPath: '', + remoteTypes: '', + shared: [], + buildVersion: 'custom', + remotesInfo: {}, + remoteEntryType: 'global', + modules: [], + version: '0.0.1', + remoteEntry: + 'http://localhost:1111/resources/app2/federation-remote-entry.js', + }, + }); + + const FederationInstance = new Embedded.FederationHost({ + name: '@federation-test/globalinfo', + remotes: [ + { + name: '@federation-test/app2', + version: '*', + }, + ], + }); + + const module = await FederationInstance.loadRemote<() => string>( + '@federation-test/app2/say', + ); + assert(module, 'module should be a function'); + expect(module()).toBe('hello app2'); + reset(); + }); + + it('loadRemote from global without hostSnapshot', async () => { + const reset = addGlobalSnapshot({ + '@load-remote/app1': { + globalName: `__FEDERATION_${'@load-remote/app1:custom'}__`, + publicPath: 'http://localhost:1111/resources/load-remote/app1/', + remoteTypes: '', + shared: [], + buildVersion: 'custom', + remotesInfo: {}, + remoteEntryType: 'global', + modules: [], + version: '0.0.1', + remoteEntry: 'federation-remote-entry.js', + }, + '@load-remote/app2:0.0.1': { + globalName: '', + publicPath: 'http://localhost:1111/resources/load-remote/app2/', + remoteTypes: '', + shared: [], + buildVersion: 'custom', + remotesInfo: {}, + remoteEntryType: 'global', + modules: [], + version: '0.0.1', + remoteEntry: 'federation-remote-entry.js', + }, + }); + + const FM = new Embedded.FederationHost({ + name: 'xxxxx', + remotes: [ + { + name: '@load-remote/app2', + version: '0.0.1', + }, + { + name: '@load-remote/app1', + version: '0.0.1', + }, + ], + }); + + const module = await FM.loadRemote<() => string>( + '@load-remote/app1/say', + ); + assert(module, 'module should be a function'); + expect(module()).toBe('hello app1'); + + const module2 = await FM.loadRemote<() => string>( + '@load-remote/app2/say', + ); + assert(module2, 'module should be a function'); + expect(module2()).toBe('hello app2'); + reset(); + }); + + it('compatible with old structor', async () => { + const reset = addGlobalSnapshot({ + '@federation-test/compatible': { + globalName: '', + buildVersion: '', + publicPath: '', + remoteTypes: '', + shared: [], + remoteEntry: '', + remoteEntryType: 'global', + modules: [], + version: '0.0.1', + remotesInfo: { + '@federation-test/app2': { + matchedVersion: '0.0.1', + }, + }, + }, + '@federation-test/app2:0.0.1': { + globalName: '', + publicPath: '', + remoteTypes: '', + shared: [], + buildVersion: 'custom', + remotesInfo: {}, + remoteEntryType: 'global', + modules: [], + version: '0.0.1', + remoteEntry: + 'http://localhost:1111/resources/app2/federation-remote-entry.js', + }, + }); + + const FederationInstance = new Embedded.FederationHost({ + name: '@federation-test/compatible', + remotes: [ + { + name: '@federation-test/app2', + version: '*', + }, + ], + }); + const module = await FederationInstance.loadRemote<() => string>( + '@federation-test/app2/say', + ); + assert(module, 'module should be a function'); + expect(module()).toBe('hello app2'); + reset(); + }); + + it('remote entry url with query', async () => { + const FederationInstance = new Embedded.FederationHost({ + name: '@federation-test/compatible', + remotes: [ + { + name: '__FEDERATION_@federation-test/app2:custom__', + alias: 'app2', + entry: + 'http://localhost:1111/resources/app2/federation-remote-entry.js?kk=2', + }, + ], + }); + const module = + await FederationInstance.loadRemote<() => string>('app2/say'); + assert(module, 'module should be a function'); + expect(module()).toBe('hello app2'); + }); + + it('different instance with same module', async () => { + const reset = addGlobalSnapshot({ + '@module-federation/load-remote-different-instance': { + buildVersion: 'custom', + publicPath: 'xx', + remoteEntry: 'xx', + remotesInfo: { + '@module-federation/sub1': { + matchedVersion: '1.0.2', + }, + }, + remoteEntryType: 'global', + modules: [], + version: '0.0.1', + globalName: '', + remoteTypes: 'index.d.ts', + shared: [], + }, + '@module-federation/sub1:1.0.2': { + buildVersion: '1.0.2', + globalName: '__FEDERATION_@module-federation/sub1:1.0.2__', + modules: [], + remoteEntryType: 'global', + remoteTypes: 'index.d.ts', + version: '0.0.1', + remotesInfo: {}, + shared: [], + publicPath: + 'http://localhost:1111/resources/load-remote/diff-instance/', + remoteEntry: 'federation-remote-entry.js', + }, + }); + const vmOptions = { + remotes: [ + { + name: '@module-federation/sub1', + version: '1.0.2', + }, + ], + plugins: [ + { + name: 'load-resouce-inbrowser', + beforeInit(args: any) { + args.options.inBrowser = true; + return args; + }, + }, + ], + }; + const FM = new Embedded.FederationHost({ + name: '@module-federation/load-remote-different-instance', + ...vmOptions, + }); + const FM2 = new Embedded.FederationHost({ + name: '@module-federation/load-remote-different-instance2', + ...vmOptions, + }); + const [res1, res2] = await Promise.all([ + FM.loadRemote<() => string>('@module-federation/sub1'), + FM2.loadRemote<() => string>('@module-federation/sub1'), + ]); + assert(res1, `res1 can't be null`); + assert(res2, `res2 can't be null`); + expect(res1()).toBe(res2()); + expect((globalThis as any).execTime).toBe(1); + reset(); + }); + }); + + describe('loadRemote with manifest.json', () => { + it('duplicate request manifest.json', async () => { + const FM = new Embedded.FederationHost({ + name: '@demo/host', + remotes: [ + { + name: '@demo/main', + entry: + 'http://localhost:1111/resources/main/federation-manifest.json', + }, + ], + }); + + const FM2 = new Embedded.FederationHost({ + name: '@demo/host2', + remotes: [ + { + name: '@demo/main', + entry: + 'http://localhost:1111/resources/main/federation-manifest.json', + }, + ], + }); + + const [module, , module2] = await Promise.all([ + FM.loadRemote string>>('@demo/main/say'), + FM.loadRemote string>>('@demo/main/add'), + FM2.loadRemote string>>('@demo/main/say'), + ]); + assert(module); + assert(module2); + expect(module()).toBe(module2()); + expect(module()).toBe('hello world'); + expect( + requestList.get( + 'http://localhost:1111/resources/main/federation-manifest.json', + ), + ).toBe(1); + }); + + it('circulate deps', async () => { + setGlobalFederationConstructor(Embedded.FederationHost, true); + const FM = Embedded.init({ + name: '@circulate-deps/app1', + remotes: [ + { + name: '@circulate-deps/app2', + entry: + 'http://localhost:1111/resources/load-remote/circulate-dep-app2/federation-manifest.json', + }, + ], + }); + + const app1Module = await FM.loadRemote string>>( + '@circulate-deps/app2/say', + ); + assert(app1Module); + const res = await app1Module(); + expect(res).toBe('@circulate-deps/app2'); + + Global.__FEDERATION__.__INSTANCES__ = []; + setGlobalFederationConstructor(undefined, true); + }); + + it('manifest.json with query', async () => { + const FM = new Embedded.FederationHost({ + name: '@demo/host', + remotes: [ + { + name: '@demo/main', + entry: + 'http://localhost:1111/resources/main/federation-manifest.json?query=2', + }, + ], + }); + + const [module] = await Promise.all([ + FM.loadRemote string>>('@demo/main/say'), + ]); + assert(module); + expect(module()).toBe('hello world'); + }); + }); + + describe('lazy loadRemote add remote into snapshot', () => { + it('load remoteEntry', async () => { + const reset = addGlobalSnapshot({ + '@demo/app2': { + buildVersion: '1.0.2', + globalName: `__FEDERATION_${'@load-remote/app2:custom'}__`, + modules: [], + remoteEntryType: 'global', + remoteTypes: 'index.d.ts', + version: '0.0.1', + remotesInfo: {}, + shared: [], + publicPath: 'http://localhost:1111/resources/load-remote/app2/', + remoteEntry: 'federation-remote-entry.js', + }, + '@demo/app1': { + consumerList: ['@demo/app2:0.0.1'], + globalName: `__FEDERATION_${'@load-remote/app1:custom'}__`, + publicPath: 'http://localhost:1111/resources/load-remote/app1/', + remoteTypes: '', + shared: [], + buildVersion: 'custom', + remotesInfo: {}, + remoteEntryType: 'global', + modules: [], + version: '0.0.1', + remoteEntry: 'federation-remote-entry.js', + }, + }); + const federationInstance = new Embedded.FederationHost({ + name: '@demo/app1', + remotes: [ + { + name: '@demo/app2', + alias: 'app2', + version: '', + }, + ], + }); + const snapshot = getGlobalSnapshot(); + const hostModuleInfo = snapshot['@demo/app1']; + assert( + hostModuleInfo && 'remotesInfo' in hostModuleInfo, + 'hostModuleInfo Cannot be empty', + ); + const beforeHostRemotesInfo = hostModuleInfo.remotesInfo; + const beforeRemotesLength = Object.keys(beforeHostRemotesInfo).length; + expect(beforeRemotesLength).toBe(0); + + await federationInstance.loadRemote('app2/say'); + const afterHostRemotesInfo = hostModuleInfo.remotesInfo; + const afterRemotesLength = Object.keys(afterHostRemotesInfo).length; + expect(afterRemotesLength).toBe(1); + reset(); + }); + + it('load manifest', async () => { + const reset = addGlobalSnapshot({ + '@demo/app1': { + globalName: `__FEDERATION_${'@load-remote/app1:custom'}__`, + publicPath: 'http://localhost:1111/resources/load-remote/app1/', + remoteTypes: '', + shared: [], + buildVersion: 'custom', + remotesInfo: {}, + remoteEntryType: 'global', + modules: [], + version: '0.0.1', + remoteEntry: 'federation-remote-entry.js', + }, + }); + + const federationInstance = new Embedded.FederationHost({ + name: '@demo/app1', + remotes: [ + { + name: '@demo/main', + alias: 'main', + entry: + 'http://localhost:1111/resources/main/federation-manifest.json', + }, + ], + }); + const snapshot = getGlobalSnapshot(); + const hostModuleInfo = snapshot['@demo/app1']; + assert( + hostModuleInfo && 'remotesInfo' in hostModuleInfo, + 'hostModuleInfo Cannot be empty', + ); + const beforeHostRemotesInfo = hostModuleInfo.remotesInfo; + const beforeRemotesLength = Object.keys(beforeHostRemotesInfo).length; + expect(beforeRemotesLength).toBe(0); + + await federationInstance.loadRemote('main/say'); + const afterHostRemotesInfo = hostModuleInfo.remotesInfo; + const afterRemotesLength = Object.keys(afterHostRemotesInfo).length; + expect(afterRemotesLength).toBe(1); + reset(); + }); + }); + + describe('loadRemote', () => { + it('api', async () => { + const jsSyncAssetPath = 'resources/load-remote/app2/say.sync.js'; + const remotePublicPath = 'http://localhost:1111/'; + const reset = addGlobalSnapshot({ + '@federation-test/globalinfo': { + globalName: '', + buildVersion: '', + publicPath: '', + remoteTypes: '', + shared: [], + remoteEntry: '', + remoteEntryType: 'global', + modules: [], + version: '0.0.1', + remotesInfo: { + '@federation-test/app2': { + matchedVersion: '0.0.1', + }, + }, + }, + '@federation-test/app2:0.0.1': { + globalName: '', + publicPath: remotePublicPath, + remoteTypes: '', + shared: [], + buildVersion: 'custom', + remotesInfo: {}, + remoteEntryType: 'global', + modules: [ + { + moduleName: 'say', + assets: { + css: { + sync: ['sub2/say.sync.css'], + async: ['sub2/say.async.css'], + }, + js: { + sync: [jsSyncAssetPath], + async: [], + }, + }, + }, + ], + version: '0.0.1', + remoteEntry: 'resources/app2/federation-remote-entry.js', + }, + }); + + const FederationInstance = new Embedded.FederationHost({ + name: '@federation-test/globalinfo', + remotes: [ + { + name: '@federation-test/app2', + version: '*', + }, + ], + }); + + await FederationInstance.loadRemote<() => string>( + '@federation-test/app2/say', + ); + // @ts-ignore fakeSrc is local mock attr, which value is the same as src + const loadedSrcs = [...document.querySelectorAll('script')].map( + (i) => (i as any).fakeSrc, + ); + expect(loadedSrcs.includes(`${remotePublicPath}${jsSyncAssetPath}`)); + reset(); + }); + }); + }); +}); diff --git a/packages/runtime/src/embedded.ts b/packages/runtime/src/embedded.ts new file mode 100644 index 00000000000..d0dd896eed8 --- /dev/null +++ b/packages/runtime/src/embedded.ts @@ -0,0 +1,366 @@ +import type * as IndexModule from './index'; + +function getRuntime(): typeof IndexModule { + // @ts-ignore + const runtime = __webpack_require__.federation.runtime as typeof IndexModule; + if (!runtime) { + throw new Error( + 'Federation runtime accessed before instantiation or installation', + ); + } + return runtime; +} + +export const registerGlobalPlugins: typeof IndexModule.registerGlobalPlugins = ( + ...args +) => { + return getRuntime().registerGlobalPlugins(...args); +}; + +export const getRemoteEntry: typeof IndexModule.getRemoteEntry = (...args) => { + return getRuntime().getRemoteEntry(...args); +}; + +export const getRemoteInfo: typeof IndexModule.getRemoteInfo = (...args) => { + return getRuntime().getRemoteInfo(...args); +}; + +export const loadScript: typeof IndexModule.loadScript = (...args) => { + return getRuntime().loadScript(...args); +}; + +export const loadScriptNode: typeof IndexModule.loadScriptNode = (...args) => { + return getRuntime().loadScriptNode(...args); +}; + +export const init: typeof IndexModule.init = (...args) => { + return getRuntime().init(...args); +}; + +export const loadRemote: typeof IndexModule.loadRemote = (...args) => { + return getRuntime().loadRemote(...args); +}; + +export const loadShare: typeof IndexModule.loadShare = (...args) => { + return getRuntime().loadShare(...args); +}; + +export const loadShareSync: typeof IndexModule.loadShareSync = (...args) => { + return getRuntime().loadShareSync(...args); +}; + +export const preloadRemote: typeof IndexModule.preloadRemote = (...args) => { + return getRuntime().preloadRemote(...args); +}; + +export const registerRemotes: typeof IndexModule.registerRemotes = ( + ...args +) => { + return getRuntime().registerRemotes(...args); +}; + +export const registerPlugins: typeof IndexModule.registerPlugins = ( + ...args +) => { + return getRuntime().registerPlugins(...args); +}; + +export const getInstance: typeof IndexModule.getInstance = (...args) => { + return getRuntime().getInstance(...args); +}; + +export class FederationHost implements IndexModule.FederationHost { + private _instance: IndexModule.FederationHost | null = null; + private _args: ConstructorParameters; + + constructor( + ...args: ConstructorParameters + ) { + this._args = args; + const RealFederationHost = getRuntime().FederationHost; + this._instance = new RealFederationHost(...this._args); + } + + private _getInstance(): IndexModule.FederationHost { + if (!this._instance) { + const RealFederationHost = getRuntime().FederationHost; + this._instance = new RealFederationHost(...this._args); + } + return this._instance; + } + + get options() { + return this._getInstance().options; + } + + set options(value) { + this._getInstance().options = value; + } + + get hooks() { + return this._getInstance().hooks; + } + + get version() { + return this._getInstance().version; + } + + get name() { + return this._getInstance().name; + } + + get moduleCache() { + return this._getInstance().moduleCache; + } + + get snapshotHandler() { + return this._getInstance().snapshotHandler; + } + + get sharedHandler() { + return this._getInstance().sharedHandler; + } + + get remoteHandler() { + return this._getInstance().remoteHandler; + } + + get shareScopeMap() { + return this._getInstance().shareScopeMap; + } + + get loaderHook() { + return this._getInstance().loaderHook; + } + + get bridgeHook() { + return this._getInstance().bridgeHook; + } + + initOptions(...args: Parameters) { + return this._getInstance().initOptions(...args); + } + + loadShare(...args: Parameters) { + return this._getInstance().loadShare(...args); + } + + loadShareSync( + ...args: Parameters + ) { + return this._getInstance().loadShareSync(...args); + } + + initializeSharing( + ...args: Parameters + ) { + return this._getInstance().initializeSharing(...args); + } + + initRawContainer( + ...args: Parameters + ) { + return this._getInstance().initRawContainer(...args); + } + + loadRemote(...args: Parameters) { + return this._getInstance().loadRemote(...args); + } + + preloadRemote( + ...args: Parameters + ) { + return this._getInstance().preloadRemote(...args); + } + + initShareScopeMap( + ...args: Parameters + ) { + return this._getInstance().initShareScopeMap(...args); + } + + registerPlugins( + ...args: Parameters + ) { + return this._getInstance().registerPlugins(...args); + } + + registerRemotes( + ...args: Parameters + ) { + return this._getInstance().registerRemotes(...args); + } + + formatOptions( + ...args: Parameters + ) { + //@ts-ignore + return this._getInstance().formatOptions(...args); + } +} + +export interface ModuleInterface { + remoteInfo: IndexModule.Module['remoteInfo']; + inited: IndexModule.Module['inited']; + lib: IndexModule.Module['lib']; + host: IndexModule.Module['host']; + + getEntry( + ...args: Parameters + ): ReturnType; + get( + ...args: Parameters + ): ReturnType; +} + +export class Module implements ModuleInterface { + private _instance: IndexModule.Module | null = null; + private _args: ConstructorParameters; + + constructor(...args: ConstructorParameters) { + this._args = args; + } + + private _getInstance(): IndexModule.Module { + if (!this._instance) { + const RealModule = getRuntime().Module; + this._instance = new RealModule(...this._args); + } + return this._instance; + } + + get remoteInfo() { + return this._getInstance().remoteInfo; + } + + set remoteInfo(value) { + this._getInstance().remoteInfo = value; + } + + get inited() { + return this._getInstance().inited; + } + + set inited(value) { + this._getInstance().inited = value; + } + + get lib() { + return this._getInstance().lib; + } + + set lib(value) { + this._getInstance().lib = value; + } + + get host() { + return this._getInstance().host; + } + + set host(value) { + this._getInstance().host = value; + } + + async getEntry(...args: Parameters) { + return this._getInstance().getEntry(...args); + } + + async get(...args: Parameters) { + return this._getInstance().get(...args); + } + + private wraperFactory( + ...args: Parameters + ) { + //@ts-ignore + return this._getInstance().wraperFactory(...args); + } +} + +//maybe use proxy? +//export class Module implements ModuleInterface { +// private _instance: IndexModule.Module | null = null; +// private _args: ConstructorParameters; +// constructor(...args: ConstructorParameters) { +// this._args = args; +// return new Proxy(this, { +// get(target, prop) { +// if (prop in target) { +// return target[prop as keyof Module]; +// } +// const instance = target._getInstance(); +// const value = instance[prop as keyof IndexModule.Module]; +// return typeof value === 'function' ? value.bind(instance) : value; +// }, +// set(target, prop, value) { +// const instance = target._getInstance(); +// instance[prop as keyof IndexModule.Module] = value; +// return true; +// }, +// }); +// } +// private _getInstance(): IndexModule.Module { +// if (!this._instance) { +// const RealModule = getRuntime().Module; +// this._instance = new RealModule(...this._args); +// } +// return this._instance; +// } +// // Keep only the methods that have custom logic +// private wraperFactory(...args: Parameters) { +// return this._getInstance().wraperFactory(...args); +// } +// } +//export class FederationHost implements IndexModule.FederationHost { +// private _instance: IndexModule.FederationHost | null = null; +// private _args: ConstructorParameters; +// constructor(...args: ConstructorParameters) { +// this._args = args; +// return new Proxy(this, { +// get(target, prop) { +// if (prop in target) { +// return target[prop as keyof FederationHost]; +// } +// const instance = target._getInstance(); +// const value = instance[prop as keyof IndexModule.FederationHost]; +// return typeof value === 'function' ? value.bind(instance) : value; +// }, +// set(target, prop, value) { +// const instance = target._getInstance(); +// instance[prop as keyof IndexModule.FederationHost] = value; +// return true; +// }, +// }); +// } +// private _getInstance(): IndexModule.FederationHost { +// if (!this._instance) { +// const RealFederationHost = getRuntime().FederationHost; +// this._instance = new RealFederationHost(...this._args); +// } +// return this._instance; +// } +// // Keep only the methods that have custom logic +// formatOptions(...args: Parameters) { +// return this._getInstance().formatOptions(...args); +// } +// } +//function createRuntimeFunction( +// name: T +// ): typeof IndexModule[T] { +// return (...args: any[]) => { +// return getRuntime()[name](...args); +// }; +// } +// export const registerGlobalPlugins = createRuntimeFunction('registerGlobalPlugins'); +// export const getRemoteEntry = createRuntimeFunction('getRemoteEntry'); +// export const getRemoteInfo = createRuntimeFunction('getRemoteInfo'); +// export const loadScript = createRuntimeFunction('loadScript'); +// export const loadScriptNode = createRuntimeFunction('loadScriptNode'); +// export const init = createRuntimeFunction('init'); +// export const loadRemote = createRuntimeFunction('loadRemote'); +// export const loadShare = createRuntimeFunction('loadShare'); +// export const loadShareSync = createRuntimeFunction('loadShareSync'); +// export const preloadRemote = createRuntimeFunction('preloadRemote'); +// export const registerRemotes = createRuntimeFunction('registerRemotes'); +// export const registerPlugins = createRuntimeFunction('registerPlugins'); +// export const getInstance = createRuntimeFunction('getInstance');