perf(core): Remove decorator DSL which depends on Reflect

BREAKING CHANGE

It is no longer possible to declare classes in this format.

```
Component({...}).
Class({
  constructor: function() {...}
})
```

This format would only work with JIT and with ES5. This mode doesn’t
allow build tools like Webpack to process and optimize the code, which
results in prohibitively large bundles. We are removing this API
because we are trying to ensure that everyone is on the fast path by
default, and it is not possible to get on the fast path using the ES5
DSL. The replacement is to use TypeScript and `@Decorator` format.

```
@Component({...})
class {
  constructor() {...}
}
```
This commit is contained in:
Miško Hevery
2017-08-08 14:03:27 -07:00
committed by Hans
parent 679608db65
commit cac130eff9
12 changed files with 456 additions and 755 deletions

View File

@ -13,7 +13,7 @@
*/
export * from './metadata';
export * from './version';
export {Class, ClassDefinition, TypeDecorator} from './util/decorators';
export {TypeDecorator} from './util/decorators';
export * from './di';
export {createPlatform, assertPlatform, destroyPlatform, getPlatform, PlatformRef, ApplicationRef, enableProdMode, isDevMode, createPlatformFactory, NgProbeToken} from './application_ref';
export {APP_ID, PACKAGE_ROOT_URL, PLATFORM_INITIALIZER, PLATFORM_ID, APP_BOOTSTRAP_LISTENER} from './application_tokens';

View File

