From acde04691704e5f5ae1fc24167616a2b4956bc90 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Wed, 13 Dec 2023 09:50:34 -0700 Subject: [PATCH 01/11] 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 dfae13e7eeced7f74729f3b728e5c618447777c6 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Wed, 13 Dec 2023 14:21:44 -0700 Subject: [PATCH 02/11] 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 a7358ed169877a4f2247e82fa921d9c1bb943c93 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Thu, 15 Feb 2024 09:36:30 -0700 Subject: [PATCH 03/11] 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 565417a108c099593a165e50b78fdc29cef3efc5 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Fri, 16 Feb 2024 08:50:25 -0700 Subject: [PATCH 04/11] 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 7e28ebd41b812ba2aa3537774f340c41087aabc3 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Fri, 28 Jun 2024 11:35:26 -0600 Subject: [PATCH 05/11] 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 4bb47d693a80945df279e91b11c76b0c8cca9ea9 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Fri, 28 Jun 2024 12:01:47 -0600 Subject: [PATCH 06/11] 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 4df701a87680ead4a880aa7948e0ec917f914dab Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Fri, 28 Jun 2024 13:21:41 -0600 Subject: [PATCH 07/11] 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 40307ae2d565a2a0d22a0cdf380ff37f8e3774ee Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Fri, 26 Jul 2024 09:57:31 -0600 Subject: [PATCH 08/11] 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 618484c91e6ac2d21a2280cd7b51e5172d6f94f4 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Wed, 31 Jul 2024 12:56:06 -0600 Subject: [PATCH 09/11] 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 bed572977839d14d3a9bcdf8ec11b5e48ffed289 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Wed, 31 Jul 2024 13:19:30 -0600 Subject: [PATCH 10/11] 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 099ed6a898f4a0c563c369e98df9e8b1476c7020 Mon Sep 17 00:00:00 2001 From: Jared Lillywhite Date: Wed, 21 Aug 2024 16:15:55 -0600 Subject: [PATCH 11/11] 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) => {