From 84223bfe471b51e1b4c99c7cb8345e8937c44b9a Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Wed, 13 Dec 2023 09:50:34 -0700 Subject: [PATCH 01/27] feat(dev): add support for using the vite dev server on remotes close #204 --- packages/lib/src/dev/expose-development.ts | 63 ++++++++++- packages/lib/src/dev/remote-development.ts | 118 ++++++++++++++++++++- 2 files changed, 177 insertions(+), 4 deletions(-) diff --git a/packages/lib/src/dev/expose-development.ts b/packages/lib/src/dev/expose-development.ts index 35d94ca0..c97fc7f7 100644 --- a/packages/lib/src/dev/expose-development.ts +++ b/packages/lib/src/dev/expose-development.ts @@ -13,17 +13,74 @@ // SPDX-License-Identifier: MulanPSL-2.0 // ***************************************************************************** -import { parseExposeOptions } from '../utils' -import { parsedOptions } from '../public' +import { resolve } from 'path' +import { getModuleMarker, normalizePath, parseExposeOptions } from '../utils' +import { EXTERNALS, SHARED, builderInfo, parsedOptions } from '../public' import type { VitePluginFederationOptions } from 'types' import type { PluginHooks } from '../../types/pluginHooks' +import { ViteDevServer } from 'vite' export function devExposePlugin( options: VitePluginFederationOptions ): PluginHooks { parsedOptions.devExpose = parseExposeOptions(options) + let moduleMap = '' + + // exposes module + for (const item of parsedOptions.devExpose) { + const moduleName = getModuleMarker(`\${${item[0]}}`, SHARED) + EXTERNALS.push(moduleName) + const importPath = normalizePath(item[1].import) + const exposeFilepath = normalizePath(resolve(item[1].import)) + moduleMap += `\n"${item[0]}":() => { + return __federation_import('/${importPath}', '/@fs/${exposeFilepath}').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},` + } + const remoteFile = ` + const exportSet = new Set(['Module', '__esModule', 'default', '_export_sfc']); + let moduleMap = { + ${moduleMap} + }; + const __federation_import = async (urlImportPath, fsImportPath) => { + let importedModule; + try { + return await import(urlImportPath); + }catch(ex) { + return await import(fsImportPath) + } + }; + export const get =(module) => { + if(!moduleMap[module]) throw new Error('Can not find remote module ' + module) + return moduleMap[module](); + }; + export const init =(shareScope) => { + globalThis.__federation_shared__= globalThis.__federation_shared__|| {}; + Object.entries(shareScope).forEach(([key, value]) => { + const versionKey = Object.keys(value)[0]; + const versionValue = Object.values(value)[0]; + const scope = versionValue.scope || 'default' + globalThis.__federation_shared__[scope] = globalThis.__federation_shared__[scope] || {}; + const shared= globalThis.__federation_shared__[scope]; + (shared[key] = shared[key]||{})[versionKey] = versionValue; + }); + } + ` return { - name: 'originjs:expose-development' + name: 'originjs:expose-development', + configureServer: (server: ViteDevServer) => { + const remoteFilePath = `${builderInfo.assetsDir}/${options.filename}` + server.middlewares.use((req, res, next) => { + if (req.url && req.url.includes(remoteFilePath)) { + res.writeHead(200, 'OK', { + 'Content-Type': 'text/javascript', + 'Access-Control-Allow-Origin': '*' + }) + res.write(remoteFile) + res.end() + } else { + next() + } + }) + } } } diff --git a/packages/lib/src/dev/remote-development.ts b/packages/lib/src/dev/remote-development.ts index a107556a..2b5ec180 100644 --- a/packages/lib/src/dev/remote-development.ts +++ b/packages/lib/src/dev/remote-development.ts @@ -14,7 +14,11 @@ // ***************************************************************************** import type { UserConfig } from 'vite' -import type { ConfigTypeSet, VitePluginFederationOptions } from 'types' +import type { + ConfigTypeSet, + ExposesConfig, + VitePluginFederationOptions +} from 'types' import { walk } from 'estree-walker' import MagicString from 'magic-string' import { readFileSync } from 'fs' @@ -31,6 +35,41 @@ import { import { builderInfo, parsedOptions, devRemotes } from '../public' import type { PluginHooks } from '../../types/pluginHooks' +const exposedItems: string[] = [] + +const importShared = ` +const importShared = async (name, shareScope = 'default') => { + return (await getSharedFromRuntime(name, shareScope)) || getSharedFromLocal(name) +} +const getSharedFromRuntime = async (name, shareScope) => { + let module = null + if (globalThis?.__federation_shared__?.[shareScope]?.[name]) { + const versionObj = globalThis.__federation_shared__[shareScope][name] + const versionKey = Object.keys(versionObj)[0] + const versionValue = Object.values(versionObj)[0] + module = await (await versionValue.get())() + } + if (module) { + return flattenModule(module, name) + } +} +const getSharedFromLocal = async (name) => { + let module = await (await moduleMap[name].get())() + return flattenModule(module, name) +} +const flattenModule = (module, name) => { + if (typeof module.default === 'function') { + Object.keys(module).forEach((key) => { + if (key !== 'default') { + module.default[key] = module[key] + } + }) + return module.default + } + if (module.default) module = Object.assign({}, module.default, module) + return module +}` + export function devRemotePlugin( options: VitePluginFederationOptions ): PluginHooks { @@ -229,6 +268,60 @@ export {__federation_method_ensure, __federation_method_getRemote , __federation ) { manualRequired = node } + if ( + isExposed(id, parsedOptions.devExpose) && + node.type === 'ImportDeclaration' && + node.source?.value + ) { + const moduleName = node.source.value + if ( + parsedOptions.devShared.some( + (sharedInfo) => sharedInfo[0] === moduleName + ) + ) { + const namedImportDeclaration: (string | never)[] = [] + let defaultImportDeclaration: string | null = null + if (!node.specifiers?.length) { + // invalid import , like import './__federation_shared_lib.js' , and remove it + magicString.remove(node.start, node.end) + } else { + node.specifiers.forEach((specify) => { + if (specify.imported?.name) { + namedImportDeclaration.push( + `${ + specify.imported.name === specify.local.name + ? specify.imported.name + : `${specify.imported.name}:${specify.local.name}` + }` + ) + } else { + defaultImportDeclaration = specify.local.name + } + }) + + if (defaultImportDeclaration && namedImportDeclaration.length) { + // import a, {b} from 'c' -> const a = await importShared('c'); const {b} = a; + const imports = namedImportDeclaration.join(',') + const line = `${importShared}\n const ${defaultImportDeclaration} = await importShared('${moduleName}');\nconst {${imports}} = ${defaultImportDeclaration};\n` + magicString.overwrite(node.start, node.end, line) + } else if (defaultImportDeclaration) { + magicString.overwrite( + node.start, + node.end, + `${importShared}\n const ${defaultImportDeclaration} = await importShared('${moduleName}');\n` + ) + } else if (namedImportDeclaration.length) { + magicString.overwrite( + node.start, + node.end, + `${importShared}\n const {${namedImportDeclaration.join( + ',' + )}} = await importShared('${moduleName}');\n` + ) + } + } + } + } if ( (node.type === 'ImportExpression' || @@ -411,4 +504,27 @@ export {__federation_method_ensure, __federation_method_getRemote , __federation } return res } + function isExposed(id: string, options: (string | ConfigTypeSet)[]) { + if (exposedItems.includes(id)) { + return true + } + if (options.length >= 2 && (options[1] as ExposesConfig).import) { + if (normalizePath((options[1] as ExposesConfig).import)) { + return true + } + } + for (let i = 0, length = options.length; i < length; i++) { + const item = options[i] + if ( + Array.isArray(item) && + item.length >= 2 && + (item[1] as ExposesConfig).import + ) { + if (normalizePath((item[1] as ExposesConfig).import)) { + return true + } + } + } + return false + } } From 15cf8f15d653e10c88b33740c41b540bccfdbad0 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Wed, 13 Dec 2023 14:21:44 -0700 Subject: [PATCH 02/27] Make it so that when multiple dependencies are loaded, importShared isn't re-declared --- packages/lib/src/dev/remote-development.ts | 91 ++++++++++++++-------- 1 file changed, 58 insertions(+), 33 deletions(-) diff --git a/packages/lib/src/dev/remote-development.ts b/packages/lib/src/dev/remote-development.ts index 2b5ec180..e6cd3ca4 100644 --- a/packages/lib/src/dev/remote-development.ts +++ b/packages/lib/src/dev/remote-development.ts @@ -37,38 +37,52 @@ import type { PluginHooks } from '../../types/pluginHooks' const exposedItems: string[] = [] -const importShared = ` -const importShared = async (name, shareScope = 'default') => { - return (await getSharedFromRuntime(name, shareScope)) || getSharedFromLocal(name) -} -const getSharedFromRuntime = async (name, shareScope) => { - let module = null - if (globalThis?.__federation_shared__?.[shareScope]?.[name]) { - const versionObj = globalThis.__federation_shared__[shareScope][name] - const versionKey = Object.keys(versionObj)[0] - const versionValue = Object.values(versionObj)[0] - module = await (await versionValue.get())() - } - if (module) { - return flattenModule(module, name) - } -} -const getSharedFromLocal = async (name) => { - let module = await (await moduleMap[name].get())() - return flattenModule(module, name) -} -const flattenModule = (module, name) => { - if (typeof module.default === 'function') { - Object.keys(module).forEach((key) => { - if (key !== 'default') { - module.default[key] = module[key] +const importShared = `(function(){ + if(!globalThis.importShared){ + const moduleCache = Object.create(null); + const getSharedFromRuntime = async (name, shareScope) => { + let module = null + if (globalThis?.__federation_shared__?.[shareScope]?.[name]) { + const versionObj = globalThis.__federation_shared__[shareScope][name] + const versionKey = Object.keys(versionObj)[0] + const versionValue = Object.values(versionObj)[0] + module = await (await versionValue.get())() } - }) - return module.default + if (module) { + return flattenModule(module, name) + } + }; + + const getSharedFromLocal = async (name) => { + if (moduleMap[name]?.import) { + let module = await (await moduleMap[name].get())() + return flattenModule(module, name) + } + }; + const flattenModule = (module, name) => { + if (typeof module.default === 'function') { + Object.keys(module).forEach((key) => { + if (key !== 'default') { + module.default[key] = module[key] + } + }) + return module.default + } + if (module.default) module = Object.assign({}, module.default, module) + return module + }; + globalThis.importShared = async (name, shareScope = 'default') => { + try{ + console.log("import shared", name) + return moduleCache[name] + ? new Promise((r) => r(moduleCache[name])) + : (await getSharedFromRuntime(name, shareScope)) || getSharedFromLocal(name) + }catch(ex){ + console.log(ex); + } + } } - if (module.default) module = Object.assign({}, module.default, module) - return module -}` +})()` export function devRemotePlugin( options: VitePluginFederationOptions @@ -260,6 +274,7 @@ export {__federation_method_ensure, __federation_method_getRemote , __federation let requiresRuntime = false let manualRequired: any = null // set static import if exists + let wasImportSharedAdded = false walk(ast, { enter(node: any) { if ( @@ -302,22 +317,32 @@ export {__federation_method_ensure, __federation_method_getRemote , __federation if (defaultImportDeclaration && namedImportDeclaration.length) { // import a, {b} from 'c' -> const a = await importShared('c'); const {b} = a; const imports = namedImportDeclaration.join(',') - const line = `${importShared}\n const ${defaultImportDeclaration} = await importShared('${moduleName}');\nconst {${imports}} = ${defaultImportDeclaration};\n` + const line = `${ + wasImportSharedAdded ? importShared : '' + }\n const ${defaultImportDeclaration} = await importShared('${moduleName}');\nconst {${imports}} = ${defaultImportDeclaration};\n` + magicString.overwrite(node.start, node.end, line) + wasImportSharedAdded = true } else if (defaultImportDeclaration) { magicString.overwrite( node.start, node.end, - `${importShared}\n const ${defaultImportDeclaration} = await importShared('${moduleName}');\n` + `${ + wasImportSharedAdded ? importShared : '' + }\n const ${defaultImportDeclaration} = await importShared('${moduleName}');\n` ) + wasImportSharedAdded = true } else if (namedImportDeclaration.length) { magicString.overwrite( node.start, node.end, - `${importShared}\n const {${namedImportDeclaration.join( + `${ + wasImportSharedAdded ? importShared : '' + }\n const {${namedImportDeclaration.join( ',' )}} = await importShared('${moduleName}');\n` ) + wasImportSharedAdded = true } } } From f29597ef669303c0c77107167acf381a7952d84d Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Thu, 15 Feb 2024 09:36:30 -0700 Subject: [PATCH 03/27] wip: resolve importShared is not defined error. Handle references to import.meta.env.BASE_URL --- packages/examples/react-vite/package.json | 1 + .../examples/vue3-advanced-demo/package.json | 1 + packages/lib/src/dev/expose-development.ts | 3 +- packages/lib/src/dev/import-shared.js | 45 ++++++++++ packages/lib/src/dev/remote-development.ts | 86 ++++++------------- 5 files changed, 75 insertions(+), 61 deletions(-) create mode 100644 packages/lib/src/dev/import-shared.js diff --git a/packages/examples/react-vite/package.json b/packages/examples/react-vite/package.json index b2fa7473..2e6f9b27 100644 --- a/packages/examples/react-vite/package.json +++ b/packages/examples/react-vite/package.json @@ -8,6 +8,7 @@ "build:remotes": "pnpm --parallel --filter \"./remote\" build", "serve:remotes": "pnpm --parallel --filter \"./remote\" serve", "dev:hosts": "pnpm --filter \"./host\" dev", + "dev:remotes": "pnpm --filter \"./remote\" dev", "stop": "kill-port --port 5000,5001" }, "devDependencies": { diff --git a/packages/examples/vue3-advanced-demo/package.json b/packages/examples/vue3-advanced-demo/package.json index e08cfe8f..bc991d2f 100644 --- a/packages/examples/vue3-advanced-demo/package.json +++ b/packages/examples/vue3-advanced-demo/package.json @@ -15,6 +15,7 @@ "build:remotes": "pnpm --parallel --filter \"./team-blue\" --filter \"./team-green\" build", "serve:remotes": "pnpm --parallel --filter \"./team-blue\" --filter \"./team-green\" serve", "dev:hosts": "pnpm --filter \"./team-red\" dev", + "dev:remotes": "pnpm --parallel --filter \"./team-blue\" --filter \"./team-green\" dev", "stop": "kill-port --port 5000,5001,5002", "clean": "pnpm run clean" }, diff --git a/packages/lib/src/dev/expose-development.ts b/packages/lib/src/dev/expose-development.ts index c97fc7f7..c036634d 100644 --- a/packages/lib/src/dev/expose-development.ts +++ b/packages/lib/src/dev/expose-development.ts @@ -19,6 +19,7 @@ import { EXTERNALS, SHARED, builderInfo, parsedOptions } from '../public' import type { VitePluginFederationOptions } from 'types' import type { PluginHooks } from '../../types/pluginHooks' import { ViteDevServer } from 'vite' +import { importShared } from './import-shared' export function devExposePlugin( options: VitePluginFederationOptions @@ -35,7 +36,7 @@ export function devExposePlugin( moduleMap += `\n"${item[0]}":() => { return __federation_import('/${importPath}', '/@fs/${exposeFilepath}').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},` } - const remoteFile = ` + const remoteFile = `(${importShared})(); const exportSet = new Set(['Module', '__esModule', 'default', '_export_sfc']); let moduleMap = { ${moduleMap} diff --git a/packages/lib/src/dev/import-shared.js b/packages/lib/src/dev/import-shared.js new file mode 100644 index 00000000..02dd52d3 --- /dev/null +++ b/packages/lib/src/dev/import-shared.js @@ -0,0 +1,45 @@ +export const importShared = function () { + if (!globalThis.importShared) { + const moduleCache = Object.create(null) + const getSharedFromRuntime = async (name, shareScope) => { + let module = null + if (globalThis?.__federation_shared__?.[shareScope]?.[name]) { + const versionObj = globalThis.__federation_shared__[shareScope][name] + const versionValue = Object.values(versionObj)[0] + module = await (await versionValue.get())() + } + if (module) { + return flattenModule(module, name) + } + } + + const getSharedFromLocal = async (name) => { + if (globalThis.moduleMap[name]?.import) { + let module = await (await globalThis.moduleMap[name].get())() + return flattenModule(module, name) + } + } + const flattenModule = (module, name) => { + if (typeof module.default === 'function') { + Object.keys(module).forEach((key) => { + if (key !== 'default') { + module.default[key] = module[key] + } + }) + return module.default + } + if (module.default) module = Object.assign({}, module.default, module) + return module + } + globalThis.importShared = async (name, shareScope = 'default') => { + try { + return moduleCache[name] + ? new Promise((r) => r(moduleCache[name])) + : (await getSharedFromRuntime(name, shareScope)) || + getSharedFromLocal(name) + } catch (ex) { + console.log(ex) + } + } + } +} diff --git a/packages/lib/src/dev/remote-development.ts b/packages/lib/src/dev/remote-development.ts index e6cd3ca4..0bffd984 100644 --- a/packages/lib/src/dev/remote-development.ts +++ b/packages/lib/src/dev/remote-development.ts @@ -34,56 +34,11 @@ import { } from '../utils' import { builderInfo, parsedOptions, devRemotes } from '../public' import type { PluginHooks } from '../../types/pluginHooks' +import { Literal } from 'estree' +import { importShared } from './import-shared' const exposedItems: string[] = [] -const importShared = `(function(){ - if(!globalThis.importShared){ - const moduleCache = Object.create(null); - const getSharedFromRuntime = async (name, shareScope) => { - let module = null - if (globalThis?.__federation_shared__?.[shareScope]?.[name]) { - const versionObj = globalThis.__federation_shared__[shareScope][name] - const versionKey = Object.keys(versionObj)[0] - const versionValue = Object.values(versionObj)[0] - module = await (await versionValue.get())() - } - if (module) { - return flattenModule(module, name) - } - }; - - const getSharedFromLocal = async (name) => { - if (moduleMap[name]?.import) { - let module = await (await moduleMap[name].get())() - return flattenModule(module, name) - } - }; - const flattenModule = (module, name) => { - if (typeof module.default === 'function') { - Object.keys(module).forEach((key) => { - if (key !== 'default') { - module.default[key] = module[key] - } - }) - return module.default - } - if (module.default) module = Object.assign({}, module.default, module) - return module - }; - globalThis.importShared = async (name, shareScope = 'default') => { - try{ - console.log("import shared", name) - return moduleCache[name] - ? new Promise((r) => r(moduleCache[name])) - : (await getSharedFromRuntime(name, shareScope)) || getSharedFromLocal(name) - }catch(ex){ - console.log(ex); - } - } - } -})()` - export function devRemotePlugin( options: VitePluginFederationOptions ): PluginHooks { @@ -270,13 +225,33 @@ export {__federation_method_ensure, __federation_method_getRemote , __federation } const magicString = new MagicString(code) + magicString.prepend(`(${importShared})();`) const hasStaticImported = new Map() let requiresRuntime = false let manualRequired: any = null // set static import if exists - let wasImportSharedAdded = false walk(ast, { enter(node: any) { + if ( + node.type === 'MemberExpression' && + node.object.type === 'MemberExpression' && + node.object.object.type === 'MetaProperty' && + node.object.object.meta.name === 'import' && + node.object.property.type === 'Identifier' && + node.object.property.name === 'env' && + node.property.name === 'BASE_URL' + ) { + const serverPort = viteDevServer.config.inlineConfig.server?.port + const baseUrlFromConfig = + viteDevServer.config.env.BASE_URL && + viteDevServer.config.env.BASE_URL !== '/' + ? viteDevServer.config.env.BASE_URL + : '' + // This assumes that the dev server will always be running on localhost. That's probably not a good assumption, but I don't know how to work around it right now. + const baseUrl = `"//localhost:${serverPort}${baseUrlFromConfig}"` + magicString.overwrite(node.start, node.end, baseUrl) + node = { type: 'Literal', value: baseUrl } as Literal + } if ( node.type === 'ImportDeclaration' && node.source?.value === 'virtual:__federation__' @@ -317,32 +292,23 @@ export {__federation_method_ensure, __federation_method_getRemote , __federation if (defaultImportDeclaration && namedImportDeclaration.length) { // import a, {b} from 'c' -> const a = await importShared('c'); const {b} = a; const imports = namedImportDeclaration.join(',') - const line = `${ - wasImportSharedAdded ? importShared : '' - }\n const ${defaultImportDeclaration} = await importShared('${moduleName}');\nconst {${imports}} = ${defaultImportDeclaration};\n` + const line = `const ${defaultImportDeclaration} = await importShared('${moduleName}');\nconst {${imports}} = ${defaultImportDeclaration};\n` magicString.overwrite(node.start, node.end, line) - wasImportSharedAdded = true } else if (defaultImportDeclaration) { magicString.overwrite( node.start, node.end, - `${ - wasImportSharedAdded ? importShared : '' - }\n const ${defaultImportDeclaration} = await importShared('${moduleName}');\n` + `const ${defaultImportDeclaration} = await importShared('${moduleName}');\n` ) - wasImportSharedAdded = true } else if (namedImportDeclaration.length) { magicString.overwrite( node.start, node.end, - `${ - wasImportSharedAdded ? importShared : '' - }\n const {${namedImportDeclaration.join( + `const {${namedImportDeclaration.join( ',' )}} = await importShared('${moduleName}');\n` ) - wasImportSharedAdded = true } } } From 757f924c832605f14953a341a0e53d0a006fcd63 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Fri, 16 Feb 2024 08:50:25 -0700 Subject: [PATCH 04/27] wip: get shared imports working in dev. --- packages/lib/src/dev/import-shared.js | 13 ++++--------- packages/lib/src/dev/remote-development.ts | 8 ++++---- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/packages/lib/src/dev/import-shared.js b/packages/lib/src/dev/import-shared.js index 02dd52d3..b1d3441b 100644 --- a/packages/lib/src/dev/import-shared.js +++ b/packages/lib/src/dev/import-shared.js @@ -12,31 +12,26 @@ export const importShared = function () { return flattenModule(module, name) } } - - const getSharedFromLocal = async (name) => { - if (globalThis.moduleMap[name]?.import) { - let module = await (await globalThis.moduleMap[name].get())() - return flattenModule(module, name) - } - } const flattenModule = (module, name) => { + // use a shared module which export default a function will getting error 'TypeError: xxx is not a function' if (typeof module.default === 'function') { Object.keys(module).forEach((key) => { if (key !== 'default') { module.default[key] = module[key] } }) + moduleCache[name] = module.default return module.default } if (module.default) module = Object.assign({}, module.default, module) + moduleCache[name] = module return module } globalThis.importShared = async (name, shareScope = 'default') => { try { return moduleCache[name] ? new Promise((r) => r(moduleCache[name])) - : (await getSharedFromRuntime(name, shareScope)) || - getSharedFromLocal(name) + : (await getSharedFromRuntime(name, shareScope)) || null } catch (ex) { console.log(ex) } diff --git a/packages/lib/src/dev/remote-development.ts b/packages/lib/src/dev/remote-development.ts index 0bffd984..f27ec208 100644 --- a/packages/lib/src/dev/remote-development.ts +++ b/packages/lib/src/dev/remote-development.ts @@ -225,7 +225,7 @@ export {__federation_method_ensure, __federation_method_getRemote , __federation } const magicString = new MagicString(code) - magicString.prepend(`(${importShared})();`) + magicString.prepend(`(${importShared})();\n`) const hasStaticImported = new Map() let requiresRuntime = false @@ -292,14 +292,14 @@ export {__federation_method_ensure, __federation_method_getRemote , __federation if (defaultImportDeclaration && namedImportDeclaration.length) { // import a, {b} from 'c' -> const a = await importShared('c'); const {b} = a; const imports = namedImportDeclaration.join(',') - const line = `const ${defaultImportDeclaration} = await importShared('${moduleName}');\nconst {${imports}} = ${defaultImportDeclaration};\n` + const line = `const ${defaultImportDeclaration} = await importShared('${moduleName}') || await import('${moduleName}');\nconst {${imports}} = ${defaultImportDeclaration};\n` magicString.overwrite(node.start, node.end, line) } else if (defaultImportDeclaration) { magicString.overwrite( node.start, node.end, - `const ${defaultImportDeclaration} = await importShared('${moduleName}');\n` + `const ${defaultImportDeclaration} = await importShared('${moduleName}') || await import('${moduleName}');\n` ) } else if (namedImportDeclaration.length) { magicString.overwrite( @@ -307,7 +307,7 @@ export {__federation_method_ensure, __federation_method_getRemote , __federation node.end, `const {${namedImportDeclaration.join( ',' - )}} = await importShared('${moduleName}');\n` + )}} = await importShared('${moduleName}') || await import('${moduleName}');\n` ) } } From b3d639c68710e102f0d8d8963b834e8b371b1be0 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Fri, 28 Jun 2024 11:35:26 -0600 Subject: [PATCH 05/27] Add a prepublishOnly step --- package.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package.json b/package.json index 213efbb5..51212df0 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,10 @@ "workspaces": [ "packages/**" ], + "files": [ + "packages/lib/dist/*", + "packages/types/*" + ], "engines": { "node": "^14.18.0 || >=16.0.0", "pnpm": ">=8.0.1" @@ -13,6 +17,7 @@ "scripts": { "preinstall": "npx only-allow pnpm", "prepare": "husky install", + "prepublishOnly": "pnpm run build", "postinstall": "npx playwright install", "lint-staged": "lint-staged", "format": "prettier -w packages/lib/**/*.ts", From 84c3e413689258492e8c562ec671363ce2053c33 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Fri, 28 Jun 2024 12:01:47 -0600 Subject: [PATCH 06/27] wip: Replace the prepare script --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 51212df0..d5c77ee9 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,7 @@ "license": "MulanPSL-2.0", "scripts": { "preinstall": "npx only-allow pnpm", - "prepare": "husky install", - "prepublishOnly": "pnpm run build", + "prepare": "pnpm run build", "postinstall": "npx playwright install", "lint-staged": "lint-staged", "format": "prettier -w packages/lib/**/*.ts", From 8017628c7d97b275bda78b87dbcdf81fe06d23a5 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Fri, 28 Jun 2024 13:21:41 -0600 Subject: [PATCH 07/27] wip: try to make installation from github work --- package.json | 10 ++++++++++ packages/lib/src/dev/expose-development.ts | 7 ++++++- packages/lib/src/dev/remote-development.ts | 4 ++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index d5c77ee9..cc6525f8 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,16 @@ "packages/lib/dist/*", "packages/types/*" ], + "main": "./packages/lib/dist/index.js", + "module": "./packages/lib/dist/index.mjs", + "types": "./packages/lib/types/index.d.ts", + "exports": { + ".": { + "types": "./packages/lib/types/index.d.ts", + "import": "./packages/lib/dist/index.mjs", + "require": "./packages/lib/dist/index.js" + } + }, "engines": { "node": "^14.18.0 || >=16.0.0", "pnpm": ">=8.0.1" diff --git a/packages/lib/src/dev/expose-development.ts b/packages/lib/src/dev/expose-development.ts index c036634d..c9ffc075 100644 --- a/packages/lib/src/dev/expose-development.ts +++ b/packages/lib/src/dev/expose-development.ts @@ -36,7 +36,12 @@ export function devExposePlugin( moduleMap += `\n"${item[0]}":() => { return __federation_import('/${importPath}', '/@fs/${exposeFilepath}').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},` } - const remoteFile = `(${importShared})(); + const remoteFile = `(${importShared})(); + import RefreshRuntime from "/@react-refresh" + RefreshRuntime.injectIntoGlobalHook(window) + window.$RefreshReg$ = () => {} + window.$RefreshSig$ = () => (type) => type + window.__vite_plugin_react_preamble_installed__ = true const exportSet = new Set(['Module', '__esModule', 'default', '_export_sfc']); let moduleMap = { ${moduleMap} diff --git a/packages/lib/src/dev/remote-development.ts b/packages/lib/src/dev/remote-development.ts index f27ec208..868bdc7d 100644 --- a/packages/lib/src/dev/remote-development.ts +++ b/packages/lib/src/dev/remote-development.ts @@ -214,6 +214,8 @@ export {__federation_method_ensure, __federation_method_getRemote , __federation return } + code += `(${importShared})();\n` + let ast: AcornNode | null = null try { ast = this.parse(code) @@ -223,9 +225,7 @@ export {__federation_method_ensure, __federation_method_getRemote , __federation if (!ast) { return null } - const magicString = new MagicString(code) - magicString.prepend(`(${importShared})();\n`) const hasStaticImported = new Map() let requiresRuntime = false From 5127edfaa14f19f688d9f58888853bae945663ce Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Fri, 26 Jul 2024 09:57:31 -0600 Subject: [PATCH 08/27] Add support for a non-root base directory --- packages/lib/src/dev/expose-development.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/lib/src/dev/expose-development.ts b/packages/lib/src/dev/expose-development.ts index c9ffc075..9952de0f 100644 --- a/packages/lib/src/dev/expose-development.ts +++ b/packages/lib/src/dev/expose-development.ts @@ -18,14 +18,16 @@ import { getModuleMarker, normalizePath, parseExposeOptions } from '../utils' import { EXTERNALS, SHARED, builderInfo, parsedOptions } from '../public' import type { VitePluginFederationOptions } from 'types' import type { PluginHooks } from '../../types/pluginHooks' -import { ViteDevServer } from 'vite' +import { UserConfig, ViteDevServer } from 'vite' import { importShared } from './import-shared' +import { config } from 'process' export function devExposePlugin( options: VitePluginFederationOptions ): PluginHooks { parsedOptions.devExpose = parseExposeOptions(options) let moduleMap = '' + let baseDir = '/' // exposes module for (const item of parsedOptions.devExpose) { @@ -37,7 +39,7 @@ export function devExposePlugin( return __federation_import('/${importPath}', '/@fs/${exposeFilepath}').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},` } const remoteFile = `(${importShared})(); - import RefreshRuntime from "/@react-refresh" + import RefreshRuntime from "${baseDir}@react-refresh" RefreshRuntime.injectIntoGlobalHook(window) window.$RefreshReg$ = () => {} window.$RefreshSig$ = () => (type) => type @@ -73,6 +75,11 @@ export function devExposePlugin( return { name: 'originjs:expose-development', + config: (config: UserConfig) => { + if (config.base) { + baseDir = config.base + } + }, configureServer: (server: ViteDevServer) => { const remoteFilePath = `${builderInfo.assetsDir}/${options.filename}` server.middlewares.use((req, res, next) => { From 277ba84dcb44e5d353be72df6b3ba1b074fc9b41 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Wed, 31 Jul 2024 12:56:06 -0600 Subject: [PATCH 09/27] add base dir to the exposed file path --- packages/lib/src/dev/expose-development.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lib/src/dev/expose-development.ts b/packages/lib/src/dev/expose-development.ts index 9952de0f..a4e06fff 100644 --- a/packages/lib/src/dev/expose-development.ts +++ b/packages/lib/src/dev/expose-development.ts @@ -36,7 +36,7 @@ export function devExposePlugin( const importPath = normalizePath(item[1].import) const exposeFilepath = normalizePath(resolve(item[1].import)) moduleMap += `\n"${item[0]}":() => { - return __federation_import('/${importPath}', '/@fs/${exposeFilepath}').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},` + return __federation_import('/${importPath}', '${baseDir}@fs/${exposeFilepath}').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},` } const remoteFile = `(${importShared})(); import RefreshRuntime from "${baseDir}@react-refresh" From 0a4e82dcd8ef7a5f2095ee2a46af41b37193cbac Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Wed, 31 Jul 2024 13:19:30 -0600 Subject: [PATCH 10/27] Wait for config to load before building remote file and exposing modules so that baseDir is defined. --- packages/lib/src/dev/expose-development.ts | 28 ++++++++++++---------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/lib/src/dev/expose-development.ts b/packages/lib/src/dev/expose-development.ts index a4e06fff..0c3e6f4b 100644 --- a/packages/lib/src/dev/expose-development.ts +++ b/packages/lib/src/dev/expose-development.ts @@ -20,25 +20,27 @@ import type { VitePluginFederationOptions } from 'types' import type { PluginHooks } from '../../types/pluginHooks' import { UserConfig, ViteDevServer } from 'vite' import { importShared } from './import-shared' -import { config } from 'process' export function devExposePlugin( options: VitePluginFederationOptions ): PluginHooks { parsedOptions.devExpose = parseExposeOptions(options) let moduleMap = '' - let baseDir = '/' + let remoteFile: string | null = null - // exposes module - for (const item of parsedOptions.devExpose) { - const moduleName = getModuleMarker(`\${${item[0]}}`, SHARED) - EXTERNALS.push(moduleName) - const importPath = normalizePath(item[1].import) - const exposeFilepath = normalizePath(resolve(item[1].import)) - moduleMap += `\n"${item[0]}":() => { - return __federation_import('/${importPath}', '${baseDir}@fs/${exposeFilepath}').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},` + const exposeModules = (baseDir) => { + for (const item of parsedOptions.devExpose) { + const moduleName = getModuleMarker(`\${${item[0]}}`, SHARED) + EXTERNALS.push(moduleName) + const importPath = normalizePath(item[1].import) + const exposeFilepath = normalizePath(resolve(item[1].import)) + moduleMap += `\n"${item[0]}":() => { + return __federation_import('/${importPath}', '${baseDir}@fs/${exposeFilepath}').then(module =>Object.keys(module).every(item => exportSet.has(item)) ? () => module.default : () => module)},` + } } - const remoteFile = `(${importShared})(); + + const buildRemoteFile = (baseDir) => { + return `(${importShared})(); import RefreshRuntime from "${baseDir}@react-refresh" RefreshRuntime.injectIntoGlobalHook(window) window.$RefreshReg$ = () => {} @@ -72,12 +74,14 @@ export function devExposePlugin( }); } ` + } return { name: 'originjs:expose-development', config: (config: UserConfig) => { if (config.base) { - baseDir = config.base + exposeModules(config.base) + remoteFile = buildRemoteFile(config.base) } }, configureServer: (server: ViteDevServer) => { From 759d09dc8c398698f8270a2688b3555f8d4f0c79 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Wed, 21 Aug 2024 16:15:55 -0600 Subject: [PATCH 11/27] Use fsImportPath first and fall back to urlImportPath --- packages/lib/src/dev/expose-development.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/lib/src/dev/expose-development.ts b/packages/lib/src/dev/expose-development.ts index 0c3e6f4b..00af7c30 100644 --- a/packages/lib/src/dev/expose-development.ts +++ b/packages/lib/src/dev/expose-development.ts @@ -53,9 +53,9 @@ export function devExposePlugin( const __federation_import = async (urlImportPath, fsImportPath) => { let importedModule; try { - return await import(urlImportPath); + return await import(fsImportPath); }catch(ex) { - return await import(fsImportPath) + return await import(urlImportPath); } }; export const get =(module) => { From 1c50305a717caceeef4c254f86bec6831c45d50e Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Mon, 13 Jan 2025 15:41:51 -0700 Subject: [PATCH 12/27] Allow consumers to set importRetryCount and onImportFail for remotes --- package.json | 3 +- packages/lib/package.json | 5 +- packages/lib/src/dev/expose-development.ts | 12 +- packages/lib/src/dev/remote-development.ts | 27 ++++- packages/lib/src/index.ts | 125 +++++++++++++++++---- packages/lib/src/prod/expose-production.ts | 29 ++++- packages/lib/src/prod/remote-production.ts | 26 ++++- packages/lib/src/utils/index.ts | 8 +- packages/lib/tsconfig.json | 7 +- packages/lib/types/import-shared.d.ts | 1 + packages/lib/types/index.d.ts | 12 +- packages/lib/vite.config.ts | 2 +- pnpm-lock.yaml | 29 ++--- 13 files changed, 215 insertions(+), 71 deletions(-) create mode 100644 packages/lib/types/import-shared.d.ts diff --git a/package.json b/package.json index cc6525f8..3d6ed710 100644 --- a/package.json +++ b/package.json @@ -66,5 +66,6 @@ "eslint --cache --fix", "eslint" ] - } + }, + "packageManager": "pnpm@9.5.0+sha512.140036830124618d624a2187b50d04289d5a087f326c9edfc0ccd733d76c4f52c3a313d4fc148794a2a9d81553016004e6742e8cf850670268a7387fc220c903" } diff --git a/packages/lib/package.json b/packages/lib/package.json index ccef202e..dcaf098b 100644 --- a/packages/lib/package.json +++ b/packages/lib/package.json @@ -37,13 +37,14 @@ }, "homepage": "https://github.com/originjs/vite-plugin-federation#readme", "scripts": { - "build": "vite build", + "build": "vite build && tsc", "dev": "vite build --watch", "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s --commit-path ." }, "dependencies": { "estree-walker": "^3.0.2", - "magic-string": "^0.27.0" + "magic-string": "^0.27.0", + "rollup": "^3.9.1" }, "devDependencies": { "@rollup/plugin-virtual": "^3.0.1", diff --git a/packages/lib/src/dev/expose-development.ts b/packages/lib/src/dev/expose-development.ts index 00af7c30..8a9ce023 100644 --- a/packages/lib/src/dev/expose-development.ts +++ b/packages/lib/src/dev/expose-development.ts @@ -39,7 +39,7 @@ export function devExposePlugin( } } - const buildRemoteFile = (baseDir) => { + const buildRemoteFile = (baseDir:string) => { return `(${importShared})(); import RefreshRuntime from "${baseDir}@react-refresh" RefreshRuntime.injectIntoGlobalHook(window) @@ -52,11 +52,11 @@ export function devExposePlugin( }; const __federation_import = async (urlImportPath, fsImportPath) => { let importedModule; - try { - return await import(fsImportPath); - }catch(ex) { - return await import(urlImportPath); - } + try { + return await import(fsImportPath); + } catch(ex) { + return await import(urlImportPath); + } }; export const get =(module) => { if(!moduleMap[module]) throw new Error('Can not find remote module ' + module) diff --git a/packages/lib/src/dev/remote-development.ts b/packages/lib/src/dev/remote-development.ts index 868bdc7d..461056af 100644 --- a/packages/lib/src/dev/remote-development.ts +++ b/packages/lib/src/dev/remote-development.ts @@ -148,10 +148,31 @@ function __federation_method_wrapDefault(module ,need){ return module; } -function __federation_method_getRemote(remoteName, componentName){ - return __federation_method_ensure(remoteName).then((remote) => remote.get(componentName).then(factory => factory())); +async function __federation_method_getRemote(remoteName, componentName) { + const remoteConfig = remotesMap[remoteName]; + let retryCount = 0; + const getRemote = async () => { + try { + const remoteModule = await __federation_method_ensure(remoteName); + const factory = await remoteModule.get(componentName); + factory(); + } catch (err) { + console.log("get remote retry count", retryCount); + retryCount++; + if (retryCount > remoteConfig.importRetryCount) { + if(remoteConfig.onImportFail){ + return remoteConfig.onImportFail(remoteName, componentName, err); + } else { + throw err; + } + } else { + return getRemote(); + } + } + }; + return getRemote(); } - + function __federation_method_setRemote(remoteName, remoteConfig) { remotesMap[remoteName] = remoteConfig; } diff --git a/packages/lib/src/index.ts b/packages/lib/src/index.ts index bdd992b7..9e830be7 100644 --- a/packages/lib/src/index.ts +++ b/packages/lib/src/index.ts @@ -26,7 +26,21 @@ import { prodRemotePlugin } from './prod/remote-production' import type { VitePluginFederationOptions } from '../types' import { builderInfo, DEFAULT_ENTRY_FILENAME, parsedOptions } from './public' import type { PluginHooks } from '../types/pluginHooks' -import type { ModuleInfo } from 'rollup' +import { + InputOptions, + NormalizedInputOptions, + NormalizedOutputOptions, + NullValue, + OutputBundle, + OutputOptions, + PluginContext, + RenderedChunk, + SourceDescription, + SourceMapInput, + TransformPluginContext, + type MinimalPluginContext, + type ModuleInfo +} from 'rollup' import { prodSharedPlugin } from './prod/shared-production' import { prodExposePlugin } from './prod/expose-production' import { devSharedPlugin } from './dev/shared-development' @@ -57,7 +71,7 @@ export default function federation( devExposePlugin(options), devRemotePlugin(options) ] - } else { + } else { pluginList = [] } builderInfo.isHost = !!( @@ -79,7 +93,7 @@ export default function federation( virtualMod = virtual(virtualFiles) } - return { + const plugin: Plugin = { name: 'originjs:federation', // for scenario vite.config.js build.cssCodeSplit: false // vite:css-post plugin will summarize all the styles in the style.xxxxxx.css file @@ -99,8 +113,12 @@ export default function federation( if (!Array.isArray(_options.external)) { _options.external = [_options.external as string] } - for (const pluginHook of pluginList) { - pluginHook.options?.call(this, _options) + for (const pluginHook of pluginList) {( + pluginHook.options as (( + this: MinimalPluginContext, + options: InputOptions + ) => InputOptions | NullValue)|undefined + )?.call(this, _options) } return _options }, @@ -108,8 +126,14 @@ export default function federation( options.mode = options.mode ?? env.mode registerPlugins(options.mode, env.command) registerCount++ - for (const pluginHook of pluginList) { - pluginHook.config?.call(this, config, env) + for (const pluginHook of pluginList) {( + pluginHook.config as (( + this: void, + config: UserConfig, + env: ConfigEnv + ) => UserConfig | null | void | Promise) | undefined + )?.call(this, config, env) + } // only run when builder is vite,rollup doesnt has hook named `config` @@ -118,17 +142,32 @@ export default function federation( }, configureServer(server: ViteDevServer) { for (const pluginHook of pluginList) { - pluginHook.configureServer?.call(this, server) + ( + pluginHook.configureServer as (( + this: void, + server: ViteDevServer + ) => (() => void) | void | Promise<(() => void) | void>)|undefined + )?.call(this, server) } }, configResolved(config: ResolvedConfig) { for (const pluginHook of pluginList) { - pluginHook.configResolved?.call(this, config) + ( + pluginHook.configResolved as (( + this: void, + config: ResolvedConfig + ) => void | Promise)| undefined + )?.call(this, config) } }, buildStart(inputOptions) { for (const pluginHook of pluginList) { - pluginHook.buildStart?.call(this, inputOptions) + ( + pluginHook.buildStart as (( + this: PluginContext, + options: NormalizedInputOptions + ) => void )| undefined + )?.call(this, inputOptions) } }, @@ -168,7 +207,19 @@ export default function federation( transform(code: string, id: string) { for (const pluginHook of pluginList) { - const result = pluginHook.transform?.call(this, code, id) + const result = ( + pluginHook.transform as (( + this: TransformPluginContext, + code: string, + id: string, + options?: { + ssr?: boolean + } + ) => + | Promise> + | (string | NullValue | Partial)) + | undefined + )?.call(this, code, id) if (result) { return result } @@ -177,27 +228,49 @@ export default function federation( }, moduleParsed(moduleInfo: ModuleInfo): void { for (const pluginHook of pluginList) { - pluginHook.moduleParsed?.call(this, moduleInfo) + ( + pluginHook.moduleParsed as ( + this: PluginContext, + info: ModuleInfo + ) => void | undefined + )?.call(this, moduleInfo) } }, outputOptions(outputOptions) { for (const pluginHook of pluginList) { - pluginHook.outputOptions?.call(this, outputOptions) + ( + pluginHook.outputOptions as + | (( + this: PluginContext, + options: OutputOptions + ) => OutputOptions | NullValue) + | undefined + )?.call(this, outputOptions) } return outputOptions }, renderChunk(code, chunkInfo, _options) { for (const pluginHook of pluginList) { - const result = pluginHook.renderChunk?.call( - this, - code, - chunkInfo, - _options - ) - if (result) { - return result + if (pluginHook.renderChunk) { + const result = ( + pluginHook.renderChunk as + | (( + this: MinimalPluginContext, + code: string, + chunk: RenderedChunk, + options: NormalizedOutputOptions, + meta: { chunks: Record } + ) => + | { code: string; map?: SourceMapInput } + | string + | NullValue) + | undefined + )?.call(this, code, chunkInfo, _options, { chunks: {} }) + if (result) { + return result + } } } return null @@ -205,8 +278,16 @@ export default function federation( generateBundle: function (_options, bundle, isWrite) { for (const pluginHook of pluginList) { - pluginHook.generateBundle?.call(this, _options, bundle, isWrite) + ( + pluginHook.generateBundle as (( + this: PluginContext, + options: NormalizedOutputOptions, + bundle: OutputBundle, + isWrite: boolean + ) => void) | undefined + )?.call(this, _options, bundle, isWrite) } } } + return plugin } diff --git a/packages/lib/src/prod/expose-production.ts b/packages/lib/src/prod/expose-production.ts index ad448c68..fad8ffd7 100644 --- a/packages/lib/src/prod/expose-production.ts +++ b/packages/lib/src/prod/expose-production.ts @@ -78,12 +78,29 @@ export function prodExposePlugin( const currentImports = {} const exportSet = new Set(['Module', '__esModule', 'default', '_export_sfc']); let moduleMap = {${moduleMap}} - const seen = {} - export const ${DYNAMIC_LOADING_CSS} = (cssFilePaths, dontAppendStylesToHead, exposeItemName) => { - const metaUrl = import.meta.url; - if (typeof metaUrl === 'undefined') { - console.warn('The remote style takes effect only when the build.target option in the vite.config.ts file is higher than that of "es2020".'); - return; + const seen = {} + export const ${DYNAMIC_LOADING_CSS} = (cssFilePaths, dontAppendStylesToHead, exposeItemName) => { + const metaUrl = import.meta.url + if (typeof metaUrl == 'undefined') { + console.warn('The remote style takes effect only when the build.target option in the vite.config.ts file is higher than that of "es2020".') + return + } + const curUrl = metaUrl.substring(0, metaUrl.lastIndexOf('${ + options.filename + }')) + + cssFilePaths.forEach(cssFilePath => { + const href = curUrl + cssFilePath + if (href in seen) return + seen[href] = true + if (dontAppendStylesToHead) { + const key = 'css__${options.name}__' + exposeItemName; + if (window[key] == null) window[key] = [] + window[key].push(href); + } else { + const element = document.head.appendChild(document.createElement('link')) + element.href = href + element.rel = 'stylesheet' } const curUrl = metaUrl.substring(0, metaUrl.lastIndexOf('${options.filename}')); diff --git a/packages/lib/src/prod/remote-production.ts b/packages/lib/src/prod/remote-production.ts index 3476286b..568960be 100644 --- a/packages/lib/src/prod/remote-production.ts +++ b/packages/lib/src/prod/remote-production.ts @@ -211,9 +211,29 @@ export function prodRemotePlugin( } return module; } - - function __federation_method_getRemote(remoteName, componentName) { - return __federation_method_ensure(remoteName).then((remote) => remote.get(componentName).then(factory => factory())); + async function __federation_method_getRemote(remoteName, componentName) { + const remoteConfig = remotesMap[remoteName]; + let retryCount = 0; + const getRemote = async () => { + try { + const remoteModule = await __federation_method_ensure(remoteName); + const factory = await remoteModule.get(componentName); + factory(); + } catch (err) { + console.log("get remote retry count", retryCount); + retryCount++; + if (retryCount > remoteConfig.importRetryCount) { + if(remoteConfig.onImportFail){ + return remoteConfig.onImportFail(remoteName, componentName, err); + } else { + throw err; + } + } else { + return getRemote(); + } + } + }; + return getRemote(); } function __federation_method_setRemote(remoteName, remoteConfig) { diff --git a/packages/lib/src/utils/index.ts b/packages/lib/src/utils/index.ts index 54f902fd..b37ee479 100644 --- a/packages/lib/src/utils/index.ts +++ b/packages/lib/src/utils/index.ts @@ -115,14 +115,16 @@ export function parseRemoteOptions( shareScope: options.shareScope || 'default', format: 'esm', from: 'vite', - externalType: 'url' + externalType: 'url', + importRetryCount: 0, }), (item) => ({ external: Array.isArray(item.external) ? item.external : [item.external], shareScope: item.shareScope || options.shareScope || 'default', format: item.format || 'esm', from: item.from ?? 'vite', - externalType: item.externalType || 'url' + externalType: item.externalType || 'url', + importRetryCount: item.format === "var" ? undefined : item.importRetryCount || 0 }) ) } @@ -234,7 +236,7 @@ ${remotes (remote) => `'${remote.id}':{url:${createUrl(remote)},format:'${ remote.config.format - }',from:'${remote.config.from}'}` + }',from:'${remote.config.from}'},importRetryCount:${remote.config.importRetryCount ?? 0},onImportFail:${remote.config.onImportFail? remote.config.onImportFail.toString(): "undefined"}` ) .join(',\n ')} };` diff --git a/packages/lib/tsconfig.json b/packages/lib/tsconfig.json index 6b2d5b00..9742eaef 100644 --- a/packages/lib/tsconfig.json +++ b/packages/lib/tsconfig.json @@ -1,7 +1,9 @@ { - "include": ["src"], + "include": ["src", "types/*.d.ts"], "exclude": ["dist", "node_modules", "test"], "compilerOptions": { + "composite": false, + "declaration": false, "outDir": "dist", "target": "es2019", "module": "commonjs", @@ -12,6 +14,7 @@ "noUnusedLocals": true, "esModuleInterop": true, "baseUrl": ".", - "noImplicitAny": false + "noImplicitAny": false, + "skipLibCheck": true } } diff --git a/packages/lib/types/import-shared.d.ts b/packages/lib/types/import-shared.d.ts new file mode 100644 index 00000000..e0088380 --- /dev/null +++ b/packages/lib/types/import-shared.d.ts @@ -0,0 +1 @@ +declare module "*.js?raw"; \ No newline at end of file diff --git a/packages/lib/types/index.d.ts b/packages/lib/types/index.d.ts index 8f52937a..765c651a 100644 --- a/packages/lib/types/index.d.ts +++ b/packages/lib/types/index.d.ts @@ -220,7 +220,7 @@ declare interface RemotesObject { /** * Advanced configuration for container locations from which modules should be resolved and loaded at runtime. */ -declare interface RemotesConfig { +declare type RemotesConfig = { /** * Container locations from which modules should be resolved and loaded at runtime. */ @@ -245,6 +245,16 @@ declare interface RemotesConfig { * from */ from?: 'vite' | 'webpack' + + /** + * The number of times to retry imports before failure + */ + importRetryCount?: number + + /** + * Method called when import fails (after `importRetryCount` has been exhausted) + */ + onImportFail?: (remoteName: string, componentName: string, error: Error) => void; } /** diff --git a/packages/lib/vite.config.ts b/packages/lib/vite.config.ts index f26a3265..8d9adc29 100644 --- a/packages/lib/vite.config.ts +++ b/packages/lib/vite.config.ts @@ -10,7 +10,7 @@ export default defineConfig({ target: 'node14', minify: false, rollupOptions: { - external: ['fs', 'path', 'crypto', 'magic-string'], + external: ['fs', 'path', 'crypto', 'magic-string', '__federation_fn_satisfy'], output: { minifyInternalExports: false } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 50b356f9..770e4f80 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -143,7 +143,7 @@ importers: devDependencies: '@originjs/vite-plugin-federation': specifier: ^1.2.3 - version: 1.3.6 + version: 1.3.7 kill-port: specifier: ^2.0.1 version: 2.0.1 @@ -1148,6 +1148,9 @@ importers: magic-string: specifier: ^0.27.0 version: 0.27.0 + rollup: + specifier: ^3.9.1 + version: 3.23.1 devDependencies: '@rollup/plugin-virtual': specifier: ^3.0.1 @@ -1330,105 +1333,90 @@ packages: '@babel/plugin-proposal-async-generator-functions@7.18.6': resolution: {integrity: sha512-WAz4R9bvozx4qwf74M+sfqPMKfSqwM0phxPTR6iJIi8robgzXwkEgmeJG1gEKhm6sDqT/U9aV3lfcqybIpev8w==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead. peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-proposal-class-properties@7.18.6': resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead. peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-proposal-class-static-block@7.18.6': resolution: {integrity: sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-static-block instead. peerDependencies: '@babel/core': ^7.12.0 '@babel/plugin-proposal-dynamic-import@7.18.6': resolution: {integrity: sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-dynamic-import instead. peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-proposal-export-namespace-from@7.18.6': resolution: {integrity: sha512-zr/QcUlUo7GPo6+X1wC98NJADqmy5QTFWWhqeQWiki4XHafJtLl/YMGkmRB2szDD2IYJCCdBTd4ElwhId9T7Xw==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-export-namespace-from instead. peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-proposal-json-strings@7.18.6': resolution: {integrity: sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-json-strings instead. peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-proposal-logical-assignment-operators@7.18.6': resolution: {integrity: sha512-zMo66azZth/0tVd7gmkxOkOjs2rpHyhpcFo565PUP37hSp6hSd9uUKIfTDFMz58BwqgQKhJ9YxtM5XddjXVn+Q==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-logical-assignment-operators instead. peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6': resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead. peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-proposal-numeric-separator@7.18.6': resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead. peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-proposal-object-rest-spread@7.18.6': resolution: {integrity: sha512-9yuM6wr4rIsKa1wlUAbZEazkCrgw2sMPEXCr4Rnwetu7cEW1NydkCWytLuYletbf8vFxdJxFhwEZqMpOx2eZyw==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead. peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-proposal-optional-catch-binding@7.18.6': resolution: {integrity: sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead. peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-proposal-optional-chaining@7.18.6': resolution: {integrity: sha512-PatI6elL5eMzoypFAiYDpYQyMtXTn+iMhuxxQt5mAXD4fEmKorpSI3PHd+i3JXBJN3xyA6MvJv7at23HffFHwA==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead. peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-proposal-private-methods@7.18.6': resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead. peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-proposal-private-property-in-object@7.18.6': resolution: {integrity: sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead. peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-proposal-unicode-property-regex@7.18.6': resolution: {integrity: sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==} engines: {node: '>=4'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-unicode-property-regex instead. peerDependencies: '@babel/core': ^7.0.0-0 @@ -2126,8 +2114,8 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@originjs/vite-plugin-federation@1.3.6': - resolution: {integrity: sha512-tHLMjdMJFPFMSJrUuJJiv8l7OFRvM19E9O1B9dhbk+04i3RnYwE9A6oNtSUM1dnvkalzCLwZIuMpti28/tnh8g==} + '@originjs/vite-plugin-federation@1.3.7': + resolution: {integrity: sha512-k73iJPON4R8dPi9y6fcNgTQDLbdeoHU5OWUesgF5/E7dnqbFxOW964JbvrRK2Ktm4beaEMSDAki3pPKKRIuAPw==} engines: {node: '>=14.0.0', pnpm: '>=7.0.1'} '@rollup/plugin-babel@6.0.3': @@ -2456,7 +2444,6 @@ packages: '@vitejs/plugin-react-refresh@1.3.6': resolution: {integrity: sha512-iNR/UqhUOmFFxiezt0em9CgmiJBdWR+5jGxB2FihaoJfqGt76kiwaKoVOJVU5NYcDWMdN06LbyN2VIGIoYdsEA==} engines: {node: '>=12.0.0'} - deprecated: This package has been deprecated in favor of @vitejs/plugin-react '@vitejs/plugin-react@3.0.0': resolution: {integrity: sha512-1mvyPc0xYW5G8CHQvJIJXLoMjl5Ct3q2g5Y2s6Ccfgwm45y48LBvsla7az+GkkAtYikWQ4Lxqcsq5RHLcZgtNQ==} @@ -3010,7 +2997,7 @@ packages: engines: {node: '>= 0.8.0'} concat-map@0.0.1: - resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} connect-history-api-fallback@2.0.0: resolution: {integrity: sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==} @@ -6621,7 +6608,7 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.13.0 - '@originjs/vite-plugin-federation@1.3.6': + '@originjs/vite-plugin-federation@1.3.7': dependencies: estree-walker: 3.0.2 magic-string: 0.27.0 From 8b4c1c1ab37d53ab8fdd015fa5a68c9befb6d0a7 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Mon, 13 Jan 2025 16:12:14 -0700 Subject: [PATCH 13/27] Fix some configuration issues --- packages/lib/src/utils/index.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/lib/src/utils/index.ts b/packages/lib/src/utils/index.ts index b37ee479..a2f829f1 100644 --- a/packages/lib/src/utils/index.ts +++ b/packages/lib/src/utils/index.ts @@ -117,6 +117,7 @@ export function parseRemoteOptions( from: 'vite', externalType: 'url', importRetryCount: 0, + onImportFail: undefined }), (item) => ({ external: Array.isArray(item.external) ? item.external : [item.external], @@ -124,7 +125,8 @@ export function parseRemoteOptions( format: item.format || 'esm', from: item.from ?? 'vite', externalType: item.externalType || 'url', - importRetryCount: item.format === "var" ? undefined : item.importRetryCount || 0 + importRetryCount: item.format === "var" ? undefined : item.importRetryCount || 0, + onImportFail: item.onImportFail ? item.onImportFail : undefined }) ) } @@ -236,7 +238,7 @@ ${remotes (remote) => `'${remote.id}':{url:${createUrl(remote)},format:'${ remote.config.format - }',from:'${remote.config.from}'},importRetryCount:${remote.config.importRetryCount ?? 0},onImportFail:${remote.config.onImportFail? remote.config.onImportFail.toString(): "undefined"}` + }',from:'${remote.config.from}',importRetryCount: ${remote.config.importRetryCount ?? 0}, onImportFail: ${remote.config.onImportFail? remote.config.onImportFail.toString(): "undefined"}}` ) .join(',\n ')} };` From 0ba072ba6f834a1d142c6b5087ef9c5cd8b9e6ae Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Mon, 13 Jan 2025 16:27:55 -0700 Subject: [PATCH 14/27] pass more information to onImportFail --- packages/lib/src/dev/remote-development.ts | 4 +++- packages/lib/src/prod/remote-production.ts | 4 +++- packages/lib/types/index.d.ts | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/lib/src/dev/remote-development.ts b/packages/lib/src/dev/remote-development.ts index 461056af..7a78db07 100644 --- a/packages/lib/src/dev/remote-development.ts +++ b/packages/lib/src/dev/remote-development.ts @@ -161,7 +161,9 @@ async function __federation_method_getRemote(remoteName, componentName) { retryCount++; if (retryCount > remoteConfig.importRetryCount) { if(remoteConfig.onImportFail){ - return remoteConfig.onImportFail(remoteName, componentName, err); + const errorConfig = {...remoteConfig}; + delete errorConfig.onImportFail; + return remoteConfig.onImportFail(remoteName, componentName, errorConfig, err); } else { throw err; } diff --git a/packages/lib/src/prod/remote-production.ts b/packages/lib/src/prod/remote-production.ts index 568960be..9d26abbd 100644 --- a/packages/lib/src/prod/remote-production.ts +++ b/packages/lib/src/prod/remote-production.ts @@ -224,7 +224,9 @@ export function prodRemotePlugin( retryCount++; if (retryCount > remoteConfig.importRetryCount) { if(remoteConfig.onImportFail){ - return remoteConfig.onImportFail(remoteName, componentName, err); + const errorConfig = {...remoteConfig}; + delete errorConfig.onImportFail; + return remoteConfig.onImportFail(remoteName, componentName, errorConfig, err); } else { throw err; } diff --git a/packages/lib/types/index.d.ts b/packages/lib/types/index.d.ts index 765c651a..5a00fd83 100644 --- a/packages/lib/types/index.d.ts +++ b/packages/lib/types/index.d.ts @@ -254,7 +254,7 @@ declare type RemotesConfig = { /** * Method called when import fails (after `importRetryCount` has been exhausted) */ - onImportFail?: (remoteName: string, componentName: string, error: Error) => void; + onImportFail?: (remoteName: string, componentName: string, errorConfig: RemotesConfig, error: Error) => void; } /** From e827e24e2678541f3f7d391c2a2aa53f1c1e67c6 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Mon, 13 Jan 2025 16:39:32 -0700 Subject: [PATCH 15/27] Allow reinit of a remote based on the retry count --- packages/lib/src/dev/remote-development.ts | 6 +++--- packages/lib/src/prod/remote-production.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/lib/src/dev/remote-development.ts b/packages/lib/src/dev/remote-development.ts index 7a78db07..6edfdc9a 100644 --- a/packages/lib/src/dev/remote-development.ts +++ b/packages/lib/src/dev/remote-development.ts @@ -95,9 +95,9 @@ const wrapShareScope = ${REMOTE_FROM_PARAMETER} => { } } const initMap = Object.create(null); -async function __federation_method_ensure(remoteId) { +async function __federation_method_ensure(remoteId, reInit) { const remote = remotesMap[remoteId]; - if (!remote.inited) { + if (!remote.inited || reInit) { if ('var' === remote.format) { // loading js with script tag return new Promise(resolve => { @@ -153,7 +153,7 @@ async function __federation_method_getRemote(remoteName, componentName) { let retryCount = 0; const getRemote = async () => { try { - const remoteModule = await __federation_method_ensure(remoteName); + const remoteModule = await __federation_method_ensure(remoteName, retryCount > 0); const factory = await remoteModule.get(componentName); factory(); } catch (err) { diff --git a/packages/lib/src/prod/remote-production.ts b/packages/lib/src/prod/remote-production.ts index 9d26abbd..4bb392c1 100644 --- a/packages/lib/src/prod/remote-production.ts +++ b/packages/lib/src/prod/remote-production.ts @@ -159,9 +159,9 @@ export function prodRemotePlugin( const initMap = Object.create(null); - async function __federation_method_ensure(remoteId) { + async function __federation_method_ensure(remoteId, reInit) { const remote = remotesMap[remoteId]; - if (!remote.inited) { + if (!remote.inited || reInit) { if ('var' === remote.format) { // loading js with script tag return new Promise(resolve => { @@ -216,7 +216,7 @@ export function prodRemotePlugin( let retryCount = 0; const getRemote = async () => { try { - const remoteModule = await __federation_method_ensure(remoteName); + const remoteModule = await __federation_method_ensure(remoteName, retryCount > 0); const factory = await remoteModule.get(componentName); factory(); } catch (err) { From 05c9caba72e470c3e49055241865db092eed038e Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Tue, 14 Jan 2025 09:24:09 -0700 Subject: [PATCH 16/27] Check reInit value when reloading libraries --- packages/lib/src/dev/remote-development.ts | 2 +- packages/lib/src/prod/remote-production.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/lib/src/dev/remote-development.ts b/packages/lib/src/dev/remote-development.ts index 6edfdc9a..726f473d 100644 --- a/packages/lib/src/dev/remote-development.ts +++ b/packages/lib/src/dev/remote-development.ts @@ -117,7 +117,7 @@ async function __federation_method_ensure(remoteId, reInit) { const getUrl = typeof remote.url === 'function' ? remote.url : () => Promise.resolve(remote.url); getUrl().then(url => { import(/* @vite-ignore */ url).then(lib => { - if (!remote.inited) { + if (!remote.inited || reInit) { const shareScope = wrapShareScope(remote.from) lib.init(shareScope); remote.lib = lib; diff --git a/packages/lib/src/prod/remote-production.ts b/packages/lib/src/prod/remote-production.ts index 4bb392c1..08b27ad6 100644 --- a/packages/lib/src/prod/remote-production.ts +++ b/packages/lib/src/prod/remote-production.ts @@ -181,7 +181,7 @@ export function prodRemotePlugin( const getUrl = typeof remote.url === 'function' ? remote.url : () => Promise.resolve(remote.url); getUrl().then(url => { import(/* @vite-ignore */ url).then(lib => { - if (!remote.inited) { + if (!remote.inited || reInit) { const shareScope = wrapShareModule(remote.from); lib.init(shareScope); remote.lib = lib; From a5c8b1db695ebf724106378e53e1246595db8ff5 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Tue, 14 Jan 2025 11:32:45 -0700 Subject: [PATCH 17/27] Make re-imports work by adding a url querystring. --- packages/lib/src/dev/remote-development.ts | 106 +++++++++-------- packages/lib/src/prod/remote-production.ts | 131 +++++++++++---------- 2 files changed, 123 insertions(+), 114 deletions(-) diff --git a/packages/lib/src/dev/remote-development.ts b/packages/lib/src/dev/remote-development.ts index 726f473d..869d0a83 100644 --- a/packages/lib/src/dev/remote-development.ts +++ b/packages/lib/src/dev/remote-development.ts @@ -95,42 +95,47 @@ const wrapShareScope = ${REMOTE_FROM_PARAMETER} => { } } const initMap = Object.create(null); -async function __federation_method_ensure(remoteId, reInit) { + +async function __federation_method_ensure(remoteId, retryCount) { const remote = remotesMap[remoteId]; - if (!remote.inited || reInit) { - if ('var' === remote.format) { - // loading js with script tag - return new Promise(resolve => { - const callback = () => { - if (!remote.inited) { - remote.lib = window[remoteId]; - remote.lib.init(wrapShareScope(remote.from)) - remote.inited = true; - } - resolve(remote.lib); - } - return loadJS(remote.url, callback); - }); - } else if (['esm', 'systemjs'].includes(remote.format)) { - // loading js with import(...) - return new Promise((resolve, reject) => { - const getUrl = typeof remote.url === 'function' ? remote.url : () => Promise.resolve(remote.url); - getUrl().then(url => { - import(/* @vite-ignore */ url).then(lib => { - if (!remote.inited || reInit) { - const shareScope = wrapShareScope(remote.from) - lib.init(shareScope); - remote.lib = lib; - remote.lib.init(shareScope); - remote.inited = true; - } - resolve(remote.lib); - }).catch(reject) - }) - }) - } + if (!remote.inited || retryCount > 0) { + if ('var' === remote.format) { + // loading js with script tag + return new Promise(resolve => { + const callback = () => { + if (!remote.inited) { + remote.lib = window[remoteId]; + remote.lib.init(wrapShareModule(remote.from)); + remote.inited = true; + } + resolve(remote.lib); + }; + return loadJS(remote.url, callback); + }); + } else if (['esm', 'systemjs'].includes(remote.format)) { + // loading js with import(...) + return new Promise((resolve, reject) => { + const getUrl = typeof remote.url === 'function' ? remote.url : () => { + const url = new URL(remote.url, window.location.origin); + url.searchParams.append("retryCount", retryCount); + return Promise.resolve(url.toString()); + } + getUrl().then(url => { + import(/* @vite-ignore */ url).then(lib => { + if (!remote.inited || retryCount > 0) { + const shareScope = wrapShareModule(remote.from); + lib.init(shareScope); + remote.lib = lib; + remote.lib.init(shareScope); + remote.inited = true; + } + resolve(remote.lib); + }).catch(reject); + }); + }) + } } else { - return remote.lib; + return remote.lib; } } @@ -149,17 +154,16 @@ function __federation_method_wrapDefault(module ,need){ } async function __federation_method_getRemote(remoteName, componentName) { - const remoteConfig = remotesMap[remoteName]; - let retryCount = 0; - const getRemote = async () => { - try { - const remoteModule = await __federation_method_ensure(remoteName, retryCount > 0); - const factory = await remoteModule.get(componentName); - factory(); - } catch (err) { - console.log("get remote retry count", retryCount); - retryCount++; - if (retryCount > remoteConfig.importRetryCount) { + const remoteConfig = remotesMap[remoteName]; + let retryCount = 0; + const getRemote = async () => { + try { + const remoteModule = await __federation_method_ensure(remoteName, retryCount); + const factory = await remoteModule.get(componentName); + factory(); + } catch (err) { + retryCount++; + if (retryCount > remoteConfig.importRetryCount) { if(remoteConfig.onImportFail){ const errorConfig = {...remoteConfig}; delete errorConfig.onImportFail; @@ -167,12 +171,12 @@ async function __federation_method_getRemote(remoteName, componentName) { } else { throw err; } - } else { - return getRemote(); - } - } - }; - return getRemote(); + } else { + return getRemote(); + } + } + }; + return getRemote(); } function __federation_method_setRemote(remoteName, remoteConfig) { diff --git a/packages/lib/src/prod/remote-production.ts b/packages/lib/src/prod/remote-production.ts index 08b27ad6..fea2e9e3 100644 --- a/packages/lib/src/prod/remote-production.ts +++ b/packages/lib/src/prod/remote-production.ts @@ -159,44 +159,49 @@ export function prodRemotePlugin( const initMap = Object.create(null); - async function __federation_method_ensure(remoteId, reInit) { - const remote = remotesMap[remoteId]; - if (!remote.inited || reInit) { - if ('var' === remote.format) { - // loading js with script tag - return new Promise(resolve => { - const callback = () => { - if (!remote.inited) { - remote.lib = window[remoteId]; - remote.lib.init(wrapShareModule(remote.from)) - remote.inited = true; - } - resolve(remote.lib); - } - return loadJS(remote.url, callback); - }); - } else if (['esm', 'systemjs'].includes(remote.format)) { - // loading js with import(...) - return new Promise((resolve, reject) => { - const getUrl = typeof remote.url === 'function' ? remote.url : () => Promise.resolve(remote.url); - getUrl().then(url => { - import(/* @vite-ignore */ url).then(lib => { - if (!remote.inited || reInit) { - const shareScope = wrapShareModule(remote.from); - lib.init(shareScope); - remote.lib = lib; - remote.lib.init(shareScope); - remote.inited = true; - } - resolve(remote.lib); - }).catch(reject) - }) - }) - } - } else { - return remote.lib; - } - } + + async function __federation_method_ensure(remoteId, retryCount) { + const remote = remotesMap[remoteId]; + if (!remote.inited || retryCount > 0) { + if ('var' === remote.format) { + // loading js with script tag + return new Promise(resolve => { + const callback = () => { + if (!remote.inited) { + remote.lib = window[remoteId]; + remote.lib.init(wrapShareModule(remote.from)); + remote.inited = true; + } + resolve(remote.lib); + }; + return loadJS(remote.url, callback); + }); + } else if (['esm', 'systemjs'].includes(remote.format)) { + // loading js with import(...) + return new Promise((resolve, reject) => { + const getUrl = typeof remote.url === 'function' ? remote.url : () => { + const url = new URL(remote.url, window.location.origin); + url.searchParams.append("retryCount", retryCount); + return Promise.resolve(url.toString()); + } + getUrl().then(url => { + import(/* @vite-ignore */ url).then(lib => { + if (!remote.inited || retryCount > 0) { + const shareScope = wrapShareModule(remote.from); + lib.init(shareScope); + remote.lib = lib; + remote.lib.init(shareScope); + remote.inited = true; + } + resolve(remote.lib); + }).catch(reject); + }); + }) + } + } else { + return remote.lib; + } + } function __federation_method_unwrapDefault(module) { return (module?.__esModule || module?.[Symbol.toStringTag] === 'Module') ? module.default : module @@ -211,32 +216,32 @@ export function prodRemotePlugin( } return module; } + async function __federation_method_getRemote(remoteName, componentName) { - const remoteConfig = remotesMap[remoteName]; - let retryCount = 0; - const getRemote = async () => { - try { - const remoteModule = await __federation_method_ensure(remoteName, retryCount > 0); - const factory = await remoteModule.get(componentName); - factory(); - } catch (err) { - console.log("get remote retry count", retryCount); - retryCount++; - if (retryCount > remoteConfig.importRetryCount) { - if(remoteConfig.onImportFail){ - const errorConfig = {...remoteConfig}; - delete errorConfig.onImportFail; - return remoteConfig.onImportFail(remoteName, componentName, errorConfig, err); - } else { - throw err; - } - } else { - return getRemote(); - } - } - }; - return getRemote(); - } + const remoteConfig = remotesMap[remoteName]; + let retryCount = 0; + const getRemote = async () => { + try { + const remoteModule = await __federation_method_ensure(remoteName, retryCount); + const factory = await remoteModule.get(componentName); + factory(); + } catch (err) { + retryCount++; + if (retryCount > remoteConfig.importRetryCount) { + if(remoteConfig.onImportFail){ + const errorConfig = {...remoteConfig}; + delete errorConfig.onImportFail; + return remoteConfig.onImportFail(remoteName, componentName, errorConfig, err); + } else { + throw err; + } + } else { + return getRemote(); + } + } + }; + return getRemote(); + } function __federation_method_setRemote(remoteName, remoteConfig) { remotesMap[remoteName] = remoteConfig; From ad09cb49de9080ab60332b3f2f6dc7ea30757f3b Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Tue, 14 Jan 2025 12:43:28 -0700 Subject: [PATCH 18/27] Return the output of the factory --- packages/lib/src/dev/remote-development.ts | 2 +- packages/lib/src/prod/remote-production.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/lib/src/dev/remote-development.ts b/packages/lib/src/dev/remote-development.ts index 869d0a83..c9781190 100644 --- a/packages/lib/src/dev/remote-development.ts +++ b/packages/lib/src/dev/remote-development.ts @@ -160,7 +160,7 @@ async function __federation_method_getRemote(remoteName, componentName) { try { const remoteModule = await __federation_method_ensure(remoteName, retryCount); const factory = await remoteModule.get(componentName); - factory(); + return factory(); } catch (err) { retryCount++; if (retryCount > remoteConfig.importRetryCount) { diff --git a/packages/lib/src/prod/remote-production.ts b/packages/lib/src/prod/remote-production.ts index fea2e9e3..77b2ab9d 100644 --- a/packages/lib/src/prod/remote-production.ts +++ b/packages/lib/src/prod/remote-production.ts @@ -224,7 +224,7 @@ export function prodRemotePlugin( try { const remoteModule = await __federation_method_ensure(remoteName, retryCount); const factory = await remoteModule.get(componentName); - factory(); + return factory(); } catch (err) { retryCount++; if (retryCount > remoteConfig.importRetryCount) { From 11692cd626b4d482ddd02c72649b0b00f2a15724 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Tue, 14 Jan 2025 13:27:17 -0700 Subject: [PATCH 19/27] Add a backoff and the ability to set it. --- packages/lib/src/dev/remote-development.ts | 14 ++++++++++---- packages/lib/src/prod/remote-production.ts | 14 ++++++++++---- packages/lib/src/utils/index.ts | 2 ++ 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/packages/lib/src/dev/remote-development.ts b/packages/lib/src/dev/remote-development.ts index c9781190..8d7b01f0 100644 --- a/packages/lib/src/dev/remote-development.ts +++ b/packages/lib/src/dev/remote-development.ts @@ -165,14 +165,20 @@ async function __federation_method_getRemote(remoteName, componentName) { retryCount++; if (retryCount > remoteConfig.importRetryCount) { if(remoteConfig.onImportFail){ - const errorConfig = {...remoteConfig}; - delete errorConfig.onImportFail; - return remoteConfig.onImportFail(remoteName, componentName, errorConfig, err); + return remoteConfig.onImportFail(remoteName, componentName, err); } else { throw err; } } else { - return getRemote(); + const retry = () => { + return new Promise((resolve) => { + setTimeout(() => { + const retryResult = getRemote(); + resolve(retryResult); + }, remoteConfig.importRetryBackoff || 500) + }) + } + return await retry(); } } }; diff --git a/packages/lib/src/prod/remote-production.ts b/packages/lib/src/prod/remote-production.ts index 77b2ab9d..bf5aadb2 100644 --- a/packages/lib/src/prod/remote-production.ts +++ b/packages/lib/src/prod/remote-production.ts @@ -229,14 +229,20 @@ export function prodRemotePlugin( retryCount++; if (retryCount > remoteConfig.importRetryCount) { if(remoteConfig.onImportFail){ - const errorConfig = {...remoteConfig}; - delete errorConfig.onImportFail; - return remoteConfig.onImportFail(remoteName, componentName, errorConfig, err); + return remoteConfig.onImportFail(remoteName, componentName, err); } else { throw err; } } else { - return getRemote(); + const retry = () => { + return new Promise((resolve) => { + setTimeout(() => { + const retryResult = getRemote(); + resolve(retryResult); + }, remoteConfig.importRetryBackoff || 500) + }) + } + return await retry(); } } }; diff --git a/packages/lib/src/utils/index.ts b/packages/lib/src/utils/index.ts index a2f829f1..cf771a9b 100644 --- a/packages/lib/src/utils/index.ts +++ b/packages/lib/src/utils/index.ts @@ -116,6 +116,7 @@ export function parseRemoteOptions( format: 'esm', from: 'vite', externalType: 'url', + importRetryBackoff: 500, importRetryCount: 0, onImportFail: undefined }), @@ -125,6 +126,7 @@ export function parseRemoteOptions( format: item.format || 'esm', from: item.from ?? 'vite', externalType: item.externalType || 'url', + importRetryBackoff: item.importRetryBackoff ?? 500, importRetryCount: item.format === "var" ? undefined : item.importRetryCount || 0, onImportFail: item.onImportFail ? item.onImportFail : undefined }) From 5aefefc1a24cec8be844c36a69a01c2958fbc565 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Tue, 14 Jan 2025 13:37:54 -0700 Subject: [PATCH 20/27] Fix typing --- packages/lib/types/index.d.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/lib/types/index.d.ts b/packages/lib/types/index.d.ts index 5a00fd83..c80abda9 100644 --- a/packages/lib/types/index.d.ts +++ b/packages/lib/types/index.d.ts @@ -246,6 +246,11 @@ declare type RemotesConfig = { */ from?: 'vite' | 'webpack' + /** + * The number of milliseconds to wait between import retries. Defaults to 500ms + */ + importRetryBackoff?: number + /** * The number of times to retry imports before failure */ From 34acd669d3d9f7adfc9642f5869fa44cd1b489a8 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Tue, 14 Jan 2025 15:57:07 -0700 Subject: [PATCH 21/27] Clean up and fix merge issue. --- packages/lib/src/prod/expose-production.ts | 2 +- packages/lib/src/utils/index.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/lib/src/prod/expose-production.ts b/packages/lib/src/prod/expose-production.ts index fad8ffd7..b89d0bd6 100644 --- a/packages/lib/src/prod/expose-production.ts +++ b/packages/lib/src/prod/expose-production.ts @@ -131,7 +131,7 @@ export function prodExposePlugin( window[key] = window[key] || []; window[key].push(href); }); - }; + }); async function __federation_import(name) { currentImports[name] ??= import(name) return currentImports[name] diff --git a/packages/lib/src/utils/index.ts b/packages/lib/src/utils/index.ts index cf771a9b..1dd059df 100644 --- a/packages/lib/src/utils/index.ts +++ b/packages/lib/src/utils/index.ts @@ -238,9 +238,9 @@ export function createRemotesMap(remotes: Remote[]): string { ${remotes .map( (remote) => - `'${remote.id}':{url:${createUrl(remote)},format:'${ + `'${remote.id}':{url:${createUrl(remote)} ,format:'${ remote.config.format - }',from:'${remote.config.from}',importRetryCount: ${remote.config.importRetryCount ?? 0}, onImportFail: ${remote.config.onImportFail? remote.config.onImportFail.toString(): "undefined"}}` + }', from:'${remote.config.from}', importRetryCount: ${remote.config.importRetryCount ?? 0}, onImportFail: ${remote.config.onImportFail? remote.config.onImportFail.toString(): "undefined"}}` ) .join(',\n ')} };` From f027cb0e13988e0f0898d36e847f8fa4bce8fced Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Tue, 14 Jan 2025 16:07:19 -0700 Subject: [PATCH 22/27] Fix another merge issue --- packages/lib/src/prod/expose-production.ts | 95 +++++++++++----------- 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/packages/lib/src/prod/expose-production.ts b/packages/lib/src/prod/expose-production.ts index b89d0bd6..6f9c174e 100644 --- a/packages/lib/src/prod/expose-production.ts +++ b/packages/lib/src/prod/expose-production.ts @@ -78,60 +78,61 @@ export function prodExposePlugin( const currentImports = {} const exportSet = new Set(['Module', '__esModule', 'default', '_export_sfc']); let moduleMap = {${moduleMap}} - const seen = {} - export const ${DYNAMIC_LOADING_CSS} = (cssFilePaths, dontAppendStylesToHead, exposeItemName) => { - const metaUrl = import.meta.url - if (typeof metaUrl == 'undefined') { - console.warn('The remote style takes effect only when the build.target option in the vite.config.ts file is higher than that of "es2020".') - return - } - const curUrl = metaUrl.substring(0, metaUrl.lastIndexOf('${ - options.filename - }')) - - cssFilePaths.forEach(cssFilePath => { - const href = curUrl + cssFilePath - if (href in seen) return - seen[href] = true - if (dontAppendStylesToHead) { - const key = 'css__${options.name}__' + exposeItemName; - if (window[key] == null) window[key] = [] - window[key].push(href); - } else { - const element = document.head.appendChild(document.createElement('link')) - element.href = href - element.rel = 'stylesheet' + const seen = {} + export const ${DYNAMIC_LOADING_CSS} = (cssFilePaths, dontAppendStylesToHead, exposeItemName) => { + const metaUrl = import.meta.url + if (typeof metaUrl == 'undefined') { + console.warn('The remote style takes effect only when the build.target option in the vite.config.ts file is higher than that of "es2020".') + return } + const curUrl = metaUrl.substring(0, metaUrl.lastIndexOf('${ + options.filename + }')) - const curUrl = metaUrl.substring(0, metaUrl.lastIndexOf('${options.filename}')); - const base = __VITE_BASE_PLACEHOLDER__; - const assetsDir = __VITE_ASSETS_DIR_PLACEHOLDER__; - - cssFilePaths.forEach(cssPath => { - let href = '' - const baseUrl = base || curUrl - if (baseUrl && baseUrl !== '/') { - href = [baseUrl, assetsDir, cssPath].filter(Boolean).join('/') - } else { - href = curUrl + cssPath - } + cssFilePaths.forEach(cssFilePath => { + const href = curUrl + cssFilePath + if (href in seen) return + seen[href] = true + if (dontAppendStylesToHead) { + const key = 'css__${options.name}__' + exposeItemName; + if (window[key] == null) window[key] = [] + window[key].push(href); + } else { + const element = document.head.appendChild(document.createElement('link')) + element.href = href + element.rel = 'stylesheet' + } - if (href in seen) return; - seen[href] = true; + const curUrl = metaUrl.substring(0, metaUrl.lastIndexOf('${options.filename}')); + const base = __VITE_BASE_PLACEHOLDER__; + const assetsDir = __VITE_ASSETS_DIR_PLACEHOLDER__; - if (!dontAppendStylesToHead) { - const element = document.createElement('link'); - element.rel = 'stylesheet'; - element.href = href; - document.head.appendChild(element); - return; + cssFilePaths.forEach(cssPath => { + let href = '' + const baseUrl = base || curUrl + if (baseUrl && baseUrl !== '/') { + href = [baseUrl, assetsDir, cssPath].filter(Boolean).join('/') + } else { + href = curUrl + cssPath } - const key = 'css__' + options.name + '__' + exposeItemName; - window[key] = window[key] || []; - window[key].push(href); + if (href in seen) return; + seen[href] = true; + + if (!dontAppendStylesToHead) { + const element = document.createElement('link'); + element.rel = 'stylesheet'; + element.href = href; + document.head.appendChild(element); + return; + } + + const key = 'css__' + options.name + '__' + exposeItemName; + window[key] = window[key] || []; + window[key].push(href); + }); }); - }); + } async function __federation_import(name) { currentImports[name] ??= import(name) return currentImports[name] From e5508677d636825a7cfa25d86358de9fcda05d20 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Tue, 14 Jan 2025 16:26:27 -0700 Subject: [PATCH 23/27] Revert unnecessary changes --- packages/lib/src/prod/expose-production.ts | 76 +++++++++------------- 1 file changed, 29 insertions(+), 47 deletions(-) diff --git a/packages/lib/src/prod/expose-production.ts b/packages/lib/src/prod/expose-production.ts index 6f9c174e..19dbe5d6 100644 --- a/packages/lib/src/prod/expose-production.ts +++ b/packages/lib/src/prod/expose-production.ts @@ -80,59 +80,41 @@ export function prodExposePlugin( let moduleMap = {${moduleMap}} const seen = {} export const ${DYNAMIC_LOADING_CSS} = (cssFilePaths, dontAppendStylesToHead, exposeItemName) => { - const metaUrl = import.meta.url - if (typeof metaUrl == 'undefined') { - console.warn('The remote style takes effect only when the build.target option in the vite.config.ts file is higher than that of "es2020".') - return + const metaUrl = import.meta.url; + if (typeof metaUrl === 'undefined') { + console.warn('The remote style takes effect only when the build.target option in the vite.config.ts file is higher than that of "es2020".'); + return; } - const curUrl = metaUrl.substring(0, metaUrl.lastIndexOf('${ - options.filename - }')) - - cssFilePaths.forEach(cssFilePath => { - const href = curUrl + cssFilePath - if (href in seen) return - seen[href] = true - if (dontAppendStylesToHead) { - const key = 'css__${options.name}__' + exposeItemName; - if (window[key] == null) window[key] = [] - window[key].push(href); - } else { - const element = document.head.appendChild(document.createElement('link')) - element.href = href - element.rel = 'stylesheet' - } - const curUrl = metaUrl.substring(0, metaUrl.lastIndexOf('${options.filename}')); - const base = __VITE_BASE_PLACEHOLDER__; - const assetsDir = __VITE_ASSETS_DIR_PLACEHOLDER__; + const curUrl = metaUrl.substring(0, metaUrl.lastIndexOf('${options.filename}')); + const base = __VITE_BASE_PLACEHOLDER__; + const assetsDir = __VITE_ASSETS_DIR_PLACEHOLDER__; - cssFilePaths.forEach(cssPath => { - let href = '' - const baseUrl = base || curUrl - if (baseUrl && baseUrl !== '/') { - href = [baseUrl, assetsDir, cssPath].filter(Boolean).join('/') - } else { - href = curUrl + cssPath - } + cssFilePaths.forEach(cssPath => { + let href = '' + const baseUrl = base || curUrl + if (baseUrl && baseUrl !== '/') { + href = [baseUrl, assetsDir, cssPath].filter(Boolean).join('/') + } else { + href = curUrl + cssPath + } - if (href in seen) return; - seen[href] = true; + if (href in seen) return; + seen[href] = true; - if (!dontAppendStylesToHead) { - const element = document.createElement('link'); - element.rel = 'stylesheet'; - element.href = href; - document.head.appendChild(element); - return; - } + if (!dontAppendStylesToHead) { + const element = document.createElement('link'); + element.rel = 'stylesheet'; + element.href = href; + document.head.appendChild(element); + return; + } - const key = 'css__' + options.name + '__' + exposeItemName; - window[key] = window[key] || []; - window[key].push(href); - }); + const key = 'css__' + options.name + '__' + exposeItemName; + window[key] = window[key] || []; + window[key].push(href); }); - } + }; async function __federation_import(name) { currentImports[name] ??= import(name) return currentImports[name] @@ -327,4 +309,4 @@ export function prodExposePlugin( } } } -} +} \ No newline at end of file From 42890220756201c554c3a056fbda516efdab7a56 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Wed, 15 Jan 2025 09:16:56 -0700 Subject: [PATCH 24/27] Address code review comments. --- packages/lib/src/dev/expose-development.ts | 12 ++++++------ packages/lib/src/dev/remote-development.ts | 3 ++- packages/lib/src/index.ts | 2 +- packages/lib/src/prod/remote-production.ts | 3 ++- packages/lib/src/utils/index.ts | 2 -- packages/lib/types/index.d.ts | 9 +++------ packages/lib/vite.config.ts | 2 +- 7 files changed, 15 insertions(+), 18 deletions(-) diff --git a/packages/lib/src/dev/expose-development.ts b/packages/lib/src/dev/expose-development.ts index 8a9ce023..058926df 100644 --- a/packages/lib/src/dev/expose-development.ts +++ b/packages/lib/src/dev/expose-development.ts @@ -51,12 +51,12 @@ export function devExposePlugin( ${moduleMap} }; const __federation_import = async (urlImportPath, fsImportPath) => { - let importedModule; - try { - return await import(fsImportPath); - } catch(ex) { - return await import(urlImportPath); - } + let importedModule; + try { + return await import(fsImportPath); + } catch(ex) { + return await import(urlImportPath); + } }; export const get =(module) => { if(!moduleMap[module]) throw new Error('Can not find remote module ' + module) diff --git a/packages/lib/src/dev/remote-development.ts b/packages/lib/src/dev/remote-development.ts index 8d7b01f0..8279864d 100644 --- a/packages/lib/src/dev/remote-development.ts +++ b/packages/lib/src/dev/remote-development.ts @@ -170,12 +170,13 @@ async function __federation_method_getRemote(remoteName, componentName) { throw err; } } else { + const retryBackoff = 10 ** retryCount; const retry = () => { return new Promise((resolve) => { setTimeout(() => { const retryResult = getRemote(); resolve(retryResult); - }, remoteConfig.importRetryBackoff || 500) + }, retryBackoff) }) } return await retry(); diff --git a/packages/lib/src/index.ts b/packages/lib/src/index.ts index 9e830be7..a85eb117 100644 --- a/packages/lib/src/index.ts +++ b/packages/lib/src/index.ts @@ -117,7 +117,7 @@ export default function federation( pluginHook.options as (( this: MinimalPluginContext, options: InputOptions - ) => InputOptions | NullValue)|undefined + ) => InputOptions | NullValue) | undefined )?.call(this, _options) } return _options diff --git a/packages/lib/src/prod/remote-production.ts b/packages/lib/src/prod/remote-production.ts index bf5aadb2..4592b3b9 100644 --- a/packages/lib/src/prod/remote-production.ts +++ b/packages/lib/src/prod/remote-production.ts @@ -234,12 +234,13 @@ export function prodRemotePlugin( throw err; } } else { + const retryBackoff = 10 ** retryCount; const retry = () => { return new Promise((resolve) => { setTimeout(() => { const retryResult = getRemote(); resolve(retryResult); - }, remoteConfig.importRetryBackoff || 500) + }, retryBackoff) }) } return await retry(); diff --git a/packages/lib/src/utils/index.ts b/packages/lib/src/utils/index.ts index 1dd059df..e3f230e8 100644 --- a/packages/lib/src/utils/index.ts +++ b/packages/lib/src/utils/index.ts @@ -116,7 +116,6 @@ export function parseRemoteOptions( format: 'esm', from: 'vite', externalType: 'url', - importRetryBackoff: 500, importRetryCount: 0, onImportFail: undefined }), @@ -126,7 +125,6 @@ export function parseRemoteOptions( format: item.format || 'esm', from: item.from ?? 'vite', externalType: item.externalType || 'url', - importRetryBackoff: item.importRetryBackoff ?? 500, importRetryCount: item.format === "var" ? undefined : item.importRetryCount || 0, onImportFail: item.onImportFail ? item.onImportFail : undefined }) diff --git a/packages/lib/types/index.d.ts b/packages/lib/types/index.d.ts index c80abda9..32ba016c 100644 --- a/packages/lib/types/index.d.ts +++ b/packages/lib/types/index.d.ts @@ -247,12 +247,9 @@ declare type RemotesConfig = { from?: 'vite' | 'webpack' /** - * The number of milliseconds to wait between import retries. Defaults to 500ms - */ - importRetryBackoff?: number - - /** - * The number of times to retry imports before failure + * The number of times to retry imports before failure. + * The first retry will happen after a 10ms delay. Subsequent retries will happen + * after a (10 ** n)ms delay. */ importRetryCount?: number diff --git a/packages/lib/vite.config.ts b/packages/lib/vite.config.ts index 8d9adc29..f26a3265 100644 --- a/packages/lib/vite.config.ts +++ b/packages/lib/vite.config.ts @@ -10,7 +10,7 @@ export default defineConfig({ target: 'node14', minify: false, rollupOptions: { - external: ['fs', 'path', 'crypto', 'magic-string', '__federation_fn_satisfy'], + external: ['fs', 'path', 'crypto', 'magic-string'], output: { minifyInternalExports: false } From 1e44e4790e81024a4f12f8d358f4e5dde3e33538 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Wed, 15 Jan 2025 09:54:02 -0700 Subject: [PATCH 25/27] Address more code review feedback --- packages/lib/src/dev/expose-development.ts | 2 +- packages/lib/src/dev/remote-development.ts | 2 +- packages/lib/src/index.ts | 98 +++------------------- packages/lib/types/pluginHooks.d.ts | 63 ++++++++++++++ 4 files changed, 77 insertions(+), 88 deletions(-) diff --git a/packages/lib/src/dev/expose-development.ts b/packages/lib/src/dev/expose-development.ts index 058926df..06305ab4 100644 --- a/packages/lib/src/dev/expose-development.ts +++ b/packages/lib/src/dev/expose-development.ts @@ -51,7 +51,7 @@ export function devExposePlugin( ${moduleMap} }; const __federation_import = async (urlImportPath, fsImportPath) => { - let importedModule; + let importedModule; try { return await import(fsImportPath); } catch(ex) { diff --git a/packages/lib/src/dev/remote-development.ts b/packages/lib/src/dev/remote-development.ts index 8279864d..e7668006 100644 --- a/packages/lib/src/dev/remote-development.ts +++ b/packages/lib/src/dev/remote-development.ts @@ -185,7 +185,7 @@ async function __federation_method_getRemote(remoteName, componentName) { }; return getRemote(); } - + function __federation_method_setRemote(remoteName, remoteConfig) { remotesMap[remoteName] = remoteConfig; } diff --git a/packages/lib/src/index.ts b/packages/lib/src/index.ts index a85eb117..6b84c437 100644 --- a/packages/lib/src/index.ts +++ b/packages/lib/src/index.ts @@ -25,19 +25,10 @@ import { dirname } from 'path' import { prodRemotePlugin } from './prod/remote-production' import type { VitePluginFederationOptions } from '../types' import { builderInfo, DEFAULT_ENTRY_FILENAME, parsedOptions } from './public' -import type { PluginHooks } from '../types/pluginHooks' +import type { PluginHookBuildStart, PluginHookConfig, PluginHookConfigResolved, PluginHookConfigureServer, PluginHookGenerateBundle, PluginHookModuleParsed, PluginHookOutputOptions, PluginHookRenderChunk, PluginHooks, PluginHookTransform } from '../types/pluginHooks' import { InputOptions, - NormalizedInputOptions, - NormalizedOutputOptions, NullValue, - OutputBundle, - OutputOptions, - PluginContext, - RenderedChunk, - SourceDescription, - SourceMapInput, - TransformPluginContext, type MinimalPluginContext, type ModuleInfo } from 'rollup' @@ -126,14 +117,8 @@ export default function federation( options.mode = options.mode ?? env.mode registerPlugins(options.mode, env.command) registerCount++ - for (const pluginHook of pluginList) {( - pluginHook.config as (( - this: void, - config: UserConfig, - env: ConfigEnv - ) => UserConfig | null | void | Promise) | undefined - )?.call(this, config, env) - + for (const pluginHook of pluginList) { + (pluginHook.config as PluginHookConfig)?.call(this, config, env); } // only run when builder is vite,rollup doesnt has hook named `config` @@ -142,32 +127,17 @@ export default function federation( }, configureServer(server: ViteDevServer) { for (const pluginHook of pluginList) { - ( - pluginHook.configureServer as (( - this: void, - server: ViteDevServer - ) => (() => void) | void | Promise<(() => void) | void>)|undefined - )?.call(this, server) + (pluginHook.configureServer as PluginHookConfigureServer)?.call(this, server); } }, configResolved(config: ResolvedConfig) { for (const pluginHook of pluginList) { - ( - pluginHook.configResolved as (( - this: void, - config: ResolvedConfig - ) => void | Promise)| undefined - )?.call(this, config) + (pluginHook.configResolved as PluginHookConfigResolved)?.call(this, config); } }, buildStart(inputOptions) { for (const pluginHook of pluginList) { - ( - pluginHook.buildStart as (( - this: PluginContext, - options: NormalizedInputOptions - ) => void )| undefined - )?.call(this, inputOptions) + (pluginHook.buildStart as PluginHookBuildStart)?.call(this, inputOptions); } }, @@ -207,46 +177,22 @@ export default function federation( transform(code: string, id: string) { for (const pluginHook of pluginList) { - const result = ( - pluginHook.transform as (( - this: TransformPluginContext, - code: string, - id: string, - options?: { - ssr?: boolean - } - ) => - | Promise> - | (string | NullValue | Partial)) - | undefined - )?.call(this, code, id) + const result = (pluginHook.transform as PluginHookTransform)?.call(this, code, id); if (result) { - return result + return result; } } return code }, moduleParsed(moduleInfo: ModuleInfo): void { for (const pluginHook of pluginList) { - ( - pluginHook.moduleParsed as ( - this: PluginContext, - info: ModuleInfo - ) => void | undefined - )?.call(this, moduleInfo) + (pluginHook.moduleParsed as PluginHookModuleParsed)?.call(this, moduleInfo); } }, outputOptions(outputOptions) { for (const pluginHook of pluginList) { - ( - pluginHook.outputOptions as - | (( - this: PluginContext, - options: OutputOptions - ) => OutputOptions | NullValue) - | undefined - )?.call(this, outputOptions) + (pluginHook.outputOptions as PluginHookOutputOptions)?.call(this, outputOptions) } return outputOptions }, @@ -254,20 +200,7 @@ export default function federation( renderChunk(code, chunkInfo, _options) { for (const pluginHook of pluginList) { if (pluginHook.renderChunk) { - const result = ( - pluginHook.renderChunk as - | (( - this: MinimalPluginContext, - code: string, - chunk: RenderedChunk, - options: NormalizedOutputOptions, - meta: { chunks: Record } - ) => - | { code: string; map?: SourceMapInput } - | string - | NullValue) - | undefined - )?.call(this, code, chunkInfo, _options, { chunks: {} }) + const result = (pluginHook.renderChunk as PluginHookRenderChunk)?.call(this, code, chunkInfo, _options, { chunks: {} }) if (result) { return result } @@ -278,14 +211,7 @@ export default function federation( generateBundle: function (_options, bundle, isWrite) { for (const pluginHook of pluginList) { - ( - pluginHook.generateBundle as (( - this: PluginContext, - options: NormalizedOutputOptions, - bundle: OutputBundle, - isWrite: boolean - ) => void) | undefined - )?.call(this, _options, bundle, isWrite) + (pluginHook.generateBundle as PluginHookGenerateBundle)?.call(this, _options, bundle, isWrite); } } } diff --git a/packages/lib/types/pluginHooks.d.ts b/packages/lib/types/pluginHooks.d.ts index d97f7052..412c5008 100644 --- a/packages/lib/types/pluginHooks.d.ts +++ b/packages/lib/types/pluginHooks.d.ts @@ -2,3 +2,66 @@ import { Plugin as VitePlugin } from 'vite' export interface PluginHooks extends VitePlugin { virtualFile?: Record } +export type PluginHookConfig = + | (( + this: void, + config: UserConfig, + env: ConfigEnv + ) => UserConfig | null | void | Promise) + | undefined + +export type PluginHookConfigureServer = + | (( + this: void, + server: ViteDevServer + ) => (() => void) | void | Promise<(() => void) | void>) + | undefined + +export type PluginHookConfigResolved = + | ((this: void, config: ResolvedConfig) => void | Promise) + | undefined + +export type PluginHookBuildStart = + | ((this: PluginContext, options: NormalizedInputOptions) => void) + | undefined + +export type PluginHookTransform = + | (( + this: TransformPluginContext, + code: string, + id: string, + options?: { + ssr?: boolean + } + ) => + | Promise> + | (string | NullValue | Partial)) + | undefined + +export type PluginHookModuleParsed = ( + this: PluginContext, + info: ModuleInfo +) => void | undefined + +export type PluginHookOutputOptions = + | ((this: PluginContext, options: OutputOptions) => OutputOptions | NullValue) + | undefined + +export type PluginHookRenderChunk = + | (( + this: MinimalPluginContext, + code: string, + chunk: RenderedChunk, + options: NormalizedOutputOptions, + meta: { chunks: Record } + ) => { code: string; map?: SourceMapInput } | string | NullValue) + | undefined + +export type PluginHookGenerateBundle = + | (( + this: PluginContext, + options: NormalizedOutputOptions, + bundle: OutputBundle, + isWrite: boolean + ) => void) + | undefined From a5aa79deec439416ce60d980c717a85ef6f1f95e Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Thu, 16 Jan 2025 13:33:20 -0700 Subject: [PATCH 26/27] Resolve merge issue --- packages/lib/src/dev/remote-development.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/lib/src/dev/remote-development.ts b/packages/lib/src/dev/remote-development.ts index e7668006..d7a8f607 100644 --- a/packages/lib/src/dev/remote-development.ts +++ b/packages/lib/src/dev/remote-development.ts @@ -43,6 +43,7 @@ export function devRemotePlugin( options: VitePluginFederationOptions ): PluginHooks { parsedOptions.devRemote = parseRemoteOptions(options) + const shareScope = options.shareScope || 'default' // const remotes: { id: string; regexp: RegExp; config: RemotesConfig }[] = [] for (const item of parsedOptions.devRemote) { devRemotes.push({ @@ -89,10 +90,10 @@ function get(name, ${REMOTE_FROM_PARAMETER}){ return module }) } -const wrapShareScope = ${REMOTE_FROM_PARAMETER} => { - return { +const wrapShareModule = ${REMOTE_FROM_PARAMETER} => { + return merge({ ${getModuleMarker('shareScope')} - } + }, (globalThis.__federation_shared__ || {})['${shareScope}'] || {}); } const initMap = Object.create(null); From e69872f158456d79e05336aed3cdbdc7686375b4 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Thu, 16 Jan 2025 13:38:40 -0700 Subject: [PATCH 27/27] More merge issues. --- packages/lib/src/dev/remote-development.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/lib/src/dev/remote-development.ts b/packages/lib/src/dev/remote-development.ts index d7a8f607..8c6974b9 100644 --- a/packages/lib/src/dev/remote-development.ts +++ b/packages/lib/src/dev/remote-development.ts @@ -90,6 +90,15 @@ function get(name, ${REMOTE_FROM_PARAMETER}){ return module }) } +function merge(obj1, obj2) { + const mergedObj = Object.assign(obj1, obj2); + for (const key of Object.keys(mergedObj)) { + if (typeof mergedObj[key] === 'object' && typeof obj2[key] === 'object') { + mergedObj[key] = merge(mergedObj[key], obj2[key]); + } + } + return mergedObj; +} const wrapShareModule = ${REMOTE_FROM_PARAMETER} => { return merge({ ${getModuleMarker('shareScope')}