diff --git a/packages/utilities/README.md b/packages/utilities/README.md index 06b84cdc728..8898c298532 100644 --- a/packages/utilities/README.md +++ b/packages/utilities/README.md @@ -50,7 +50,9 @@ Usage looks something like this: ```js import { importRemote } from '@module-federation/utilities'; +// -- // If it's a regular js module: +// -- importRemote({ url: 'http://localhost:3001', scope: 'Foo', @@ -65,7 +67,20 @@ importRemote({ }, ); +// -- +// If it's a ESM module (this is currently the default for NX): +// -- +const Bar = lazy(() => importRemote({ url: 'http://localhost:3001', scope: 'Foo', module: 'Bar', esm: true })); + +return ( + Loading Bar...}> + + +); + +// -- // If Bar is a React component you can use it with lazy and Suspense just like a dynamic import: +// -- const Bar = lazy(() => importRemote({ url: 'http://localhost:3001', scope: 'Foo', module: 'Bar' })); return ( diff --git a/packages/utilities/project.json b/packages/utilities/project.json index dc230717c22..7d2a6acea1b 100644 --- a/packages/utilities/project.json +++ b/packages/utilities/project.json @@ -13,7 +13,7 @@ "tsConfig": "packages/utilities/tsconfig.lib.json", "assets": ["packages/utilities/*.md"], "buildableProjectDepsInPackageJsonType": "dependencies", - "updateBuildableProjectDepsInPackageJson": true, + "updateBuildableProjectDepsInPackageJson": true } }, "publish": { diff --git a/packages/utilities/src/utils/importRemote.ts b/packages/utilities/src/utils/importRemote.ts index fab01bfd3cf..4b2a90101de 100644 --- a/packages/utilities/src/utils/importRemote.ts +++ b/packages/utilities/src/utils/importRemote.ts @@ -26,6 +26,7 @@ export interface ImportRemoteOptions { module: string; remoteEntryFileName?: string; bustRemoteEntryCache?: boolean; + esm?: boolean; } /** @@ -43,7 +44,7 @@ const REMOTE_ENTRY_FILE = 'remoteEntry.js'; * @returns {Promise} A promise that resolves when the remote is loaded */ const loadRemote = ( - url: ImportRemoteOptions['url'], + url: RemoteData['url'], scope: ImportRemoteOptions['scope'], bustRemoteEntryCache: ImportRemoteOptions['bustRemoteEntryCache'], ) => @@ -67,6 +68,25 @@ const loadRemote = ( ); }); +const loadEsmRemote = async ( + url: RemoteData['url'], + scope: ImportRemoteOptions['scope'], +) => { + const module = await import(/* webpackIgnore: true */ url); + + if (!module) { + throw new Error( + `Unable to load requested remote from ${url} with scope ${scope}`, + ); + } + + window[scope] = { + ...module, + __initializing: false, + __initialized: false, + } satisfies WebpackRemoteContainer; +}; + /** * Function to initialize sharing * @async @@ -114,6 +134,7 @@ export const importRemote = async ({ module, remoteEntryFileName = REMOTE_ENTRY_FILE, bustRemoteEntryCache = true, + esm = false, }: ImportRemoteOptions): Promise => { const remoteScope = scope as unknown as number; if (!window[remoteScope]) { @@ -125,15 +146,14 @@ export const importRemote = async ({ remoteUrl = await url(); } + const remoteUrlWithEntryFile = `${remoteUrl}/${remoteEntryFileName}`; + + const asyncContainer = !esm + ? loadRemote(remoteUrlWithEntryFile, scope, bustRemoteEntryCache) + : loadEsmRemote(remoteUrlWithEntryFile, scope); + // Load the remote and initialize the share scope if it's empty - await Promise.all([ - loadRemote( - `${remoteUrl}/${remoteEntryFileName}`, - scope, - bustRemoteEntryCache, - ), - initSharing(), - ]); + await Promise.all([asyncContainer, initSharing()]); if (!window[remoteScope]) { throw new Error( `Remote loaded successfully but ${scope} could not be found! Verify that the name is correct in the Webpack configuration!`, diff --git a/packages/utilities/tsconfig.json b/packages/utilities/tsconfig.json index 828a2b0b65c..963cd7748c9 100644 --- a/packages/utilities/tsconfig.json +++ b/packages/utilities/tsconfig.json @@ -2,13 +2,15 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "target": "ES2015", - "module": "commonjs", + "module": "Node16", + "moduleResolution": "Node16", "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "resolveJsonModule": true }, "files": [], "include": [], diff --git a/packages/utilities/tsconfig.lib.json b/packages/utilities/tsconfig.lib.json index d1989338eb1..ad80c6dc113 100644 --- a/packages/utilities/tsconfig.lib.json +++ b/packages/utilities/tsconfig.lib.json @@ -4,7 +4,7 @@ "jsx": "react", "outDir": "../../dist/out-tsc", "declaration": true, - "types": ["node"] + "types": ["node"], }, "include": ["**/*.ts", "**/*.tsx"], "exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.test.ts", "**/*.test.js"]