mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
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 915a03ae85)
This commit is contained in:
@@ -91,6 +91,13 @@ export function compileComponent(type: Type<any>, 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) {
|
||||
|
||||
@@ -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<unknown>, 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(
|
||||
|
||||
@@ -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<TProps>(component: (props: TProps) => Node[]) {
|
||||
return foreignImport<TProps>(
|
||||
(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<SomeCmp>;
|
||||
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', () => {});
|
||||
|
||||
Reference in New Issue
Block a user