Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 46 additions & 34 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
export * from '@softarc/native-federation/domain';
export * from "@softarc/native-federation/domain";
import {
initFederation as internalInitFederation,
type NativeFederationResult,
} from '@softarc/native-federation-orchestrator';
} from "@softarc/native-federation-orchestrator";
import {
useShimImportMap,
useDefaultImportMap,
consoleLogger,
globalThisStorageEntry,
type LogType,
} from '@softarc/native-federation-orchestrator/options';
type NFOptions,
} from "@softarc/native-federation-orchestrator/options";

export type Imports = Record<string, string>;
export type Scopes = Record<string, Imports>;
Expand Down Expand Up @@ -38,45 +37,54 @@ export type LoadRemoteModuleOptions<T = any> = {
fallback?: T;
};

export interface InitFederationOptions {
export type NgOrchestratorOptions = {
/**
* @deprecated Use `hostRemoteEntry` instead and pass the cache tag there:
* ```ts
* hostRemoteEntry: { url: "./remoteEntry.json", cacheTag: "your-cache-tag" }
* ```
*/
cacheTag?: string;
logging?: LogType;
sse?: boolean;
/**
* Use es-module-shims shim mode (default `true`). Set `false` for native
* import maps; the build option `esmsInitOptions: { shimMode: false }` must
* match. See #70 for when native mode is needed (e.g. DevExtreme).
*/
shimMode?: boolean;
}
};

export type NgNFOptions = NgOrchestratorOptions & NFOptions;

let resolveFirstInit!: (
value: NativeFederationResult | PromiseLike<NativeFederationResult>
value: NativeFederationResult | PromiseLike<NativeFederationResult>,
) => void;
let rejectFirstInit!: (reason?: unknown) => void;
let firstInitCaptured = false;
let federationPromise: Promise<NativeFederationResult> = new Promise(
(resolve, reject) => {
resolveFirstInit = resolve;
rejectFirstInit = reject;
}
},
);

export function initFederation(
remotesOrManifestUrl?: Record<string, string> | string,
options?: InitFederationOptions
options: NgNFOptions = {},
) {
const { cacheTag, shimMode, ...nfOpts } = options as Omit<
NgNFOptions,
"cacheTag"
> & { cacheTag?: string };

const importMapProvider =
options?.shimMode === false
shimMode === false
? useDefaultImportMap()
: useShimImportMap({ shimMode: true });
const p = internalInitFederation(remotesOrManifestUrl ?? {}, {
...importMapProvider,
logger: consoleLogger,
storage: globalThisStorageEntry,
hostRemoteEntry: { url: './remoteEntry.json', cacheTag: options?.cacheTag },
logLevel: options?.logging ?? 'debug',
sse: options?.sse,
hostRemoteEntry: { url: "./remoteEntry.json", cacheTag },
...nfOpts,
});
if (!firstInitCaptured) {
firstInitCaptured = true;
Expand All @@ -88,30 +96,32 @@ export function initFederation(

function normalizeOptions<T>(
optionsOrRemoteName: LoadRemoteModuleOptions<T> | string,
exposedModule?: string
exposedModule?: string,
): LoadRemoteModuleOptions<T> {
if (typeof optionsOrRemoteName === 'string' && exposedModule) {
if (typeof optionsOrRemoteName === "string" && exposedModule) {
return { remoteName: optionsOrRemoteName, exposedModule };
}
if (typeof optionsOrRemoteName === 'object' && !exposedModule) {
if (typeof optionsOrRemoteName === "object" && !exposedModule) {
return optionsOrRemoteName;
}
throw new Error(
'unexpected arguments: please pass options or a remoteName/exposedModule-pair'
"unexpected arguments: please pass options or a remoteName/exposedModule-pair",
);
}

function logClientError(error: string): void {
if (typeof window !== 'undefined') {
if (typeof window !== "undefined") {
console.error(error);
}
}

async function resolveRemoteNameFromEntry(remoteEntry: string): Promise<string> {
async function resolveRemoteNameFromEntry(
remoteEntry: string,
): Promise<string> {
const res = await fetch(remoteEntry);
if (!res.ok) {
throw new Error(
`Failed to fetch remoteEntry at ${remoteEntry}: ${res.status} ${res.statusText}`
`Failed to fetch remoteEntry at ${remoteEntry}: ${res.status} ${res.statusText}`,
);
}
const info = (await res.json()) as { name?: string };
Expand Down Expand Up @@ -152,17 +162,17 @@ async function resolveRemoteNameFromEntry(remoteEntry: string): Promise<string>
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export async function loadRemoteModule<T = any>(
options: LoadRemoteModuleOptions<T>
options: LoadRemoteModuleOptions<T>,
): Promise<T>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export async function loadRemoteModule<T = any>(
remoteName: string,
exposedModule: string
exposedModule: string,
): Promise<T>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export async function loadRemoteModule<T = any>(
optionsOrRemoteName: LoadRemoteModuleOptions<T> | string,
exposedModule?: string
exposedModule?: string,
): Promise<T> {
const options = normalizeOptions<T>(optionsOrRemoteName, exposedModule);
const { fallback } = options;
Expand All @@ -171,37 +181,39 @@ export async function loadRemoteModule<T = any>(
let federation = await federationPromise;

if (!options.remoteName && options.remoteEntry) {
options.remoteName = await resolveRemoteNameFromEntry(options.remoteEntry);
options.remoteName = await resolveRemoteNameFromEntry(
options.remoteEntry,
);
}

if (options.remoteEntry) {
federation = await federation.initRemoteEntry(
options.remoteEntry,
options.remoteName
options.remoteName,
);
}

if (!options.remoteName) {
const err = 'unexpected arguments: Please pass remoteName or remoteEntry';
const err = "unexpected arguments: Please pass remoteName or remoteEntry";
if (!fallback) throw new Error(err);
logClientError(err);
return fallback;
}

return await federation.loadRemoteModule<T>(
options.remoteName,
options.exposedModule
options.exposedModule,
);
} catch (err) {
if (fallback) {
logClientError(
'error loading remote module: ' +
(err instanceof Error ? err.message : String(err))
"error loading remote module: " +
(err instanceof Error ? err.message : String(err)),
);
return fallback;
}
throw err;
}
}

export { type NativeFederationResult } from '@softarc/native-federation-orchestrator';
export { type NativeFederationResult } from "@softarc/native-federation-orchestrator";
Loading