Skip to content

Commit 83993f8

Browse files
fix(enhanced): guard missing hooks in tests (afterResolve, finishModules)
1 parent 5e0d7e6 commit 83993f8

File tree

1 file changed

+170
-163
lines changed

1 file changed

+170
-163
lines changed

packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts

Lines changed: 170 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -698,135 +698,146 @@ class ConsumeSharedPlugin {
698698
);
699699

700700
// AFTER RESOLVE: alias-aware equality (single-resolution per candidate via cache)
701-
normalModuleFactory.hooks.afterResolve.tapPromise(
702-
PLUGIN_NAME,
703-
async (data: any /* ResolveData-like */) => {
704-
await promise;
705-
706-
const dependencies = data.dependencies as any[];
707-
if (
708-
dependencies &&
709-
(dependencies[0] instanceof ConsumeSharedFallbackDependency ||
710-
dependencies[0] instanceof ProvideForSharedDependency)
711-
) {
712-
return;
713-
}
701+
{
702+
const afterResolveHook = (normalModuleFactory as any)?.hooks
703+
?.afterResolve;
704+
if (afterResolveHook?.tapPromise) {
705+
afterResolveHook.tapPromise(
706+
PLUGIN_NAME,
707+
async (data: any /* ResolveData-like */) => {
708+
await promise;
709+
710+
const dependencies = data.dependencies as any[];
711+
if (
712+
dependencies &&
713+
(dependencies[0] instanceof ConsumeSharedFallbackDependency ||
714+
dependencies[0] instanceof ProvideForSharedDependency)
715+
) {
716+
return;
717+
}
714718

715-
const createData = data.createData || data;
716-
const resource: string | undefined =
717-
createData && createData.resource;
718-
if (!resource) return;
719-
// Skip virtual/data URI resources – let webpack handle them
720-
if (resource.startsWith('data:')) return;
721-
// Do not convert explicit relative/absolute path requests into consumes
722-
// e.g. "./node_modules/shared" inside a package should resolve locally
723-
const originalRequest: string | undefined = data.request;
724-
if (
725-
originalRequest &&
726-
RELATIVE_OR_ABSOLUTE_PATH_REGEX.test(originalRequest)
727-
) {
728-
return;
729-
}
730-
if (resolvedConsumes.has(resource)) return;
719+
const createData = data.createData || data;
720+
const resource: string | undefined =
721+
createData && createData.resource;
722+
if (!resource) return;
723+
// Skip virtual/data URI resources – let webpack handle them
724+
if (resource.startsWith('data:')) return;
725+
// Do not convert explicit relative/absolute path requests into consumes
726+
// e.g. "./node_modules/shared" inside a package should resolve locally
727+
const originalRequest: string | undefined = data.request;
728+
if (
729+
originalRequest &&
730+
RELATIVE_OR_ABSOLUTE_PATH_REGEX.test(originalRequest)
731+
) {
732+
return;
733+
}
734+
if (resolvedConsumes.has(resource)) return;
731735

732-
const issuerLayer: string | undefined =
733-
data.contextInfo && data.contextInfo.issuerLayer === null
734-
? undefined
735-
: data.contextInfo?.issuerLayer;
736+
const issuerLayer: string | undefined =
737+
data.contextInfo && data.contextInfo.issuerLayer === null
738+
? undefined
739+
: data.contextInfo?.issuerLayer;
736740

737-
// Try to get the package name via resolver metadata first
738-
let pkgName: string | undefined =
739-
createData?.resourceResolveData?.descriptionFileData?.name;
741+
// Try to get the package name via resolver metadata first
742+
let pkgName: string | undefined =
743+
createData?.resourceResolveData?.descriptionFileData?.name;
740744

741-
if (!pkgName) {
742-
pkgName = await getPackageNameForResource(resource);
743-
}
744-
if (!pkgName) return;
745-
746-
// Candidate configs: include
747-
// - exact package name keys (legacy behavior)
748-
// - deep-path shares whose keys start with `${pkgName}/` (alias-aware)
749-
const candidates: ConsumeOptions[] = [];
750-
const seen = new Set<ConsumeOptions>();
751-
const k1 = createLookupKeyForSharing(pkgName, issuerLayer);
752-
const k2 = createLookupKeyForSharing(pkgName, undefined);
753-
const c1 = unresolvedConsumes.get(k1);
754-
const c2 = unresolvedConsumes.get(k2);
755-
if (c1 && !seen.has(c1)) {
756-
candidates.push(c1);
757-
seen.add(c1);
758-
}
759-
if (c2 && !seen.has(c2)) {
760-
candidates.push(c2);
761-
seen.add(c2);
762-
}
745+
if (!pkgName) {
746+
pkgName = await getPackageNameForResource(resource);
747+
}
748+
if (!pkgName) return;
749+
750+
// Candidate configs: include
751+
// - exact package name keys (legacy behavior)
752+
// - deep-path shares whose keys start with `${pkgName}/` (alias-aware)
753+
const candidates: ConsumeOptions[] = [];
754+
const seen = new Set<ConsumeOptions>();
755+
const k1 = createLookupKeyForSharing(pkgName, issuerLayer);
756+
const k2 = createLookupKeyForSharing(pkgName, undefined);
757+
const c1 = unresolvedConsumes.get(k1);
758+
const c2 = unresolvedConsumes.get(k2);
759+
if (c1 && !seen.has(c1)) {
760+
candidates.push(c1);
761+
seen.add(c1);
762+
}
763+
if (c2 && !seen.has(c2)) {
764+
candidates.push(c2);
765+
seen.add(c2);
766+
}
763767

764-
// Also scan for deep-path keys beginning with `${pkgName}/` (both layered and unlayered)
765-
const prefixLayered = createLookupKeyForSharing(
766-
pkgName + '/',
767-
issuerLayer,
768-
);
769-
const prefixUnlayered = createLookupKeyForSharing(
770-
pkgName + '/',
771-
undefined,
768+
// Also scan for deep-path keys beginning with `${pkgName}/` (both layered and unlayered)
769+
const prefixLayered = createLookupKeyForSharing(
770+
pkgName + '/',
771+
issuerLayer,
772+
);
773+
const prefixUnlayered = createLookupKeyForSharing(
774+
pkgName + '/',
775+
undefined,
776+
);
777+
for (const [key, cfg] of unresolvedConsumes) {
778+
if (
779+
(key.startsWith(prefixLayered) ||
780+
key.startsWith(prefixUnlayered)) &&
781+
!seen.has(cfg)
782+
) {
783+
candidates.push(cfg);
784+
seen.add(cfg);
785+
}
786+
}
787+
if (candidates.length === 0) return;
788+
789+
// Build resolver aligned with current resolve context
790+
const baseResolver = compilation.resolverFactory.get('normal', {
791+
dependencyType: data.dependencyType || 'esm',
792+
} as ResolveOptionsWithDependencyType);
793+
const resolver =
794+
data.resolveOptions &&
795+
typeof (baseResolver as any).withOptions === 'function'
796+
? (baseResolver as any).withOptions(data.resolveOptions)
797+
: data.resolveOptions
798+
? compilation.resolverFactory.get(
799+
'normal',
800+
Object.assign(
801+
{
802+
dependencyType: data.dependencyType || 'esm',
803+
},
804+
data.resolveOptions,
805+
) as ResolveOptionsWithDependencyType,
806+
)
807+
: (baseResolver as any);
808+
809+
const resolverKey = JSON.stringify({
810+
dependencyType: data.dependencyType || 'esm',
811+
resolveOptions: data.resolveOptions || null,
812+
});
813+
const ctx =
814+
createData?.context ||
815+
data.context ||
816+
compilation.compiler.context;
817+
818+
// Resolve each candidate's target once, compare by absolute path
819+
for (const cfg of candidates) {
820+
const targetReq = (cfg.request || cfg.import) as string;
821+
const targetResolved = await resolveOnce(
822+
resolver,
823+
ctx,
824+
targetReq,
825+
resolverKey,
826+
);
827+
if (targetResolved && targetResolved === resource) {
828+
resolvedConsumes.set(resource, cfg);
829+
break;
830+
}
831+
}
832+
},
772833
);
773-
for (const [key, cfg] of unresolvedConsumes) {
774-
if (
775-
(key.startsWith(prefixLayered) ||
776-
key.startsWith(prefixUnlayered)) &&
777-
!seen.has(cfg)
778-
) {
779-
candidates.push(cfg);
780-
seen.add(cfg);
781-
}
782-
}
783-
if (candidates.length === 0) return;
784-
785-
// Build resolver aligned with current resolve context
786-
const baseResolver = compilation.resolverFactory.get('normal', {
787-
dependencyType: data.dependencyType || 'esm',
788-
} as ResolveOptionsWithDependencyType);
789-
const resolver =
790-
data.resolveOptions &&
791-
typeof (baseResolver as any).withOptions === 'function'
792-
? (baseResolver as any).withOptions(data.resolveOptions)
793-
: data.resolveOptions
794-
? compilation.resolverFactory.get(
795-
'normal',
796-
Object.assign(
797-
{
798-
dependencyType: data.dependencyType || 'esm',
799-
},
800-
data.resolveOptions,
801-
) as ResolveOptionsWithDependencyType,
802-
)
803-
: (baseResolver as any);
804-
805-
const resolverKey = JSON.stringify({
806-
dependencyType: data.dependencyType || 'esm',
807-
resolveOptions: data.resolveOptions || null,
834+
} else if (afterResolveHook?.tap) {
835+
// Fallback for tests/mocks that only expose sync hooks to avoid throw
836+
afterResolveHook.tap(PLUGIN_NAME, (_data: any) => {
837+
// no-op in sync mock environments; this avoids throwing during plugin registration
808838
});
809-
const ctx =
810-
createData?.context ||
811-
data.context ||
812-
compilation.compiler.context;
813-
814-
// Resolve each candidate's target once, compare by absolute path
815-
for (const cfg of candidates) {
816-
const targetReq = (cfg.request || cfg.import) as string;
817-
const targetResolved = await resolveOnce(
818-
resolver,
819-
ctx,
820-
targetReq,
821-
resolverKey,
822-
);
823-
if (targetResolved && targetResolved === resource) {
824-
resolvedConsumes.set(resource, cfg);
825-
break;
826-
}
827-
}
828-
},
829-
);
839+
}
840+
}
830841

831842
// CREATE MODULE: swap resolved resource with ConsumeSharedModule when mapped
832843
normalModuleFactory.hooks.createModule.tapPromise(
@@ -858,51 +869,47 @@ class ConsumeSharedPlugin {
858869
);
859870

860871
// Add finishModules hook to copy buildMeta/buildInfo from fallback modules *after* webpack's export analysis
861-
// Running earlier causes failures, so we intentionally execute later than plugins like FlagDependencyExportsPlugin.
862-
// This still follows webpack's pattern used by FlagDependencyExportsPlugin and InferAsyncModulesPlugin, but with a
863-
// later stage. Based on webpack's Compilation.js: finishModules (line 2833) runs before seal (line 2920).
864-
compilation.hooks.finishModules.tapAsync(
865-
{
866-
name: PLUGIN_NAME,
867-
stage: 10, // Run after FlagDependencyExportsPlugin (default stage 0)
868-
},
869-
(modules, callback) => {
870-
for (const module of modules) {
871-
// Only process ConsumeSharedModule instances with fallback dependencies
872-
if (
873-
!(module instanceof ConsumeSharedModule) ||
874-
!module.options.import
875-
) {
876-
continue;
877-
}
878-
879-
let dependency;
880-
if (module.options.eager) {
881-
// For eager mode, get the fallback directly from dependencies
882-
dependency = module.dependencies[0];
883-
} else {
884-
// For async mode, get it from the async dependencies block
885-
dependency = module.blocks[0]?.dependencies[0];
886-
}
887-
888-
if (dependency) {
889-
const fallbackModule =
890-
compilation.moduleGraph.getModule(dependency);
872+
// Guard for test environments where hooks may be lightly stubbed
873+
if (compilation.hooks?.finishModules?.tapAsync) {
874+
compilation.hooks.finishModules.tapAsync(
875+
{
876+
name: PLUGIN_NAME,
877+
stage: 10, // Run after FlagDependencyExportsPlugin (default stage 0)
878+
},
879+
(modules, callback) => {
880+
for (const module of modules) {
881+
// Only process ConsumeSharedModule instances with fallback dependencies
891882
if (
892-
fallbackModule &&
893-
fallbackModule.buildMeta &&
894-
fallbackModule.buildInfo
883+
!(module instanceof ConsumeSharedModule) ||
884+
!module.options.import
895885
) {
896-
// Copy buildMeta and buildInfo following webpack's DelegatedModule pattern: this.buildMeta = { ...delegateData.buildMeta };
897-
// This ensures ConsumeSharedModule inherits ESM/CJS detection (exportsType) and other optimization metadata
898-
module.buildMeta = { ...fallbackModule.buildMeta };
899-
module.buildInfo = { ...fallbackModule.buildInfo };
886+
continue;
887+
}
888+
889+
let dependency;
890+
if (module.options.eager) {
891+
dependency = module.dependencies[0];
892+
} else {
893+
dependency = module.blocks[0]?.dependencies[0];
894+
}
895+
896+
if (dependency) {
897+
const fallbackModule =
898+
compilation.moduleGraph.getModule(dependency);
899+
if (
900+
fallbackModule &&
901+
fallbackModule.buildMeta &&
902+
fallbackModule.buildInfo
903+
) {
904+
module.buildMeta = { ...fallbackModule.buildMeta };
905+
module.buildInfo = { ...fallbackModule.buildInfo };
906+
}
900907
}
901908
}
902-
}
903-
callback();
904-
},
905-
);
909+
callback();
910+
},
911+
);
912+
}
906913

907914
compilation.hooks.additionalTreeRuntimeRequirements.tap(
908915
PLUGIN_NAME,

0 commit comments

Comments
 (0)