From 1c7ead0fc897f34f1603187d4b7f6600679a0feb Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 12 Sep 2025 13:55:02 +0800 Subject: [PATCH 01/11] refactor(enhanced): resolve alias-aware consumes in afterResolve with caching Move path equality checks to afterResolve for determinism. Cache target resolutions and track deps to reduce resolver churn. --- .../src/lib/sharing/ConsumeSharedPlugin.ts | 329 +++++++++++------- 1 file changed, 199 insertions(+), 130 deletions(-) diff --git a/packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts b/packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts index dfd52f15bcf..b099b752c4c 100644 --- a/packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts +++ b/packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts @@ -1,6 +1,6 @@ /* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra, Zackary Jackson @ScriptedAlchemy + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra, Zackary Jackson @ScriptedAlchemy */ 'use strict'; @@ -40,9 +40,11 @@ import type { ModuleFactoryCreateDataContextInfo } from 'webpack/lib/ModuleFacto import type { ConsumeOptions } from '../../declarations/plugins/sharing/ConsumeSharedModule'; import { createSchemaValidation } from '../../utils'; import path from 'path'; + const { satisfy, parseRange } = require( normalizeWebpackPath('webpack/lib/util/semver'), ) as typeof import('webpack/lib/util/semver'); + import { addSingletonFilterWarning, testRequestFilters, @@ -90,11 +92,10 @@ class ConsumeSharedPlugin { options.consumes, (item, key) => { if (Array.isArray(item)) throw new Error('Unexpected array in options'); - //@ts-ignore + // @ts-ignore const result: ConsumeOptions = item === key || !isRequiredVersion(item) - ? // item is a request/key - { + ? { import: key, shareScope: options.shareScope || 'default', shareKey: key, @@ -110,13 +111,10 @@ class ConsumeSharedPlugin { exclude: undefined, allowNodeModulesSuffixMatch: undefined, } - : // key is a request/key - // item is a version - { + : { import: key, shareScope: options.shareScope || 'default', shareKey: key, - // webpack internal semver has some issue, use runtime semver , related issue: https://github.com/webpack/webpack/issues/17756 requiredVersion: item, strictVersion: true, packageName: undefined, @@ -140,7 +138,7 @@ class ConsumeSharedPlugin { requiredVersion: item.requiredVersion === false ? false - : // @ts-ignore webpack internal semver has some issue, use runtime semver , related issue: https://github.com/webpack/webpack/issues/17756 + : // @ts-ignore (item.requiredVersion as SemVerRange), strictVersion: typeof item.strictVersion === 'boolean' @@ -213,7 +211,7 @@ class ConsumeSharedPlugin { ); return resolve(undefined); } - //@ts-ignore + // @ts-ignore resolve(result); }, ); @@ -225,8 +223,6 @@ class ConsumeSharedPlugin { let packageName = config.packageName; if (packageName === undefined) { if (ABSOLUTE_PATH_REGEX.test(request)) { - // For relative or absolute requests we don't automatically use a packageName. - // If wished one can specify one with the packageName option. return resolve(undefined); } const match = PACKAGE_NAME_REGEX.exec(request); @@ -263,19 +259,16 @@ class ConsumeSharedPlugin { `Unable to find description file in ${context}.`, ); } - return resolve(undefined); } if (data['name'] === packageName) { - // Package self-referencing return resolve(undefined); } const requiredVersion = getRequiredVersionFromDescriptionFile( data, packageName, ); - //TODO: align with webpck semver parser again - // @ts-ignore webpack internal semver has some issue, use runtime semver , related issue: https://github.com/webpack/webpack/issues/17756 + // @ts-ignore resolve(requiredVersion); }, (result) => { @@ -304,7 +297,7 @@ class ConsumeSharedPlugin { currentConfig, ); - // Check for include version first + // include.version if (config.include && typeof config.include.version === 'string') { if (!importResolved) { return consumedModule; @@ -324,7 +317,6 @@ class ConsumeSharedPlugin { return resolveFilter(consumedModule); } - // Only include if version satisfies the include constraint if ( config.include && satisfy( @@ -332,7 +324,6 @@ class ConsumeSharedPlugin { data['version'], ) ) { - // Validate singleton usage with include.version if ( config.include && config.include.version && @@ -344,15 +335,14 @@ class ConsumeSharedPlugin { 'include', 'version', config.include.version, - request, // moduleRequest - importResolved, // moduleResource (might be undefined) + request, + importResolved, ); } return resolveFilter(consumedModule); } - // Check fallback version if ( config.include && typeof config.include.fallbackVersion === 'string' && @@ -377,7 +367,7 @@ class ConsumeSharedPlugin { }); } - // Check for exclude version (existing logic) + // exclude.version if (config.exclude && typeof config.exclude.version === 'string') { if (!importResolved) { return consumedModule; @@ -423,7 +413,6 @@ class ConsumeSharedPlugin { ); } - // Validate singleton usage with exclude.version if ( config.exclude && config.exclude.version && @@ -435,8 +424,8 @@ class ConsumeSharedPlugin { 'exclude', 'version', config.exclude.version, - request, // moduleRequest - importResolved, // moduleResource (might be undefined) + request, + importResolved, ); } @@ -458,14 +447,21 @@ class ConsumeSharedPlugin { compiler.hooks.thisCompilation.tap( PLUGIN_NAME, (compilation: Compilation, { normalModuleFactory }) => { + // Dependency factories compilation.dependencyFactories.set( ConsumeSharedFallbackDependency, normalModuleFactory, ); + // Shared state let unresolvedConsumes: Map, resolvedConsumes: Map, prefixedConsumes: Map; + + // Caches + const targetResolveCache = new Map(); // key: resolverSig|ctx|targetReq -> resolved path or false + const packageNameByDirCache = new Map(); // key: dirname(resource) -> package name + const promise = resolveMatchedConfigs(compilation, this._consumes).then( ({ resolved, unresolved, prefixed }) => { resolvedConsumes = resolved; @@ -474,12 +470,83 @@ class ConsumeSharedPlugin { }, ); + // util: resolve once with tracking + caching + const resolveOnce = ( + resolver: any, + ctx: string, + req: string, + resolverKey: string, + ): Promise => { + const cacheKey = `${resolverKey}||${ctx}||${req}`; + if (targetResolveCache.has(cacheKey)) { + return Promise.resolve(targetResolveCache.get(cacheKey)!); + } + return new Promise((res) => { + const resolveContext = { + fileDependencies: new LazySet(), + contextDependencies: new LazySet(), + missingDependencies: new LazySet(), + }; + resolver.resolve( + {}, + ctx, + req, + resolveContext, + (err: any, result: string | false) => { + // track deps for watch fidelity + compilation.contextDependencies.addAll( + resolveContext.contextDependencies, + ); + compilation.fileDependencies.addAll( + resolveContext.fileDependencies, + ); + compilation.missingDependencies.addAll( + resolveContext.missingDependencies, + ); + + if (err || result === false) { + targetResolveCache.set(cacheKey, false); + return res(false); + } + targetResolveCache.set(cacheKey, result as string); + res(result as string); + }, + ); + }); + }; + + // util: get package name for a resolved resource + const getPackageNameForResource = ( + resource: string, + ): Promise => { + const dir = path.dirname(resource); + if (packageNameByDirCache.has(dir)) { + return Promise.resolve(packageNameByDirCache.get(dir)!); + } + return new Promise((resolvePkg) => { + getDescriptionFile( + compilation.inputFileSystem, + dir, + ['package.json'], + (err, result) => { + if (err || !result || !result.data) { + packageNameByDirCache.set(dir, undefined); + return resolvePkg(undefined); + } + const name = (result.data as any)['name']; + packageNameByDirCache.set(dir, name); + resolvePkg(name); + }, + ); + }); + }; + + // FACTORIZE: direct + path-based + prefix matches (fast paths). Alias-aware path equality moved to afterResolve. normalModuleFactory.hooks.factorize.tapPromise( PLUGIN_NAME, async (resolveData: ResolveData): Promise => { const { context, request, dependencies, contextInfo } = resolveData; - // wait for resolving to be complete - // Small helper to create a consume module without binding boilerplate + const createConsume = ( ctx: string, req: string, @@ -494,7 +561,7 @@ class ConsumeSharedPlugin { return; } - // 1) Direct unresolved match using original request + // 1) direct unresolved key const directMatch = unresolvedConsumes.get( createLookupKeyForSharing(request, contextInfo.issuerLayer), @@ -506,7 +573,7 @@ class ConsumeSharedPlugin { return createConsume(context, request, directMatch); } - // Prepare potential reconstructed variants for relative requests + // Prepare reconstructed variants let reconstructed: string | undefined; let afterNodeModules: string | undefined; if ( @@ -519,7 +586,7 @@ class ConsumeSharedPlugin { if (nm) afterNodeModules = nm; } - // 2) Try unresolved match with path after node_modules (if allowed) + // 2) unresolved match with path after node_modules (suffix match) if (afterNodeModules) { const moduleMatch = unresolvedConsumes.get( @@ -537,7 +604,7 @@ class ConsumeSharedPlugin { } } - // 3) Try unresolved match with fully reconstructed path + // 3) unresolved match with fully reconstructed path if (reconstructed) { const reconstructedMatch = unresolvedConsumes.get( @@ -558,13 +625,13 @@ class ConsumeSharedPlugin { } } - // Normalize issuerLayer to undefined when null for TS compatibility + // issuerLayer normalize const issuerLayer: string | undefined = contextInfo.issuerLayer === null ? undefined : contextInfo.issuerLayer; - // 4) Prefixed consumes with original request + // 4) prefixed consumes with original request for (const [prefix, options] of prefixedConsumes) { const lookup = options.request || prefix; if (options.issuerLayer) { @@ -593,7 +660,7 @@ class ConsumeSharedPlugin { } } - // 5) Prefixed consumes with path after node_modules + // 5) prefixed consumes with path after node_modules if (afterNodeModules) { for (const [prefix, options] of prefixedConsumes) { if (!options.allowNodeModulesSuffixMatch) continue; @@ -625,105 +692,103 @@ class ConsumeSharedPlugin { } } - // 6) Alias-aware matching using webpack's resolver - // Only for bare requests (not relative/absolute) - if (!RELATIVE_OR_ABSOLUTE_PATH_REGEX.test(request)) { - const LazySet = require( - normalizeWebpackPath('webpack/lib/util/LazySet'), - ) as typeof import('webpack/lib/util/LazySet'); - const resolveOnce = ( - resolver: any, - req: string, - ): Promise => { - return new Promise((res) => { - const resolveContext = { - fileDependencies: new LazySet(), - contextDependencies: new LazySet(), - missingDependencies: new LazySet(), - }; - resolver.resolve( - {}, - context, - req, - resolveContext, - (err: any, result: string | false) => { - if (err || result === false) return res(false); - // track dependencies for watch mode fidelity - compilation.contextDependencies.addAll( - resolveContext.contextDependencies, - ); - compilation.fileDependencies.addAll( - resolveContext.fileDependencies, - ); - compilation.missingDependencies.addAll( - resolveContext.missingDependencies, - ); - res(result as string); - }, - ); - }); - }; - - const baseResolver = compilation.resolverFactory.get('normal', { - dependencyType: resolveData.dependencyType || 'esm', - } as ResolveOptionsWithDependencyType); - let resolver: any = baseResolver as any; - if (resolveData.resolveOptions) { - resolver = - typeof (baseResolver as any).withOptions === 'function' - ? (baseResolver as any).withOptions( - resolveData.resolveOptions, - ) - : compilation.resolverFactory.get( - 'normal', - Object.assign( - { - dependencyType: - resolveData.dependencyType || 'esm', - }, - resolveData.resolveOptions, - ) as ResolveOptionsWithDependencyType, - ); - } + return; + }); + }, + ); - const supportsAliasResolve = - resolver && - typeof (resolver as any).resolve === 'function' && - (resolver as any).resolve.length >= 5; - if (!supportsAliasResolve) { - return undefined as unknown as Module; - } - return resolveOnce(resolver, request).then( - async (resolvedRequestPath) => { - if (!resolvedRequestPath) - return undefined as unknown as Module; - // Try to find a consume config whose target resolves to the same path - for (const [key, cfg] of unresolvedConsumes) { - if (cfg.issuerLayer) { - if (!issuerLayer) continue; - if (issuerLayer !== cfg.issuerLayer) continue; - } - const targetReq = (cfg.request || cfg.import) as string; - const targetResolved = await resolveOnce( - resolver, - targetReq, - ); - if ( - targetResolved && - targetResolved === resolvedRequestPath - ) { - return createConsume(context, request, cfg); - } - } - return undefined as unknown as Module; - }, - ); - } + // AFTER RESOLVE: alias-aware equality (single-resolution per candidate via cache) + normalModuleFactory.hooks.afterResolve.tapPromise( + PLUGIN_NAME, + async (data: any /* ResolveData-like */) => { + await promise; + const dependencies = data.dependencies as any[]; + if ( + dependencies && + (dependencies[0] instanceof ConsumeSharedFallbackDependency || + dependencies[0] instanceof ProvideForSharedDependency) + ) { return; + } + + const createData = data.createData || data; + const resource: string | undefined = + createData && createData.resource; + if (!resource) return; + if (resolvedConsumes.has(resource)) return; + + const issuerLayer: string | undefined = + data.contextInfo && data.contextInfo.issuerLayer === null + ? undefined + : data.contextInfo?.issuerLayer; + + // Try to get the package name via resolver metadata first + let pkgName: string | undefined = + createData?.resourceResolveData?.descriptionFileData?.name; + + if (!pkgName) { + pkgName = await getPackageNameForResource(resource); + } + if (!pkgName) return; + + // Candidate configs narrowed by package name + issuerLayer + const candidates: ConsumeOptions[] = []; + const k1 = createLookupKeyForSharing(pkgName, issuerLayer); + const k2 = createLookupKeyForSharing(pkgName, undefined); + const c1 = unresolvedConsumes.get(k1); + const c2 = unresolvedConsumes.get(k2); + if (c1) candidates.push(c1); + if (c2 && c2 !== c1) candidates.push(c2); + if (candidates.length === 0) return; + + // Build resolver aligned with current resolve context + const baseResolver = compilation.resolverFactory.get('normal', { + dependencyType: data.dependencyType || 'esm', + } as ResolveOptionsWithDependencyType); + const resolver = + data.resolveOptions && + typeof (baseResolver as any).withOptions === 'function' + ? (baseResolver as any).withOptions(data.resolveOptions) + : data.resolveOptions + ? compilation.resolverFactory.get( + 'normal', + Object.assign( + { + dependencyType: data.dependencyType || 'esm', + }, + data.resolveOptions, + ) as ResolveOptionsWithDependencyType, + ) + : (baseResolver as any); + + const resolverKey = JSON.stringify({ + dependencyType: data.dependencyType || 'esm', + resolveOptions: data.resolveOptions || null, }); + const ctx = + createData?.context || + data.context || + compilation.compiler.context; + + // Resolve each candidate's target once, compare by absolute path + for (const cfg of candidates) { + const targetReq = (cfg.request || cfg.import) as string; + const targetResolved = await resolveOnce( + resolver, + ctx, + targetReq, + resolverKey, + ); + if (targetResolved && targetResolved === resource) { + resolvedConsumes.set(resource, cfg); + break; + } + } }, ); + + // CREATE MODULE: swap resolved resource with ConsumeSharedModule when mapped normalModuleFactory.hooks.createModule.tapPromise( PLUGIN_NAME, ({ resource }, { context, dependencies }) => { @@ -732,12 +797,14 @@ class ConsumeSharedPlugin { req: string, cfg: ConsumeOptions, ) => this.createConsumeSharedModule(compilation, ctx, req, cfg); + if ( dependencies[0] instanceof ConsumeSharedFallbackDependency || dependencies[0] instanceof ProvideForSharedDependency ) { return Promise.resolve(); } + if (resource) { const options = resolvedConsumes.get(resource); if (options !== undefined) { @@ -747,6 +814,8 @@ class ConsumeSharedPlugin { return Promise.resolve(); }, ); + + // runtime requirements compilation.hooks.additionalTreeRuntimeRequirements.tap( PLUGIN_NAME, (chunk, set) => { @@ -760,7 +829,7 @@ class ConsumeSharedPlugin { chunk, new ConsumeSharedRuntimeModule(set), ); - // FIXME: need to remove webpack internal inject ShareRuntimeModule, otherwise there will be two ShareRuntimeModule + // keep compatibility with existing runtime injection compilation.addRuntimeModule(chunk, new ShareRuntimeModule()); }, ); From c701d850cc61fc368c56323125b2de624fc9209b Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 12 Sep 2025 14:01:27 +0800 Subject: [PATCH 02/11] test(enhanced): mock afterResolve hook in NMF tests Align tests with plugin tapping afterResolve to prevent undefined hook errors. --- .../ConsumeSharedPlugin.apply.test.ts | 1 + .../ConsumeSharedPlugin.factorize.test.ts | 18 ++++++++++++++++++ .../unit/sharing/SharePlugin.improved.test.ts | 1 + packages/enhanced/test/unit/sharing/utils.ts | 3 +++ 4 files changed, 23 insertions(+) diff --git a/packages/enhanced/test/unit/sharing/ConsumeSharedPlugin/ConsumeSharedPlugin.apply.test.ts b/packages/enhanced/test/unit/sharing/ConsumeSharedPlugin/ConsumeSharedPlugin.apply.test.ts index 7fdbe7dd334..b6ac7b1a154 100644 --- a/packages/enhanced/test/unit/sharing/ConsumeSharedPlugin/ConsumeSharedPlugin.apply.test.ts +++ b/packages/enhanced/test/unit/sharing/ConsumeSharedPlugin/ConsumeSharedPlugin.apply.test.ts @@ -89,6 +89,7 @@ describe('ConsumeSharedPlugin', () => { hooks: { factorize: mockFactorizeHook, createModule: mockCreateModuleHook, + afterResolve: { tapPromise: jest.fn() }, }, }; diff --git a/packages/enhanced/test/unit/sharing/ConsumeSharedPlugin/ConsumeSharedPlugin.factorize.test.ts b/packages/enhanced/test/unit/sharing/ConsumeSharedPlugin/ConsumeSharedPlugin.factorize.test.ts index 96ee0726e8b..b189ad2af96 100644 --- a/packages/enhanced/test/unit/sharing/ConsumeSharedPlugin/ConsumeSharedPlugin.factorize.test.ts +++ b/packages/enhanced/test/unit/sharing/ConsumeSharedPlugin/ConsumeSharedPlugin.factorize.test.ts @@ -160,6 +160,9 @@ describe('ConsumeSharedPlugin - factorize hook logic', () => { createModule: { tapPromise: jest.fn(), }, + afterResolve: { + tapPromise: jest.fn(), + }, }, }; @@ -252,6 +255,9 @@ describe('ConsumeSharedPlugin - factorize hook logic', () => { createModule: { tapPromise: jest.fn(), }, + afterResolve: { + tapPromise: jest.fn(), + }, }, }; @@ -339,6 +345,9 @@ describe('ConsumeSharedPlugin - factorize hook logic', () => { createModule: { tapPromise: jest.fn(), }, + afterResolve: { + tapPromise: jest.fn(), + }, }, }; @@ -413,6 +422,9 @@ describe('ConsumeSharedPlugin - factorize hook logic', () => { createModule: { tapPromise: jest.fn(), }, + afterResolve: { + tapPromise: jest.fn(), + }, }, }; @@ -477,6 +489,9 @@ describe('ConsumeSharedPlugin - factorize hook logic', () => { createModule: { tapPromise: jest.fn(), }, + afterResolve: { + tapPromise: jest.fn(), + }, }, }; @@ -571,6 +586,9 @@ describe('ConsumeSharedPlugin - factorize hook logic', () => { createModule: { tapPromise: jest.fn(), }, + afterResolve: { + tapPromise: jest.fn(), + }, }, }; diff --git a/packages/enhanced/test/unit/sharing/SharePlugin.improved.test.ts b/packages/enhanced/test/unit/sharing/SharePlugin.improved.test.ts index ec0ad718c80..364bacd8478 100644 --- a/packages/enhanced/test/unit/sharing/SharePlugin.improved.test.ts +++ b/packages/enhanced/test/unit/sharing/SharePlugin.improved.test.ts @@ -91,6 +91,7 @@ const createMockNormalModuleFactory = () => ({ module: { tap: jest.fn() }, factorize: { tapPromise: jest.fn() }, createModule: { tapPromise: jest.fn() }, + afterResolve: { tapPromise: jest.fn() }, }, }); diff --git a/packages/enhanced/test/unit/sharing/utils.ts b/packages/enhanced/test/unit/sharing/utils.ts index ca2e9f82103..bf0efe4b05c 100644 --- a/packages/enhanced/test/unit/sharing/utils.ts +++ b/packages/enhanced/test/unit/sharing/utils.ts @@ -411,6 +411,9 @@ export const createSharingTestEnvironment = () => { createModule: { tapPromise: jest.fn(), }, + afterResolve: { + tapPromise: jest.fn(), + }, }, }; From 095f6aa018733ce8b956e752a5a25284184f278b Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 12 Sep 2025 16:33:43 +0800 Subject: [PATCH 03/11] fix(enhanced): alias-aware ConsumeSharedPlugin + data URI guard Skip data: resources in afterResolve and createModule. Preserve virtual module handling by webpack. Broaden afterResolve candidates for deep-path shares. Example: next/dist/compiled/react alias. Avoid converting explicit relative or absolute requests into consumes. Preserves local nested resolution for deep sharing. Keep prefix and node_modules suffix matching as before. Add a changeset for @module-federation/enhanced. --- .changeset/fix-alias-aware-consume-plugin.md | 16 +++++++ nx.json | 1 + .../src/lib/sharing/ConsumeSharedPlugin.ts | 48 +++++++++++++++++-- 3 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 .changeset/fix-alias-aware-consume-plugin.md diff --git a/.changeset/fix-alias-aware-consume-plugin.md b/.changeset/fix-alias-aware-consume-plugin.md new file mode 100644 index 00000000000..ef31fae4027 --- /dev/null +++ b/.changeset/fix-alias-aware-consume-plugin.md @@ -0,0 +1,16 @@ +--- +'@module-federation/enhanced': patch +--- + +fix(enhanced): ConsumeSharedPlugin alias-aware and virtual resource handling + +- Skip `data:` (virtual) resources in `afterResolve` and `createModule` so webpack's scheme resolver handles them (fixes container virtual-entry compile failure) +- Broaden alias-aware matching in `afterResolve` to include deep-path shares that start with the resolved package name (e.g. `next/dist/compiled/react`), ensuring aliased modules are consumed from federation when configured +- Avoid converting explicit relative/absolute requests into consumes to preserve local nested resolution (fixes deep module sharing version selection) +- Keep prefix and node_modules suffix matching intact; no behavior change there + +These changes restore expected behavior for: +- Virtual entry compilation +- Deep module sharing (distinct versions for nested paths) +- Alias-based sharing (Next.js compiled React) + diff --git a/nx.json b/nx.json index 4d62dbd7190..b78493a7603 100644 --- a/nx.json +++ b/nx.json @@ -1,5 +1,6 @@ { "$schema": "./node_modules/nx/schemas/nx-schema.json", + "useDaemonProcess": false, "targetDefaults": { "build": { "inputs": ["production", "^production"], diff --git a/packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts b/packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts index b099b752c4c..d8ce1ed208f 100644 --- a/packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts +++ b/packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts @@ -716,6 +716,17 @@ class ConsumeSharedPlugin { const resource: string | undefined = createData && createData.resource; if (!resource) return; + // Skip virtual/data URI resources – let webpack handle them + if (resource.startsWith('data:')) return; + // Do not convert explicit relative/absolute path requests into consumes + // e.g. "./node_modules/shared" inside a package should resolve locally + const originalRequest: string | undefined = data.request; + if ( + originalRequest && + RELATIVE_OR_ABSOLUTE_PATH_REGEX.test(originalRequest) + ) { + return; + } if (resolvedConsumes.has(resource)) return; const issuerLayer: string | undefined = @@ -732,14 +743,43 @@ class ConsumeSharedPlugin { } if (!pkgName) return; - // Candidate configs narrowed by package name + issuerLayer + // Candidate configs: include + // - exact package name keys (legacy behavior) + // - deep-path shares whose keys start with `${pkgName}/` (alias-aware) const candidates: ConsumeOptions[] = []; + const seen = new Set(); const k1 = createLookupKeyForSharing(pkgName, issuerLayer); const k2 = createLookupKeyForSharing(pkgName, undefined); const c1 = unresolvedConsumes.get(k1); const c2 = unresolvedConsumes.get(k2); - if (c1) candidates.push(c1); - if (c2 && c2 !== c1) candidates.push(c2); + if (c1 && !seen.has(c1)) { + candidates.push(c1); + seen.add(c1); + } + if (c2 && !seen.has(c2)) { + candidates.push(c2); + seen.add(c2); + } + + // Also scan for deep-path keys beginning with `${pkgName}/` (both layered and unlayered) + const prefixLayered = createLookupKeyForSharing( + pkgName + '/', + issuerLayer, + ); + const prefixUnlayered = createLookupKeyForSharing( + pkgName + '/', + undefined, + ); + for (const [key, cfg] of unresolvedConsumes) { + if ( + (key.startsWith(prefixLayered) || + key.startsWith(prefixUnlayered)) && + !seen.has(cfg) + ) { + candidates.push(cfg); + seen.add(cfg); + } + } if (candidates.length === 0) return; // Build resolver aligned with current resolve context @@ -806,6 +846,8 @@ class ConsumeSharedPlugin { } if (resource) { + // Skip virtual/data URI resources – let webpack handle them + if (resource.startsWith('data:')) return Promise.resolve(); const options = resolvedConsumes.get(resource); if (options !== undefined) { return createConsume(context, resource, options); From 953b8ab8b9f42a1363a747e7bbb14ea9f6848d24 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 19 Sep 2025 15:15:39 -0700 Subject: [PATCH 04/11] chore: patch --- packages/nextjs-mf/src/internal.ts | 48 +++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/packages/nextjs-mf/src/internal.ts b/packages/nextjs-mf/src/internal.ts index f25ced295bb..e21aee5757f 100644 --- a/packages/nextjs-mf/src/internal.ts +++ b/packages/nextjs-mf/src/internal.ts @@ -203,15 +203,49 @@ export const DEFAULT_SHARE_SCOPE: moduleFederationPlugin.SharedObject = { * @returns {SharedObject} - The modified share scope for the browser environment. */ -export const DEFAULT_SHARE_SCOPE_BROWSER: moduleFederationPlugin.SharedObject = - Object.entries(DEFAULT_SHARE_SCOPE).reduce((acc, item) => { - const [key, value] = item as [string, moduleFederationPlugin.SharedConfig]; +// Build base browser share scope (allow local fallback by default) +const BASE_BROWSER_SCOPE: moduleFederationPlugin.SharedObject = Object.entries( + DEFAULT_SHARE_SCOPE, +).reduce((acc, item) => { + const [key, value] = item as [string, moduleFederationPlugin.SharedConfig]; + acc[key] = { ...value, import: undefined }; + return acc; +}, {} as moduleFederationPlugin.SharedObject); - // Set eager and import to undefined for all entries, except for the ones specified above - acc[key] = { ...value, import: undefined }; +// Ensure the pages directory browser layer uses shared consumption for core React entries +const PAGES_DIR_BROWSER_LAYER = 'pages-dir-browser'; +const addPagesDirBrowserLayerFor = ( + scope: moduleFederationPlugin.SharedObject, + name: string, + request: string, +) => { + const key = `${name}-${PAGES_DIR_BROWSER_LAYER}`; + (scope as Record)[key] = { + singleton: true, + requiredVersion: false, + import: undefined, + shareKey: request, + request, + layer: PAGES_DIR_BROWSER_LAYER, + issuerLayer: PAGES_DIR_BROWSER_LAYER, + } as ExtendedSharedConfig; +}; - return acc; - }, {} as moduleFederationPlugin.SharedObject); +addPagesDirBrowserLayerFor(BASE_BROWSER_SCOPE, 'react', 'react'); +addPagesDirBrowserLayerFor(BASE_BROWSER_SCOPE, 'react', 'react-dom'); +addPagesDirBrowserLayerFor( + BASE_BROWSER_SCOPE, + 'react/jsx-runtime', + 'react/jsx-runtime', +); +addPagesDirBrowserLayerFor( + BASE_BROWSER_SCOPE, + 'react/jsx-dev-runtime', + 'react/jsx-dev-runtime', +); + +export const DEFAULT_SHARE_SCOPE_BROWSER: moduleFederationPlugin.SharedObject = + BASE_BROWSER_SCOPE; /** * Checks if the remote value is an internal or promise delegate module reference. From 341e0458fe1de295539b1756fd6673528354a551 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 19 Sep 2025 16:41:00 -0700 Subject: [PATCH 05/11] ci(core): enable tag push and npm publish in release workflow --- .github/workflows/release.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2a142388df1..fdbdf789a77 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,6 +18,9 @@ on: default: 'main' permissions: + # Needed for pushing git tags during release + contents: write + # Kept for npm provenance/OIDC if used id-token: write jobs: @@ -32,6 +35,8 @@ jobs: with: fetch-depth: 25 ref: ${{ github.event.inputs.branch }} + # Use repo-scoped token so pushing tags always works + token: ${{ secrets.REPO_SCOPED_TOKEN }} # Use corepack to install pnpm - name: Setup Pnpm @@ -72,7 +77,9 @@ jobs: return 'v' + packageJson.version; - name: Publish to npm + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: | git tag ${{ steps.version_to_release.outputs.result }} git push origin ${{ steps.version_to_release.outputs.result }} - pnpm -r publish --tag ${{ github.event.inputs.npm_tag }} --publish-branch ${{ github.event.inputs.branch }} + pnpm -r publish --tag ${{ github.event.inputs.version }} --publish-branch ${{ github.event.inputs.branch }} From 5f44b8aaa8a408e4877b25ed52c69c8c2637f631 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 19 Sep 2025 16:43:45 -0700 Subject: [PATCH 06/11] chore: x --- .github/workflows/preview.yml | 57 ----------------------------------- .github/workflows/release.yml | 37 +++++++++-------------- 2 files changed, 14 insertions(+), 80 deletions(-) delete mode 100644 .github/workflows/preview.yml diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml deleted file mode 100644 index 28512183eac..00000000000 --- a/.github/workflows/preview.yml +++ /dev/null @@ -1,57 +0,0 @@ -# https://github.com/stackblitz-labs/pkg.pr.new -name: Preview Release - -on: - workflow_dispatch: - inputs: - branch: - description: 'Release preview version branch (confirm release branch)' - required: true - default: 'main' - -permissions: - contents: read - -jobs: - preview: - if: github.repository == 'module-federation/core' - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 25 - ref: ${{ github.event.inputs.branch }} - - # Use corepack to install pnpm - - name: Setup Pnpm - run: | - npm install -g corepack@latest --force - corepack prepare pnpm@8.11.0 --activate - corepack enable - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: 22 - cache: 'pnpm' - - # Update npm to the latest version to enable OIDC - - name: Update npm - run: | - npm install -g npm@latest - npm --version - - - name: Install deps - run: pnpm install - - - name: Build and test Packages - run: | - git fetch origin main - npx nx run-many --targets=build --projects=tag:type:pkg --skip-nx-cache - ls -l packages/*/dist packages/*/package.json - - - name: Publish Preview - run: | - pnpx pkg-pr-new@0.0.58 publish --compact --pnpm ./packages/* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fdbdf789a77..9cde888a2c1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,23 +4,18 @@ on: inputs: version: type: choice - description: 'Release Version (next, beta, alpha, latest)' + description: 'Release Version' required: true default: 'next' options: - - next - - beta - - alpha - latest + - next branch: description: 'Release Branch (confirm release branch)' required: true default: 'main' permissions: - # Needed for pushing git tags during release - contents: write - # Kept for npm provenance/OIDC if used id-token: write jobs: @@ -35,8 +30,6 @@ jobs: with: fetch-depth: 25 ref: ${{ github.event.inputs.branch }} - # Use repo-scoped token so pushing tags always works - token: ${{ secrets.REPO_SCOPED_TOKEN }} # Use corepack to install pnpm - name: Setup Pnpm @@ -60,6 +53,11 @@ jobs: - name: Install deps run: pnpm install + - name: Generate preview version + if: github.event.inputs.version == 'next' + run: | + npx changeset version --snapshot next + - name: Build and test Packages run: | git fetch origin main @@ -67,19 +65,12 @@ jobs: npx nx run-many --targets=build --projects=tag:type:metro ls -l packages/*/dist packages/*/package.json - - uses: actions/github-script@v7 - id: version_to_release - with: - result-encoding: string - script: | - const fs = require('fs'); - const packageJson = JSON.parse(fs.readFileSync('./packages/runtime/package.json', 'utf8')); - return 'v' + packageJson.version; - - - name: Publish to npm - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Publish latest version + if: github.event.inputs.version == 'latest' run: | - git tag ${{ steps.version_to_release.outputs.result }} - git push origin ${{ steps.version_to_release.outputs.result }} pnpm -r publish --tag ${{ github.event.inputs.version }} --publish-branch ${{ github.event.inputs.branch }} + + - name: Publish preview version + if: github.event.inputs.version == 'next' + run: | + pnpm -r publish --tag next --no-git-checks From 038ebe8acd04884d1bb1dcc97351c86f5a0af2bc Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 19 Sep 2025 16:45:51 -0700 Subject: [PATCH 07/11] ci(core): refine release workflow (npm auth, registry, branch fetch) --- .github/workflows/release.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9cde888a2c1..cb84f2c69cc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,10 @@ on: default: 'main' permissions: + # Needed for npm provenance or token auth id-token: write + # Harmless here; no tag push, but keeps flexibility if added later + contents: write jobs: release: @@ -43,6 +46,7 @@ jobs: with: node-version: 22 cache: 'pnpm' + registry-url: 'https://registry.npmjs.org' # Update npm to the latest version to enable OIDC - name: Update npm @@ -60,17 +64,22 @@ jobs: - name: Build and test Packages run: | - git fetch origin main + git fetch origin ${{ github.event.inputs.branch }} --tags --prune npx nx run-many --targets=build --projects=tag:type:pkg --skip-nx-cache npx nx run-many --targets=build --projects=tag:type:metro ls -l packages/*/dist packages/*/package.json - name: Publish latest version if: github.event.inputs.version == 'latest' + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: | pnpm -r publish --tag ${{ github.event.inputs.version }} --publish-branch ${{ github.event.inputs.branch }} - name: Publish preview version if: github.event.inputs.version == 'next' + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_CONFIG_PROVENANCE: 'true' run: | pnpm -r publish --tag next --no-git-checks From 83993f8e43f1b984c371965ae66049508097d73e Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 19 Sep 2025 19:18:47 -0700 Subject: [PATCH 08/11] fix(enhanced): guard missing hooks in tests (afterResolve, finishModules) --- .../src/lib/sharing/ConsumeSharedPlugin.ts | 333 +++++++++--------- 1 file changed, 170 insertions(+), 163 deletions(-) diff --git a/packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts b/packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts index 9f9b3e958d5..fc0bd20adc6 100644 --- a/packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts +++ b/packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts @@ -698,135 +698,146 @@ class ConsumeSharedPlugin { ); // AFTER RESOLVE: alias-aware equality (single-resolution per candidate via cache) - normalModuleFactory.hooks.afterResolve.tapPromise( - PLUGIN_NAME, - async (data: any /* ResolveData-like */) => { - await promise; - - const dependencies = data.dependencies as any[]; - if ( - dependencies && - (dependencies[0] instanceof ConsumeSharedFallbackDependency || - dependencies[0] instanceof ProvideForSharedDependency) - ) { - return; - } + { + const afterResolveHook = (normalModuleFactory as any)?.hooks + ?.afterResolve; + if (afterResolveHook?.tapPromise) { + afterResolveHook.tapPromise( + PLUGIN_NAME, + async (data: any /* ResolveData-like */) => { + await promise; + + const dependencies = data.dependencies as any[]; + if ( + dependencies && + (dependencies[0] instanceof ConsumeSharedFallbackDependency || + dependencies[0] instanceof ProvideForSharedDependency) + ) { + return; + } - const createData = data.createData || data; - const resource: string | undefined = - createData && createData.resource; - if (!resource) return; - // Skip virtual/data URI resources – let webpack handle them - if (resource.startsWith('data:')) return; - // Do not convert explicit relative/absolute path requests into consumes - // e.g. "./node_modules/shared" inside a package should resolve locally - const originalRequest: string | undefined = data.request; - if ( - originalRequest && - RELATIVE_OR_ABSOLUTE_PATH_REGEX.test(originalRequest) - ) { - return; - } - if (resolvedConsumes.has(resource)) return; + const createData = data.createData || data; + const resource: string | undefined = + createData && createData.resource; + if (!resource) return; + // Skip virtual/data URI resources – let webpack handle them + if (resource.startsWith('data:')) return; + // Do not convert explicit relative/absolute path requests into consumes + // e.g. "./node_modules/shared" inside a package should resolve locally + const originalRequest: string | undefined = data.request; + if ( + originalRequest && + RELATIVE_OR_ABSOLUTE_PATH_REGEX.test(originalRequest) + ) { + return; + } + if (resolvedConsumes.has(resource)) return; - const issuerLayer: string | undefined = - data.contextInfo && data.contextInfo.issuerLayer === null - ? undefined - : data.contextInfo?.issuerLayer; + const issuerLayer: string | undefined = + data.contextInfo && data.contextInfo.issuerLayer === null + ? undefined + : data.contextInfo?.issuerLayer; - // Try to get the package name via resolver metadata first - let pkgName: string | undefined = - createData?.resourceResolveData?.descriptionFileData?.name; + // Try to get the package name via resolver metadata first + let pkgName: string | undefined = + createData?.resourceResolveData?.descriptionFileData?.name; - if (!pkgName) { - pkgName = await getPackageNameForResource(resource); - } - if (!pkgName) return; - - // Candidate configs: include - // - exact package name keys (legacy behavior) - // - deep-path shares whose keys start with `${pkgName}/` (alias-aware) - const candidates: ConsumeOptions[] = []; - const seen = new Set(); - const k1 = createLookupKeyForSharing(pkgName, issuerLayer); - const k2 = createLookupKeyForSharing(pkgName, undefined); - const c1 = unresolvedConsumes.get(k1); - const c2 = unresolvedConsumes.get(k2); - if (c1 && !seen.has(c1)) { - candidates.push(c1); - seen.add(c1); - } - if (c2 && !seen.has(c2)) { - candidates.push(c2); - seen.add(c2); - } + if (!pkgName) { + pkgName = await getPackageNameForResource(resource); + } + if (!pkgName) return; + + // Candidate configs: include + // - exact package name keys (legacy behavior) + // - deep-path shares whose keys start with `${pkgName}/` (alias-aware) + const candidates: ConsumeOptions[] = []; + const seen = new Set(); + const k1 = createLookupKeyForSharing(pkgName, issuerLayer); + const k2 = createLookupKeyForSharing(pkgName, undefined); + const c1 = unresolvedConsumes.get(k1); + const c2 = unresolvedConsumes.get(k2); + if (c1 && !seen.has(c1)) { + candidates.push(c1); + seen.add(c1); + } + if (c2 && !seen.has(c2)) { + candidates.push(c2); + seen.add(c2); + } - // Also scan for deep-path keys beginning with `${pkgName}/` (both layered and unlayered) - const prefixLayered = createLookupKeyForSharing( - pkgName + '/', - issuerLayer, - ); - const prefixUnlayered = createLookupKeyForSharing( - pkgName + '/', - undefined, + // Also scan for deep-path keys beginning with `${pkgName}/` (both layered and unlayered) + const prefixLayered = createLookupKeyForSharing( + pkgName + '/', + issuerLayer, + ); + const prefixUnlayered = createLookupKeyForSharing( + pkgName + '/', + undefined, + ); + for (const [key, cfg] of unresolvedConsumes) { + if ( + (key.startsWith(prefixLayered) || + key.startsWith(prefixUnlayered)) && + !seen.has(cfg) + ) { + candidates.push(cfg); + seen.add(cfg); + } + } + if (candidates.length === 0) return; + + // Build resolver aligned with current resolve context + const baseResolver = compilation.resolverFactory.get('normal', { + dependencyType: data.dependencyType || 'esm', + } as ResolveOptionsWithDependencyType); + const resolver = + data.resolveOptions && + typeof (baseResolver as any).withOptions === 'function' + ? (baseResolver as any).withOptions(data.resolveOptions) + : data.resolveOptions + ? compilation.resolverFactory.get( + 'normal', + Object.assign( + { + dependencyType: data.dependencyType || 'esm', + }, + data.resolveOptions, + ) as ResolveOptionsWithDependencyType, + ) + : (baseResolver as any); + + const resolverKey = JSON.stringify({ + dependencyType: data.dependencyType || 'esm', + resolveOptions: data.resolveOptions || null, + }); + const ctx = + createData?.context || + data.context || + compilation.compiler.context; + + // Resolve each candidate's target once, compare by absolute path + for (const cfg of candidates) { + const targetReq = (cfg.request || cfg.import) as string; + const targetResolved = await resolveOnce( + resolver, + ctx, + targetReq, + resolverKey, + ); + if (targetResolved && targetResolved === resource) { + resolvedConsumes.set(resource, cfg); + break; + } + } + }, ); - for (const [key, cfg] of unresolvedConsumes) { - if ( - (key.startsWith(prefixLayered) || - key.startsWith(prefixUnlayered)) && - !seen.has(cfg) - ) { - candidates.push(cfg); - seen.add(cfg); - } - } - if (candidates.length === 0) return; - - // Build resolver aligned with current resolve context - const baseResolver = compilation.resolverFactory.get('normal', { - dependencyType: data.dependencyType || 'esm', - } as ResolveOptionsWithDependencyType); - const resolver = - data.resolveOptions && - typeof (baseResolver as any).withOptions === 'function' - ? (baseResolver as any).withOptions(data.resolveOptions) - : data.resolveOptions - ? compilation.resolverFactory.get( - 'normal', - Object.assign( - { - dependencyType: data.dependencyType || 'esm', - }, - data.resolveOptions, - ) as ResolveOptionsWithDependencyType, - ) - : (baseResolver as any); - - const resolverKey = JSON.stringify({ - dependencyType: data.dependencyType || 'esm', - resolveOptions: data.resolveOptions || null, + } else if (afterResolveHook?.tap) { + // Fallback for tests/mocks that only expose sync hooks to avoid throw + afterResolveHook.tap(PLUGIN_NAME, (_data: any) => { + // no-op in sync mock environments; this avoids throwing during plugin registration }); - const ctx = - createData?.context || - data.context || - compilation.compiler.context; - - // Resolve each candidate's target once, compare by absolute path - for (const cfg of candidates) { - const targetReq = (cfg.request || cfg.import) as string; - const targetResolved = await resolveOnce( - resolver, - ctx, - targetReq, - resolverKey, - ); - if (targetResolved && targetResolved === resource) { - resolvedConsumes.set(resource, cfg); - break; - } - } - }, - ); + } + } // CREATE MODULE: swap resolved resource with ConsumeSharedModule when mapped normalModuleFactory.hooks.createModule.tapPromise( @@ -858,51 +869,47 @@ class ConsumeSharedPlugin { ); // Add finishModules hook to copy buildMeta/buildInfo from fallback modules *after* webpack's export analysis - // Running earlier causes failures, so we intentionally execute later than plugins like FlagDependencyExportsPlugin. - // This still follows webpack's pattern used by FlagDependencyExportsPlugin and InferAsyncModulesPlugin, but with a - // later stage. Based on webpack's Compilation.js: finishModules (line 2833) runs before seal (line 2920). - compilation.hooks.finishModules.tapAsync( - { - name: PLUGIN_NAME, - stage: 10, // Run after FlagDependencyExportsPlugin (default stage 0) - }, - (modules, callback) => { - for (const module of modules) { - // Only process ConsumeSharedModule instances with fallback dependencies - if ( - !(module instanceof ConsumeSharedModule) || - !module.options.import - ) { - continue; - } - - let dependency; - if (module.options.eager) { - // For eager mode, get the fallback directly from dependencies - dependency = module.dependencies[0]; - } else { - // For async mode, get it from the async dependencies block - dependency = module.blocks[0]?.dependencies[0]; - } - - if (dependency) { - const fallbackModule = - compilation.moduleGraph.getModule(dependency); + // Guard for test environments where hooks may be lightly stubbed + if (compilation.hooks?.finishModules?.tapAsync) { + compilation.hooks.finishModules.tapAsync( + { + name: PLUGIN_NAME, + stage: 10, // Run after FlagDependencyExportsPlugin (default stage 0) + }, + (modules, callback) => { + for (const module of modules) { + // Only process ConsumeSharedModule instances with fallback dependencies if ( - fallbackModule && - fallbackModule.buildMeta && - fallbackModule.buildInfo + !(module instanceof ConsumeSharedModule) || + !module.options.import ) { - // Copy buildMeta and buildInfo following webpack's DelegatedModule pattern: this.buildMeta = { ...delegateData.buildMeta }; - // This ensures ConsumeSharedModule inherits ESM/CJS detection (exportsType) and other optimization metadata - module.buildMeta = { ...fallbackModule.buildMeta }; - module.buildInfo = { ...fallbackModule.buildInfo }; + continue; + } + + let dependency; + if (module.options.eager) { + dependency = module.dependencies[0]; + } else { + dependency = module.blocks[0]?.dependencies[0]; + } + + if (dependency) { + const fallbackModule = + compilation.moduleGraph.getModule(dependency); + if ( + fallbackModule && + fallbackModule.buildMeta && + fallbackModule.buildInfo + ) { + module.buildMeta = { ...fallbackModule.buildMeta }; + module.buildInfo = { ...fallbackModule.buildInfo }; + } } } - } - callback(); - }, - ); + callback(); + }, + ); + } compilation.hooks.additionalTreeRuntimeRequirements.tap( PLUGIN_NAME, From 72211ab8dd6831ed0420c2920e7deb5d5ec2e8c4 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Mon, 22 Sep 2025 12:54:37 -0700 Subject: [PATCH 09/11] refactor: always register finishModules hook --- .../src/lib/sharing/ConsumeSharedPlugin.ts | 71 +++++++++---------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts b/packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts index fc0bd20adc6..3cc90871c30 100644 --- a/packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts +++ b/packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts @@ -869,47 +869,44 @@ class ConsumeSharedPlugin { ); // Add finishModules hook to copy buildMeta/buildInfo from fallback modules *after* webpack's export analysis - // Guard for test environments where hooks may be lightly stubbed - if (compilation.hooks?.finishModules?.tapAsync) { - compilation.hooks.finishModules.tapAsync( - { - name: PLUGIN_NAME, - stage: 10, // Run after FlagDependencyExportsPlugin (default stage 0) - }, - (modules, callback) => { - for (const module of modules) { - // Only process ConsumeSharedModule instances with fallback dependencies - if ( - !(module instanceof ConsumeSharedModule) || - !module.options.import - ) { - continue; - } + compilation.hooks.finishModules.tapAsync( + { + name: PLUGIN_NAME, + stage: 10, // Run after FlagDependencyExportsPlugin (default stage 0) + }, + (modules, callback) => { + for (const module of modules) { + // Only process ConsumeSharedModule instances with fallback dependencies + if ( + !(module instanceof ConsumeSharedModule) || + !module.options.import + ) { + continue; + } - let dependency; - if (module.options.eager) { - dependency = module.dependencies[0]; - } else { - dependency = module.blocks[0]?.dependencies[0]; - } + let dependency; + if (module.options.eager) { + dependency = module.dependencies[0]; + } else { + dependency = module.blocks[0]?.dependencies[0]; + } - if (dependency) { - const fallbackModule = - compilation.moduleGraph.getModule(dependency); - if ( - fallbackModule && - fallbackModule.buildMeta && - fallbackModule.buildInfo - ) { - module.buildMeta = { ...fallbackModule.buildMeta }; - module.buildInfo = { ...fallbackModule.buildInfo }; - } + if (dependency) { + const fallbackModule = + compilation.moduleGraph.getModule(dependency); + if ( + fallbackModule && + fallbackModule.buildMeta && + fallbackModule.buildInfo + ) { + module.buildMeta = { ...fallbackModule.buildMeta }; + module.buildInfo = { ...fallbackModule.buildInfo }; } } - callback(); - }, - ); - } + } + callback(); + }, + ); compilation.hooks.additionalTreeRuntimeRequirements.tap( PLUGIN_NAME, From d4be790128ca3b44c251989d59f084cd73abc7ce Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Mon, 22 Sep 2025 19:59:09 +0000 Subject: [PATCH 10/11] chore(module-federation): restore release workflow from main --- .github/workflows/release.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 252d3422138..9cde888a2c1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,10 +16,7 @@ on: default: 'main' permissions: - # Needed for npm provenance or token auth id-token: write - # Harmless here; no tag push, but keeps flexibility if added later - contents: write jobs: release: @@ -46,7 +43,6 @@ jobs: with: node-version: 22 cache: 'pnpm' - registry-url: 'https://registry.npmjs.org' # Update npm to the latest version to enable OIDC - name: Update npm @@ -64,7 +60,7 @@ jobs: - name: Build and test Packages run: | - git fetch origin ${{ github.event.inputs.branch }} --tags --prune + git fetch origin main npx nx run-many --targets=build --projects=tag:type:pkg --skip-nx-cache npx nx run-many --targets=build --projects=tag:type:metro ls -l packages/*/dist packages/*/package.json From 47fd166ba7f865fa6ec96703747fb20e4c2f60f6 Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Mon, 29 Sep 2025 11:53:46 -0700 Subject: [PATCH 11/11] feat(enhanced): gate alias-aware consumption behind experiments.aliasConsumption (#4087) --- .github/workflows/e2e-manifest.yml | 4 +- .gitignore | 7 +- .../plugins/sharing/ConsumeSharedPlugin.d.ts | 7 + .../plugins/sharing/SharePlugin.d.ts | 7 + .../lib/container/ModuleFederationPlugin.ts | 11 +- .../src/lib/sharing/ConsumeSharedPlugin.ts | 18 +- .../enhanced/src/lib/sharing/SharePlugin.ts | 8 + .../container/ModuleFederationPlugin.check.ts | 693 +++++++++--------- .../container/ModuleFederationPlugin.json | 4 + .../container/ModuleFederationPlugin.ts | 5 + .../sharing/ConsumeSharedPlugin.check.ts | 6 +- .../schemas/sharing/ConsumeSharedPlugin.json | 4 +- .../schemas/sharing/ConsumeSharedPlugin.ts | 5 +- .../sharing/ProvideSharedPlugin.check.ts | 167 ++--- .../schemas/sharing/ProvideSharedPlugin.json | 7 +- .../schemas/sharing/ProvideSharedPlugin.ts | 7 +- .../src/schemas/sharing/SharePlugin.check.ts | 20 +- .../src/schemas/sharing/SharePlugin.json | 4 +- .../src/schemas/sharing/SharePlugin.ts | 5 +- .../sharing/next-pages-layer-unify/index.js | 9 + .../next/dist/compiled/react-dom/index.js | 4 + .../node_modules/next/dist/compiled/react.js | 4 + .../next/dist/compiled/react/index.js | 4 + .../next/dist/compiled/react/jsx-runtime.js | 4 + .../dist/compiled/react/jsx-runtime/index.js | 4 + .../node_modules/next/package.json | 5 + .../node_modules/react-dom/index.js | 7 + .../node_modules/react-dom/package.json | 5 + .../node_modules/react/index.js | 8 + .../node_modules/react/jsx-runtime/index.js | 5 + .../node_modules/react/package.json | 5 + .../next-pages-layer-unify/package.json | 4 + .../sharing/next-pages-layer-unify/suite.js | 32 + .../next-pages-layer-unify/webpack.config.js | 52 ++ .../errors.js | 2 + .../index.js | 12 + .../next/dist/compiled/react-allowed.js | 9 + .../node_modules/next/package.json | 5 + .../package.json | 4 + .../warnings.js | 20 + .../webpack.config.js | 30 + .../share-with-aliases-filters/errors.js | 2 + .../share-with-aliases-filters/index.js | 27 + .../next/dist/compiled/react-allowed.js | 10 + .../node_modules/next/dist/compiled/react.js | 10 + .../node_modules/next/package.json | 6 + .../node_modules/react/package.json | 7 + .../share-with-aliases-filters/package.json | 7 + .../share-with-aliases-filters/warnings.js | 2 + .../webpack.config.js | 40 + .../webpack.config.js | 1 + .../share-with-aliases/webpack.config.js | 1 + ...edPlugin.alias-consumption-filters.test.ts | 125 ++++ .../types/plugins/ModuleFederationPlugin.ts | 5 + tools/scripts/run-manifest-e2e.mjs | 353 +++++++++ 55 files changed, 1350 insertions(+), 469 deletions(-) create mode 100644 packages/enhanced/test/configCases/sharing/next-pages-layer-unify/index.js create mode 100644 packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/next/dist/compiled/react-dom/index.js create mode 100644 packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/next/dist/compiled/react.js create mode 100644 packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/next/dist/compiled/react/index.js create mode 100644 packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/next/dist/compiled/react/jsx-runtime.js create mode 100644 packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/next/dist/compiled/react/jsx-runtime/index.js create mode 100644 packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/next/package.json create mode 100644 packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/react-dom/index.js create mode 100644 packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/react-dom/package.json create mode 100644 packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/react/index.js create mode 100644 packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/react/jsx-runtime/index.js create mode 100644 packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/react/package.json create mode 100644 packages/enhanced/test/configCases/sharing/next-pages-layer-unify/package.json create mode 100644 packages/enhanced/test/configCases/sharing/next-pages-layer-unify/suite.js create mode 100644 packages/enhanced/test/configCases/sharing/next-pages-layer-unify/webpack.config.js create mode 100644 packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/errors.js create mode 100644 packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/index.js create mode 100644 packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/node_modules/next/dist/compiled/react-allowed.js create mode 100644 packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/node_modules/next/package.json create mode 100644 packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/package.json create mode 100644 packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/warnings.js create mode 100644 packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/webpack.config.js create mode 100644 packages/enhanced/test/configCases/sharing/share-with-aliases-filters/errors.js create mode 100644 packages/enhanced/test/configCases/sharing/share-with-aliases-filters/index.js create mode 100644 packages/enhanced/test/configCases/sharing/share-with-aliases-filters/node_modules/next/dist/compiled/react-allowed.js create mode 100644 packages/enhanced/test/configCases/sharing/share-with-aliases-filters/node_modules/next/dist/compiled/react.js create mode 100644 packages/enhanced/test/configCases/sharing/share-with-aliases-filters/node_modules/next/package.json create mode 100644 packages/enhanced/test/configCases/sharing/share-with-aliases-filters/node_modules/react/package.json create mode 100644 packages/enhanced/test/configCases/sharing/share-with-aliases-filters/package.json create mode 100644 packages/enhanced/test/configCases/sharing/share-with-aliases-filters/warnings.js create mode 100644 packages/enhanced/test/configCases/sharing/share-with-aliases-filters/webpack.config.js create mode 100644 packages/enhanced/test/unit/sharing/ConsumeSharedPlugin/ConsumeSharedPlugin.alias-consumption-filters.test.ts create mode 100644 tools/scripts/run-manifest-e2e.mjs diff --git a/.github/workflows/e2e-manifest.yml b/.github/workflows/e2e-manifest.yml index 7c8481b495f..2eb275fdecd 100644 --- a/.github/workflows/e2e-manifest.yml +++ b/.github/workflows/e2e-manifest.yml @@ -46,8 +46,8 @@ jobs: - name: E2E Test for Manifest Demo Development if: steps.check-ci.outcome == 'success' - run: pnpm run app:manifest:dev & echo "done" && npx wait-on tcp:3009 && npx wait-on tcp:3012 && npx wait-on http://127.0.0.1:4001/ && npx nx run-many --target=e2e --projects=manifest-webpack-host --parallel=2 && npx kill-port 3013 3009 3010 3011 3012 4001 + run: node tools/scripts/run-manifest-e2e.mjs --mode=dev - name: E2E Test for Manifest Demo Production if: steps.check-ci.outcome == 'success' - run: pnpm run app:manifest:prod & echo "done" && npx wait-on tcp:3009 && npx wait-on tcp:3012 && npx wait-on http://127.0.0.1:4001/ && npx nx run-many --target=e2e --projects=manifest-webpack-host --parallel=1 && npx kill-port 3013 3009 3010 3011 3012 4001 + run: node tools/scripts/run-manifest-e2e.mjs --mode=prod diff --git a/.gitignore b/.gitignore index 518c4526ecd..84cb1ecd183 100644 --- a/.gitignore +++ b/.gitignore @@ -90,6 +90,7 @@ ssg __mocks__/ # test mock modules -!packages/enhanced/test/configCases/**/**/node_modules -!packages/enhanced/test/configCases/sharing/share-with-aliases/node_modules/next/dist -!packages/enhanced/test/configCases/sharing/share-with-aliases-provide-only/node_modules/next/dist +# Keep ALL test configCases node_modules (and all nested files) tracked, +# so we don't need per-path exceptions like next/dist. +!packages/enhanced/test/configCases/**/node_modules/ +!packages/enhanced/test/configCases/**/node_modules/** diff --git a/packages/enhanced/src/declarations/plugins/sharing/ConsumeSharedPlugin.d.ts b/packages/enhanced/src/declarations/plugins/sharing/ConsumeSharedPlugin.d.ts index 7f29717fd3f..64d1ebde2c9 100644 --- a/packages/enhanced/src/declarations/plugins/sharing/ConsumeSharedPlugin.d.ts +++ b/packages/enhanced/src/declarations/plugins/sharing/ConsumeSharedPlugin.d.ts @@ -25,6 +25,13 @@ export interface ConsumeSharedPluginOptions { * Share scope name used for all consumed modules (defaults to 'default'). */ shareScope?: string | string[]; + /** + * Experimental features configuration. + */ + experiments?: { + /** Enable alias-aware consuming via NormalModuleFactory.afterResolve (experimental). */ + aliasConsumption?: boolean; + }; } /** * Modules that should be consumed from share scope. Property names are used to match requested modules in this compilation. Relative requests are resolved, module requests are matched unresolved, absolute paths will match resolved requests. A trailing slash will match all requests with this prefix. In this case shareKey must also have a trailing slash. diff --git a/packages/enhanced/src/declarations/plugins/sharing/SharePlugin.d.ts b/packages/enhanced/src/declarations/plugins/sharing/SharePlugin.d.ts index 1f32822b382..473692174ad 100644 --- a/packages/enhanced/src/declarations/plugins/sharing/SharePlugin.d.ts +++ b/packages/enhanced/src/declarations/plugins/sharing/SharePlugin.d.ts @@ -25,6 +25,13 @@ export interface SharePluginOptions { * Modules that should be shared in the share scope. When provided, property names are used to match requested modules in this compilation. */ shared: Shared; + /** + * Experimental features configuration. + */ + experiments?: { + /** Enable alias-aware consuming via NormalModuleFactory.afterResolve (experimental). */ + aliasConsumption?: boolean; + }; } /** * Modules that should be shared in the share scope. Property names are used to match requested modules in this compilation. Relative requests are resolved, module requests are matched unresolved, absolute paths will match resolved requests. A trailing slash will match all requests with this prefix. In this case shareKey must also have a trailing slash. diff --git a/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts b/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts index 3f195bfc1b0..38d1eaf9d22 100644 --- a/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts +++ b/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts @@ -107,6 +107,8 @@ class ModuleFederationPlugin implements WebpackPluginInstance { (new RemoteEntryPlugin(options) as unknown as WebpackPluginInstance).apply( compiler, ); + + // Do not use process.env for alias consumption; flag is forwarded via options if (options.experiments?.provideExternalRuntime) { if (options.exposes) { throw new Error( @@ -212,10 +214,15 @@ class ModuleFederationPlugin implements WebpackPluginInstance { }).apply(compiler); } if (options.shared) { - new SharePlugin({ + // Build SharePlugin options and pass through aliasConsumption directly + const shareOpts = { shared: options.shared, shareScope: options.shareScope, - }).apply(compiler); + experiments: { + aliasConsumption: options.experiments?.aliasConsumption, + }, + }; + new SharePlugin(shareOpts).apply(compiler); } }); diff --git a/packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts b/packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts index 791f858ee62..5863a678270 100644 --- a/packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts +++ b/packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts @@ -82,6 +82,7 @@ const PLUGIN_NAME = 'ConsumeSharedPlugin'; class ConsumeSharedPlugin { private _consumes: [string, ConsumeOptions][]; + private _aliasConsumption: boolean; constructor(options: ConsumeSharedPluginOptions) { if (typeof options !== 'string') { @@ -157,6 +158,10 @@ class ConsumeSharedPlugin { } as ConsumeOptions; }, ); + + // read experiments flag if provided via options + const aliasConsumptionFlag = options.experiments?.aliasConsumption; + this._aliasConsumption = Boolean(aliasConsumptionFlag); } createConsumeSharedModule( @@ -313,9 +318,14 @@ class ConsumeSharedPlugin { return resolveFilter(consumedModule); } const { data } = result || {}; - if (!data || !data['version'] || data['name'] !== request) { + // If pkg data is missing or lacks version, keep module + if (!data || !data['version']) { return resolveFilter(consumedModule); } + // For deep-path keys (alias consumption), the request may be a path like + // "next/dist/compiled/react" or an absolute resource path. In that case, + // data['name'] will be the package name (e.g., "next"). Do not require + // strict equality with the request string; rely solely on semver check. if ( config.include && @@ -399,7 +409,8 @@ class ConsumeSharedPlugin { return resolveFilter(consumedModule); } const { data } = result || {}; - if (!data || !data['version'] || data['name'] !== request) { + // If pkg data is missing or lacks version, keep module + if (!data || !data['version']) { return resolveFilter(consumedModule); } @@ -698,7 +709,8 @@ class ConsumeSharedPlugin { ); // AFTER RESOLVE: alias-aware equality (single-resolution per candidate via cache) - { + // Guarded by experimental flag provided via options + if (this._aliasConsumption) { const afterResolveHook = (normalModuleFactory as any)?.hooks ?.afterResolve; if (afterResolveHook?.tapPromise) { diff --git a/packages/enhanced/src/lib/sharing/SharePlugin.ts b/packages/enhanced/src/lib/sharing/SharePlugin.ts index e65806279c0..fdf7ddc70a9 100644 --- a/packages/enhanced/src/lib/sharing/SharePlugin.ts +++ b/packages/enhanced/src/lib/sharing/SharePlugin.ts @@ -28,10 +28,13 @@ const validate = createSchemaValidation( }, ); +// Use declaration-derived type directly where needed; no local alias. + class SharePlugin { private _shareScope: string | string[]; private _consumes: Record[]; private _provides: Record[]; + private _experiments?: SharePluginOptions['experiments']; constructor(options: SharePluginOptions) { validate(options); @@ -98,6 +101,9 @@ class SharePlugin { this._shareScope = options.shareScope || 'default'; this._consumes = consumes; this._provides = provides; + // keep experiments object if present (validated by schema) + // includes only aliasConsumption (experimental) + this._experiments = options.experiments; } /** @@ -111,6 +117,8 @@ class SharePlugin { new ConsumeSharedPlugin({ shareScope: this._shareScope, consumes: this._consumes, + // forward experiments to ConsumeSharedPlugin + experiments: this._experiments, }).apply(compiler); new ProvideSharedPlugin({ diff --git a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.check.ts b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.check.ts index 666cb30205d..49461f51121 100644 --- a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.check.ts +++ b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.check.ts @@ -378,6 +378,7 @@ const t = { asyncStartup: { type: 'boolean' }, externalRuntime: { type: 'boolean' }, provideExternalRuntime: { type: 'boolean' }, + aliasConsumption: { type: 'boolean' }, }, }, bridge: { @@ -501,8 +502,8 @@ function a( let r = e.import; const n = l, y = l; - let c = !1; - const u = l; + let u = !1; + const c = l; if (l == l) if ('string' == typeof r) { if (r.length < 1) { @@ -513,8 +514,8 @@ function a( const e = { params: { type: 'string' } }; null === i ? (i = [e]) : i.push(e), l++; } - var p = u === l; - if (((c = c || p), !c)) { + var p = c === l; + if (((u = u || p), !u)) { const n = l; o(r, { instancePath: t + '/import', @@ -525,9 +526,9 @@ function a( ((i = null === i ? o.errors : i.concat(o.errors)), (l = i.length)), (p = n === l), - (c = c || p); + (u = u || p); } - if (!c) { + if (!u) { const e = { params: {} }; return ( null === i ? (i = [e]) : i.push(e), l++, (a.errors = i), !1 @@ -566,8 +567,8 @@ function i( for (const r in e) { let n = e[r]; const y = p, - c = p; - let u = !1; + u = p; + let c = !1; const m = p; a(n, { instancePath: t + '/' + r.replace(/~/g, '~0').replace(/\//g, '~1'), @@ -576,7 +577,7 @@ function i( rootData: s, }) || ((l = null === l ? a.errors : l.concat(a.errors)), (p = l.length)); var f = m === p; - if (((u = u || f), !u)) { + if (((c = c || f), !c)) { const a = p; if (p == p) if ('string' == typeof n) { @@ -588,7 +589,7 @@ function i( const e = { params: { type: 'string' } }; null === l ? (l = [e]) : l.push(e), p++; } - if (((f = a === p), (u = u || f), !u)) { + if (((f = a === p), (c = c || f), !c)) { const a = p; o(n, { instancePath: t + '/' + r.replace(/~/g, '~0').replace(/\//g, '~1'), @@ -598,14 +599,14 @@ function i( }) || ((l = null === l ? o.errors : l.concat(o.errors)), (p = l.length)), (f = a === p), - (u = u || f); + (c = c || f); } } - if (!u) { + if (!c) { const e = { params: {} }; return null === l ? (l = [e]) : l.push(e), p++, (i.errors = l), !1; } - if (((p = c), null !== l && (c ? (l.length = c) : (l = null)), y !== p)) + if (((p = u), null !== l && (u ? (l.length = u) : (l = null)), y !== p)) break; } } @@ -644,8 +645,8 @@ function l( const e = { params: { type: 'string' } }; null === o ? (o = [e]) : o.push(e), a++; } - var c = y === a; - if (((f = f || c), !f)) { + var u = y === a; + if (((f = f || u), !f)) { const l = a; i(r, { instancePath: t + '/' + n, @@ -654,8 +655,8 @@ function l( rootData: s, }) || ((o = null === o ? i.errors : o.concat(i.errors)), (a = o.length)), - (c = l === a), - (f = f || c); + (u = l === a), + (f = f || u); } if (f) (a = p), null !== o && (p ? (o.length = p) : (o = null)); else { @@ -668,8 +669,8 @@ function l( const e = { params: { type: 'array' } }; null === o ? (o = [e]) : o.push(e), a++; } - var u = y === a; - if (((f = f || u), !f)) { + var c = y === a; + if (((f = f || c), !f)) { const l = a; i(e, { instancePath: t, @@ -677,8 +678,8 @@ function l( parentDataProperty: n, rootData: s, }) || ((o = null === o ? i.errors : o.concat(i.errors)), (a = o.length)), - (u = l === a), - (f = f || u); + (c = l === a), + (f = f || c); } if (!f) { const e = { params: {} }; @@ -760,35 +761,35 @@ function f( const e = { params: { type: 'string' } }; null === o ? (o = [e]) : o.push(e), a++; } - var c = t === a; - } else c = !0; - if (c) { + var u = t === a; + } else u = !0; + if (u) { if (void 0 !== e.commonjs) { const t = a; if ('string' != typeof e.commonjs) { const e = { params: { type: 'string' } }; null === o ? (o = [e]) : o.push(e), a++; } - c = t === a; - } else c = !0; - if (c) { + u = t === a; + } else u = !0; + if (u) { if (void 0 !== e.commonjs2) { const t = a; if ('string' != typeof e.commonjs2) { const e = { params: { type: 'string' } }; null === o ? (o = [e]) : o.push(e), a++; } - c = t === a; - } else c = !0; - if (c) + u = t === a; + } else u = !0; + if (u) if (void 0 !== e.root) { const t = a; if ('string' != typeof e.root) { const e = { params: { type: 'string' } }; null === o ? (o = [e]) : o.push(e), a++; } - c = t === a; - } else c = !0; + u = t === a; + } else u = !0; } } } @@ -888,9 +889,9 @@ function y( const e = { params: { type: 'string' } }; null === o ? (o = [e]) : o.push(e), a++; } - var c = r === a; - } else c = !0; - if (c) { + var u = r === a; + } else u = !0; + if (u) { if (void 0 !== e.commonjs) { let t = e.commonjs; const r = a; @@ -904,9 +905,9 @@ function y( const e = { params: { type: 'string' } }; null === o ? (o = [e]) : o.push(e), a++; } - c = r === a; - } else c = !0; - if (c) + u = r === a; + } else u = !0; + if (u) if (void 0 !== e.root) { let t = e.root; const r = a, @@ -935,8 +936,8 @@ function y( const e = { params: { type: 'array' } }; null === o ? (o = [e]) : o.push(e), a++; } - var u = i === a; - if (((s = s || u), !s)) { + var c = i === a; + if (((s = s || c), !s)) { const e = a; if (a === e) if ('string' == typeof t) { @@ -948,7 +949,7 @@ function y( const e = { params: { type: 'string' } }; null === o ? (o = [e]) : o.push(e), a++; } - (u = e === a), (s = s || u); + (c = e === a), (s = s || c); } if (s) (a = n), null !== o && (n ? (o.length = n) : (o = null)); @@ -956,8 +957,8 @@ function y( const e = { params: {} }; null === o ? (o = [e]) : o.push(e), a++; } - c = r === a; - } else c = !0; + u = r === a; + } else u = !0; } } } else { @@ -978,7 +979,7 @@ function y( 0 === a ); } -function c( +function u( e, { instancePath: t = '', @@ -991,11 +992,11 @@ function c( a = 0; if (0 === a) { if (!e || 'object' != typeof e || Array.isArray(e)) - return (c.errors = [{ params: { type: 'object' } }]), !1; + return (u.errors = [{ params: { type: 'object' } }]), !1; { let r; if (void 0 === e.type && (r = 'type')) - return (c.errors = [{ params: { missingProperty: r } }]), !1; + return (u.errors = [{ params: { missingProperty: r } }]), !1; { const r = a; for (const t in e) @@ -1007,15 +1008,15 @@ function c( 'type' !== t && 'umdNamedDefine' !== t ) - return (c.errors = [{ params: { additionalProperty: t } }]), !1; + return (u.errors = [{ params: { additionalProperty: t } }]), !1; if (r === a) { if (void 0 !== e.amdContainer) { let t = e.amdContainer; const r = a; if (a == a) { if ('string' != typeof t) - return (c.errors = [{ params: { type: 'string' } }]), !1; - if (t.length < 1) return (c.errors = [{ params: {} }]), !1; + return (u.errors = [{ params: { type: 'string' } }]), !1; + if (t.length < 1) return (u.errors = [{ params: {} }]), !1; } var i = r === a; } else i = !0; @@ -1079,7 +1080,7 @@ function c( if (!s) { const e = { params: {} }; return ( - null === o ? (o = [e]) : o.push(e), a++, (c.errors = o), !1 + null === o ? (o = [e]) : o.push(e), a++, (u.errors = o), !1 ); } (a = n), @@ -1129,21 +1130,21 @@ function c( const e = { params: { allowedValues: p.anyOf[0].enum } }; null === o ? (o = [e]) : o.push(e), a++; } - var u = l === a; - if (((s = s || u), !s)) { + var c = l === a; + if (((s = s || c), !s)) { const e = a; if ('string' != typeof t) { const e = { params: { type: 'string' } }; null === o ? (o = [e]) : o.push(e), a++; } - (u = e === a), (s = s || u); + (c = e === a), (s = s || c); } if (!s) { const e = { params: {} }; return ( null === o ? (o = [e]) : o.push(e), a++, - (c.errors = o), + (u.errors = o), !1 ); } @@ -1156,7 +1157,7 @@ function c( const t = a; if ('boolean' != typeof e.umdNamedDefine) return ( - (c.errors = [{ params: { type: 'boolean' } }]), !1 + (u.errors = [{ params: { type: 'boolean' } }]), !1 ); i = t === a; } else i = !0; @@ -1168,9 +1169,9 @@ function c( } } } - return (c.errors = o), 0 === a; + return (u.errors = o), 0 === a; } -function u( +function c( e, { instancePath: t = '', @@ -1180,19 +1181,19 @@ function u( } = {}, ) { if (!Array.isArray(e)) - return (u.errors = [{ params: { type: 'array' } }]), !1; + return (c.errors = [{ params: { type: 'array' } }]), !1; { const t = e.length; for (let r = 0; r < t; r++) { let t = e[r]; const n = 0; if ('string' != typeof t) - return (u.errors = [{ params: { type: 'string' } }]), !1; - if (t.length < 1) return (u.errors = [{ params: {} }]), !1; + return (c.errors = [{ params: { type: 'string' } }]), !1; + if (t.length < 1) return (c.errors = [{ params: {} }]), !1; if (0 !== n) break; } } - return (u.errors = null), !0; + return (c.errors = null), !0; } function m( e, @@ -1237,13 +1238,13 @@ function m( var i = y === a; if (((f = f || i), !f)) { const n = a; - u(r, { + c(r, { instancePath: t + '/external', parentData: e, parentDataProperty: 'external', rootData: s, }) || - ((o = null === o ? u.errors : o.concat(u.errors)), + ((o = null === o ? c.errors : o.concat(c.errors)), (a = o.length)), (i = n === a), (f = f || i); @@ -1358,13 +1359,13 @@ function d( } if (((i = l === a), (f = f || i), !f)) { const l = a; - u(n, { + c(n, { instancePath: t + '/' + r.replace(/~/g, '~0').replace(/\//g, '~1'), parentData: e, parentDataProperty: r, rootData: s, }) || - ((o = null === o ? u.errors : o.concat(u.errors)), (a = o.length)), + ((o = null === o ? c.errors : o.concat(c.errors)), (a = o.length)), (i = l === a), (f = f || i); } @@ -1704,26 +1705,26 @@ function v( ]), !1 ); - var c = r === i; - } else c = !0; - if (c) { + var u = r === i; + } else u = !0; + if (u) { if (void 0 !== t.version) { const e = i; if ('string' != typeof t.version) return ( (v.errors = [{ params: { type: 'string' } }]), !1 ); - c = e === i; - } else c = !0; - if (c) + u = e === i; + } else u = !0; + if (u) if (void 0 !== t.fallbackVersion) { const e = i; if ('string' != typeof t.fallbackVersion) return ( (v.errors = [{ params: { type: 'string' } }]), !1 ); - c = e === i; - } else c = !0; + u = e === i; + } else u = !0; } } } @@ -1745,8 +1746,8 @@ function v( }; null === a ? (a = [e]) : a.push(e), i++; } - var u = o === i; - if (((s = s || u), !s)) { + var c = o === i; + if (((s = s || c), !s)) { const e = i; if (i == i) if ('string' == typeof t) { @@ -1758,7 +1759,7 @@ function v( const e = { params: { type: 'string' } }; null === a ? (a = [e]) : a.push(e), i++; } - (u = e === i), (s = s || u); + (c = e === i), (s = s || c); } if (!s) { const e = { params: {} }; @@ -2178,25 +2179,25 @@ function D( } = {}, ) { let y = null, - u = 0; - if (0 === u) { + c = 0; + if (0 === c) { if (!o || 'object' != typeof o || Array.isArray(o)) return (D.errors = [{ params: { type: 'object' } }]), !1; { - const i = u; + const i = c; for (const e in o) if (!s.call(t.properties, e)) return (D.errors = [{ params: { additionalProperty: e } }]), !1; - if (i === u) { + if (i === c) { if (void 0 !== o.async) { - const e = u; + const e = c; if ('boolean' != typeof o.async) return (D.errors = [{ params: { type: 'boolean' } }]), !1; - var m = e === u; + var m = e === c; } else m = !0; if (m) { if (void 0 !== o.exposes) { - const e = u; + const e = c; l(o.exposes, { instancePath: a + '/exposes', parentData: o, @@ -2204,54 +2205,54 @@ function D( rootData: f, }) || ((y = null === y ? l.errors : y.concat(l.errors)), - (u = y.length)), - (m = e === u); + (c = y.length)), + (m = e === c); } else m = !0; if (m) { if (void 0 !== o.filename) { let t = o.filename; - const r = u; - if (u === r) { + const r = c; + if (c === r) { if ('string' != typeof t) return (D.errors = [{ params: { type: 'string' } }]), !1; if (t.length < 1) return (D.errors = [{ params: {} }]), !1; if (t.includes('!') || !1 !== e.test(t)) return (D.errors = [{ params: {} }]), !1; } - m = r === u; + m = r === c; } else m = !0; if (m) { if (void 0 !== o.library) { - const e = u; - c(o.library, { + const e = c; + u(o.library, { instancePath: a + '/library', parentData: o, parentDataProperty: 'library', rootData: f, }) || - ((y = null === y ? c.errors : y.concat(c.errors)), - (u = y.length)), - (m = e === u); + ((y = null === y ? u.errors : y.concat(u.errors)), + (c = y.length)), + (m = e === c); } else m = !0; if (m) { if (void 0 !== o.name) { let e = o.name; - const t = u; - if (u === t) { + const t = c; + if (c === t) { if ('string' != typeof e) return (D.errors = [{ params: { type: 'string' } }]), !1; if (e.length < 1) return (D.errors = [{ params: {} }]), !1; } - m = t === u; + m = t === c; } else m = !0; if (m) { if (void 0 !== o.remoteType) { let e = o.remoteType; - const t = u, - n = u; + const t = c, + n = c; let s = !1, a = null; - const i = u; + const i = c; if ( 'var' !== e && 'module' !== e && @@ -2277,24 +2278,24 @@ function D( 'node-commonjs' !== e ) { const e = { params: { allowedValues: r.enum } }; - null === y ? (y = [e]) : y.push(e), u++; + null === y ? (y = [e]) : y.push(e), c++; } - if ((i === u && ((s = !0), (a = 0)), !s)) { + if ((i === c && ((s = !0), (a = 0)), !s)) { const e = { params: { passingSchemas: a } }; return ( null === y ? (y = [e]) : y.push(e), - u++, + c++, (D.errors = y), !1 ); } - (u = n), + (c = n), null !== y && (n ? (y.length = n) : (y = null)), - (m = t === u); + (m = t === c); } else m = !0; if (m) { if (void 0 !== o.remotes) { - const e = u; + const e = c; g(o.remotes, { instancePath: a + '/remotes', parentData: o, @@ -2302,111 +2303,111 @@ function D( rootData: f, }) || ((y = null === y ? g.errors : y.concat(g.errors)), - (u = y.length)), - (m = e === u); + (c = y.length)), + (m = e === c); } else m = !0; if (m) { if (void 0 !== o.runtime) { let e = o.runtime; - const t = u, - r = u; + const t = c, + r = c; let s = !1; - const a = u; + const a = c; if (!1 !== e) { const e = { params: { allowedValues: n.anyOf[0].enum }, }; - null === y ? (y = [e]) : y.push(e), u++; + null === y ? (y = [e]) : y.push(e), c++; } - var d = a === u; + var d = a === c; if (((s = s || d), !s)) { - const t = u; - if (u === t) + const t = c; + if (c === t) if ('string' == typeof e) { if (e.length < 1) { const e = { params: {} }; - null === y ? (y = [e]) : y.push(e), u++; + null === y ? (y = [e]) : y.push(e), c++; } } else { const e = { params: { type: 'string' } }; - null === y ? (y = [e]) : y.push(e), u++; + null === y ? (y = [e]) : y.push(e), c++; } - (d = t === u), (s = s || d); + (d = t === c), (s = s || d); } if (!s) { const e = { params: {} }; return ( null === y ? (y = [e]) : y.push(e), - u++, + c++, (D.errors = y), !1 ); } - (u = r), + (c = r), null !== y && (r ? (y.length = r) : (y = null)), - (m = t === u); + (m = t === c); } else m = !0; if (m) { if (void 0 !== o.shareScope) { let e = o.shareScope; - const t = u, - r = u; + const t = c, + r = c; let n = !1; - const s = u; - if (u === s) + const s = c; + if (c === s) if ('string' == typeof e) { if (e.length < 1) { const e = { params: {} }; - null === y ? (y = [e]) : y.push(e), u++; + null === y ? (y = [e]) : y.push(e), c++; } } else { const e = { params: { type: 'string' } }; - null === y ? (y = [e]) : y.push(e), u++; + null === y ? (y = [e]) : y.push(e), c++; } - var h = s === u; + var h = s === c; if (((n = n || h), !n)) { - const t = u; - if (u === t) + const t = c; + if (c === t) if (Array.isArray(e)) { const t = e.length; for (let r = 0; r < t; r++) { let t = e[r]; - const n = u; - if (u === n) + const n = c; + if (c === n) if ('string' == typeof t) { if (t.length < 1) { const e = { params: {} }; - null === y ? (y = [e]) : y.push(e), u++; + null === y ? (y = [e]) : y.push(e), c++; } } else { const e = { params: { type: 'string' } }; - null === y ? (y = [e]) : y.push(e), u++; + null === y ? (y = [e]) : y.push(e), c++; } - if (n !== u) break; + if (n !== c) break; } } else { const e = { params: { type: 'array' } }; - null === y ? (y = [e]) : y.push(e), u++; + null === y ? (y = [e]) : y.push(e), c++; } - (h = t === u), (n = n || h); + (h = t === c), (n = n || h); } if (!n) { const e = { params: {} }; return ( null === y ? (y = [e]) : y.push(e), - u++, + c++, (D.errors = y), !1 ); } - (u = r), + (c = r), null !== y && (r ? (y.length = r) : (y = null)), - (m = t === u); + (m = t === c); } else m = !0; if (m) { if (void 0 !== o.shareStrategy) { let e = o.shareStrategy; - const r = u; + const r = c; if ('string' != typeof e) return ( (D.errors = [{ params: { type: 'string' } }]), @@ -2424,11 +2425,11 @@ function D( ]), !1 ); - m = r === u; + m = r === c; } else m = !0; if (m) { if (void 0 !== o.shared) { - const e = u; + const e = c; j(o.shared, { instancePath: a + '/shared', parentData: o, @@ -2437,24 +2438,24 @@ function D( }) || ((y = null === y ? j.errors : y.concat(j.errors)), - (u = y.length)), - (m = e === u); + (c = y.length)), + (m = e === c); } else m = !0; if (m) { if (void 0 !== o.dts) { let e = o.dts; - const t = u, - r = u; + const t = c, + r = c; let n = !1; - const s = u; + const s = c; if ('boolean' != typeof e) { const e = { params: { type: 'boolean' } }; - null === y ? (y = [e]) : y.push(e), u++; + null === y ? (y = [e]) : y.push(e), c++; } - var b = s === u; + var b = s === c; if (((n = n || b), !n)) { - const t = u; - if (u === t) + const t = c; + if (c === t) if ( e && 'object' == typeof e && @@ -2462,28 +2463,28 @@ function D( ) { if (void 0 !== e.generateTypes) { let t = e.generateTypes; - const r = u, - n = u; + const r = c, + n = c; let s = !1; - const o = u; + const o = c; if ('boolean' != typeof t) { const e = { params: { type: 'boolean' }, }; null === y ? (y = [e]) : y.push(e), - u++; + c++; } - var v = o === u; + var v = o === c; if (((s = s || v), !s)) { - const e = u; - if (u === e) + const e = c; + if (c === e) if ( t && 'object' == typeof t && !Array.isArray(t) ) { if (void 0 !== t.tsConfigPath) { - const e = u; + const e = c; if ( 'string' != typeof t.tsConfigPath @@ -2494,13 +2495,13 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - var P = e === u; + var P = e === c; } else P = !0; if (P) { if (void 0 !== t.typesFolder) { - const e = u; + const e = c; if ( 'string' != typeof t.typesFolder @@ -2513,16 +2514,16 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - P = e === u; + P = e === c; } else P = !0; if (P) { if ( void 0 !== t.compiledTypesFolder ) { - const e = u; + const e = c; if ( 'string' != typeof t.compiledTypesFolder @@ -2535,16 +2536,16 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - P = e === u; + P = e === c; } else P = !0; if (P) { if ( void 0 !== t.deleteTypesFolder ) { - const e = u; + const e = c; if ( 'boolean' != typeof t.deleteTypesFolder @@ -2557,9 +2558,9 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - P = e === u; + P = e === c; } else P = !0; if (P) { if ( @@ -2568,8 +2569,8 @@ function D( ) { let e = t.additionalFilesToCompile; - const r = u; - if (u === r) + const r = c; + if (c === r) if ( Array.isArray(e) ) { @@ -2579,7 +2580,7 @@ function D( r < t; r++ ) { - const t = u; + const t = c; if ( 'string' != typeof e[r] @@ -2592,9 +2593,9 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - if (t !== u) + if (t !== c) break; } } else { @@ -2606,16 +2607,16 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - P = r === u; + P = r === c; } else P = !0; if (P) { if ( void 0 !== t.compileInChildProcess ) { - const e = u; + const e = c; if ( 'boolean' != typeof t.compileInChildProcess @@ -2628,16 +2629,16 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - P = e === u; + P = e === c; } else P = !0; if (P) { if ( void 0 !== t.compilerInstance ) { - const e = u; + const e = c; if ( 'string' != typeof t.compilerInstance @@ -2650,16 +2651,16 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - P = e === u; + P = e === c; } else P = !0; if (P) { if ( void 0 !== t.generateAPITypes ) { - const e = u; + const e = c; if ( 'boolean' != typeof t.generateAPITypes @@ -2672,16 +2673,16 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - P = e === u; + P = e === c; } else P = !0; if (P) { if ( void 0 !== t.extractThirdParty ) { - const e = u; + const e = c; if ( 'boolean' != typeof t.extractThirdParty @@ -2694,16 +2695,16 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - P = e === u; + P = e === c; } else P = !0; if (P) { if ( void 0 !== t.extractRemoteTypes ) { - const e = u; + const e = c; if ( 'boolean' != typeof t.extractRemoteTypes @@ -2720,16 +2721,16 @@ function D( : y.push( e, ), - u++; + c++; } - P = e === u; + P = e === c; } else P = !0; if (P) if ( void 0 !== t.abortOnError ) { - const e = u; + const e = c; if ( 'boolean' != typeof t.abortOnError @@ -2748,9 +2749,9 @@ function D( : y.push( e, ), - u++; + c++; } - P = e === u; + P = e === c; } else P = !0; } } @@ -2768,46 +2769,46 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - (v = e === u), (s = s || v); + (v = e === c), (s = s || v); } if (s) - (u = n), + (c = n), null !== y && (n ? (y.length = n) : (y = null)); else { const e = { params: {} }; null === y ? (y = [e]) : y.push(e), - u++; + c++; } - var A = r === u; + var A = r === c; } else A = !0; if (A) { if (void 0 !== e.consumeTypes) { let t = e.consumeTypes; - const r = u, - n = u; + const r = c, + n = c; let s = !1; - const o = u; + const o = c; if ('boolean' != typeof t) { const e = { params: { type: 'boolean' }, }; null === y ? (y = [e]) : y.push(e), - u++; + c++; } - var x = o === u; + var x = o === c; if (((s = s || x), !s)) { - const e = u; - if (u === e) + const e = c; + if (c === e) if ( t && 'object' == typeof t && !Array.isArray(t) ) { if (void 0 !== t.typesFolder) { - const e = u; + const e = c; if ( 'string' != typeof t.typesFolder @@ -2820,15 +2821,15 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - var O = e === u; + var O = e === c; } else O = !0; if (O) { if ( void 0 !== t.abortOnError ) { - const e = u; + const e = c; if ( 'boolean' != typeof t.abortOnError @@ -2841,16 +2842,16 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - O = e === u; + O = e === c; } else O = !0; if (O) { if ( void 0 !== t.remoteTypesFolder ) { - const e = u; + const e = c; if ( 'string' != typeof t.remoteTypesFolder @@ -2863,16 +2864,16 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - O = e === u; + O = e === c; } else O = !0; if (O) { if ( void 0 !== t.deleteTypesFolder ) { - const e = u; + const e = c; if ( 'boolean' != typeof t.deleteTypesFolder @@ -2885,16 +2886,16 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - O = e === u; + O = e === c; } else O = !0; if (O) { if ( void 0 !== t.maxRetries ) { - const e = u; + const e = c; if ( 'number' != typeof t.maxRetries @@ -2907,16 +2908,16 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - O = e === u; + O = e === c; } else O = !0; if (O) { if ( void 0 !== t.consumeAPITypes ) { - const e = u; + const e = c; if ( 'boolean' != typeof t.consumeAPITypes @@ -2929,9 +2930,9 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - O = e === u; + O = e === c; } else O = !0; if (O) if ( @@ -2940,8 +2941,8 @@ function D( ) { let e = t.runtimePkgs; - const r = u; - if (u === r) + const r = c; + if (c === r) if ( Array.isArray( e, @@ -2954,7 +2955,7 @@ function D( r < t; r++ ) { - const t = u; + const t = c; if ( 'string' != typeof e[ @@ -2975,9 +2976,9 @@ function D( : y.push( e, ), - u++; + c++; } - if (t !== u) + if (t !== c) break; } } else { @@ -2989,9 +2990,9 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - O = r === u; + O = r === c; } else O = !0; } } @@ -3005,12 +3006,12 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - (x = e === u), (s = s || x); + (x = e === c), (s = s || x); } if (s) - (u = n), + (c = n), null !== y && (n ? (y.length = n) @@ -3018,13 +3019,13 @@ function D( else { const e = { params: {} }; null === y ? (y = [e]) : y.push(e), - u++; + c++; } - A = r === u; + A = r === c; } else A = !0; if (A) { if (void 0 !== e.tsConfigPath) { - const t = u; + const t = c; if ( 'string' != typeof e.tsConfigPath ) { @@ -3034,14 +3035,14 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - A = t === u; + A = t === c; } else A = !0; if (A) { if (void 0 !== e.extraOptions) { let t = e.extraOptions; - const r = u; + const r = c; if ( !t || 'object' != typeof t || @@ -3053,13 +3054,13 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - A = r === u; + A = r === c; } else A = !0; if (A) { if (void 0 !== e.implementation) { - const t = u; + const t = c; if ( 'string' != typeof e.implementation @@ -3070,13 +3071,13 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - A = t === u; + A = t === c; } else A = !0; if (A) { if (void 0 !== e.cwd) { - const t = u; + const t = c; if ( 'string' != typeof e.cwd ) { @@ -3088,16 +3089,16 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - A = t === u; + A = t === c; } else A = !0; if (A) if ( void 0 !== e.displayErrorInTerminal ) { - const t = u; + const t = c; if ( 'boolean' != typeof e.displayErrorInTerminal @@ -3110,9 +3111,9 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - A = t === u; + A = t === c; } else A = !0; } } @@ -3121,29 +3122,29 @@ function D( } } else { const e = { params: { type: 'object' } }; - null === y ? (y = [e]) : y.push(e), u++; + null === y ? (y = [e]) : y.push(e), c++; } - (b = t === u), (n = n || b); + (b = t === c), (n = n || b); } if (!n) { const e = { params: {} }; return ( null === y ? (y = [e]) : y.push(e), - u++, + c++, (D.errors = y), !1 ); } - (u = r), + (c = r), null !== y && (r ? (y.length = r) : (y = null)), - (m = t === u); + (m = t === c); } else m = !0; if (m) { if (void 0 !== o.experiments) { let e = o.experiments; - const t = u; - if (u === t) { + const t = c; + if (c === t) { if ( !e || 'object' != typeof e || @@ -3156,7 +3157,7 @@ function D( !1 ); if (void 0 !== e.asyncStartup) { - const t = u; + const t = c; if ('boolean' != typeof e.asyncStartup) return ( (D.errors = [ @@ -3164,11 +3165,11 @@ function D( ]), !1 ); - var L = t === u; + var L = t === c; } else L = !0; if (L) { if (void 0 !== e.externalRuntime) { - const t = u; + const t = c; if ( 'boolean' != typeof e.externalRuntime ) @@ -3178,13 +3179,13 @@ function D( ]), !1 ); - L = t === u; + L = t === c; } else L = !0; - if (L) + if (L) { if ( void 0 !== e.provideExternalRuntime ) { - const t = u; + const t = c; if ( 'boolean' != typeof e.provideExternalRuntime @@ -3195,17 +3196,35 @@ function D( ]), !1 ); - L = t === u; + L = t === c; } else L = !0; + if (L) + if (void 0 !== e.aliasConsumption) { + const t = c; + if ( + 'boolean' != + typeof e.aliasConsumption + ) + return ( + (D.errors = [ + { + params: { type: 'boolean' }, + }, + ]), + !1 + ); + L = t === c; + } else L = !0; + } } } - m = t === u; + m = t === c; } else m = !0; if (m) { if (void 0 !== o.bridge) { let e = o.bridge; - const t = u; - if (u === t) { + const t = c; + if (c === t) { if ( !e || 'object' != typeof e || @@ -3218,7 +3237,7 @@ function D( !1 ); { - const t = u; + const t = c; for (const t in e) if ('disableAlias' !== t) return ( @@ -3232,7 +3251,7 @@ function D( !1 ); if ( - t === u && + t === c && void 0 !== e.disableAlias && 'boolean' != typeof e.disableAlias ) @@ -3244,11 +3263,11 @@ function D( ); } } - m = t === u; + m = t === c; } else m = !0; if (m) { if (void 0 !== o.virtualRuntimeEntry) { - const e = u; + const e = c; if ( 'boolean' != typeof o.virtualRuntimeEntry @@ -3259,32 +3278,32 @@ function D( ]), !1 ); - m = e === u; + m = e === c; } else m = !0; if (m) { if (void 0 !== o.dev) { let e = o.dev; - const t = u, - r = u; + const t = c, + r = c; let n = !1; - const s = u; + const s = c; if ('boolean' != typeof e) { const e = { params: { type: 'boolean' }, }; null === y ? (y = [e]) : y.push(e), - u++; + c++; } - var T = s === u; + var T = s === c; if (((n = n || T), !n)) { - const t = u; - if (u === t) + const t = c; + if (c === t) if ( e && 'object' == typeof e && !Array.isArray(e) ) { - const t = u; + const t = c; for (const t in e) if ( 'disableLiveReload' !== t && @@ -3301,14 +3320,14 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; break; } - if (t === u) { + if (t === c) { if ( void 0 !== e.disableLiveReload ) { - const t = u; + const t = c; if ( 'boolean' != typeof e.disableLiveReload @@ -3321,16 +3340,16 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - var R = t === u; + var R = t === c; } else R = !0; if (R) { if ( void 0 !== e.disableHotTypesReload ) { - const t = u; + const t = c; if ( 'boolean' != typeof e.disableHotTypesReload @@ -3343,16 +3362,16 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - R = t === u; + R = t === c; } else R = !0; if (R) if ( void 0 !== e.disableDynamicRemoteTypeHints ) { - const t = u; + const t = c; if ( 'boolean' != typeof e.disableDynamicRemoteTypeHints @@ -3365,9 +3384,9 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - R = t === u; + R = t === c; } else R = !0; } } @@ -3378,48 +3397,48 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - (T = t === u), (n = n || T); + (T = t === c), (n = n || T); } if (!n) { const e = { params: {} }; return ( null === y ? (y = [e]) : y.push(e), - u++, + c++, (D.errors = y), !1 ); } - (u = r), + (c = r), null !== y && (r ? (y.length = r) : (y = null)), - (m = t === u); + (m = t === c); } else m = !0; if (m) { if (void 0 !== o.manifest) { let e = o.manifest; - const t = u, - r = u; + const t = c, + r = c; let n = !1; - const s = u; + const s = c; if ('boolean' != typeof e) { const e = { params: { type: 'boolean' }, }; null === y ? (y = [e]) : y.push(e), - u++; + c++; } - var k = s === u; + var k = s === c; if (((n = n || k), !n)) { - const t = u; - if (u === t) + const t = c; + if (c === t) if ( e && 'object' == typeof e && !Array.isArray(e) ) { - const t = u; + const t = c; for (const t in e) if ( 'filePath' !== t && @@ -3436,12 +3455,12 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; break; } - if (t === u) { + if (t === c) { if (void 0 !== e.filePath) { - const t = u; + const t = c; if ( 'string' != typeof e.filePath @@ -3454,16 +3473,16 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - var E = t === u; + var E = t === c; } else E = !0; if (E) { if ( void 0 !== e.disableAssetsAnalyze ) { - const t = u; + const t = c; if ( 'boolean' != typeof e.disableAssetsAnalyze @@ -3476,15 +3495,15 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - E = t === u; + E = t === c; } else E = !0; if (E) { if ( void 0 !== e.fileName ) { - const t = u; + const t = c; if ( 'string' != typeof e.fileName @@ -3497,16 +3516,16 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - E = t === u; + E = t === c; } else E = !0; if (E) if ( void 0 !== e.additionalData ) { - const t = u; + const t = c; if ( !( e.additionalData instanceof @@ -3519,9 +3538,9 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - E = t === u; + E = t === c; } else E = !0; } } @@ -3533,9 +3552,9 @@ function D( null === y ? (y = [e]) : y.push(e), - u++; + c++; } - (k = t === u), (n = n || k); + (k = t === c), (n = n || k); } if (!n) { const e = { params: {} }; @@ -3543,21 +3562,21 @@ function D( null === y ? (y = [e]) : y.push(e), - u++, + c++, (D.errors = y), !1 ); } - (u = r), + (c = r), null !== y && (r ? (y.length = r) : (y = null)), - (m = t === u); + (m = t === c); } else m = !0; if (m) { if (void 0 !== o.runtimePlugins) { let e = o.runtimePlugins; - const t = u; - if (u === t) { + const t = c; + if (c === t) { if (!Array.isArray(e)) return ( (D.errors = [ @@ -3570,7 +3589,7 @@ function D( { const t = e.length; for (let r = 0; r < t; r++) { - const t = u; + const t = c; if ('string' != typeof e[r]) return ( (D.errors = [ @@ -3582,15 +3601,15 @@ function D( ]), !1 ); - if (t !== u) break; + if (t !== c) break; } } } - m = t === u; + m = t === c; } else m = !0; if (m) { if (void 0 !== o.getPublicPath) { - const e = u; + const e = c; if ( 'string' != typeof o.getPublicPath @@ -3605,11 +3624,11 @@ function D( ]), !1 ); - m = e === u; + m = e === c; } else m = !0; if (m) { if (void 0 !== o.dataPrefetch) { - const e = u; + const e = c; if ( 'boolean' != typeof o.dataPrefetch @@ -3624,13 +3643,13 @@ function D( ]), !1 ); - m = e === u; + m = e === c; } else m = !0; if (m) if ( void 0 !== o.implementation ) { - const e = u; + const e = c; if ( 'string' != typeof o.implementation @@ -3645,7 +3664,7 @@ function D( ]), !1 ); - m = e === u; + m = e === c; } else m = !0; } } @@ -3669,5 +3688,5 @@ function D( } } } - return (D.errors = y), 0 === u; + return (D.errors = y), 0 === c; } diff --git a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.json b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.json index 3425c8b877a..76ba4091625 100644 --- a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.json +++ b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.json @@ -819,6 +819,10 @@ "provideExternalRuntime": { "type": "boolean" }, + "aliasConsumption": { + "description": "Enable alias-aware consuming via NormalModuleFactory.afterResolve (experimental)", + "type": "boolean" + }, "optimization": { "description": "Options related to build optimizations.", "type": "object", diff --git a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.ts b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.ts index 126cc6aea0f..42cfd8df560 100644 --- a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.ts +++ b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.ts @@ -817,6 +817,11 @@ export default { provideExternalRuntime: { type: 'boolean', }, + aliasConsumption: { + description: + 'Enable alias-aware consuming via NormalModuleFactory.afterResolve (experimental)', + type: 'boolean', + }, }, }, bridge: { diff --git a/packages/enhanced/src/schemas/sharing/ConsumeSharedPlugin.check.ts b/packages/enhanced/src/schemas/sharing/ConsumeSharedPlugin.check.ts index 20cf71cbbbd..4c633f06a39 100644 --- a/packages/enhanced/src/schemas/sharing/ConsumeSharedPlugin.check.ts +++ b/packages/enhanced/src/schemas/sharing/ConsumeSharedPlugin.check.ts @@ -761,15 +761,15 @@ function o( { const r = l; for (const r in e) - if ('allowNodeModulesSuffixMatch' !== r) + if ('aliasConsumption' !== r) return ( (o.errors = [{ params: { additionalProperty: r } }]), !1 ); if ( r === l && - void 0 !== e.allowNodeModulesSuffixMatch && - 'boolean' != typeof e.allowNodeModulesSuffixMatch + void 0 !== e.aliasConsumption && + 'boolean' != typeof e.aliasConsumption ) return (o.errors = [{ params: { type: 'boolean' } }]), !1; } diff --git a/packages/enhanced/src/schemas/sharing/ConsumeSharedPlugin.json b/packages/enhanced/src/schemas/sharing/ConsumeSharedPlugin.json index c900dfa2db8..0bea71d5f65 100644 --- a/packages/enhanced/src/schemas/sharing/ConsumeSharedPlugin.json +++ b/packages/enhanced/src/schemas/sharing/ConsumeSharedPlugin.json @@ -214,8 +214,8 @@ "type": "object", "additionalProperties": false, "properties": { - "allowNodeModulesSuffixMatch": { - "description": "Allow matching against path suffix after node_modules", + "aliasConsumption": { + "description": "Enable alias-aware consuming via NormalModuleFactory.afterResolve (experimental)", "type": "boolean" } } diff --git a/packages/enhanced/src/schemas/sharing/ConsumeSharedPlugin.ts b/packages/enhanced/src/schemas/sharing/ConsumeSharedPlugin.ts index aaefb40714f..31fbece58ac 100644 --- a/packages/enhanced/src/schemas/sharing/ConsumeSharedPlugin.ts +++ b/packages/enhanced/src/schemas/sharing/ConsumeSharedPlugin.ts @@ -238,8 +238,9 @@ export default { type: 'object', additionalProperties: false, properties: { - allowNodeModulesSuffixMatch: { - description: 'Allow matching against path suffix after node_modules', + aliasConsumption: { + description: + 'Enable alias-aware consuming via NormalModuleFactory.afterResolve (experimental)', type: 'boolean', }, }, diff --git a/packages/enhanced/src/schemas/sharing/ProvideSharedPlugin.check.ts b/packages/enhanced/src/schemas/sharing/ProvideSharedPlugin.check.ts index b271919200f..5bb614dd9a7 100644 --- a/packages/enhanced/src/schemas/sharing/ProvideSharedPlugin.check.ts +++ b/packages/enhanced/src/schemas/sharing/ProvideSharedPlugin.check.ts @@ -36,8 +36,8 @@ function t( { instancePath: n = '', parentData: o, - parentDataProperty: a, - rootData: i = s, + parentDataProperty: i, + rootData: a = s, } = {}, ) { let l = null, @@ -85,8 +85,8 @@ function t( const e = p, n = p; let o = !1; - const a = p; - if (p === a) + const i = p; + if (p === i) if ('string' == typeof r) { if (r.length < 1) { const r = { params: {} }; @@ -96,7 +96,7 @@ function t( const r = { params: { type: 'string' } }; null === l ? (l = [r]) : l.push(r), p++; } - var u = a === p; + var u = i === p; if (((o = o || u), !o)) { const e = p; if (p === e) @@ -138,8 +138,8 @@ function t( let e = s.requiredVersion; const n = p, o = p; - let a = !1; - const i = p; + let i = !1; + const a = p; if (!1 !== e) { const e = { params: { @@ -149,16 +149,16 @@ function t( }; null === l ? (l = [e]) : l.push(e), p++; } - var c = i === p; - if (((a = a || c), !a)) { + var c = a === p; + if (((i = i || c), !i)) { const r = p; if ('string' != typeof e) { const r = { params: { type: 'string' } }; null === l ? (l = [r]) : l.push(r), p++; } - (c = r === p), (a = a || c); + (c = r === p), (i = i || c); } - if (!a) { + if (!i) { const r = { params: {} }; return ( null === l ? (l = [r]) : l.push(r), @@ -221,8 +221,8 @@ function t( let e = s.version; const n = p, o = p; - let a = !1; - const i = p; + let i = !1; + const a = p; if (!1 !== e) { const e = { params: { @@ -232,16 +232,16 @@ function t( }; null === l ? (l = [e]) : l.push(e), p++; } - var y = i === p; - if (((a = a || y), !a)) { + var y = a === p; + if (((i = i || y), !i)) { const r = p; if ('string' != typeof e) { const r = { params: { type: 'string' } }; null === l ? (l = [r]) : l.push(r), p++; } - (y = r === p), (a = a || y); + (y = r === p), (i = i || y); } - if (!a) { + if (!i) { const r = { params: {} }; return ( null === l ? (l = [r]) : l.push(r), @@ -260,8 +260,8 @@ function t( const e = p, n = p, o = p; - let a = !1; - const i = p; + let i = !1; + const a = p; if ( r && 'object' == typeof r && @@ -273,8 +273,8 @@ function t( null === l ? (l = [r]) : l.push(r), p++; } } - var h = i === p; - if (((a = a || h), !a)) { + var g = a === p; + if (((i = i || g), !i)) { const e = p; if ( r && @@ -289,9 +289,9 @@ function t( null === l ? (l = [r]) : l.push(r), p++; } } - (h = e === p), (a = a || h); + (g = e === p), (i = i || g); } - if (!a) { + if (!i) { const r = { params: {} }; return ( null === l ? (l = [r]) : l.push(r), @@ -336,22 +336,22 @@ function t( const s = p, n = p; let o = !1; - const a = p; + const i = p; if ('string' != typeof e) { const r = { params: { type: 'string' }, }; null === l ? (l = [r]) : l.push(r), p++; } - var g = a === p; - if (((o = o || g), !o)) { + var h = i === p; + if (((o = o || h), !o)) { const r = p; if (!(e instanceof RegExp)) { const r = { params: {} }; null === l ? (l = [r]) : l.push(r), p++; } - (g = r === p), (o = o || g); + (h = r === p), (o = o || h); } if (!o) { const r = { params: {} }; @@ -405,8 +405,8 @@ function t( const e = p, n = p, o = p; - let a = !1; - const i = p; + let i = !1; + const a = p; if ( r && 'object' == typeof r && @@ -420,8 +420,8 @@ function t( null === l ? (l = [r]) : l.push(r), p++; } } - var d = i === p; - if (((a = a || d), !a)) { + var d = a === p; + if (((i = i || d), !i)) { const e = p; if ( r && @@ -439,9 +439,9 @@ function t( null === l ? (l = [r]) : l.push(r), p++; } } - (d = e === p), (a = a || d); + (d = e === p), (i = i || d); } - if (!a) { + if (!i) { const r = { params: {} }; return ( null === l ? (l = [r]) : l.push(r), @@ -489,7 +489,7 @@ function t( const s = p, n = p; let o = !1; - const a = p; + const i = p; if ('string' != typeof e) { const r = { params: { type: 'string' }, @@ -497,7 +497,7 @@ function t( null === l ? (l = [r]) : l.push(r), p++; } - var v = a === p; + var v = i === p; if (((o = o || v), !o)) { const r = p; if (!(e instanceof RegExp)) { @@ -593,10 +593,10 @@ function s( instancePath: e = '', parentData: n, parentDataProperty: o, - rootData: a = r, + rootData: i = r, } = {}, ) { - let i = null, + let a = null, l = 0; if (0 === l) { if (!r || 'object' != typeof r || Array.isArray(r)) @@ -611,8 +611,8 @@ function s( instancePath: e + '/' + n.replace(/~/g, '~0').replace(/\//g, '~1'), parentData: r, parentDataProperty: n, - rootData: a, - }) || ((i = null === i ? t.errors : i.concat(t.errors)), (l = i.length)); + rootData: i, + }) || ((a = null === a ? t.errors : a.concat(t.errors)), (l = a.length)); var p = y === l; if (((c = c || p), !c)) { const r = l; @@ -620,23 +620,23 @@ function s( if ('string' == typeof o) { if (o.length < 1) { const r = { params: {} }; - null === i ? (i = [r]) : i.push(r), l++; + null === a ? (a = [r]) : a.push(r), l++; } } else { const r = { params: { type: 'string' } }; - null === i ? (i = [r]) : i.push(r), l++; + null === a ? (a = [r]) : a.push(r), l++; } (p = r === l), (c = c || p); } if (!c) { const r = { params: {} }; - return null === i ? (i = [r]) : i.push(r), l++, (s.errors = i), !1; + return null === a ? (a = [r]) : a.push(r), l++, (s.errors = a), !1; } - if (((l = u), null !== i && (u ? (i.length = u) : (i = null)), f !== l)) + if (((l = u), null !== a && (u ? (a.length = u) : (a = null)), f !== l)) break; } } - return (s.errors = i), 0 === l; + return (s.errors = a), 0 === l; } function n( r, @@ -644,10 +644,10 @@ function n( instancePath: e = '', parentData: t, parentDataProperty: o, - rootData: a = r, + rootData: i = r, } = {}, ) { - let i = null, + let a = null, l = 0; const p = l; let f = !1; @@ -665,11 +665,11 @@ function n( if ('string' == typeof t) { if (t.length < 1) { const r = { params: {} }; - null === i ? (i = [r]) : i.push(r), l++; + null === a ? (a = [r]) : a.push(r), l++; } } else { const r = { params: { type: 'string' } }; - null === i ? (i = [r]) : i.push(r), l++; + null === a ? (a = [r]) : a.push(r), l++; } var c = u === l; if (((f = f || c), !f)) { @@ -678,22 +678,22 @@ function n( instancePath: e + '/' + n, parentData: r, parentDataProperty: n, - rootData: a, + rootData: i, }) || - ((i = null === i ? s.errors : i.concat(s.errors)), (l = i.length)), + ((a = null === a ? s.errors : a.concat(s.errors)), (l = a.length)), (c = o === l), (f = f || c); } - if (f) (l = p), null !== i && (p ? (i.length = p) : (i = null)); + if (f) (l = p), null !== a && (p ? (a.length = p) : (a = null)); else { const r = { params: {} }; - null === i ? (i = [r]) : i.push(r), l++; + null === a ? (a = [r]) : a.push(r), l++; } if (o !== l) break; } } else { const r = { params: { type: 'array' } }; - null === i ? (i = [r]) : i.push(r), l++; + null === a ? (a = [r]) : a.push(r), l++; } var y = u === l; if (((f = f || y), !f)) { @@ -702,19 +702,19 @@ function n( instancePath: e, parentData: t, parentDataProperty: o, - rootData: a, - }) || ((i = null === i ? s.errors : i.concat(s.errors)), (l = i.length)), + rootData: i, + }) || ((a = null === a ? s.errors : a.concat(s.errors)), (l = a.length)), (y = n === l), (f = f || y); } if (!f) { const r = { params: {} }; - return null === i ? (i = [r]) : i.push(r), l++, (n.errors = i), !1; + return null === a ? (a = [r]) : a.push(r), l++, (n.errors = a), !1; } return ( (l = p), - null !== i && (p ? (i.length = p) : (i = null)), - (n.errors = i), + null !== a && (p ? (a.length = p) : (a = null)), + (n.errors = a), 0 === l ); } @@ -724,10 +724,10 @@ function o( instancePath: e = '', parentData: t, parentDataProperty: s, - rootData: a = r, + rootData: i = r, } = {}, ) { - let i = null, + let a = null, l = 0; if (0 === l) { if (!r || 'object' != typeof r || Array.isArray(r)) @@ -748,10 +748,10 @@ function o( instancePath: e + '/provides', parentData: r, parentDataProperty: 'provides', - rootData: a, + rootData: i, }) || - ((i = null === i ? n.errors : i.concat(n.errors)), - (l = i.length)); + ((a = null === a ? n.errors : a.concat(n.errors)), + (l = a.length)); var p = t === l; } else p = !0; if (p) { @@ -760,18 +760,18 @@ function o( const t = l, s = l; let n = !1; - const a = l; - if (l === a) + const i = l; + if (l === i) if ('string' == typeof e) { if (e.length < 1) { const r = { params: {} }; - null === i ? (i = [r]) : i.push(r), l++; + null === a ? (a = [r]) : a.push(r), l++; } } else { const r = { params: { type: 'string' } }; - null === i ? (i = [r]) : i.push(r), l++; + null === a ? (a = [r]) : a.push(r), l++; } - var f = a === l; + var f = i === l; if (((n = n || f), !n)) { const r = l; if (l === r) @@ -784,28 +784,28 @@ function o( if ('string' == typeof r) { if (r.length < 1) { const r = { params: {} }; - null === i ? (i = [r]) : i.push(r), l++; + null === a ? (a = [r]) : a.push(r), l++; } } else { const r = { params: { type: 'string' } }; - null === i ? (i = [r]) : i.push(r), l++; + null === a ? (a = [r]) : a.push(r), l++; } if (s !== l) break; } } else { const r = { params: { type: 'array' } }; - null === i ? (i = [r]) : i.push(r), l++; + null === a ? (a = [r]) : a.push(r), l++; } (f = r === l), (n = n || f); } if (!n) { const r = { params: {} }; return ( - null === i ? (i = [r]) : i.push(r), l++, (o.errors = i), !1 + null === a ? (a = [r]) : a.push(r), l++, (o.errors = a), !1 ); } (l = s), - null !== i && (s ? (i.length = s) : (i = null)), + null !== a && (s ? (a.length = s) : (a = null)), (p = t === l); } else p = !0; if (p) @@ -815,21 +815,10 @@ function o( if (l === t) { if (!e || 'object' != typeof e || Array.isArray(e)) return (o.errors = [{ params: { type: 'object' } }]), !1; - { - const r = l; - for (const r in e) - if ('allowNodeModulesSuffixMatch' !== r) - return ( - (o.errors = [{ params: { additionalProperty: r } }]), - !1 - ); - if ( - r === l && - void 0 !== e.allowNodeModulesSuffixMatch && - 'boolean' != typeof e.allowNodeModulesSuffixMatch - ) - return (o.errors = [{ params: { type: 'boolean' } }]), !1; - } + for (const r in e) + return ( + (o.errors = [{ params: { additionalProperty: r } }]), !1 + ); } p = t === l; } else p = !0; @@ -838,5 +827,5 @@ function o( } } } - return (o.errors = i), 0 === l; + return (o.errors = a), 0 === l; } diff --git a/packages/enhanced/src/schemas/sharing/ProvideSharedPlugin.json b/packages/enhanced/src/schemas/sharing/ProvideSharedPlugin.json index d477b399789..afe9399a24f 100644 --- a/packages/enhanced/src/schemas/sharing/ProvideSharedPlugin.json +++ b/packages/enhanced/src/schemas/sharing/ProvideSharedPlugin.json @@ -197,12 +197,7 @@ "description": "Experimental features configuration", "type": "object", "additionalProperties": false, - "properties": { - "allowNodeModulesSuffixMatch": { - "description": "Allow matching against path suffix after node_modules", - "type": "boolean" - } - } + "properties": {} } }, "required": ["provides"] diff --git a/packages/enhanced/src/schemas/sharing/ProvideSharedPlugin.ts b/packages/enhanced/src/schemas/sharing/ProvideSharedPlugin.ts index 6aac7185a9d..fe0b0f9ae81 100644 --- a/packages/enhanced/src/schemas/sharing/ProvideSharedPlugin.ts +++ b/packages/enhanced/src/schemas/sharing/ProvideSharedPlugin.ts @@ -230,12 +230,7 @@ export default { description: 'Experimental features configuration', type: 'object', additionalProperties: false, - properties: { - allowNodeModulesSuffixMatch: { - description: 'Allow matching against path suffix after node_modules', - type: 'boolean', - }, - }, + properties: {}, }, }, required: ['provides'], diff --git a/packages/enhanced/src/schemas/sharing/SharePlugin.check.ts b/packages/enhanced/src/schemas/sharing/SharePlugin.check.ts index 11c9a20a6c8..bb615f26d9a 100644 --- a/packages/enhanced/src/schemas/sharing/SharePlugin.check.ts +++ b/packages/enhanced/src/schemas/sharing/SharePlugin.check.ts @@ -189,8 +189,8 @@ function s( null === p ? (p = [r]) : p.push(r), f++; } } - var h = l === f; - if (((i = i || h), !i)) { + var m = l === f; + if (((i = i || m), !i)) { const e = f; if (r && 'object' == typeof r && !Array.isArray(r)) { let e; @@ -199,7 +199,7 @@ function s( null === p ? (p = [r]) : p.push(r), f++; } } - if (((h = e === f), (i = i || h), !i)) { + if (((m = e === f), (i = i || m), !i)) { const e = f; if (r && 'object' == typeof r && !Array.isArray(r)) { let e; @@ -211,7 +211,7 @@ function s( null === p ? (p = [r]) : p.push(r), f++; } } - (h = e === f), (i = i || h); + (m = e === f), (i = i || m); } } if (!i) { @@ -293,8 +293,8 @@ function s( }; null === p ? (p = [e]) : p.push(e), f++; } - var m = i === f; - if (((a = a || m), !a)) { + var h = i === f; + if (((a = a || h), !a)) { const r = f; if (f == f) if ('string' == typeof e) { @@ -306,7 +306,7 @@ function s( const r = { params: { type: 'string' } }; null === p ? (p = [r]) : p.push(r), f++; } - (m = r === f), (a = a || m); + (h = r === f), (a = a || h); } if (!a) { const r = { params: {} }; @@ -826,7 +826,7 @@ function a( { const r = l; for (const r in e) - if ('allowNodeModulesSuffixMatch' !== r) + if ('aliasConsumption' !== r) return ( (a.errors = [ { params: { additionalProperty: r } }, @@ -835,8 +835,8 @@ function a( ); if ( r === l && - void 0 !== e.allowNodeModulesSuffixMatch && - 'boolean' != typeof e.allowNodeModulesSuffixMatch + void 0 !== e.aliasConsumption && + 'boolean' != typeof e.aliasConsumption ) return ( (a.errors = [{ params: { type: 'boolean' } }]), !1 diff --git a/packages/enhanced/src/schemas/sharing/SharePlugin.json b/packages/enhanced/src/schemas/sharing/SharePlugin.json index 19ee9f1f49e..38782331dc1 100644 --- a/packages/enhanced/src/schemas/sharing/SharePlugin.json +++ b/packages/enhanced/src/schemas/sharing/SharePlugin.json @@ -228,8 +228,8 @@ "type": "object", "additionalProperties": false, "properties": { - "allowNodeModulesSuffixMatch": { - "description": "Allow matching against path suffix after node_modules", + "aliasConsumption": { + "description": "Enable alias-aware consuming via NormalModuleFactory.afterResolve (experimental)", "type": "boolean" } } diff --git a/packages/enhanced/src/schemas/sharing/SharePlugin.ts b/packages/enhanced/src/schemas/sharing/SharePlugin.ts index f7f44d6a6a7..347b9d41ce4 100644 --- a/packages/enhanced/src/schemas/sharing/SharePlugin.ts +++ b/packages/enhanced/src/schemas/sharing/SharePlugin.ts @@ -263,8 +263,9 @@ export default { type: 'object', additionalProperties: false, properties: { - allowNodeModulesSuffixMatch: { - description: 'Allow matching against path suffix after node_modules', + aliasConsumption: { + description: + 'Enable alias-aware consuming via NormalModuleFactory.afterResolve (experimental)', type: 'boolean', }, }, diff --git a/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/index.js b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/index.js new file mode 100644 index 00000000000..8ddf18e3978 --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/index.js @@ -0,0 +1,9 @@ +it('unifies React/DOM/JSX via pages-dir aliases with full federation', () => { + // Important: use a dynamic import to create an async boundary so + // federation runtime initializes before we touch shared consumes. + return import('./suite').then(({ run }) => run()); +}); + +module.exports = { + testName: 'next-pages-layer-unify', +}; diff --git a/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/next/dist/compiled/react-dom/index.js b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/next/dist/compiled/react-dom/index.js new file mode 100644 index 00000000000..19be52f545e --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/next/dist/compiled/react-dom/index.js @@ -0,0 +1,4 @@ +const stub = { id: 'compiled-react-dom', marker: 'compiled-react-dom' }; +stub.__esModule = true; +stub.default = stub; +module.exports = stub; diff --git a/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/next/dist/compiled/react.js b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/next/dist/compiled/react.js new file mode 100644 index 00000000000..5fdc8ffe819 --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/next/dist/compiled/react.js @@ -0,0 +1,4 @@ +const stub = { id: 'compiled-react', marker: 'compiled-react', jsx: 'compiled-jsx' }; +stub.__esModule = true; +stub.default = stub; +module.exports = stub; diff --git a/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/next/dist/compiled/react/index.js b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/next/dist/compiled/react/index.js new file mode 100644 index 00000000000..5fdc8ffe819 --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/next/dist/compiled/react/index.js @@ -0,0 +1,4 @@ +const stub = { id: 'compiled-react', marker: 'compiled-react', jsx: 'compiled-jsx' }; +stub.__esModule = true; +stub.default = stub; +module.exports = stub; diff --git a/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/next/dist/compiled/react/jsx-runtime.js b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/next/dist/compiled/react/jsx-runtime.js new file mode 100644 index 00000000000..5fdc8ffe819 --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/next/dist/compiled/react/jsx-runtime.js @@ -0,0 +1,4 @@ +const stub = { id: 'compiled-react', marker: 'compiled-react', jsx: 'compiled-jsx' }; +stub.__esModule = true; +stub.default = stub; +module.exports = stub; diff --git a/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/next/dist/compiled/react/jsx-runtime/index.js b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/next/dist/compiled/react/jsx-runtime/index.js new file mode 100644 index 00000000000..5fdc8ffe819 --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/next/dist/compiled/react/jsx-runtime/index.js @@ -0,0 +1,4 @@ +const stub = { id: 'compiled-react', marker: 'compiled-react', jsx: 'compiled-jsx' }; +stub.__esModule = true; +stub.default = stub; +module.exports = stub; diff --git a/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/next/package.json b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/next/package.json new file mode 100644 index 00000000000..cc4138805ee --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/next/package.json @@ -0,0 +1,5 @@ +{ + "name": "next", + "version": "13.4.0", + "description": "Next.js compiled stubs for layer alias consumption tests" +} diff --git a/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/react-dom/index.js b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/react-dom/index.js new file mode 100644 index 00000000000..8db9b92f615 --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/react-dom/index.js @@ -0,0 +1,7 @@ +// Regular ReactDOM stub that should be replaced by the compiled Next build via aliasing +module.exports = { + name: 'regular-react-dom', + version: '18.0.0', + source: 'node_modules/react-dom', + marker: 'regular-react-dom', +}; diff --git a/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/react-dom/package.json b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/react-dom/package.json new file mode 100644 index 00000000000..be018f4bc0f --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/react-dom/package.json @@ -0,0 +1,5 @@ +{ + "name": "react-dom", + "version": "18.0.0", + "description": "Regular ReactDOM stub used to validate alias layer consumption" +} diff --git a/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/react/index.js b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/react/index.js new file mode 100644 index 00000000000..76e2854d581 --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/react/index.js @@ -0,0 +1,8 @@ +// Regular React stub that should be replaced by the compiled Next build via aliasing +module.exports = { + name: 'regular-react', + version: '18.0.0', + source: 'node_modules/react', + marker: 'regular-react', + jsx: 'WRONG-regular-react-jsx', +}; diff --git a/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/react/jsx-runtime/index.js b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/react/jsx-runtime/index.js new file mode 100644 index 00000000000..7eda6474db4 --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/react/jsx-runtime/index.js @@ -0,0 +1,5 @@ +// Regular JSX runtime stub that should not be used when aliasing layers is active +module.exports = { + source: 'node_modules/react/jsx-runtime', + jsx: 'WRONG-regular-react-jsx', +}; diff --git a/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/react/package.json b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/react/package.json new file mode 100644 index 00000000000..a6c1cf5f750 --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/node_modules/react/package.json @@ -0,0 +1,5 @@ +{ + "name": "react", + "version": "18.0.0", + "description": "Regular React stub used to validate alias layer consumption" +} diff --git a/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/package.json b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/package.json new file mode 100644 index 00000000000..f9da69b8854 --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/package.json @@ -0,0 +1,4 @@ +{ + "name": "next-pages-layer-unify", + "version": "1.0.0" +} diff --git a/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/suite.js b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/suite.js new file mode 100644 index 00000000000..9de0a2d1db3 --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/suite.js @@ -0,0 +1,32 @@ +export async function run() { + // Require ids unify to the shared targets + const reactId = require.resolve('react'); + const reactTargetId = require.resolve('next/dist/compiled/react'); + expect(reactId).toBe(reactTargetId); + expect(reactId).toMatch(/webpack\/sharing/); + + const domId = require.resolve('react-dom'); + const domTargetId = require.resolve('next/dist/compiled/react-dom'); + expect(domId).toBe(domTargetId); + expect(domId).toMatch(/webpack\/sharing/); + + const jsxId = require.resolve('react/jsx-runtime'); + const jsxTargetId = require.resolve('next/dist/compiled/react/jsx-runtime'); + expect(jsxId).toBe(jsxTargetId); + + // Imports resolve to compiled Next stubs and are identical via alias or direct + const React = await import('react'); + const ReactDirect = await import('next/dist/compiled/react'); + expect(React.id).toBe('compiled-react'); + expect(React).toEqual(ReactDirect); + + const ReactDOM = await import('react-dom'); + const ReactDOMDirect = await import('next/dist/compiled/react-dom'); + expect(ReactDOM.id).toBe('compiled-react-dom'); + expect(ReactDOM).toEqual(ReactDOMDirect); + + const jsx = await import('react/jsx-runtime'); + const jsxDirect = await import('next/dist/compiled/react/jsx-runtime'); + expect(jsx.jsx).toBe('compiled-jsx'); + expect(jsx).toEqual(jsxDirect); +} diff --git a/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/webpack.config.js b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/webpack.config.js new file mode 100644 index 00000000000..2f6159706c6 --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/next-pages-layer-unify/webpack.config.js @@ -0,0 +1,52 @@ +const { ModuleFederationPlugin } = require('../../../../dist/src'); +const path = require('path'); + +module.exports = { + mode: 'development', + devtool: false, + experiments: { + layers: true, + }, + module: { + rules: [ + { + test: /\.(js|jsx)$/, + include: __dirname, + layer: 'pages-dir-browser', + }, + ], + }, + resolve: { + alias: { + react: path.resolve(__dirname, 'node_modules/next/dist/compiled/react'), + 'react-dom': path.resolve( + __dirname, + 'node_modules/next/dist/compiled/react-dom', + ), + 'react/jsx-runtime': path.resolve( + __dirname, + 'node_modules/next/dist/compiled/react/jsx-runtime.js', + ), + }, + }, + plugins: [ + new ModuleFederationPlugin({ + name: 'next-pages-layer-unify', + experiments: { asyncStartup: false, aliasConsumption: true }, + shared: { + 'next/dist/compiled/react': { + singleton: true, + eager: true, + requiredVersion: false, + allowNodeModulesSuffixMatch: true, + }, + 'next/dist/compiled/react-dom': { + singleton: true, + eager: true, + requiredVersion: false, + allowNodeModulesSuffixMatch: true, + }, + }, + }), + ], +}; diff --git a/packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/errors.js b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/errors.js new file mode 100644 index 00000000000..975da187de9 --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/errors.js @@ -0,0 +1,2 @@ +// No build errors expected +module.exports = []; diff --git a/packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/index.js b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/index.js new file mode 100644 index 00000000000..a5e016dfc73 --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/index.js @@ -0,0 +1,12 @@ +it('should warn when singleton is combined with include.version for alias-resolved share', async () => { + const viaAlias = await import('react-allowed'); + const direct = await import('next/dist/compiled/react-allowed'); + + // Shared identity should match direct + expect(viaAlias.name).toBe(direct.name); + expect(viaAlias.source).toBe(direct.source); +}); + +module.exports = { + testName: 'share-with-aliases-filters-singleton', +}; diff --git a/packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/node_modules/next/dist/compiled/react-allowed.js b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/node_modules/next/dist/compiled/react-allowed.js new file mode 100644 index 00000000000..1886ba6df52 --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/node_modules/next/dist/compiled/react-allowed.js @@ -0,0 +1,9 @@ +module.exports = { + name: 'compiled-react-allowed', + version: '18.2.0', + source: 'node_modules/next/dist/compiled/react-allowed', + createElement: function () { + return 'SHARED-compiled-react-allowed-element'; + }, +}; + diff --git a/packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/node_modules/next/package.json b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/node_modules/next/package.json new file mode 100644 index 00000000000..7a757311cd6 --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/node_modules/next/package.json @@ -0,0 +1,5 @@ +{ + "name": "next", + "version": "18.2.0" +} + diff --git a/packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/package.json b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/package.json new file mode 100644 index 00000000000..a1b19fe5746 --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/package.json @@ -0,0 +1,4 @@ +{ + "name": "test-share-with-aliases-filters-singleton", + "version": "1.0.0" +} diff --git a/packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/warnings.js b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/warnings.js new file mode 100644 index 00000000000..16abf0a96c7 --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/warnings.js @@ -0,0 +1,20 @@ +// Expect singleton + include.version warning +module.exports = [ + // ProvideSharedPlugin warnings (emitted twice: provide and finalize) + { + file: /shared module next\/dist\/compiled\/react-allowed .*->.*react-allowed\.js/, + message: + /\"singleton: true\" is used together with \"include\.version: \"\^18\.0\.0\"\"/, + }, + { + file: /shared module next\/dist\/compiled\/react-allowed .*->.*react-allowed\.js/, + message: + /\"singleton: true\" is used together with \"include\.version: \"\^18\.0\.0\"\"/, + }, + // ConsumeSharedPlugin warning (moduleRequest is absolute resource path) + { + file: /shared module .*react-allowed\.js .*->.*react-allowed\.js/, + message: + /\"singleton: true\" is used together with \"include\.version: \"\^18\.0\.0\"\"/, + }, +]; diff --git a/packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/webpack.config.js b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/webpack.config.js new file mode 100644 index 00000000000..ccb4a5944fc --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters-singleton/webpack.config.js @@ -0,0 +1,30 @@ +const { ModuleFederationPlugin } = require('../../../../dist/src'); +const path = require('path'); + +module.exports = { + mode: 'development', + devtool: false, + resolve: { + alias: { + 'react-allowed': path.resolve( + __dirname, + 'node_modules/next/dist/compiled/react-allowed.js', + ), + }, + }, + plugins: [ + new ModuleFederationPlugin({ + name: 'share-with-aliases-filters-singleton', + experiments: { asyncStartup: false, aliasConsumption: true }, + shared: { + // Include + singleton: expect singleton+filter warning + 'next/dist/compiled/react-allowed': { + import: 'next/dist/compiled/react-allowed', + requiredVersion: false, + singleton: true, + include: { version: '^18.0.0' }, + }, + }, + }), + ], +}; diff --git a/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/errors.js b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/errors.js new file mode 100644 index 00000000000..91c551b1ad8 --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/errors.js @@ -0,0 +1,2 @@ +// No build errors expected for this case +module.exports = []; diff --git a/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/index.js b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/index.js new file mode 100644 index 00000000000..6b48632c3af --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/index.js @@ -0,0 +1,27 @@ +it('should load direct compiled stub for aliased react when excluded by version filter', async () => { + const mod = await import('react'); + // Validate we loaded the direct compiled stub (not the shared instance) + expect(mod.name).toBe('compiled-react'); + expect(mod.source).toBe('node_modules/next/dist/compiled/react'); + expect(mod.createElement()).toBe('DIRECT-compiled-react-element'); +}); + +it('should share aliased react-allowed when included by version filter', async () => { + const viaAlias = await import('react-allowed'); + const direct = await import('next/dist/compiled/react-allowed'); + + // Identity and behavior checks + expect(viaAlias.name).toBe('compiled-react-allowed'); + expect(viaAlias.source).toBe('node_modules/next/dist/compiled/react-allowed'); + expect(viaAlias.createElement()).toBe( + 'SHARED-compiled-react-allowed-element', + ); + + // Identity should match direct import as well + expect(viaAlias.name).toBe(direct.name); + expect(viaAlias.source).toBe(direct.source); +}); + +module.exports = { + testName: 'share-with-aliases-filters', +}; diff --git a/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/node_modules/next/dist/compiled/react-allowed.js b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/node_modules/next/dist/compiled/react-allowed.js new file mode 100644 index 00000000000..09777b9ef8e --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/node_modules/next/dist/compiled/react-allowed.js @@ -0,0 +1,10 @@ +// Compiled React stub (included by version filter; should be shared) +module.exports = { + name: 'compiled-react-allowed', + version: '18.2.0', + source: 'node_modules/next/dist/compiled/react-allowed', + createElement: function () { + return 'SHARED-compiled-react-allowed-element'; + }, +}; + diff --git a/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/node_modules/next/dist/compiled/react.js b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/node_modules/next/dist/compiled/react.js new file mode 100644 index 00000000000..57d2640d429 --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/node_modules/next/dist/compiled/react.js @@ -0,0 +1,10 @@ +// Compiled React stub (excluded by version filter; should load directly) +module.exports = { + name: 'compiled-react', + version: '18.2.0', + source: 'node_modules/next/dist/compiled/react', + createElement: function () { + return 'DIRECT-compiled-react-element'; + }, +}; + diff --git a/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/node_modules/next/package.json b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/node_modules/next/package.json new file mode 100644 index 00000000000..14359d441a4 --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/node_modules/next/package.json @@ -0,0 +1,6 @@ +{ + "name": "next", + "version": "18.2.0", + "description": "Stub Next.js package to host compiled React entries" +} + diff --git a/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/node_modules/react/package.json b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/node_modules/react/package.json new file mode 100644 index 00000000000..510b7028d97 --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/node_modules/react/package.json @@ -0,0 +1,7 @@ +{ + "name": "react", + "version": "18.2.0", + "description": "Regular React package (not used directly when alias is applied)", + "main": "index.js" +} + diff --git a/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/package.json b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/package.json new file mode 100644 index 00000000000..cd355b703ed --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/package.json @@ -0,0 +1,7 @@ +{ + "name": "test-share-with-aliases-filters", + "version": "1.0.0", + "dependencies": { + "react": "18.2.0" + } +} diff --git a/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/warnings.js b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/warnings.js new file mode 100644 index 00000000000..68c16feaa0c --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/warnings.js @@ -0,0 +1,2 @@ +// Expected warnings for aliasConsumption + include/exclude filters scenario +module.exports = []; diff --git a/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/webpack.config.js b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/webpack.config.js new file mode 100644 index 00000000000..ecce6aa68f1 --- /dev/null +++ b/packages/enhanced/test/configCases/sharing/share-with-aliases-filters/webpack.config.js @@ -0,0 +1,40 @@ +const { ModuleFederationPlugin } = require('../../../../dist/src'); +const path = require('path'); + +module.exports = { + mode: 'development', + devtool: false, + resolve: { + alias: { + // Alias bare imports to compiled targets (simulating Next.js-style aliases) + react: path.resolve( + __dirname, + 'node_modules/next/dist/compiled/react.js', + ), + 'react-allowed': path.resolve( + __dirname, + 'node_modules/next/dist/compiled/react-allowed.js', + ), + }, + }, + plugins: [ + new ModuleFederationPlugin({ + name: 'share-with-aliases-filters', + experiments: { asyncStartup: false, aliasConsumption: true }, + shared: { + // Exclude 18.x: alias 'react' -> should load fallback (direct compiled stub) via import + 'next/dist/compiled/react': { + import: 'next/dist/compiled/react', + requiredVersion: false, + exclude: { version: '^18.0.0' }, + }, + // Include 18.x: alias 'react-allowed' -> should be shared + 'next/dist/compiled/react-allowed': { + import: 'next/dist/compiled/react-allowed', + requiredVersion: false, + include: { version: '^18.0.0' }, + }, + }, + }), + ], +}; diff --git a/packages/enhanced/test/configCases/sharing/share-with-aliases-provide-only/webpack.config.js b/packages/enhanced/test/configCases/sharing/share-with-aliases-provide-only/webpack.config.js index 3ce464a549e..161be8e5364 100644 --- a/packages/enhanced/test/configCases/sharing/share-with-aliases-provide-only/webpack.config.js +++ b/packages/enhanced/test/configCases/sharing/share-with-aliases-provide-only/webpack.config.js @@ -16,6 +16,7 @@ module.exports = { experiments: { // Force sync startup for test harness to pick up exported tests asyncStartup: false, + aliasConsumption: true, }, shared: { // Only provide the aliased target; do not share 'react' by name diff --git a/packages/enhanced/test/configCases/sharing/share-with-aliases/webpack.config.js b/packages/enhanced/test/configCases/sharing/share-with-aliases/webpack.config.js index 05af2df285f..ab36c4c6379 100644 --- a/packages/enhanced/test/configCases/sharing/share-with-aliases/webpack.config.js +++ b/packages/enhanced/test/configCases/sharing/share-with-aliases/webpack.config.js @@ -35,6 +35,7 @@ module.exports = { experiments: { // Force sync startup for test harness to pick up exported tests asyncStartup: false, + aliasConsumption: true, }, shared: { // CRITICAL: Only share the aliased/vendor versions diff --git a/packages/enhanced/test/unit/sharing/ConsumeSharedPlugin/ConsumeSharedPlugin.alias-consumption-filters.test.ts b/packages/enhanced/test/unit/sharing/ConsumeSharedPlugin/ConsumeSharedPlugin.alias-consumption-filters.test.ts new file mode 100644 index 00000000000..5d91e00556d --- /dev/null +++ b/packages/enhanced/test/unit/sharing/ConsumeSharedPlugin/ConsumeSharedPlugin.alias-consumption-filters.test.ts @@ -0,0 +1,125 @@ +/* + * @jest-environment node + */ + +import { + ConsumeSharedPlugin, + mockGetDescriptionFile, + resetAllMocks, +} from './shared-test-utils'; + +describe('ConsumeSharedPlugin alias consumption - version filters', () => { + let plugin: ConsumeSharedPlugin; + let mockCompilation: any; + let mockResolver: any; + + beforeEach(() => { + resetAllMocks(); + + plugin = new ConsumeSharedPlugin({ + shareScope: 'default', + consumes: { + 'next/dist/compiled/react': { + import: 'next/dist/compiled/react', + requiredVersion: false, + // filters will be set per-test + }, + }, + }); + + mockResolver = { + resolve: jest.fn(), + }; + + mockCompilation = { + inputFileSystem: {}, + resolverFactory: { + get: jest.fn(() => mockResolver), + }, + warnings: [], + errors: [], + contextDependencies: { addAll: jest.fn() }, + fileDependencies: { addAll: jest.fn() }, + missingDependencies: { addAll: jest.fn() }, + compiler: { + context: '/test/context', + }, + }; + }); + + it('excludes alias-resolved module when exclude.version matches (deep path request)', async () => { + const config: any = { + import: 'next/dist/compiled/react', + shareScope: 'default', + shareKey: 'next/dist/compiled/react', + requiredVersion: false, + strictVersion: false, + singleton: false, + eager: false, + issuerLayer: undefined, + layer: undefined, + request: 'next/dist/compiled/react', + exclude: { version: '^18.0.0' }, + }; + + // Simulate resolved import path to compiled target (alias path) + const importResolved = '/abs/node_modules/next/dist/compiled/react.js'; + mockResolver.resolve.mockImplementation((_c, _start, _req, _ctx, cb) => + cb(null, importResolved), + ); + + // Package.json belongs to "next" with version 18.2.0 + mockGetDescriptionFile.mockImplementation((_fs, _dir, _files, cb) => { + cb(null, { + data: { name: 'next', version: '18.2.0' }, + path: '/abs/node_modules/next/package.json', + }); + }); + + const result = await plugin.createConsumeSharedModule( + mockCompilation, + '/test/context', + importResolved, // alias consumption passes resolved resource as request + config, + ); + + expect(result).toBeUndefined(); + }); + + it('includes alias-resolved module when include.version matches (deep path request)', async () => { + const config: any = { + import: 'next/dist/compiled/react', + shareScope: 'default', + shareKey: 'next/dist/compiled/react', + requiredVersion: false, + strictVersion: false, + singleton: false, + eager: false, + issuerLayer: undefined, + layer: undefined, + request: 'next/dist/compiled/react', + include: { version: '^18.0.0' }, + }; + + const importResolved = '/abs/node_modules/next/dist/compiled/react.js'; + mockResolver.resolve.mockImplementation((_c, _start, _req, _ctx, cb) => + cb(null, importResolved), + ); + + mockGetDescriptionFile.mockImplementation((_fs, _dir, _files, cb) => { + cb(null, { + data: { name: 'next', version: '18.2.0' }, + path: '/abs/node_modules/next/package.json', + }); + }); + + const result = await plugin.createConsumeSharedModule( + mockCompilation, + '/test/context', + importResolved, + config, + ); + + expect(result).toBeDefined(); + }); +}); diff --git a/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts b/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts index de865cfcb63..89c47716e7e 100644 --- a/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts +++ b/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts @@ -257,6 +257,11 @@ export interface ModuleFederationPluginOptions { externalRuntime?: boolean; provideExternalRuntime?: boolean; asyncStartup?: boolean; + /** + * Enable alias-aware consuming via NormalModuleFactory.afterResolve. + * Defaults to false while experimental. + */ + aliasConsumption?: boolean; /** * Options related to build optimizations. */ diff --git a/tools/scripts/run-manifest-e2e.mjs b/tools/scripts/run-manifest-e2e.mjs new file mode 100644 index 00000000000..71d3f7509eb --- /dev/null +++ b/tools/scripts/run-manifest-e2e.mjs @@ -0,0 +1,353 @@ +#!/usr/bin/env node +import { spawn } from 'node:child_process'; + +const SUPPORTS_PROCESS_GROUP_SIGNALS = + process.platform !== 'win32' && process.platform !== 'cygwin'; + +const MANIFEST_WAIT_TARGETS = [ + 'tcp:3009', + 'tcp:3012', + 'http://127.0.0.1:4001/', +]; + +const KILL_PORT_ARGS = [ + 'npx', + 'kill-port', + '3013', + '3009', + '3010', + '3011', + '3012', + '4001', +]; + +const SCENARIOS = { + dev: { + label: 'manifest development', + serveCmd: ['pnpm', 'run', 'app:manifest:dev'], + e2eCmd: [ + 'npx', + 'nx', + 'run-many', + '--target=e2e', + '--projects=manifest-webpack-host', + '--parallel=2', + ], + waitTargets: MANIFEST_WAIT_TARGETS, + }, + prod: { + label: 'manifest production', + serveCmd: ['pnpm', 'run', 'app:manifest:prod'], + e2eCmd: [ + 'npx', + 'nx', + 'run-many', + '--target=e2e', + '--projects=manifest-webpack-host', + '--parallel=1', + ], + waitTargets: MANIFEST_WAIT_TARGETS, + }, +}; + +const VALID_MODES = new Set(['dev', 'prod', 'all']); + +async function main() { + const modeArg = process.argv.find((arg) => arg.startsWith('--mode=')); + const mode = modeArg ? modeArg.split('=')[1] : 'all'; + + if (!VALID_MODES.has(mode)) { + console.error( + `Unknown mode "${mode}". Expected one of ${Array.from(VALID_MODES).join(', ')}`, + ); + process.exitCode = 1; + return; + } + + const targets = mode === 'all' ? ['dev', 'prod'] : [mode]; + + for (const target of targets) { + await runScenario(target); + } +} + +async function runScenario(name) { + const scenario = SCENARIOS[name]; + if (!scenario) { + throw new Error(`Unknown scenario: ${name}`); + } + + console.log(`\n[manifest-e2e] Starting ${scenario.label}`); + + const serve = spawn(scenario.serveCmd[0], scenario.serveCmd.slice(1), { + stdio: 'inherit', + detached: true, + }); + + let serveExitInfo; + let shutdownRequested = false; + + const serveExitPromise = new Promise((resolve, reject) => { + serve.on('exit', (code, signal) => { + serveExitInfo = { code, signal }; + resolve(serveExitInfo); + }); + serve.on('error', reject); + }); + + const guard = (commandDescription, factory) => { + const controller = new AbortController(); + const { signal } = controller; + const { child, promise } = factory(signal); + + const watchingPromise = serveExitPromise.then((info) => { + if (!shutdownRequested) { + if (child.exitCode === null && child.signalCode === null) { + controller.abort(); + } + throw new Error( + `Serve process exited while ${commandDescription}: ${formatExit(info)}`, + ); + } + return info; + }); + + return Promise.race([promise, watchingPromise]).finally(() => { + if (child.exitCode === null && child.signalCode === null) { + controller.abort(); + } + }); + }; + + const runCommand = (cmd, args, signal) => { + const child = spawn(cmd, args, { + stdio: 'inherit', + signal, + }); + + const promise = new Promise((resolve, reject) => { + child.on('exit', (code, childSignal) => { + if (code === 0) { + resolve({ code, signal: childSignal }); + } else { + reject( + new Error( + `${cmd} ${args.join(' ')} exited with ${formatExit({ code, signal: childSignal })}`, + ), + ); + } + }); + child.on('error', reject); + }); + + return { child, promise }; + }; + + try { + await guard('waiting for manifest services', (signal) => + runCommand('npx', ['wait-on', ...scenario.waitTargets], signal), + ); + + await guard('running manifest e2e tests', (signal) => + runCommand(scenario.e2eCmd[0], scenario.e2eCmd.slice(1), signal), + ); + } finally { + shutdownRequested = true; + + let serveExitError = null; + try { + await shutdownServe(serve, serveExitPromise); + } catch (error) { + console.error('[manifest-e2e] Serve command emitted error:', error); + serveExitError = error; + } + + await runKillPort(); + + if (serveExitError) { + throw serveExitError; + } + } + + if (!isExpectedServeExit(serveExitInfo)) { + throw new Error( + `Serve command for ${scenario.label} exited unexpectedly with ${formatExit(serveExitInfo)}`, + ); + } + + console.log(`[manifest-e2e] Finished ${scenario.label}`); +} + +async function runKillPort() { + const { promise } = spawnWithPromise( + KILL_PORT_ARGS[0], + KILL_PORT_ARGS.slice(1), + ); + try { + await promise; + } catch (error) { + console.warn('[manifest-e2e] kill-port command failed:', error.message); + } +} + +function spawnWithPromise(cmd, args, options = {}) { + const child = spawn(cmd, args, { + stdio: 'inherit', + ...options, + }); + + const promise = new Promise((resolve, reject) => { + child.on('exit', (code, signal) => { + if (code === 0) { + resolve({ code, signal }); + } else { + reject( + new Error( + `${cmd} ${args.join(' ')} exited with ${formatExit({ code, signal })}`, + ), + ); + } + }); + child.on('error', reject); + }); + + return { child, promise }; +} + +async function shutdownServe(proc, exitPromise) { + if (proc.exitCode !== null || proc.signalCode !== null) { + return exitPromise; + } + + const sequence = [ + { signal: 'SIGINT', timeoutMs: 8000 }, + { signal: 'SIGTERM', timeoutMs: 5000 }, + { signal: 'SIGKILL', timeoutMs: 3000 }, + ]; + + for (const { signal, timeoutMs } of sequence) { + if (proc.exitCode !== null || proc.signalCode !== null) { + break; + } + + sendSignal(proc, signal); + + try { + await waitWithTimeout(exitPromise, timeoutMs); + break; + } catch (error) { + if (error?.name !== 'TimeoutError') { + throw error; + } + // escalate to next signal on timeout + } + } + + return exitPromise; +} + +function sendSignal(proc, signal) { + if (proc.exitCode !== null || proc.signalCode !== null) { + return; + } + + if (SUPPORTS_PROCESS_GROUP_SIGNALS) { + try { + process.kill(-proc.pid, signal); + return; + } catch (error) { + if ( + error.code !== 'ESRCH' && + error.code !== 'EPERM' && + error.code !== 'ERR_INVALID_ARG_VALUE' + ) { + throw error; + } + } + } + + try { + proc.kill(signal); + } catch (error) { + if ( + error.code !== 'ESRCH' && + error.code !== 'EPERM' && + error.code !== 'ERR_INVALID_ARG_VALUE' + ) { + throw error; + } + } +} + +function waitWithTimeout(promise, timeoutMs) { + return new Promise((resolve, reject) => { + let settled = false; + + const timer = setTimeout(() => { + if (settled) { + return; + } + settled = true; + const timeoutError = new Error(`Timed out after ${timeoutMs}ms`); + timeoutError.name = 'TimeoutError'; + reject(timeoutError); + }, timeoutMs); + + promise.then( + (value) => { + if (settled) { + return; + } + settled = true; + clearTimeout(timer); + resolve(value); + }, + (error) => { + if (settled) { + return; + } + settled = true; + clearTimeout(timer); + reject(error); + }, + ); + }); +} + +function isExpectedServeExit(info) { + if (!info) { + return false; + } + + const { code, signal } = info; + + if (code === 0) { + return true; + } + + if (code === 130 || code === 137 || code === 143) { + return true; + } + + if (code == null && ['SIGINT', 'SIGTERM', 'SIGKILL'].includes(signal)) { + return true; + } + + return false; +} + +function formatExit({ code, signal }) { + const parts = []; + if (code !== null && code !== undefined) { + parts.push(`code ${code}`); + } + if (signal) { + parts.push(`signal ${signal}`); + } + return parts.length > 0 ? parts.join(', ') : 'unknown status'; +} + +main().catch((error) => { + console.error('[manifest-e2e] Error:', error); + process.exitCode = 1; +});