diff --git a/package-lock.json b/package-lock.json index 953fddf..df95416 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5222,6 +5222,12 @@ } } }, + "reflect-metadata": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.12.tgz", + "integrity": "sha512-n+IyV+nGz3+0q3/Yf1ra12KpCyi001bi4XFxSjbiWWjfqb52iTTtpGXmCCAOWWIAn9KEuFZKGqBERHmrtScZ3A==", + "dev": true + }, "regenerate": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", diff --git a/package.json b/package.json index 5b0769a..df971d8 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "chai": "^4.1.2", "css-loader": "^0.28.9", "mocha": "^5.0.1", + "reflect-metadata": "^0.1.12", "rimraf": "^2.6.2", "rollup": "^0.55.5", "rollup-plugin-replace": "^2.0.0", @@ -60,5 +61,6 @@ "vue-loader": "^14.1.1", "vue-template-compiler": "^2.5.13", "webpack": "^3.11.0" - } + }, + "dependencies": {} } diff --git a/src/component.ts b/src/component.ts index d110df1..c07063b 100644 --- a/src/component.ts +++ b/src/component.ts @@ -1,4 +1,5 @@ import Vue, { ComponentOptions } from 'vue' +import { copyReflectionMetadata, reflectionIsSupported, ReflectionMap } from './reflect' import { VueClass, DecoratedClass } from './declarations' import { collectDataFromConstructor } from './data' import { hasProto, isPrimitive, warn } from './util' @@ -23,6 +24,15 @@ export function componentFactory ( Component: VueClass, options: ComponentOptions = {} ): VueClass { + const reflectionMap: ReflectionMap = { + instance: {}, + static: {}, + constructor: [] + } + + if (reflectionIsSupported()) { + reflectionMap.constructor = Reflect.getOwnMetadataKeys(Component) + } options.name = options.name || (Component as any)._componentTag || (Component as any).name // prototype props. const proto = Component.prototype @@ -30,6 +40,11 @@ export function componentFactory ( if (key === 'constructor') { return } + + if (reflectionIsSupported()) { + reflectionMap.instance[key] = Reflect.getOwnMetadataKeys(proto, key) + } + // hooks if ($internalHooks.indexOf(key) > -1) { options[key] = proto[key] @@ -69,7 +84,11 @@ export function componentFactory ( : Vue const Extended = Super.extend(options) - forwardStaticMembers(Extended, Component, Super) + forwardStaticMembersAndCollectReflection(Extended, Component, Super, reflectionMap) + + if (reflectionIsSupported()) { + copyReflectionMetadata(Component, Extended, reflectionMap) + } return Extended } @@ -93,7 +112,12 @@ const reservedPropertyNames = [ 'filter' ] -function forwardStaticMembers (Extended: typeof Vue, Original: typeof Vue, Super: typeof Vue): void { +function forwardStaticMembersAndCollectReflection ( + Extended: typeof Vue, + Original: typeof Vue, + Super: typeof Vue, + reflectionMap: ReflectionMap +): void { // We have to use getOwnPropertyNames since Babel registers methods as non-enumerable Object.getOwnPropertyNames(Original).forEach(key => { // `prototype` should not be overwritten @@ -101,6 +125,10 @@ function forwardStaticMembers (Extended: typeof Vue, Original: typeof Vue, Super return } + if (reflectionIsSupported()) { + reflectionMap.static[key] = Reflect.getOwnMetadataKeys(Original, key) + } + // Some browsers does not allow reconfigure built-in properties const extendedDescriptor = Object.getOwnPropertyDescriptor(Extended, key) if (extendedDescriptor && !extendedDescriptor.configurable) { diff --git a/src/globals.d.ts b/src/globals.d.ts index 89a7ede..eca4ca2 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -3,8 +3,33 @@ * should not expose to userland */ +declare namespace Reflect { + function decorate(decorators: ClassDecorator[], target: Function): Function + function decorate(decorators: (PropertyDecorator | MethodDecorator)[], target: Object, propertyKey: string | symbol, attributes?: PropertyDescriptor): PropertyDescriptor + function metadata(metadataKey: any, metadataValue: any): { + (target: Function): void + (target: Object, propertyKey: string | symbol): void + } + function defineMetadata(metadataKey: any, metadataValue: any, target: Object): void + function defineMetadata(metadataKey: any, metadataValue: any, target: Object, propertyKey: string | symbol): void + function hasMetadata(metadataKey: any, target: Object): boolean + function hasMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): boolean + function hasOwnMetadata(metadataKey: any, target: Object): boolean + function hasOwnMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): boolean + function getMetadata(metadataKey: any, target: Object): any + function getMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): any + function getOwnMetadata(metadataKey: any, target: Object): any + function getOwnMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): any + function getMetadataKeys(target: Object): any[] + function getMetadataKeys(target: Object, propertyKey: string | symbol): any[] + function getOwnMetadataKeys(target: Object): any[] + function getOwnMetadataKeys(target: Object, propertyKey: string | symbol): any[] + function deleteMetadata(metadataKey: any, target: Object): boolean + function deleteMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): boolean + } + declare const process: { env: { NODE_ENV: string } -} \ No newline at end of file +} diff --git a/src/reflect.ts b/src/reflect.ts new file mode 100644 index 0000000..56b7a27 --- /dev/null +++ b/src/reflect.ts @@ -0,0 +1,39 @@ +import { VueConstructor } from 'vue' + +export type StringToArrayMap = { + [key: string]: Array +} + +export type ReflectionMap = { + constructor: Array, + instance: StringToArrayMap, + static: StringToArrayMap +} + +export function reflectionIsSupported () { + return (Reflect && Reflect.defineMetadata) !== undefined +} + +export function copyReflectionMetadata ( + from: VueConstructor, + to: VueConstructor, + reflectionMap: ReflectionMap +) { + shallowCopy(from.prototype, to.prototype, reflectionMap.instance) + shallowCopy(from, to, reflectionMap.static) + shallowCopy(from, to, {'constructor': reflectionMap.constructor}) +} + +function shallowCopy (from: VueConstructor, to: VueConstructor, propertyKeys: StringToArrayMap) { + for (const propertyKey in propertyKeys) { + propertyKeys[propertyKey].forEach((metadataKey) => { + if (propertyKey == 'constructor') { + const metadata = Reflect.getOwnMetadata(metadataKey, from) + Reflect.defineMetadata(metadataKey, metadata, to) + } else { + const metadata = Reflect.getOwnMetadata(metadataKey, from, propertyKey) + Reflect.defineMetadata(metadataKey, metadata, to, propertyKey) + } + }) + } +} diff --git a/test/test.ts b/test/test.ts index 9ff686c..8fb1557 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1,3 +1,4 @@ +import 'reflect-metadata'; import Component, { createDecorator, mixins } from '../lib' import { expect } from 'chai' import * as td from 'testdouble' @@ -369,4 +370,34 @@ describe('vue-class-component', () => { expect(vm.valueA).to.equal('hi') expect(vm.valueB).to.equal(456) }) + + it('copies reflection metadata', function () { + @Component + @Reflect.metadata('worksConstructor', true) + class Test extends Vue { + @Reflect.metadata('worksStatic', true) + static staticValue: string = 'staticValue' + + private _test: boolean = false; + + @Reflect.metadata('worksMethod', true) + test (): void { + void 0 + } + + @Reflect.metadata('worksAccessor', true) + get testAccessor (): boolean { + return this._test + } + + set testAccessor (value: boolean) { + this._test = value + } + } + + expect(Reflect.getOwnMetadata('worksConstructor', Test)).to.equal(true) + expect(Reflect.getOwnMetadata('worksStatic', Test, 'staticValue')).to.equal(true) + expect(Reflect.getOwnMetadata('worksMethod', Test.prototype, 'test')).to.equal(true) + expect(Reflect.getOwnMetadata('worksAccessor', Test.prototype, 'testAccessor')).to.equal(true) + }) })