UNPKG

@vert/core

Version:

Library to build OOP applications which are based on Vue.

773 lines (752 loc) 27.1 kB
export { Prop, Inject as VueInject, Provide as VueProvide, Watch } from 'vue-property-decorator'; import Vue from 'vue'; /* eslint-disable no-redeclare */ var fakeArray = { __proto__: [] }; var hasProto = fakeArray instanceof Array; function isPrimitive(value) { var type = typeof value; return value == null || (type !== 'object' && type !== 'function'); } function warn(message) { if (typeof console !== 'undefined') { console.warn('[vue-class-component] ' + message); } } function collectDataFromConstructor(vm, Component) { // override _init to prevent to init as Vue instance var originalInit = Component.prototype._init; Component.prototype._init = function () { var _this = this; // proxy to actual vm var keys = Object.getOwnPropertyNames(vm); // 2.2.0 compat (props are no longer exposed as self properties) if (vm.$options.props) { for (var key in vm.$options.props) { // eslint-disable-next-line no-prototype-builtins if (!vm.hasOwnProperty(key)) { keys.push(key); } } } keys.forEach(function (key) { if (key.charAt(0) !== '_') { Object.defineProperty(_this, key, { get: function () { return vm[key]; }, set: function (value) { vm[key] = value; }, configurable: true }); } }); }; // should be acquired class property values var data = new Component(); // restore original _init to avoid memory leak (#209) Component.prototype._init = originalInit; // create plain data object var plainData = {}; Object.keys(data).forEach(function (key) { if (data[key] !== undefined) { plainData[key] = data[key]; } }); if (process.env.NODE_ENV !== 'production') { if (!(Component.prototype instanceof Vue) && Object.keys(plainData).length > 0) { warn('Component class must inherit Vue or its descendant class ' + 'when class property is used.'); } } return plainData; } // The rational behind the verbose Reflect-feature check below is the fact that there are polyfills // which add an implementation for Reflect.defineMetadata but not for Reflect.getOwnMetadataKeys. // Without this check consumers will encounter hard to track down runtime errors. // @ts-ignore var reflectionIsSupported = typeof Reflect !== 'undefined' && Reflect.defineMetadata && Reflect.getOwnMetadataKeys; function copyReflectionMetadata(to, from) { forwardMetadata(to, from); Object.getOwnPropertyNames(from.prototype).forEach(function (key) { forwardMetadata(to.prototype, from.prototype, key); }); Object.getOwnPropertyNames(from).forEach(function (key) { forwardMetadata(to, from, key); }); } function forwardMetadata(to, from, propertyKey) { // @ts-ignore var metaKeys = propertyKey ? Reflect.getOwnMetadataKeys(from, propertyKey) : Reflect.getOwnMetadataKeys(from); metaKeys.forEach(function (metaKey) { // @ts-ignore var metadata = propertyKey ? Reflect.getOwnMetadata(metaKey, from, propertyKey) : Reflect.getOwnMetadata(metaKey, from); if (propertyKey) { // @ts-ignore Reflect.defineMetadata(metaKey, metadata, to, propertyKey); } else { // @ts-ignore Reflect.defineMetadata(metaKey, metadata, to); } }); } var $internalHooks = [ 'data', 'beforeCreate', 'created', 'beforeMount', 'mounted', 'beforeDestroy', 'destroyed', 'beforeUpdate', 'updated', 'activated', 'deactivated', 'render', 'errorCaptured', 'serverPrefetch' // 2.6 ]; function componentFactory(Component, options) { if (options === void 0) { options = {}; } options.name = options.name || Component._componentTag || Component.name; // prototype props. var proto = Component.prototype; Object.getOwnPropertyNames(proto).forEach(function (key) { if (key === 'constructor') { return; } // hooks if ($internalHooks.indexOf(key) > -1) { options[key] = proto[key]; return; } var descriptor = Object.getOwnPropertyDescriptor(proto, key); if (descriptor.value !== undefined) { // methods if (typeof descriptor.value === 'function') { (options.methods || (options.methods = {}))[key] = descriptor.value; } else { // typescript decorated data (options.mixins || (options.mixins = [])).push({ data: function () { var _a; return _a = {}, _a[key] = descriptor.value, _a; } }); } } else if (descriptor.get || descriptor.set) { // computed properties (options.computed || (options.computed = {}))[key] = { get: descriptor.get, set: descriptor.set }; } }); (options.mixins || (options.mixins = [])).push({ data: function () { return collectDataFromConstructor(this, Component); } }); // decorate options var decorators = Component.__decorators__; if (decorators) { decorators.forEach(function (fn) { return fn(options); }); delete Component.__decorators__; } // find super var superProto = Object.getPrototypeOf(Component.prototype); var Super = superProto instanceof Vue ? superProto.constructor : Vue; var Extended = Super.extend(options); forwardStaticMembers(Extended, Component, Super); if (reflectionIsSupported) { copyReflectionMetadata(Extended, Component); } return Extended; } var reservedPropertyNames = [ // Unique id 'cid', // Super Vue constructor 'super', // Component options that will be used by the component 'options', 'superOptions', 'extendOptions', 'sealedOptions', // Private assets 'component', 'directive', 'filter' ]; function forwardStaticMembers(Extended, Original, Super) { // We have to use getOwnPropertyNames since Babel registers methods as non-enumerable Object.getOwnPropertyNames(Original).forEach(function (key) { // `prototype` should not be overwritten if (key === 'prototype') { return; } // Some browsers does not allow reconfigure built-in properties var extendedDescriptor = Object.getOwnPropertyDescriptor(Extended, key); if (extendedDescriptor && !extendedDescriptor.configurable) { return; } var descriptor = Object.getOwnPropertyDescriptor(Original, key); // If the user agent does not support `__proto__` or its family (IE <= 10), // the sub class properties may be inherited properties from the super class in TypeScript. // We need to exclude such properties to prevent to overwrite // the component options object which stored on the extended constructor (See #192). // If the value is a referenced value (object or function), // we can check equality of them and exclude it if they have the same reference. // If it is a primitive value, it will be forwarded for safety. if (!hasProto) { // Only `cid` is explicitly exluded from property forwarding // because we cannot detect whether it is a inherited property or not // on the no `__proto__` environment even though the property is reserved. if (key === 'cid') { return; } var superDescriptor = Object.getOwnPropertyDescriptor(Super, key); if (!isPrimitive(descriptor.value) && superDescriptor && superDescriptor.value === descriptor.value) { return; } } // Warn if the users manually declare reserved properties if (process.env.NODE_ENV !== 'production' && reservedPropertyNames.indexOf(key) >= 0) { warn("Static property name '".concat(key, "' declared on class '").concat(Original.name, "' ") + 'conflicts with reserved property name of Vue internal. ' + 'It may cause unexpected behavior of the component. Consider renaming the property.'); } Object.defineProperty(Extended, key, descriptor); }); } function registerHooks(keys) { $internalHooks.push.apply($internalHooks, keys); } /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ function __spreadArray(to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); } var ReflectionUtils = /** @class */ (function () { function ReflectionUtils() { } ReflectionUtils.getProvidersFromParams = function (target) { // @ts-ignore var result = Reflect.getMetadata('design:paramtypes', target); return (result || []).filter(function (item) { return typeof item === 'function' && item !== Object && item !== Function; }); }; return ReflectionUtils; }()); var INJECTED_FLAG = 'Vert:Injected'; var INJECTED_PARAMS_METADATA_KEY = 'Vert:ParamTypes'; /** * Injectable decorator. */ function Injectable() { return function (Provider) { // @ts-ignore var types = Reflect.getMetadata('design:paramtypes', Provider); // @ts-ignore Reflect.defineMetadata(INJECTED_FLAG, true, Provider); // @ts-ignore Reflect.defineMetadata(INJECTED_PARAMS_METADATA_KEY, types, Provider); }; } /** * Check whether a class is injected. * * @param target */ function checkIsInjected(target) { // @ts-ignore return Reflect.getMetadata(INJECTED_FLAG, target) === true; } /** * Standalone injector class. */ var Injector = /** @class */ (function () { function Injector() { /** * This map keeps singleton provider and its instance. */ this.singletonMap = new WeakMap(); /** * This map keeps transient provider. */ this.transient = new WeakMap(); } /** * Create a new class injector. * * @return {Injector} */ Injector.create = function () { return new Injector(); }; /** * Check whether a class is injected. * * @param Provider */ Injector.checkIsInjected = function (Provider) { if (!checkIsInjected(Provider)) { throw new Error("[@ver/core] \"".concat(Provider.name, "\" is not an injectable class.")); } }; /** * Register target as a singleton provider. * You will get the same instance in every single initialization. * * @param {TConstructor[]} Providers */ Injector.prototype.addSingleton = function () { var _this = this; var Providers = []; for (var _i = 0; _i < arguments.length; _i++) { Providers[_i] = arguments[_i]; } Providers.forEach(function (Provider) { Injector.checkIsInjected(Provider); if (_this.transient.has(Provider)) { throw new Error("[@vert/core] \"".concat(Provider.name, "\" has been registered as transient provider.")); } _this.singletonMap.set(Provider, null); }); return this; }; /** * Register target as a transient provider. * You will get different instances in every single initialization. * * @param {TConstructor[]} Providers */ Injector.prototype.addTransient = function () { var _this = this; var Providers = []; for (var _i = 0; _i < arguments.length; _i++) { Providers[_i] = arguments[_i]; } Providers.forEach(function (Provider) { Injector.checkIsInjected(Provider); if (_this.singletonMap.has(Provider)) { throw new Error("[@vert/core] \"".concat(Provider.name, "\" has been registered as singleton provider.")); } _this.transient.set(Provider, null); }); return this; }; /** * Get target instance from injector by providing provider. * * @param {{new(...args): T}} Provider * @return {T} */ Injector.prototype.get = function (Provider) { var _this = this; var isSingletonProvider = this.singletonMap.has(Provider); var isTransientProvider = this.transient.has(Provider); if (!isSingletonProvider && !isTransientProvider) { return null; } switch (true) { case isSingletonProvider: { var instance = this.singletonMap.get(Provider); if (!instance) { var dependencyInstance = ReflectionUtils .getProvidersFromParams(Provider) .map(function (item) { return _this.get(item); }); instance = new (Provider.bind.apply(Provider, __spreadArray([void 0], dependencyInstance, false)))(); this.singletonMap.set(Provider, instance); } return instance; } case isTransientProvider: { var dependencyInstance = ReflectionUtils .getProvidersFromParams(Provider) .map(function (item) { return _this.get(item); }); var instance = new (Provider.bind.apply(Provider, __spreadArray([void 0], dependencyInstance, false)))(); return instance; } default: return null; } }; /** * Check whether injector has registered this provider. * * @param target */ Injector.prototype.has = function (target) { return this.transient.has(target) || this.singletonMap.has(target); }; return Injector; }()); /** * Singleton injector holds all singleton instance. */ var GlobalInjector = /** @class */ (function () { function GlobalInjector() { } /** * Get target instance from injector by providing provider. * * @param {{new(...args): T}} Provider * @return {T} */ GlobalInjector.get = function (Provider) { return GlobalInjector.injector.get(Provider); }; /** * Register target as singleton provider into global injector. * * @param Providers */ GlobalInjector.addSingleton = function () { var _a; var Providers = []; for (var _i = 0; _i < arguments.length; _i++) { Providers[_i] = arguments[_i]; } (_a = GlobalInjector.injector).addSingleton.apply(_a, Providers); }; /** * Register target as transient provider into global injector. * * @param Providers */ GlobalInjector.addTransient = function () { var _a; var Providers = []; for (var _i = 0; _i < arguments.length; _i++) { Providers[_i] = arguments[_i]; } (_a = GlobalInjector.injector).addTransient.apply(_a, Providers); }; /** * Check whether injector has registered this provider. * * @param Provider */ GlobalInjector.has = function (Provider) { return GlobalInjector.injector.has(Provider); }; GlobalInjector.injector = Injector.create(); return GlobalInjector; }()); var Logger = /** @class */ (function () { function Logger() { } Logger.warn = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } console.warn.apply(console, __spreadArray(['[@vert/core]'], args, false)); }; Logger.error = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } console.error.apply(console, __spreadArray(['[@vert/core]'], args, false)); }; return Logger; }()); var InjectionUtils = /** @class */ (function () { function InjectionUtils() { } /** * Class a class that has already been injected. * * @param {*} targetClass * @param {TProviders} Providers * @return {*} */ InjectionUtils.createInjectedConstructor = function (targetClass, Providers) { // Return a new constructor. // This new constructor has no params so you can not get any info by using 'design:paramtypes'. var Constructor = function () { var providers = []; Providers.forEach(function (Provider) { if (!GlobalInjector.has(Provider)) { Logger.warn("Provider \"".concat(Provider.name, "\" hasn't been registered in global.")); providers.push(undefined); return; } var instance = GlobalInjector.get(Provider); providers.push(instance); }); return new (targetClass.bind.apply(targetClass, __spreadArray([void 0], providers, false)))(); }; Constructor.prototype = targetClass.prototype; try { Object.defineProperty(Constructor, 'name', { writable: true, configurable: true, value: targetClass.name }); } catch (error) { console.warn('[@Vert/Core] This browser can not redefine property name for injected constructor:', error.message); } return Constructor; }; return InjectionUtils; }()); /* eslint-disable no-redeclare */ var componentId = 1; // Nuxt support. registerHooks([ 'beforeRouteEnter', 'beforeRouteUpdate', 'beforeRouteLeave', 'asyncData', 'fetch', 'head', 'layout', 'meta', 'middleware', 'title', 'transition', 'scrollToTop', 'validate' ]); function Component(param) { if (typeof param === 'function') { var Providers = ReflectionUtils.getProvidersFromParams(param); var Constructor = InjectionUtils.createInjectedConstructor(param, Providers); // Keep targetClass.__decorators__. // "__decorators__" is defined in vue-class-component, and it holds all customized decorators' data // such as @Prop, @Watch, .ect. keepDecorators(param, Constructor); return componentFactory(Constructor, {}); } return function (targetClass) { param = param || {}; var componentName = targetClass.prototype.constructor.name || 'AppComponent-' + componentId++; param = Object.assign({ name: componentName }, param); var Providers = ReflectionUtils.getProvidersFromParams(targetClass); var Constructor = InjectionUtils.createInjectedConstructor(targetClass, Providers); keepDecorators(targetClass, Constructor); var ComponentConstructor = componentFactory(Constructor, param); return ComponentConstructor; }; } /** * Function to keep targetClass.__decorators__. * "__decorators__" is defined in vue-class-component, and it holds all customized decorators' data * such as @Prop, @Watch, .ect. * * @link https://github.com/vuejs/vue-class-component/blob/master/src/component.ts#L59 * * @param {*} targetClass * @param {*} Constructor */ function keepDecorators(targetClass, Constructor) { if (targetClass.__decorators__) { Constructor.__decorators__ = targetClass.__decorators__; } } var TypeUtils = /** @class */ (function () { function TypeUtils() { } TypeUtils.isUndefined = function (target) { return typeof target === 'undefined'; }; TypeUtils.isDefined = function (target) { return !TypeUtils.isUndefined(target); }; TypeUtils.isFunction = function (target) { return typeof target === 'function'; }; return TypeUtils; }()); var appId = 1; /** * App is the basic unit for a project. * * @description * Page is the root member for an app. Create an instance to initialize your app. * * @class App */ var App = /** @class */ (function () { function App(option) { this._element = option.element; this._name = option.name || 'DefaultApp-' + appId++; this._router = option.router; this._store = option.store; this._rootComponent = option.RootComponent; this.initViewModel(option.RootComponent, option.created, option.mounted, option.beforeDestroy); } /** * Register target as a singleton provider in global. * * @static * @template T * @param {TConstructor[]} Providers */ App.addSingleton = function () { var Providers = []; for (var _i = 0; _i < arguments.length; _i++) { Providers[_i] = arguments[_i]; } GlobalInjector.addSingleton.apply(GlobalInjector, Providers); return App; }; /** * Register target as a transient provider in global. * * @static * @template T * @param {TConstructor[]} Providers */ App.addTransient = function () { var Providers = []; for (var _i = 0; _i < arguments.length; _i++) { Providers[_i] = arguments[_i]; } GlobalInjector.addTransient.apply(GlobalInjector, Providers); return App; }; Object.defineProperty(App.prototype, "name", { get: function () { return this._name; }, enumerable: false, configurable: true }); Object.defineProperty(App.prototype, "store", { get: function () { return this._store; }, enumerable: false, configurable: true }); Object.defineProperty(App.prototype, "viewModel", { get: function () { return this._viewModel; }, enumerable: false, configurable: true }); Object.defineProperty(App.prototype, "RootComponent", { get: function () { return this._rootComponent; }, enumerable: false, configurable: true }); App.prototype.initViewModel = function (RootComponent, created, mounted, beforeDestroy) { var option = { name: this.name, render: function (h) { return h(RootComponent); }, created: function () { TypeUtils.isFunction(created) && created(this); }, mounted: function () { TypeUtils.isFunction(mounted) && mounted(this); }, beforeDestroy: function () { TypeUtils.isFunction(beforeDestroy) && beforeDestroy(this); } }; if (TypeUtils.isDefined(this._router)) { Object.assign(option, { router: this._router }); } if (TypeUtils.isDefined(this._store)) { Object.assign(option, { store: this._store }); } this._viewModel = new Vue(option); }; /** * Start up this app. * * @memberof App */ App.prototype.start = function () { if (this._element) { this._viewModel.$mount(this._element); } }; /** * Destroy app. * * @memberof App */ App.prototype.destroy = function () { this._viewModel.$destroy(); }; return App; }()); var isSupportProxy = true; try { // eslint-disable-next-line no-unused-vars var proxy = new Proxy({}, { set: function () { return true; } }); } catch (error) { isSupportProxy = false; if (process.env.NODE_ENV === 'development') { console.warn('[@Vert/core] Your browser doesn\'t support Proxy.'); } } var Data = /** @class */ (function () { function Data() { } Data.createTypeSafetyInstance = function (Constructor) { var args = []; for (var _i = 1; _i < arguments.length; _i++) { args[_i - 1] = arguments[_i]; } var obj = new (Constructor.bind.apply(Constructor, __spreadArray([void 0], args, false)))(); if (!isSupportProxy) { return obj; } return new Proxy(obj, { set: function (target, keyName, value, proxy) { var newType = getType(value); var correctType = getType(target[keyName]); if (newType === correctType) { Reflect.set(target, keyName, value); } else { console.warn("[Warn] Incorrect data type was given to property \"".concat(String(keyName), "\" on \"").concat(Constructor.name, "\":\n") + " \"".concat(value, "\" (").concat(getTypeText(newType), ") was given, but should be a ").concat(getTypeText(correctType), ".")); } // Always return true to avoid error throwing. return true; } }); }; return Data; }()); function getType(target) { return Object.prototype.toString.call(target); } function getTypeText(fullTypeString) { return fullTypeString.replace(/\[object |\]/g, ''); } export { App, Component, Data, Injectable, Injector };