diff --git a/packages/core/src/render3/deps_tracker/api.ts b/packages/core/src/render3/deps_tracker/api.ts index 9bdc2d4f7486a..1d73e14044f61 100644 --- a/packages/core/src/render3/deps_tracker/api.ts +++ b/packages/core/src/render3/deps_tracker/api.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Type} from '../../core'; +import {Type} from '../../interface/type'; import {NgModuleType} from '../../metadata/ng_module_def'; import {ComponentType, DependencyTypeList, DirectiveType, NgModuleScopeInfoFromDecorator, PipeType} from '../interfaces/definition'; @@ -84,7 +84,7 @@ export interface DepsTrackerApi { * The main application of this method is for test beds where we want to clear the cache to * enforce scope update after overriding. */ - clearScopeCacheFor(type: ComponentType|NgModuleType): void; + clearScopeCacheFor(type: Type): void; /** * Returns the scope of NgModule. Mainly to be used by JIT and test bed. diff --git a/packages/core/src/render3/deps_tracker/deps_tracker.ts b/packages/core/src/render3/deps_tracker/deps_tracker.ts index f3e83b50f8763..adb340911894e 100644 --- a/packages/core/src/render3/deps_tracker/deps_tracker.ts +++ b/packages/core/src/render3/deps_tracker/deps_tracker.ts @@ -12,12 +12,20 @@ import {Type} from '../../interface/type'; import {NgModuleType} from '../../metadata/ng_module_def'; import {getComponentDef, getNgModuleDef, isStandalone} from '../definition'; import {ComponentType, NgModuleScopeInfoFromDecorator} from '../interfaces/definition'; -import {verifyStandaloneImport} from '../jit/directive'; -import {isComponent, isDirective, isModuleWithProviders, isNgModule, isPipe} from '../jit/util'; +import {isComponent, isDirective, isNgModule, isPipe, verifyStandaloneImport} from '../jit/util'; import {maybeUnwrapFn} from '../util/misc_utils'; import {ComponentDependencies, DepsTrackerApi, NgModuleScope, StandaloneComponentScope} from './api'; +/** + * Indicates whether to use the runtime dependency tracker for scope calculation in JIT compilation. + * The value "false" means the old code path based on patching scope info into the types will be + * used. + * + * @deprecated For migration purposes only, to be removed soon. + */ +export const USE_RDT_FOR_JIT = false; + /** * An implementation of DepsTrackerApi which will be used for JIT and local compilation. */ @@ -115,7 +123,7 @@ class DepsTracker implements DepsTrackerApi { } /** @override */ - clearScopeCacheFor(type: ComponentType|NgModuleType): void { + clearScopeCacheFor(type: Type): void { if (isNgModule(type)) { this.ngModulesScopeCache.delete(type); } else if (isComponent(type)) { diff --git a/packages/core/src/render3/jit/directive.ts b/packages/core/src/render3/jit/directive.ts index 4d63659d9ea6f..12847898653c6 100644 --- a/packages/core/src/render3/jit/directive.ts +++ b/packages/core/src/render3/jit/directive.ts @@ -8,7 +8,7 @@ import {getCompilerFacade, JitCompilerUsage, R3DirectiveMetadataFacade} from '../../compiler/compiler_facade'; import {R3ComponentMetadataFacade, R3QueryMetadataFacade} from '../../compiler/compiler_facade_interface'; -import {isForwardRef, resolveForwardRef} from '../../di/forward_ref'; +import {resolveForwardRef} from '../../di/forward_ref'; import {getReflect, reflectDependencies} from '../../di/jit/util'; import {Type} from '../../interface/type'; import {Query} from '../../metadata/di'; @@ -19,6 +19,7 @@ import {flatten} from '../../util/array_utils'; import {EMPTY_ARRAY, EMPTY_OBJ} from '../../util/empty'; import {initNgDevMode} from '../../util/ng_dev_mode'; import {getComponentDef, getDirectiveDef, getNgModuleDef, getPipeDef} from '../definition'; +import {depsTracker, USE_RDT_FOR_JIT} from '../deps_tracker/deps_tracker'; import {NG_COMP_DEF, NG_DIR_DEF, NG_FACTORY_DEF} from '../fields'; import {ComponentDef, ComponentType, DirectiveDefList, PipeDefList} from '../interfaces/definition'; import {stringifyForError} from '../util/stringify_utils'; @@ -26,7 +27,7 @@ import {stringifyForError} from '../util/stringify_utils'; import {angularCoreEnv} from './environment'; import {getJitOptions} from './jit_options'; import {flushModuleScopingQueueAsMuchAsPossible, patchComponentDefWithScope, transitiveScopesFor} from './module'; -import {isModuleWithProviders} from './util'; +import {isComponent, verifyStandaloneImport} from './util'; /** * Keep track of the compilation depth to avoid reentrancy issues during JIT compilation. This @@ -186,48 +187,6 @@ export function compileComponent(type: Type, metadata: Component): void { }); } -function getDependencyTypeForError(type: Type) { - if (getComponentDef(type)) return 'component'; - if (getDirectiveDef(type)) return 'directive'; - if (getPipeDef(type)) return 'pipe'; - return 'type'; -} - -export function verifyStandaloneImport(depType: Type, importingType: Type) { - if (isForwardRef(depType)) { - depType = resolveForwardRef(depType); - if (!depType) { - throw new Error(`Expected forwardRef function, imported from "${ - stringifyForError(importingType)}", to return a standalone entity or NgModule but got "${ - stringifyForError(depType) || depType}".`); - } - } - - if (getNgModuleDef(depType) == null) { - const def = getComponentDef(depType) || getDirectiveDef(depType) || getPipeDef(depType); - if (def != null) { - // if a component, directive or pipe is imported make sure that it is standalone - if (!def.standalone) { - throw new Error(`The "${stringifyForError(depType)}" ${ - getDependencyTypeForError(depType)}, imported from "${ - stringifyForError( - importingType)}", is not standalone. Did you forget to add the standalone: true flag?`); - } - } else { - // it can be either a module with provider or an unknown (not annotated) type - if (isModuleWithProviders(depType)) { - throw new Error(`A module with providers was imported from "${ - stringifyForError( - importingType)}". Modules with providers are not supported in standalone components imports.`); - } else { - throw new Error(`The "${stringifyForError(depType)}" type, imported from "${ - stringifyForError( - importingType)}", must be a standalone component / directive / pipe or an NgModule. Did you forget to add the required @Component / @Directive / @Pipe or @NgModule annotation?`); - } - } - } -} - /** * Build memoized `directiveDefs` and `pipeDefs` functions for the component definition of a * standalone component, which process `imports` and filter out directives and pipes. The use of @@ -241,71 +200,105 @@ function getStandaloneDefFunctions(type: Type, imports: Type[]): { let cachedDirectiveDefs: DirectiveDefList|null = null; let cachedPipeDefs: PipeDefList|null = null; const directiveDefs = () => { - if (cachedDirectiveDefs === null) { - // Standalone components are always able to self-reference, so include the component's own - // definition in its `directiveDefs`. - cachedDirectiveDefs = [getComponentDef(type)!]; - const seen = new Set>([type]); - - for (const rawDep of imports) { - ngDevMode && verifyStandaloneImport(rawDep, type); - - const dep = resolveForwardRef(rawDep); - if (seen.has(dep)) { - continue; - } - seen.add(dep); - - if (!!getNgModuleDef(dep)) { - const scope = transitiveScopesFor(dep); - for (const dir of scope.exported.directives) { - const def = getComponentDef(dir) || getDirectiveDef(dir); - if (def && !seen.has(dir)) { - seen.add(dir); + if (!USE_RDT_FOR_JIT) { + if (cachedDirectiveDefs === null) { + // Standalone components are always able to self-reference, so include the component's own + // definition in its `directiveDefs`. + cachedDirectiveDefs = [getComponentDef(type)!]; + const seen = new Set>([type]); + + for (const rawDep of imports) { + ngDevMode && verifyStandaloneImport(rawDep, type); + + const dep = resolveForwardRef(rawDep); + if (seen.has(dep)) { + continue; + } + seen.add(dep); + + if (!!getNgModuleDef(dep)) { + const scope = transitiveScopesFor(dep); + for (const dir of scope.exported.directives) { + const def = getComponentDef(dir) || getDirectiveDef(dir); + if (def && !seen.has(dir)) { + seen.add(dir); + cachedDirectiveDefs.push(def); + } + } + } else { + const def = getComponentDef(dep) || getDirectiveDef(dep); + if (def) { cachedDirectiveDefs.push(def); } } - } else { - const def = getComponentDef(dep) || getDirectiveDef(dep); - if (def) { - cachedDirectiveDefs.push(def); - } } } + return cachedDirectiveDefs; + } else { + if (ngDevMode) { + for (const rawDep of imports) { + verifyStandaloneImport(rawDep, type); + } + } + + if (!isComponent(type)) { + return []; + } + + const scope = depsTracker.getStandaloneComponentScope(type, imports); + + return [...scope.compilation.directives] + .map(p => (getComponentDef(p) || getDirectiveDef(p))!) + .filter(d => d !== null); } - return cachedDirectiveDefs; }; const pipeDefs = () => { - if (cachedPipeDefs === null) { - cachedPipeDefs = []; - const seen = new Set>(); - - for (const rawDep of imports) { - const dep = resolveForwardRef(rawDep); - if (seen.has(dep)) { - continue; - } - seen.add(dep); - - if (!!getNgModuleDef(dep)) { - const scope = transitiveScopesFor(dep); - for (const pipe of scope.exported.pipes) { - const def = getPipeDef(pipe); - if (def && !seen.has(pipe)) { - seen.add(pipe); + if (!USE_RDT_FOR_JIT) { + if (cachedPipeDefs === null) { + cachedPipeDefs = []; + const seen = new Set>(); + + for (const rawDep of imports) { + const dep = resolveForwardRef(rawDep); + if (seen.has(dep)) { + continue; + } + seen.add(dep); + + if (!!getNgModuleDef(dep)) { + const scope = transitiveScopesFor(dep); + for (const pipe of scope.exported.pipes) { + const def = getPipeDef(pipe); + if (def && !seen.has(pipe)) { + seen.add(pipe); + cachedPipeDefs.push(def); + } + } + } else { + const def = getPipeDef(dep); + if (def) { cachedPipeDefs.push(def); } } - } else { - const def = getPipeDef(dep); - if (def) { - cachedPipeDefs.push(def); - } } } + return cachedPipeDefs; + } else { + if (ngDevMode) { + for (const rawDep of imports) { + verifyStandaloneImport(rawDep, type); + } + } + + if (!isComponent(type)) { + return []; + } + + const scope = depsTracker.getStandaloneComponentScope(type, imports); + + return [...scope.compilation.pipes].map(p => getPipeDef(p)!).filter(d => d !== null); } - return cachedPipeDefs; }; return { diff --git a/packages/core/src/render3/jit/module.ts b/packages/core/src/render3/jit/module.ts index 023507406dedd..c3fd0154a899d 100644 --- a/packages/core/src/render3/jit/module.ts +++ b/packages/core/src/render3/jit/module.ts @@ -20,6 +20,7 @@ import {deepForEach, flatten} from '../../util/array_utils'; import {assertDefined} from '../../util/assert'; import {EMPTY_ARRAY} from '../../util/empty'; import {GENERATED_COMP_IDS, getComponentDef, getDirectiveDef, getNgModuleDef, getPipeDef, isStandalone} from '../definition'; +import {depsTracker, USE_RDT_FOR_JIT} from '../deps_tracker/deps_tracker'; import {NG_COMP_DEF, NG_DIR_DEF, NG_FACTORY_DEF, NG_MOD_DEF, NG_PIPE_DEF} from '../fields'; import {ComponentDef} from '../interfaces/definition'; import {maybeUnwrapFn} from '../util/misc_utils'; @@ -489,7 +490,16 @@ export function patchComponentDefWithScope( */ export function transitiveScopesFor(type: Type): NgModuleTransitiveScopes { if (isNgModule(type)) { - return transitiveScopesForNgModule(type); + if (USE_RDT_FOR_JIT) { + const scope = depsTracker.getNgModuleScope(type); + const def = getNgModuleDef(type, true); + return { + schemas: def.schemas || null, + ...scope, + }; + } else { + return transitiveScopesForNgModule(type); + } } else if (isStandalone(type)) { const directiveDef = getComponentDef(type) || getDirectiveDef(type); if (directiveDef !== null) { diff --git a/packages/core/src/render3/jit/util.ts b/packages/core/src/render3/jit/util.ts index ef549de3edc9c..b844b16c4f62f 100644 --- a/packages/core/src/render3/jit/util.ts +++ b/packages/core/src/render3/jit/util.ts @@ -6,11 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ +import {isForwardRef, resolveForwardRef} from '../../di/forward_ref'; import {ModuleWithProviders} from '../../di/interface/provider'; import {Type} from '../../interface/type'; import {NgModuleDef} from '../../metadata/ng_module_def'; import {getComponentDef, getDirectiveDef, getNgModuleDef, getPipeDef} from '../definition'; import {ComponentType, DirectiveType, PipeType} from '../interfaces/definition'; +import {stringifyForError} from '../util/stringify_utils'; export function isModuleWithProviders(value: any): value is ModuleWithProviders<{}> { return (value as {ngModule?: any}).ngModule !== undefined; @@ -31,3 +33,45 @@ export function isDirective(value: Type): value is DirectiveType { export function isComponent(value: Type): value is ComponentType { return !!getComponentDef(value); } + +function getDependencyTypeForError(type: Type) { + if (getComponentDef(type)) return 'component'; + if (getDirectiveDef(type)) return 'directive'; + if (getPipeDef(type)) return 'pipe'; + return 'type'; +} + +export function verifyStandaloneImport(depType: Type, importingType: Type) { + if (isForwardRef(depType)) { + depType = resolveForwardRef(depType); + if (!depType) { + throw new Error(`Expected forwardRef function, imported from "${ + stringifyForError(importingType)}", to return a standalone entity or NgModule but got "${ + stringifyForError(depType) || depType}".`); + } + } + + if (getNgModuleDef(depType) == null) { + const def = getComponentDef(depType) || getDirectiveDef(depType) || getPipeDef(depType); + if (def != null) { + // if a component, directive or pipe is imported make sure that it is standalone + if (!def.standalone) { + throw new Error(`The "${stringifyForError(depType)}" ${ + getDependencyTypeForError(depType)}, imported from "${ + stringifyForError( + importingType)}", is not standalone. Did you forget to add the standalone: true flag?`); + } + } else { + // it can be either a module with provider or an unknown (not annotated) type + if (isModuleWithProviders(depType)) { + throw new Error(`A module with providers was imported from "${ + stringifyForError( + importingType)}". Modules with providers are not supported in standalone components imports.`); + } else { + throw new Error(`The "${stringifyForError(depType)}" type, imported from "${ + stringifyForError( + importingType)}", must be a standalone component / directive / pipe or an NgModule. Did you forget to add the required @Component / @Directive / @Pipe or @NgModule annotation?`); + } + } + } +} diff --git a/packages/core/testing/src/test_bed_compiler.ts b/packages/core/testing/src/test_bed_compiler.ts index 4a22195d51096..5df526c5a5872 100644 --- a/packages/core/testing/src/test_bed_compiler.ts +++ b/packages/core/testing/src/test_bed_compiler.ts @@ -11,6 +11,7 @@ import {ApplicationInitStatus, Compiler, COMPILER_OPTIONS, Component, Directive, import {clearResolutionOfComponentResourcesQueue, isComponentDefPendingResolution, resolveComponentResources, restoreComponentResolutionQueue} from '../../src/metadata/resource_loading'; import {ComponentDef, ComponentType} from '../../src/render3'; +import {depsTracker, USE_RDT_FOR_JIT} from '../../src/render3/deps_tracker/deps_tracker'; import {generateStandaloneInDeclarationsError} from '../../src/render3/jit/module'; import {MetadataOverride} from './metadata_override'; @@ -142,6 +143,9 @@ export class TestBedCompiler { } overrideModule(ngModule: Type, override: MetadataOverride): void { + if (USE_RDT_FOR_JIT) { + depsTracker.clearScopeCacheFor(ngModule); + } this.overriddenModules.add(ngModule as NgModuleType); // Compile the module right away. @@ -388,8 +392,12 @@ export class TestBedCompiler { const affectedModules = this.collectModulesAffectedByOverrides(testingModuleDef.imports); if (affectedModules.size > 0) { affectedModules.forEach(moduleType => { - this.storeFieldOfDefOnType(moduleType as any, NG_MOD_DEF, 'transitiveCompileScopes'); - (moduleType as any)[NG_MOD_DEF].transitiveCompileScopes = null; + if (!USE_RDT_FOR_JIT) { + this.storeFieldOfDefOnType(moduleType as any, NG_MOD_DEF, 'transitiveCompileScopes'); + (moduleType as any)[NG_MOD_DEF].transitiveCompileScopes = null; + } else { + depsTracker.clearScopeCacheFor(moduleType); + } }); } } @@ -726,6 +734,12 @@ export class TestBedCompiler { // Restore initial component/directive/pipe defs this.initialNgDefs.forEach( (defs: Map, type: Type) => { + if (USE_RDT_FOR_JIT) { + try { + depsTracker.clearScopeCacheFor(type); + } catch (e) { + } + } defs.forEach((descriptor, prop) => { if (!descriptor) { // Delete operations are generally undesirable since they have performance