From bda3d12aefa1ad39f278a303f74bc14ed7ec7666 Mon Sep 17 00:00:00 2001 From: Hugo Burton Date: Mon, 29 Sep 2025 17:30:30 +1000 Subject: [PATCH 1/9] docs: fix missing quote in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 54a535be..a31c6a25 100644 --- a/README.md +++ b/README.md @@ -466,7 +466,7 @@ function __federation_method_setRemote(name: string, config: IRemoteConfig): voi interface IRemoteConfig { url: (() => Promise) | string; format: "esm" | "systemjs" | "var"; - from: "vite" | "webpack; + from: "vite" | "webpack"; } ``` From 4f503ddad1050d189412dc817bf0918535a9845b Mon Sep 17 00:00:00 2001 From: Hugo Burton Date: Mon, 29 Sep 2025 17:34:33 +1000 Subject: [PATCH 2/9] feat: cjs interop initial commit --- packages/lib/src/prod/remote-production.ts | 47 +++++++++++++++++++--- packages/lib/src/utils/index.ts | 1 + 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/packages/lib/src/prod/remote-production.ts b/packages/lib/src/prod/remote-production.ts index 3476286b..7c69bfc0 100644 --- a/packages/lib/src/prod/remote-production.ts +++ b/packages/lib/src/prod/remote-production.ts @@ -34,6 +34,7 @@ import { createRemotesMap, getModuleMarker, parseRemoteOptions, + REMOTE_FORMAT_PARAMETER, REMOTE_FROM_PARAMETER, injectToHead, toPreloadTag @@ -146,11 +147,39 @@ export function prodRemotePlugin( return mergedObj; } - const wrapShareModule = ${REMOTE_FROM_PARAMETER} => { - return merge({ + const wrapShareModule = (${REMOTE_FORMAT_PARAMETER}, ${REMOTE_FROM_PARAMETER}) => { + const scope = (globalThis.__federation_shared__ || {})['${shareScope}'] || {}; + const merged = { ${getModuleMarker('shareScope')} - }, (globalThis.__federation_shared__ || {})['${shareScope}'] || {}); - } + }; + + for (const key in scope) { + const versions = scope[key]; + const normalized = {}; + for (const ver in versions) { + const conf = versions[ver]; + normalized[ver] = { + ...conf, + get: async () => { + const mod = await conf.get(); + + // If the remote is webpack/var, normalize to CJS-style (unwrap .default) + if (${REMOTE_FROM_PARAMETER} === 'webpack' || ${REMOTE_FORMAT_PARAMETER} === 'var') { + return (mod?.__esModule || mod?.[Symbol.toStringTag] === 'Module') + ? mod.default + : mod; + } + + // Otherwise (vite/esm), leave as is + return mod; + }, + }; + } + merged[key] = normalized; + } + + return merged; + }; async function __federation_import(name) { currentImports[name] ??= import(name) @@ -168,7 +197,7 @@ export function prodRemotePlugin( const callback = () => { if (!remote.inited) { remote.lib = window[remoteId]; - remote.lib.init(wrapShareModule(remote.from)) + remote.lib.init(wrapShareModule(remote.format, remote.from)) remote.inited = true; } resolve(remote.lib); @@ -182,7 +211,7 @@ export function prodRemotePlugin( getUrl().then(url => { import(/* @vite-ignore */ url).then(lib => { if (!remote.inited) { - const shareScope = wrapShareModule(remote.from); + const shareScope = wrapShareModule(remote.format, remote.from); lib.init(shareScope); remote.lib = lib; remote.lib.init(shareScope); @@ -292,6 +321,12 @@ export function prodRemotePlugin( if (typeof obj === 'object') { const fileUrl = `import.meta.ROLLUP_FILE_URL_${obj.emitFile}` str += `get:()=>get(${fileUrl}, ${REMOTE_FROM_PARAMETER}), loaded:1` + // If interop === 'cjs', unwrap ESM namespace to default before seeding the share scope. + // Otherwise (default 'esm'), keep the module as-is. + // const needsCJS = obj.interop === 'cjs' + // str += `get:()=>get(${fileUrl}, ${REMOTE_FROM_PARAMETER})${ + // needsCJS ? '.then(m=>__federation_method_unwrapDefault(m))' : '' + // }, loaded:1` res.push(`'${arr[0]}':{'${obj.version}':{${str}}}`) } }) diff --git a/packages/lib/src/utils/index.ts b/packages/lib/src/utils/index.ts index 54f902fd..b6927447 100644 --- a/packages/lib/src/utils/index.ts +++ b/packages/lib/src/utils/index.ts @@ -251,5 +251,6 @@ export function getFileExtname(url: string): string { return path.extname(fileName) } +export const REMOTE_FORMAT_PARAMETER = 'remoteFormat' export const REMOTE_FROM_PARAMETER = 'remoteFrom' export const NAME_CHAR_REG = new RegExp('[0-9a-zA-Z@_-]+') From 9ab0ea1ee63e071206c4610d3f98581a2be51790 Mon Sep 17 00:00:00 2001 From: Hugo Burton Date: Mon, 29 Sep 2025 22:08:19 +1000 Subject: [PATCH 3/9] fix: apply cjs interop after factory loaded We do it this way to prevent reloading a share scope module --- packages/lib/src/prod/remote-production.ts | 59 ++++++++-------------- 1 file changed, 20 insertions(+), 39 deletions(-) diff --git a/packages/lib/src/prod/remote-production.ts b/packages/lib/src/prod/remote-production.ts index 7c69bfc0..d1015d58 100644 --- a/packages/lib/src/prod/remote-production.ts +++ b/packages/lib/src/prod/remote-production.ts @@ -34,7 +34,6 @@ import { createRemotesMap, getModuleMarker, parseRemoteOptions, - REMOTE_FORMAT_PARAMETER, REMOTE_FROM_PARAMETER, injectToHead, toPreloadTag @@ -147,39 +146,11 @@ export function prodRemotePlugin( return mergedObj; } - const wrapShareModule = (${REMOTE_FORMAT_PARAMETER}, ${REMOTE_FROM_PARAMETER}) => { - const scope = (globalThis.__federation_shared__ || {})['${shareScope}'] || {}; - const merged = { + const wrapShareModule = ${REMOTE_FROM_PARAMETER} => { + return merge({ ${getModuleMarker('shareScope')} - }; - - for (const key in scope) { - const versions = scope[key]; - const normalized = {}; - for (const ver in versions) { - const conf = versions[ver]; - normalized[ver] = { - ...conf, - get: async () => { - const mod = await conf.get(); - - // If the remote is webpack/var, normalize to CJS-style (unwrap .default) - if (${REMOTE_FROM_PARAMETER} === 'webpack' || ${REMOTE_FORMAT_PARAMETER} === 'var') { - return (mod?.__esModule || mod?.[Symbol.toStringTag] === 'Module') - ? mod.default - : mod; - } - - // Otherwise (vite/esm), leave as is - return mod; - }, - }; - } - merged[key] = normalized; - } - - return merged; - }; + }, (globalThis.__federation_shared__ || {})['${shareScope}'] || {}); + } async function __federation_import(name) { currentImports[name] ??= import(name) @@ -197,10 +168,10 @@ export function prodRemotePlugin( const callback = () => { if (!remote.inited) { remote.lib = window[remoteId]; - remote.lib.init(wrapShareModule(remote.format, remote.from)) + remote.lib.init(wrapShareModule(remote.from)); remote.inited = true; } - resolve(remote.lib); + resolve(remote); } return loadJS(remote.url, callback); }); @@ -211,19 +182,19 @@ export function prodRemotePlugin( getUrl().then(url => { import(/* @vite-ignore */ url).then(lib => { if (!remote.inited) { - const shareScope = wrapShareModule(remote.format, remote.from); + const shareScope = wrapShareModule(remote.from); lib.init(shareScope); remote.lib = lib; remote.lib.init(shareScope); remote.inited = true; } - resolve(remote.lib); + resolve(remote); }).catch(reject) }) }) } } else { - return remote.lib; + return remote; } } @@ -242,7 +213,17 @@ export function prodRemotePlugin( } function __federation_method_getRemote(remoteName, componentName) { - return __federation_method_ensure(remoteName).then((remote) => remote.get(componentName).then(factory => factory())); + return __federation_method_ensure(remoteName).then(async (remote) => { + const factory = await remote.lib.get(componentName); + const module = factory(); + if (remote.format === 'var') { + // CJS interop compatibility in nested federations + // in case loaded shareScope is ESM + return __federation_method_unwrapDefault(module); + } else { + return module; + } + }); } function __federation_method_setRemote(remoteName, remoteConfig) { From 91a7ee4a3ea23a552cdae5cac8f16b2d314856bf Mon Sep 17 00:00:00 2001 From: Hugo Burton Date: Mon, 29 Sep 2025 22:20:43 +1000 Subject: [PATCH 4/9] fix: applying esinterop --- packages/lib/src/prod/remote-production.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/lib/src/prod/remote-production.ts b/packages/lib/src/prod/remote-production.ts index d1015d58..8f7c8dbf 100644 --- a/packages/lib/src/prod/remote-production.ts +++ b/packages/lib/src/prod/remote-production.ts @@ -152,6 +152,16 @@ export function prodRemotePlugin( }, (globalThis.__federation_shared__ || {})['${shareScope}'] || {}); } + const factoryESInterop = (remote, factory) => { + if (remote.format === 'var') { + // CJS interop compatibility in nested federations + // in case loaded shareScope is ESM + return __federation_method_unwrapDefault(factory); + } else { + return factory; + } + } + async function __federation_import(name) { currentImports[name] ??= import(name) return currentImports[name] @@ -215,14 +225,8 @@ export function prodRemotePlugin( function __federation_method_getRemote(remoteName, componentName) { return __federation_method_ensure(remoteName).then(async (remote) => { const factory = await remote.lib.get(componentName); - const module = factory(); - if (remote.format === 'var') { - // CJS interop compatibility in nested federations - // in case loaded shareScope is ESM - return __federation_method_unwrapDefault(module); - } else { - return module; - } + const interopFactory = factoryESInterop(remote, factory); + return interopFactory(); }); } From d2a60b5e731f3e399bfc66d4c36b6cf5a036d757 Mon Sep 17 00:00:00 2001 From: Hugo Burton Date: Mon, 29 Sep 2025 23:09:11 +1000 Subject: [PATCH 5/9] style: syntax formatting --- packages/lib/src/dev/remote-development.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/lib/src/dev/remote-development.ts b/packages/lib/src/dev/remote-development.ts index 15e57381..d7bb93cf 100644 --- a/packages/lib/src/dev/remote-development.ts +++ b/packages/lib/src/dev/remote-development.ts @@ -141,7 +141,7 @@ function __federation_method_wrapDefault(module ,need){ return module; } -function __federation_method_getRemote(remoteName, componentName){ +function __federation_method_getRemote(remoteName, componentName) { return __federation_method_ensure(remoteName).then((remote) => remote.get(componentName).then(factory => factory())); } @@ -395,7 +395,9 @@ export {__federation_method_ensure, __federation_method_getRemote , __federation const idx = moduleFilePath.indexOf(cwdPath) const relativePath = - idx === 0 ? posix.join(base, moduleFilePath.slice(cwdPath.length)) : null + idx === 0 + ? posix.join(base, moduleFilePath.slice(cwdPath.length)) + : null const sharedName = item[0] const obj = item[1] @@ -406,7 +408,7 @@ export {__federation_method_ensure, __federation_method_getRemote , __federation const url = origin ? `'${origin}${pathname}'` : `window.location.origin+'${pathname}'` - str += `get:()=> get(${url}, ${REMOTE_FROM_PARAMETER})` + str += `get:() => get(${url}, ${REMOTE_FROM_PARAMETER})` res.push(`'${sharedName}':{'${obj.version}':{${str}}}`) } } From abcda9c35b32854ccd6b3246aad44f6171c18bd5 Mon Sep 17 00:00:00 2001 From: Hugo Burton Date: Mon, 29 Sep 2025 23:30:32 +1000 Subject: [PATCH 6/9] fix: load remote from via globalThis --- packages/lib/src/dev/remote-development.ts | 2 +- packages/lib/src/prod/remote-production.ts | 38 ++++++++++------------ 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/packages/lib/src/dev/remote-development.ts b/packages/lib/src/dev/remote-development.ts index d7bb93cf..cc123259 100644 --- a/packages/lib/src/dev/remote-development.ts +++ b/packages/lib/src/dev/remote-development.ts @@ -76,7 +76,7 @@ const loadJS = async (url, fn) => { } function get(name, ${REMOTE_FROM_PARAMETER}){ return import(/* @vite-ignore */ name).then(module => ()=> { - if (${REMOTE_FROM_PARAMETER} === 'webpack') { + if ((globalThis.__federation_shared_remote_from__ ?? ${REMOTE_FROM_PARAMETER}) === 'webpack') { return Object.prototype.toString.call(module).indexOf('Module') > -1 && module.default ? module.default : module } return module diff --git a/packages/lib/src/prod/remote-production.ts b/packages/lib/src/prod/remote-production.ts index 8f7c8dbf..c36c3fb8 100644 --- a/packages/lib/src/prod/remote-production.ts +++ b/packages/lib/src/prod/remote-production.ts @@ -129,7 +129,7 @@ export function prodRemotePlugin( function get(name, ${REMOTE_FROM_PARAMETER}) { return __federation_import(name).then(module => () => { - if (${REMOTE_FROM_PARAMETER} === 'webpack') { + if ((globalThis.__federation_shared_remote_from__ ?? ${REMOTE_FROM_PARAMETER}) === 'webpack') { return Object.prototype.toString.call(module).indexOf('Module') > -1 && module.default ? module.default : module } return module @@ -147,21 +147,12 @@ export function prodRemotePlugin( } const wrapShareModule = ${REMOTE_FROM_PARAMETER} => { + globalThis.__federation_shared_remote_from__ = ${REMOTE_FROM_PARAMETER}; return merge({ ${getModuleMarker('shareScope')} }, (globalThis.__federation_shared__ || {})['${shareScope}'] || {}); } - - const factoryESInterop = (remote, factory) => { - if (remote.format === 'var') { - // CJS interop compatibility in nested federations - // in case loaded shareScope is ESM - return __federation_method_unwrapDefault(factory); - } else { - return factory; - } - } - + async function __federation_import(name) { currentImports[name] ??= import(name) return currentImports[name] @@ -181,7 +172,7 @@ export function prodRemotePlugin( remote.lib.init(wrapShareModule(remote.from)); remote.inited = true; } - resolve(remote); + resolve(remote.lib); } return loadJS(remote.url, callback); }); @@ -198,13 +189,13 @@ export function prodRemotePlugin( remote.lib.init(shareScope); remote.inited = true; } - resolve(remote); + resolve(remote.lib); }).catch(reject) }) }) } } else { - return remote; + return remote.lib; } } @@ -224,9 +215,8 @@ export function prodRemotePlugin( function __federation_method_getRemote(remoteName, componentName) { return __federation_method_ensure(remoteName).then(async (remote) => { - const factory = await remote.lib.get(componentName); - const interopFactory = factoryESInterop(remote, factory); - return interopFactory(); + const factory = await remote.get(componentName); + return factory(); }); } @@ -300,12 +290,14 @@ export function prodRemotePlugin( if (builderInfo.isHost) { if (id === '\0virtual:__federation__') { const res: string[] = [] + console.log('parsed options prod shared', parsedOptions.prodShared) parsedOptions.prodShared.forEach((arr) => { const obj = arr[1] let str = '' if (typeof obj === 'object') { + console.log('Replacing host code', arr) const fileUrl = `import.meta.ROLLUP_FILE_URL_${obj.emitFile}` - str += `get:()=>get(${fileUrl}, ${REMOTE_FROM_PARAMETER}), loaded:1` + str += `get:() => get(${fileUrl}, ${REMOTE_FROM_PARAMETER}), loaded:1` // If interop === 'cjs', unwrap ESM namespace to default before seeding the share scope. // Otherwise (default 'esm'), keep the module as-is. // const needsCJS = obj.interop === 'cjs' @@ -315,7 +307,13 @@ export function prodRemotePlugin( res.push(`'${arr[0]}':{'${obj.version}':{${str}}}`) } }) - return code.replace(getModuleMarker('shareScope'), res.join(',')) + const moduleMarker = getModuleMarker('shareScope') + console.log('Module marker', moduleMarker) + const replacement = res.join(', ') + console.log('Replacement:', replacement) + const replacedCode = code.replace(moduleMarker, replacement) + console.debug('Replaced code', replacedCode) + return replacedCode } } From d304eb3302ab5f944515e648151777cccc571cce Mon Sep 17 00:00:00 2001 From: Hugo Burton Date: Mon, 29 Sep 2025 23:50:48 +1000 Subject: [PATCH 7/9] fix: remove commented code --- packages/lib/src/prod/remote-production.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/lib/src/prod/remote-production.ts b/packages/lib/src/prod/remote-production.ts index c36c3fb8..0f2af6a7 100644 --- a/packages/lib/src/prod/remote-production.ts +++ b/packages/lib/src/prod/remote-production.ts @@ -290,20 +290,12 @@ export function prodRemotePlugin( if (builderInfo.isHost) { if (id === '\0virtual:__federation__') { const res: string[] = [] - console.log('parsed options prod shared', parsedOptions.prodShared) parsedOptions.prodShared.forEach((arr) => { const obj = arr[1] let str = '' if (typeof obj === 'object') { - console.log('Replacing host code', arr) const fileUrl = `import.meta.ROLLUP_FILE_URL_${obj.emitFile}` str += `get:() => get(${fileUrl}, ${REMOTE_FROM_PARAMETER}), loaded:1` - // If interop === 'cjs', unwrap ESM namespace to default before seeding the share scope. - // Otherwise (default 'esm'), keep the module as-is. - // const needsCJS = obj.interop === 'cjs' - // str += `get:()=>get(${fileUrl}, ${REMOTE_FROM_PARAMETER})${ - // needsCJS ? '.then(m=>__federation_method_unwrapDefault(m))' : '' - // }, loaded:1` res.push(`'${arr[0]}':{'${obj.version}':{${str}}}`) } }) From 34645586231a4e5266e804e746e2ea346af99c79 Mon Sep 17 00:00:00 2001 From: Hugo Burton Date: Mon, 29 Sep 2025 23:51:22 +1000 Subject: [PATCH 8/9] fix: remove console logs + unused code --- packages/lib/src/prod/remote-production.ts | 8 +------- packages/lib/src/utils/index.ts | 1 - 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/lib/src/prod/remote-production.ts b/packages/lib/src/prod/remote-production.ts index 0f2af6a7..69b8bcfd 100644 --- a/packages/lib/src/prod/remote-production.ts +++ b/packages/lib/src/prod/remote-production.ts @@ -299,13 +299,7 @@ export function prodRemotePlugin( res.push(`'${arr[0]}':{'${obj.version}':{${str}}}`) } }) - const moduleMarker = getModuleMarker('shareScope') - console.log('Module marker', moduleMarker) - const replacement = res.join(', ') - console.log('Replacement:', replacement) - const replacedCode = code.replace(moduleMarker, replacement) - console.debug('Replaced code', replacedCode) - return replacedCode + return code.replace(getModuleMarker('shareScope'), res.join(',')) } } diff --git a/packages/lib/src/utils/index.ts b/packages/lib/src/utils/index.ts index b6927447..54f902fd 100644 --- a/packages/lib/src/utils/index.ts +++ b/packages/lib/src/utils/index.ts @@ -251,6 +251,5 @@ export function getFileExtname(url: string): string { return path.extname(fileName) } -export const REMOTE_FORMAT_PARAMETER = 'remoteFormat' export const REMOTE_FROM_PARAMETER = 'remoteFrom' export const NAME_CHAR_REG = new RegExp('[0-9a-zA-Z@_-]+') From fad88035090ffa8eaf6c28a4d00dd94988c58259 Mon Sep 17 00:00:00 2001 From: Hugo Burton Date: Mon, 29 Sep 2025 23:53:05 +1000 Subject: [PATCH 9/9] refactor: restore inline ensure promise callback --- packages/lib/src/prod/remote-production.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/lib/src/prod/remote-production.ts b/packages/lib/src/prod/remote-production.ts index 69b8bcfd..dacefc78 100644 --- a/packages/lib/src/prod/remote-production.ts +++ b/packages/lib/src/prod/remote-production.ts @@ -214,10 +214,7 @@ export function prodRemotePlugin( } function __federation_method_getRemote(remoteName, componentName) { - return __federation_method_ensure(remoteName).then(async (remote) => { - const factory = await remote.get(componentName); - return factory(); - }); + return __federation_method_ensure(remoteName).then((remote) => remote.get(componentName).then(factory => factory())); } function __federation_method_setRemote(remoteName, remoteConfig) {