Skip to content
Closed
Show file tree
Hide file tree
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
11 changes: 10 additions & 1 deletion libs/native-federation-runtime/src/lib/init-federation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { getExternalUrl, setExternalUrl } from './model/externals';
import {
getExternalUrl,
setExternalUrl,
setUseShareConfig,
} from './model/externals';
import {
FederationInfo,
InitFederationOptions,
Expand Down Expand Up @@ -46,6 +50,11 @@ export async function initFederation(
remotesOrManifestUrl: Record<string, string> | string = {},
options?: InitFederationOptions,
): Promise<ImportMap> {
// TODO: Enable share config enforcement if requested
// TODO: Call setUseShareConfig(options?.useShareConfig ?? false)
// TODO: This determines whether runtime uses exact version matching (legacy)
// TODO: or semver matching with singleton/strictVersion enforcement

const cacheTag = options?.cacheTag ? `?t=${options.cacheTag}` : '';

const normalizedRemotes =
Expand Down
187 changes: 185 additions & 2 deletions libs/native-federation-runtime/src/lib/model/externals.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,201 @@
import { SharedInfo } from './federation-info';
import { globalCache } from './global-cache';
// TODO: Uncomment when implementing Phase 3
import { satisfies, getHighestVersion } from '../utils/semver';

const externals = globalCache.externals;

/**
* Registry entry for a shared module with semver support
*/
interface RegisteredShared {
info: SharedInfo;
url: string;
}

/**
* Registry for semver-based sharing
* Maps package name to array of registered versions
* Example: 'foo' -> [{ info: { version: '18.0.0', ... }, url: 'http://...' }]
*/
const sharedRegistry = new Map<string, RegisteredShared[]>();

/**
* Global flag for share config enforcement
* When false: uses exact version matching (legacy behavior)
* When true: uses semver matching with singleton/strictVersion enforcement
*/
let useShareConfig = false;

/**
* Sets the global flag for share config enforcement
*
* Called from initFederation when useShareConfig option is provided.
* This determines whether the runtime uses exact version matching (legacy)
* or semver matching with config enforcement.
*
* @param enabled - Whether to enable share config enforcement
*/
export function setUseShareConfig(enabled: boolean): void {
useShareConfig = enabled;
}

/**
* Generates the exact-match key for a shared dependency
*
* This is used for the legacy behavior (exact version matching).
* Format: "packageName@version"
*
* @param shared - Shared dependency info
* @returns Key string in format "packageName@version"
*/
function getExternalKey(shared: SharedInfo) {
return `${shared.packageName}@${shared.version}`;
}

/**
* Helper to detect if a shared dependency has non-default config
*
* This is used to warn developers when they have config that won't be enforced
* because useShareConfig is disabled.
*
* Non-default config includes:
* - singleton: true (default is false)
* - strictVersion: true (default is false)
* - requiredVersion that's not '*' or 'false'
*
* @param shared - The shared dependency info
* @returns true if any non-default config is present
*/
function hasNonDefaultConfig(shared: SharedInfo): boolean {
// TODO: Check if singleton is true
// TODO: Check if strictVersion is true
// TODO: Check if requiredVersion is not '*' and not 'false'
// TODO: Return true if any of above are true
throw new Error('Not implemented');
}

/**
* Find compatible shared module based on config (singleton, strictVersion, requiredVersion)
*
* This implements the following resolution algorithm.
* Used when useShareConfig is enabled.
*
* Resolution steps:
* 1. Try exact match first (fast path)
* 2. If singleton mode:
* - Use first registered version (singleton)
* - Check version compatibility if requiredVersion specified
* - Handle strictVersion enforcement
* 3. If non-singleton mode:
* - Find all compatible versions
* - Use highest compatible version
* - Handle strictVersion enforcement
*
* @param shared - The shared dependency being requested
* @returns URL to the shared bundle, or undefined if no compatible version found (use own bundle)
*/
function findCompatibleShared(shared: SharedInfo): string | undefined {
// TODO: Get registered versions for this package name
// TODO: Return undefined if no versions registered

// TODO: STEP 1 - Try exact match first (fast path)
// TODO: If exact match found, return its URL

// TODO: STEP 2 - Handle singleton mode
// TODO: If singleton, get first registered version
// TODO: If requiredVersion is 'false', skip version check and return singleton URL
// TODO: Check if singleton version satisfies requiredVersion
// TODO: If satisfied, return singleton URL
// TODO: If not satisfied and strictVersion is true, warn and return undefined (use own bundle)
// TODO: If not satisfied and strictVersion is false, warn and return singleton URL anyway

// TODO: STEP 3 - Handle non-singleton mode
// TODO: If requiredVersion is 'false', return highest available version
// TODO: Find all versions that satisfy requiredVersion
// TODO: If compatible versions found, return highest compatible version
// TODO: If no compatible versions and strictVersion is true, warn and return undefined
// TODO: If no compatible versions and strictVersion is false, warn and return highest version anyway

// TODO: Return undefined if no suitable version found
throw new Error('Not implemented');
}

/**
* Gets the URL for a shared dependency
*
* This is the main entry point for shared dependency resolution.
* Behavior depends on useShareConfig flag:
*
* Legacy mode (useShareConfig: false):
* - Uses exact version matching only
* - Returns URL only if exact version match exists
*
* Share config mode (useShareConfig: true):
* - Uses semver range matching
* - Enforces singleton, strictVersion, requiredVersion
* - Follows Webpack Module Federation algorithm
*
* @param shared - The shared dependency info
* @returns URL to the shared bundle, or undefined if not found/compatible
*/
export function getExternalUrl(shared: SharedInfo): string | undefined {
const packageKey = getExternalKey(shared);
return externals.get(packageKey);
// Legacy behavior: exact version matching only
if (!useShareConfig) {
const packageKey = getExternalKey(shared);
return externals.get(packageKey);
}

// New behavior: use share config for smart matching
// TODO: Call findCompatibleShared and return result
throw new Error('Not implemented');
}

/**
* Registers a shared dependency URL
*
* This is called when processing host and remote shared dependencies.
* It maintains both:
* 1. Legacy exact-match registry (for backwards compatibility)
* 2. Semver-based registry (for share config mode)
*
* Singleton enforcement:
* - In share config mode, if singleton is true and a version already exists,
* the new version is rejected (first registration wins)
*
* @param shared - The shared dependency info
* @param url - The URL where this shared dependency can be loaded from
*/
export function setExternalUrl(shared: SharedInfo, url: string): void {
// Always maintain legacy exact-match registry
const packageKey = getExternalKey(shared);
externals.set(packageKey, url);

// TODO: Warn if useShareConfig is FALSE but sharing config is set
// TODO: This helps developers know their config (singleton, strictVersion, requiredVersion) is ignored
// TODO: Only warn if any config is non-default (singleton=true, strictVersion=true, or requiredVersion != '*')
// TODO: Consider checking shared.dev to only warn in dev mode
// TODO: Example:
// TODO: if (!useShareConfig && hasNonDefaultConfig(shared)) {
// TODO: if (shared.dev) { // Only warn in dev mode
// TODO: console.warn(
// TODO: `[Federation] Shared dependency "${shared.packageName}@${shared.version}" has ` +
// TODO: `sharing config (singleton=${shared.singleton}, strictVersion=${shared.strictVersion}, ` +
// TODO: `requiredVersion=${shared.requiredVersion}) but useShareConfig is disabled. ` +
// TODO: `Config will be ignored. Set useShareConfig: true in initFederation() to enforce it.`
// TODO: );
// TODO: }
// TODO: }

// TODO: Get or create array for this package in sharedRegistry

// TODO: If singleton is true and versions already registered (when useShareConfig is enabled):
// TODO: - Warn about ignored registration
// TODO: - Example: console.warn(
// TODO: `[Federation] Singleton "${shared.packageName}" already registered with version ` +
// TODO: `${existing[0].info.version}. Ignoring version ${shared.version}.`
// TODO: );
// TODO: - Return early (don't register duplicate singleton)

// TODO: Add { info: shared, url } to sharedRegistry
}
48 changes: 48 additions & 0 deletions libs/native-federation-runtime/src/lib/model/federation-info.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,41 @@
export type SharedInfo = {
/**
* Ensures only ONE instance of the library exists across all federated modules.
* Enforced when useShareConfig is enabled.
*/
singleton: boolean;

/**
* Controls whether to reject incompatible versions or just warn.
* Enforced when useShareConfig is enabled.
*/
strictVersion: boolean;

/**
* Specifies acceptable version range using semver (e.g., '^3.0.0', '>=3.0.0 <5.0.0').
* Can be 'false' to disable version checking.
* Enforced when useShareConfig is enabled.
*/
requiredVersion: string;

/**
* The version being provided by this module.
*/
version?: string;

/**
* The package name (e.g. '@angular/core').
*/
packageName: string;

/**
* The output filename for the shared bundle.
*/
outFileName: string;

/**
* Development mode configuration.
*/
dev?: {
entryPoint: string;
};
Expand All @@ -27,6 +58,23 @@ export interface FederationInfo {

export interface InitFederationOptions {
cacheTag?: string;

/**
* Enables enforcement of shared dependency configuration at runtime
*
* When false (default):
* - Uses exact version matching only (current behavior)
* - singleton, strictVersion, requiredVersion are metadata-only
*
* When true:
* - Enforces singleton, strictVersion, requiredVersion at runtime
* - Uses semver range matching (^, ~, >=, etc.)
* - Provides warnings/errors for version mismatches
*
* @default false
* @since 3.6.0
*/
useShareConfig?: boolean;
}

export interface ProcessRemoteInfoOptions extends InitFederationOptions {
Expand Down
Loading