From b199bdfa2ad8b3fe189e7b4cef9dbea600cd4732 Mon Sep 17 00:00:00 2001 From: leonsenft Date: Tue, 25 Aug 2026 21:22:04 +0000 Subject: [PATCH] fix(core): explicitly reject foreign components in JIT mode Foreign components are only supported in AOT mode. Using them in JIT mode previously resulted in silent failures or confusing runtime errors (such as unknown element errors or crashed template ingestion). This commit adds explicit validation in JIT compilation: - Throws an error during component compilation if `foreignImports` is specified on `@Component`. - Throws an error during standalone import verification if a foreign component is mistakenly passed to `@Component.imports`. (cherry picked from commit 915a03ae856a595bd5fa0f364928869db8daec19) --- packages/core/src/render3/jit/directive.ts | 7 ++++ packages/core/src/render3/jit/util.ts | 11 +++++ packages/core/test/render3/ivy/jit_spec.ts | 47 ++++++++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/packages/core/src/render3/jit/directive.ts b/packages/core/src/render3/jit/directive.ts index 358cf80f183..dadc5978232 100644 --- a/packages/core/src/render3/jit/directive.ts +++ b/packages/core/src/render3/jit/directive.ts @@ -91,6 +91,13 @@ export function compileComponent(type: Type, metadata: Component): void { type: type, }); + if (metadata.foreignImports !== undefined) { + throw new Error( + `Foreign components are not supported in JIT mode. ` + + `Component '${type.name}' cannot specify 'foreignImports'.`, + ); + } + if (componentNeedsResolution(metadata)) { const error = [`Component '${type.name}' is not resolved:`]; if (metadata.templateUrl) { diff --git a/packages/core/src/render3/jit/util.ts b/packages/core/src/render3/jit/util.ts index 596e0a067a0..d0416cc2700 100644 --- a/packages/core/src/render3/jit/util.ts +++ b/packages/core/src/render3/jit/util.ts @@ -8,12 +8,17 @@ import {isForwardRef, resolveForwardRef} from '../../di/forward_ref'; import {ModuleWithProviders} from '../../di/interface/provider'; +import {ForeignComponent, RENDER} from '../../interface/foreign_component'; import {Type} from '../../interface/type'; import {NgModuleDef} from '../../metadata/ng_module_def'; import {getComponentDef, getDirectiveDef, getPipeDef, getNgModuleDef} from '../def_getters'; import type {ComponentType, DirectiveType, PipeType} from '../interfaces/definition'; import {stringifyForError} from '../util/stringify_utils'; +export function isForeignComponent(value: any): value is ForeignComponent { + return typeof value === 'object' && value !== null && RENDER in value; +} + export function isModuleWithProviders(value: any): value is ModuleWithProviders<{}> { return (value as {ngModule?: any}).ngModule !== undefined; } @@ -75,6 +80,12 @@ export function verifyStandaloneImport(depType: Type, importingType: Ty importingType, )}". Modules with providers are not supported in standalone components imports.`, ); + } else if (isForeignComponent(depType)) { + throw new Error( + `A foreign component, imported from "${stringifyForError( + importingType, + )}", cannot be imported using 'imports'. Foreign components are only supported in AOT mode and must be registered in 'foreignImports'.`, + ); } else { throw new Error( `The "${stringifyForError(depType)}" type, imported from "${stringifyForError( diff --git a/packages/core/test/render3/ivy/jit_spec.ts b/packages/core/test/render3/ivy/jit_spec.ts index 6115dac9bbd..b40f3aad1d0 100644 --- a/packages/core/test/render3/ivy/jit_spec.ts +++ b/packages/core/test/render3/ivy/jit_spec.ts @@ -31,6 +31,7 @@ import {ɵɵdefineInjectable, ɵɵInjectorDef} from '../../../src/di/interface/d import {FactoryFn} from '../../../src/render3/definition_factory'; import {ComponentDef, PipeDef} from '../../../src/render3/interfaces/definition'; import {InputFlags} from '../../../src/render3/interfaces/input_flags'; +import {foreignImport} from '../../../src/render3/foreign_import'; describe('render3 jit', () => { let injector: any; @@ -546,6 +547,52 @@ describe('render3 jit', () => { ); }); }); + + describe('foreign components', () => { + function fooImport(component: (props: TProps) => Node[]) { + return foreignImport( + (props) => [component(props)], + () => {}, + (producer) => producer(), + ); + } + + function FooComponent(props: {children?: Node[]}): Node[] { + return []; + } + + it('should error when foreignImports is specified on a component in JIT mode', () => { + expect(() => { + @Component({ + selector: 'test-cmp', + template: 'test', + // @ts-ignore + foreignImports: [fooImport(FooComponent)], + }) + class SomeCmp {} + + const _ = (SomeCmp as any).ɵcmp; + }).toThrowError( + `Foreign components are not supported in JIT mode. Component 'SomeCmp' cannot specify 'foreignImports'.`, + ); + }); + + it('should error when a foreign component is imported via imports array in JIT mode', () => { + @Component({ + selector: 'test-cmp', + template: 'test', + imports: [fooImport(FooComponent) as any], + }) + class SomeCmp {} + + const cmpDef = (SomeCmp as any).ɵcmp as ComponentDef; + expect(() => { + (cmpDef.directiveDefs! as () => unknown)(); + }).toThrowError( + /A foreign component, imported from "SomeCmp", cannot be imported using 'imports'\. Foreign components are only supported in AOT mode and must be registered in 'foreignImports'\./, + ); + }); + }); }); it('ensure at least one spec exists', () => {});