@ -277,6 +277,7 @@ export abstract class ReflectiveInjector implements Injector {
}
export class ReflectiveInjector_ implements ReflectiveInjector {
private static INJECTOR_KEY = ReflectiveKey.get(Injector);
/** @internal */
_constructionCounter: number = 0;
/** @internal */
@ -389,7 +390,7 @@ export class ReflectiveInjector_ implements ReflectiveInjector {
}
private _getByKey(key: ReflectiveKey, visibility: Self|SkipSelf|null, notFoundValue: any): any {
if (key === INJECTOR_KEY) {
if (key === ReflectiveInjector_.INJECTOR_KEY) {
return this;
}
@ -463,8 +464,6 @@ export class ReflectiveInjector_ implements ReflectiveInjector {
toString(): string { return this.displayName; }
}
const INJECTOR_KEY = ReflectiveKey.get(Injector);
function _mapProviders(injector: ReflectiveInjector_, fn: Function): any[] {
const res: any[] = new Array(injector._providers.length);
for (let i = 0; i < injector._providers.length; ++i) {

View File

@ -73,18 +73,6 @@ export interface AttributeDecorator {
*
* {@example core/ts/metadata/metadata.ts region='attributeFactory'}
*
* ### Example as ES5 DSL
*
* ```
* var MyComponent = ng
* .Component({...})
* .Class({
* constructor: [new ng.Attribute('title'), function(title) {
* ...
* }]
* })
* ```
*
* ### Example as ES5 annotation
*
* ```

View File

@ -13,8 +13,20 @@ export interface PlatformReflectionCapabilities {
isReflectionEnabled(): boolean;
factory(type: Type<any>): Function;
hasLifecycleHook(type: any, lcProperty: string): boolean;
/**
* Return a list of annotations/types for constructor parameters
*/
parameters(type: Type<any>): any[][];
/**
* Return a list of annotations declared on the class
*/
annotations(type: Type<any>): any[];
/**
* Return a object literal which describes the annotations on Class fields/properties.
*/
propMetadata(typeOrFunc: Type<any>): {[key: string]: any[]};
getter(name: string): GetterFn;
setter(name: string): SetterFn;

View File

@ -8,9 +8,12 @@
import {Type, isType} from '../type';
import {global, stringify} from '../util';
import {ANNOTATIONS, PARAMETERS, PROP_METADATA} from '../util/decorators';
import {PlatformReflectionCapabilities} from './platform_reflection_capabilities';
import {GetterFn, MethodFn, SetterFn} from './types';
/**
* Attention: This regex has to hold even if the code is minified!
*/
@ -85,12 +88,11 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
}
// API for metadata created by invoking the decorators.
if (this._reflect != null && this._reflect.getOwnMetadata != null) {
const paramAnnotations = this._reflect.getOwnMetadata('parameters', type);
const paramTypes = this._reflect.getOwnMetadata('design:paramtypes', type);
if (paramTypes || paramAnnotations) {
return this._zipTypesAndAnnotations(paramTypes, paramAnnotations);
}
const paramAnnotations = type.hasOwnProperty(PARAMETERS) && (type as any)[PARAMETERS];
const paramTypes = this._reflect && this._reflect.getOwnMetadata &&
this._reflect.getOwnMetadata('design:paramtypes', type);
if (paramTypes || paramAnnotations) {
return this._zipTypesAndAnnotations(paramTypes, paramAnnotations);
}
// If a class has no decorators, at least create metadata
@ -130,8 +132,8 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
}
// API for metadata created by invoking the decorators.
if (this._reflect && this._reflect.getOwnMetadata) {
return this._reflect.getOwnMetadata('annotations', typeOrFunc);
if (typeOrFunc.hasOwnProperty(ANNOTATIONS)) {
return (typeOrFunc as any)[ANNOTATIONS];
}
return null;
}
@ -169,8 +171,8 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
}
// API for metadata created by invoking the decorators.
if (this._reflect && this._reflect.getOwnMetadata) {
return this._reflect.getOwnMetadata('propMetadata', typeOrFunc);
if (typeOrFunc.hasOwnProperty(PROP_METADATA)) {
return (typeOrFunc as any)[PROP_METADATA];
}
return null;
}

View File

@ -7,54 +7,12 @@
*/
import {Type} from '../type';
import {global, stringify} from '../util';
let _nextClassId = 0;
const Reflect = global['Reflect'];
/**
* Declares the interface to be used with {@link Class}.
*
* @stable
*/
export type ClassDefinition = {
/**
* Optional argument for specifying the superclass.
*/
extends?: Type<any>;
/**
* Required constructor function for a class.
*
* The function may be optionally wrapped in an `Array`, in which case additional parameter
* annotations may be specified.
* The number of arguments and the number of parameter annotations must match.
*
* See {@link Class} for example of usage.
*/
constructor: Function | any[];
} &
{
/**
* Other methods on the class. Note that values should have type 'Function' but TS requires
* all properties to have a narrower type than the index signature.
*/
[x: string]: Type<any>|Function|any[];
};
/**
* An interface implemented by all Angular type decorators, which allows them to be used as ES7
* decorators as well as
* Angular DSL syntax.
*
* DSL syntax:
*
* ```
* var MyClass = ng
* .Component({...})
* .Class({...});
* ```
*
* ES7 syntax:
*
* ```
@ -74,189 +32,11 @@ export interface TypeDecorator {
// so we cannot declare this interface as a subtype.
// see https://github.com/angular/angular/issues/3379#issuecomment-126169417
(target: Object, propertyKey?: string|symbol, parameterIndex?: number): void;
/**
* Storage for the accumulated annotations so far used by the DSL syntax.
*
* Used by {@link Class} to annotate the generated class.
*/
annotations: any[];
/**
* Generate a class from the definition and annotate it with {@link TypeDecorator#annotations}.
*/
Class(obj: ClassDefinition): Type<any>;
}
function extractAnnotation(annotation: any): any {
if (typeof annotation === 'function' && annotation.hasOwnProperty('annotation')) {
// it is a decorator, extract annotation
annotation = annotation.annotation;
}
return annotation;
}
function applyParams(fnOrArray: Function | any[] | undefined, key: string): Function {
if (fnOrArray === Object || fnOrArray === String || fnOrArray === Function ||
fnOrArray === Number || fnOrArray === Array) {
throw new Error(`Can not use native ${stringify(fnOrArray)} as constructor`);
}
if (typeof fnOrArray === 'function') {
return fnOrArray;
}
if (Array.isArray(fnOrArray)) {
const annotations: any[] = fnOrArray as any[];
const annoLength = annotations.length - 1;
const fn: Function = fnOrArray[annoLength];
if (typeof fn !== 'function') {
throw new Error(
`Last position of Class method array must be Function in key ${key} was '${stringify(fn)}'`);
}
if (annoLength != fn.length) {
throw new Error(
`Number of annotations (${annoLength}) does not match number of arguments (${fn.length}) in the function: ${stringify(fn)}`);
}
const paramsAnnotations: any[][] = [];
for (let i = 0, ii = annotations.length - 1; i < ii; i++) {
const paramAnnotations: any[] = [];
paramsAnnotations.push(paramAnnotations);
const annotation = annotations[i];
if (Array.isArray(annotation)) {
for (let j = 0; j < annotation.length; j++) {
paramAnnotations.push(extractAnnotation(annotation[j]));
}
} else if (typeof annotation === 'function') {
paramAnnotations.push(extractAnnotation(annotation));
} else {
paramAnnotations.push(annotation);
}
}
Reflect.defineMetadata('parameters', paramsAnnotations, fn);
return fn;
}
throw new Error(
`Only Function or Array is supported in Class definition for key '${key}' is '${stringify(fnOrArray)}'`);
}
/**
* Provides a way for expressing ES6 classes with parameter annotations in ES5.
*
* ## Basic Example
*
* ```
* var Greeter = ng.Class({
* constructor: function(name) {
* this.name = name;
* },
*
* greet: function() {
* alert('Hello ' + this.name + '!');
* }
* });
* ```
*
* is equivalent to ES6:
*
* ```
* class Greeter {
* constructor(name) {
* this.name = name;
* }
*
* greet() {
* alert('Hello ' + this.name + '!');
* }
* }
* ```
*
* or equivalent to ES5:
*
* ```
* var Greeter = function (name) {
* this.name = name;
* }
*
* Greeter.prototype.greet = function () {
* alert('Hello ' + this.name + '!');
* }
* ```
*
* ### Example with parameter annotations
*
* ```
* var MyService = ng.Class({
* constructor: [String, [new Optional(), Service], function(name, myService) {
* ...
* }]
* });
* ```
*
* is equivalent to ES6:
*
* ```
* class MyService {
* constructor(name: string, @Optional() myService: Service) {
* ...
* }
* }
* ```
*
* ### Example with inheritance
*
* ```
* var Shape = ng.Class({
* constructor: (color) {
* this.color = color;
* }
* });
*
* var Square = ng.Class({
* extends: Shape,
* constructor: function(color, size) {
* Shape.call(this, color);
* this.size = size;
* }
* });
* ```
* @suppress {globalThis}
* @stable
*/
export function Class(clsDef: ClassDefinition): Type<any> {
const constructor = applyParams(
clsDef.hasOwnProperty('constructor') ? clsDef.constructor : undefined, 'constructor');
let proto = constructor.prototype;
if (clsDef.hasOwnProperty('extends')) {
if (typeof clsDef.extends === 'function') {
(<Function>constructor).prototype = proto =
Object.create((<Function>clsDef.extends).prototype);
} else {
throw new Error(
`Class definition 'extends' property must be a constructor function was: ${stringify(clsDef.extends)}`);
}
}
for (const key in clsDef) {
if (key !== 'extends' && key !== 'prototype' && clsDef.hasOwnProperty(key)) {
proto[key] = applyParams(clsDef[key], key);
}
}
if (this && this.annotations instanceof Array) {
Reflect.defineMetadata('annotations', this.annotations, constructor);
}
const constructorName = constructor['name'];
if (!constructorName || constructorName === 'constructor') {
(constructor as any)['overriddenName'] = `class${_nextClassId++}`;
}
return <Type<any>>constructor;
}
export const ANNOTATIONS = '__annotations__';
export const PARAMETERS = '__paramaters__';
export const PROP_METADATA = '__prop__metadata__';
/**
* @suppress {globalThis}
@ -268,27 +48,21 @@ export function makeDecorator(
const metaCtor = makeMetadataCtor(props);
function DecoratorFactory(objOrType: any): (cls: any) => any {
if (!(Reflect && Reflect.getOwnMetadata)) {
throw 'reflect-metadata shim is required when using class decorators';
}
if (this instanceof DecoratorFactory) {
metaCtor.call(this, objOrType);
return this;
}
const annotationInstance = new (<any>DecoratorFactory)(objOrType);
const chainAnnotation =
typeof this === 'function' && Array.isArray(this.annotations) ? this.annotations : [];
chainAnnotation.push(annotationInstance);
const TypeDecorator: TypeDecorator = <TypeDecorator>function TypeDecorator(cls: Type<any>) {
const annotations = Reflect.getOwnMetadata('annotations', cls) || [];
// Use of Object.defineProperty is important since it creates non-enumerable property which
// prevents the property is copied during subclassing.
const annotations = cls.hasOwnProperty(ANNOTATIONS) ?
(cls as any)[ANNOTATIONS] :
Object.defineProperty(cls, ANNOTATIONS, {value: []})[ANNOTATIONS];
annotations.push(annotationInstance);
Reflect.defineMetadata('annotations', annotations, cls);
return cls;
};
TypeDecorator.annotations = chainAnnotation;
TypeDecorator.Class = Class;
if (chainFn) chainFn(TypeDecorator);
return TypeDecorator;
}
@ -327,7 +101,11 @@ export function makeParamDecorator(
return ParamDecorator;
function ParamDecorator(cls: any, unusedKey: any, index: number): any {
const parameters: (any[] | null)[] = Reflect.getOwnMetadata('parameters', cls) || [];
// Use of Object.defineProperty is important since it creates non-enumerable property which
// prevents the property is copied during subclassing.
const parameters = cls.hasOwnProperty(PARAMETERS) ?
(cls as any)[PARAMETERS] :
Object.defineProperty(cls, PARAMETERS, {value: []})[PARAMETERS];
// there might be gaps if some in between parameters do not have annotations.
// we pad with nulls.
@ -335,10 +113,7 @@ export function makeParamDecorator(
parameters.push(null);
}
parameters[index] = parameters[index] || [];
parameters[index] !.push(annotationInstance);
Reflect.defineMetadata('parameters', parameters, cls);
(parameters[index] = parameters[index] || []).push(annotationInstance);
return cls;
}
}
@ -363,10 +138,14 @@ export function makePropDecorator(
const decoratorInstance = new (<any>PropDecoratorFactory)(...args);
return function PropDecorator(target: any, name: string) {
const meta = Reflect.getOwnMetadata('propMetadata', target.constructor) || {};
const constructor = target.constructor;
// Use of Object.defineProperty is important since it creates non-enumerable property which
// prevents the property is copied during subclassing.
const meta = constructor.hasOwnProperty(PROP_METADATA) ?
(constructor as any)[PROP_METADATA] :
Object.defineProperty(constructor, PROP_METADATA, {value: {}})[PROP_METADATA];
meta[name] = meta.hasOwnProperty(name) && meta[name] || [];
meta[name].unshift(decoratorInstance);
Reflect.defineMetadata('propMetadata', meta, target.constructor);
};
}