perf(core): make DI decorators tree-shakable when used for `useFactory` deps config (#40145)
This commit updates the logic that calculates `useFactory` function arguments to avoid relying on `instanceof` checks (thus always retaining symbols) and relies on flags that DI decorators contain (as a monkey-patched property). Another perf benefit is having less megamorphic reads while calculating args for the `useFactory` call: we used to check whether a token has `ngMetadataName` property 4 times (in worst case), now we have just 1 megamorphic read in all cases. Closes #40143. PR Close #40145
This commit is contained in:
parent
25788106e5
commit
6cff877f4f
|
@ -838,11 +838,11 @@ export declare interface RendererType2 {
|
|||
}
|
||||
|
||||
export declare class ResolvedReflectiveFactory {
|
||||
dependencies: ɵangular_packages_core_core_d[];
|
||||
dependencies: ɵangular_packages_core_core_e[];
|
||||
factory: Function;
|
||||
constructor(
|
||||
factory: Function,
|
||||
dependencies: ɵangular_packages_core_core_d[]);
|
||||
dependencies: ɵangular_packages_core_core_e[]);
|
||||
}
|
||||
|
||||
export declare interface ResolvedReflectiveProvider {
|
||||
|
|
|
@ -16,14 +16,20 @@ import {resolveForwardRef} from './forward_ref';
|
|||
import {getInjectImplementation, injectRootLimpMode} from './inject_switch';
|
||||
import {InjectionToken} from './injection_token';
|
||||
import {Injector} from './injector';
|
||||
import {InjectFlags} from './interface/injector';
|
||||
import {DecoratorFlags, InjectFlags, InternalInjectFlags} from './interface/injector';
|
||||
import {ValueProvider} from './interface/provider';
|
||||
import {Host, Inject, Optional, Self, SkipSelf} from './metadata';
|
||||
|
||||
|
||||
const _THROW_IF_NOT_FOUND = {};
|
||||
export const THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND;
|
||||
|
||||
/*
|
||||
* Name of a property (that we patch onto DI decorator), which is used as an annotation of which
|
||||
* InjectFlag this decorator represents. This allows to avoid direct references to the DI decorators
|
||||
* in the code, thus making them tree-shakable.
|
||||
*/
|
||||
const DI_DECORATOR_FLAG = '__NG_DI_FLAG__';
|
||||
|
||||
export const NG_TEMP_TOKEN_PATH = 'ngTempTokenPath';
|
||||
const NG_TOKEN_PATH = 'ngTokenPath';
|
||||
const NEW_LINE = /\n/gm;
|
||||
|
@ -145,17 +151,14 @@ export function injectArgs(types: (Type<any>|InjectionToken<any>|any[])[]): any[
|
|||
|
||||
for (let j = 0; j < arg.length; j++) {
|
||||
const meta = arg[j];
|
||||
if (meta instanceof Optional || meta.ngMetadataName === 'Optional' || meta === Optional) {
|
||||
flags |= InjectFlags.Optional;
|
||||
} else if (
|
||||
meta instanceof SkipSelf || meta.ngMetadataName === 'SkipSelf' || meta === SkipSelf) {
|
||||
flags |= InjectFlags.SkipSelf;
|
||||
} else if (meta instanceof Self || meta.ngMetadataName === 'Self' || meta === Self) {
|
||||
flags |= InjectFlags.Self;
|
||||
} else if (meta instanceof Host || meta.ngMetadataName === 'Host' || meta === Host) {
|
||||
flags |= InjectFlags.Host;
|
||||
} else if (meta instanceof Inject || meta === Inject) {
|
||||
type = meta.token;
|
||||
const flag = getInjectFlag(meta);
|
||||
if (typeof flag === 'number') {
|
||||
// Special case when we handle @Inject decorator.
|
||||
if (flag === DecoratorFlags.Inject) {
|
||||
type = meta.token;
|
||||
} else {
|
||||
flags |= flag;
|
||||
}
|
||||
} else {
|
||||
type = meta;
|
||||
}
|
||||
|
@ -169,6 +172,30 @@ export function injectArgs(types: (Type<any>|InjectionToken<any>|any[])[]): any[
|
|||
return args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a given InjectFlag to a given decorator using monkey-patching.
|
||||
* Since DI decorators can be used in providers `deps` array (when provider is configured using
|
||||
* `useFactory`) without initialization (e.g. `Host`) and as an instance (e.g. `new Host()`), we
|
||||
* attach the flag to make it available both as a static property and as a field on decorator
|
||||
* instance.
|
||||
*
|
||||
* @param decorator Provided DI decorator.
|
||||
* @param flag InjectFlag that should be applied.
|
||||
*/
|
||||
export function attachInjectFlag(decorator: any, flag: InternalInjectFlags|DecoratorFlags): any {
|
||||
decorator[DI_DECORATOR_FLAG] = flag;
|
||||
decorator.prototype[DI_DECORATOR_FLAG] = flag;
|
||||
return decorator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads monkey-patched property that contains InjectFlag attached to a decorator.
|
||||
*
|
||||
* @param token Token that may contain monkey-patched DI flags property.
|
||||
*/
|
||||
export function getInjectFlag(token: any): number|undefined {
|
||||
return token[DI_DECORATOR_FLAG];
|
||||
}
|
||||
|
||||
export function catchInjectorError(
|
||||
e: any, token: any, injectorErrorName: string, source: string|null): never {
|
||||
|
|
|
@ -7,25 +7,67 @@
|
|||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Special flag indicating that a decorator is of type `Inject`. It's used to make `Inject`
|
||||
* decorator tree-shakable (so we don't have to rely on the `instanceof` checks).
|
||||
* Note: this flag is not included into the `InjectFlags` since it's an internal-only API.
|
||||
*/
|
||||
export const enum DecoratorFlags {
|
||||
Inject = -1
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection flags for DI.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export enum InjectFlags {
|
||||
// TODO(alxhub): make this 'const' when ngc no longer writes exports of it into ngfactory files.
|
||||
// TODO(alxhub): make this 'const' (and remove `InternalInjectFlags` enum) when ngc no longer
|
||||
// writes exports of it into ngfactory files.
|
||||
|
||||
/** Check self and check parent injector if needed */
|
||||
Default = 0b0000,
|
||||
|
||||
/**
|
||||
* Specifies that an injector should retrieve a dependency from any injector until reaching the
|
||||
* host element of the current component. (Only used with Element Injector)
|
||||
*/
|
||||
Host = 0b0001,
|
||||
|
||||
/** Don't ascend to ancestors of the node requesting injection. */
|
||||
Self = 0b0010,
|
||||
|
||||
/** Skip the node that is requesting injection. */
|
||||
SkipSelf = 0b0100,
|
||||
|
||||
/** Inject `defaultValue` instead if token not found. */
|
||||
Optional = 0b1000,
|
||||
}
|
||||
|
||||
/**
|
||||
* This enum is an exact copy of the `InjectFlags` enum above, but the difference is that this is a
|
||||
* const enum, so actual enum values would be inlined in generated code. The `InjectFlags` enum can
|
||||
* be turned into a const enum when ViewEngine is removed (see TODO at the `InjectFlags` enum
|
||||
* above). The benefit of inlining is that we can use these flags at the top level without affecting
|
||||
* tree-shaking (see "no-toplevel-property-access" tslint rule for more info).
|
||||
* Keep this enum in sync with `InjectFlags` enum above.
|
||||
*/
|
||||
export const enum InternalInjectFlags {
|
||||
/** Check self and check parent injector if needed */
|
||||
Default = 0b0000,
|
||||
|
||||
/**
|
||||
* Specifies that an injector should retrieve a dependency from any injector until reaching the
|
||||
* host element of the current component. (Only used with Element Injector)
|
||||
*/
|
||||
Host = 0b0001,
|
||||
|
||||
/** Don't ascend to ancestors of the node requesting injection. */
|
||||
Self = 0b0010,
|
||||
|
||||
/** Skip the node that is requesting injection. */
|
||||
SkipSelf = 0b0100,
|
||||
|
||||
/** Inject `defaultValue` instead if token not found. */
|
||||
Optional = 0b1000,
|
||||
}
|
|
@ -8,6 +8,9 @@
|
|||
|
||||
import {makeParamDecorator} from '../util/decorators';
|
||||
|
||||
import {attachInjectFlag} from './injector_compatibility';
|
||||
import {DecoratorFlags, InternalInjectFlags} from './interface/injector';
|
||||
|
||||
|
||||
/**
|
||||
* Type of the Inject decorator / constructor function.
|
||||
|
@ -54,8 +57,10 @@ export interface Inject {
|
|||
* @Annotation
|
||||
* @publicApi
|
||||
*/
|
||||
export const Inject: InjectDecorator = makeParamDecorator('Inject', (token: any) => ({token}));
|
||||
|
||||
export const Inject: InjectDecorator = attachInjectFlag(
|
||||
// Disable tslint because `DecoratorFlags` is a const enum which gets inlined.
|
||||
// tslint:disable-next-line: no-toplevel-property-access
|
||||
makeParamDecorator('Inject', (token: any) => ({token})), DecoratorFlags.Inject);
|
||||
|
||||
/**
|
||||
* Type of the Optional decorator / constructor function.
|
||||
|
@ -97,7 +102,10 @@ export interface Optional {}
|
|||
* @Annotation
|
||||
* @publicApi
|
||||
*/
|
||||
export const Optional: OptionalDecorator = makeParamDecorator('Optional');
|
||||
export const Optional: OptionalDecorator =
|
||||
// Disable tslint because `InternalInjectFlags` is a const enum which gets inlined.
|
||||
// tslint:disable-next-line: no-toplevel-property-access
|
||||
attachInjectFlag(makeParamDecorator('Optional'), InternalInjectFlags.Optional);
|
||||
|
||||
/**
|
||||
* Type of the Self decorator / constructor function.
|
||||
|
@ -142,7 +150,10 @@ export interface Self {}
|
|||
* @Annotation
|
||||
* @publicApi
|
||||
*/
|
||||
export const Self: SelfDecorator = makeParamDecorator('Self');
|
||||
export const Self: SelfDecorator =
|
||||
// Disable tslint because `InternalInjectFlags` is a const enum which gets inlined.
|
||||
// tslint:disable-next-line: no-toplevel-property-access
|
||||
attachInjectFlag(makeParamDecorator('Self'), InternalInjectFlags.Self);
|
||||
|
||||
|
||||
/**
|
||||
|
@ -187,7 +198,10 @@ export interface SkipSelf {}
|
|||
* @Annotation
|
||||
* @publicApi
|
||||
*/
|
||||
export const SkipSelf: SkipSelfDecorator = makeParamDecorator('SkipSelf');
|
||||
export const SkipSelf: SkipSelfDecorator =
|
||||
// Disable tslint because `InternalInjectFlags` is a const enum which gets inlined.
|
||||
// tslint:disable-next-line: no-toplevel-property-access
|
||||
attachInjectFlag(makeParamDecorator('SkipSelf'), InternalInjectFlags.SkipSelf);
|
||||
|
||||
/**
|
||||
* Type of the `Host` decorator / constructor function.
|
||||
|
@ -227,4 +241,7 @@ export interface Host {}
|
|||
* @Annotation
|
||||
* @publicApi
|
||||
*/
|
||||
export const Host: HostDecorator = makeParamDecorator('Host');
|
||||
export const Host: HostDecorator =
|
||||
// Disable tslint because `InternalInjectFlags` is a const enum which gets inlined.
|
||||
// tslint:disable-next-line: no-toplevel-property-access
|
||||
attachInjectFlag(makeParamDecorator('Host'), InternalInjectFlags.Host);
|
|
@ -236,9 +236,6 @@
|
|||
{
|
||||
"name": "FormsModule"
|
||||
},
|
||||
{
|
||||
"name": "Host"
|
||||
},
|
||||
{
|
||||
"name": "INJECTOR"
|
||||
},
|
||||
|
@ -569,9 +566,6 @@
|
|||
{
|
||||
"name": "SelectMultipleControlValueAccessor"
|
||||
},
|
||||
{
|
||||
"name": "Self"
|
||||
},
|
||||
{
|
||||
"name": "ShadowDomRenderer"
|
||||
},
|
||||
|
@ -743,6 +737,9 @@
|
|||
{
|
||||
"name": "applyView"
|
||||
},
|
||||
{
|
||||
"name": "attachInjectFlag"
|
||||
},
|
||||
{
|
||||
"name": "attachPatchData"
|
||||
},
|
||||
|
|
|
@ -5,18 +5,12 @@
|
|||
{
|
||||
"name": "EMPTY_ARRAY"
|
||||
},
|
||||
{
|
||||
"name": "Host"
|
||||
},
|
||||
{
|
||||
"name": "INJECTOR"
|
||||
},
|
||||
{
|
||||
"name": "INJECTOR_SCOPE"
|
||||
},
|
||||
{
|
||||
"name": "Inject"
|
||||
},
|
||||
{
|
||||
"name": "InjectFlags"
|
||||
},
|
||||
|
@ -50,21 +44,12 @@
|
|||
{
|
||||
"name": "NullInjector"
|
||||
},
|
||||
{
|
||||
"name": "Optional"
|
||||
},
|
||||
{
|
||||
"name": "R3Injector"
|
||||
},
|
||||
{
|
||||
"name": "ScopedService"
|
||||
},
|
||||
{
|
||||
"name": "Self"
|
||||
},
|
||||
{
|
||||
"name": "SkipSelf"
|
||||
},
|
||||
{
|
||||
"name": "THROW_IF_NOT_FOUND"
|
||||
},
|
||||
|
@ -113,9 +98,6 @@
|
|||
{
|
||||
"name": "isValueProvider"
|
||||
},
|
||||
{
|
||||
"name": "makeParamDecorator"
|
||||
},
|
||||
{
|
||||
"name": "makeRecord"
|
||||
},
|
||||
|
|
|
@ -290,9 +290,6 @@
|
|||
{
|
||||
"name": "HashLocationStrategy"
|
||||
},
|
||||
{
|
||||
"name": "Host"
|
||||
},
|
||||
{
|
||||
"name": "INITIAL_VALUE"
|
||||
},
|
||||
|
@ -731,9 +728,6 @@
|
|||
{
|
||||
"name": "SecurityContext"
|
||||
},
|
||||
{
|
||||
"name": "Self"
|
||||
},
|
||||
{
|
||||
"name": "ShadowDomRenderer"
|
||||
},
|
||||
|
@ -971,6 +965,9 @@
|
|||
{
|
||||
"name": "applyView"
|
||||
},
|
||||
{
|
||||
"name": "attachInjectFlag"
|
||||
},
|
||||
{
|
||||
"name": "attachPatchData"
|
||||
},
|
||||
|
|
|
@ -221,6 +221,9 @@
|
|||
{
|
||||
"name": "assertTemplate"
|
||||
},
|
||||
{
|
||||
"name": "attachInjectFlag"
|
||||
},
|
||||
{
|
||||
"name": "attachPatchData"
|
||||
},
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {InjectFlags, InternalInjectFlags} from '../../src/di/interface/injector';
|
||||
|
||||
describe('InjectFlags', () => {
|
||||
it('should always match InternalInjectFlags', () => {
|
||||
expect(InjectFlags.Default).toEqual(InternalInjectFlags.Default as number);
|
||||
expect(InjectFlags.Host).toEqual(InternalInjectFlags.Host as number);
|
||||
expect(InjectFlags.Self).toEqual(InternalInjectFlags.Self as number);
|
||||
expect(InjectFlags.SkipSelf).toEqual(InternalInjectFlags.SkipSelf as number);
|
||||
expect(InjectFlags.Optional).toEqual(InternalInjectFlags.Optional as number);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue