├── .editorconfig ├── .eslintrc.js ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .jest └── setup.ts ├── LICENSE ├── README.md ├── doc ├── 00.quick-start.md ├── 01.register-service.md ├── 02.create-app.md ├── 03.create-vue-component.md ├── 04.inject-service-into-vue-component.md ├── 05.use-injector.md └── 06.faq.md ├── jest.config.js ├── lib ├── app-component │ └── component-decorator.ts ├── core │ └── index.ts ├── data │ └── index.ts ├── index.ts ├── injection │ ├── injectable.ts │ └── injector.ts ├── internal-injectors │ └── global.ts ├── libs │ ├── vue-class-component │ │ ├── component.ts │ │ ├── data.ts │ │ ├── declarations.ts │ │ ├── index.ts │ │ ├── reflect.ts │ │ └── util.ts │ └── vue-property-decorator.ts ├── shim.d.ts ├── types │ └── index.ts └── utils │ ├── injection-utils.ts │ ├── logger.ts │ ├── reflection-utils.ts │ └── type-utils.ts ├── logo.png ├── package-lock.json ├── package.json ├── rollup.config.js ├── test ├── app.spec.ts ├── component.spec.ts ├── core.spec.ts ├── data.spec.ts └── injector.spec.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | # Use 4 spaces for the Python files 13 | [*.py] 14 | indent_size = 4 15 | max_line_length = 80 16 | 17 | # The JSON files contain newlines inconsistently 18 | [*.json] 19 | insert_final_newline = ignore 20 | 21 | # Minified JavaScript files shouldn't be changed 22 | [**.min.js] 23 | indent_style = ignore 24 | insert_final_newline = ignore 25 | 26 | # Makefiles always use tabs for indentation 27 | [Makefile] 28 | indent_style = tab 29 | 30 | # Batch files use tabs for indentation 31 | [*.bat] 32 | indent_style = tab 33 | 34 | [*.md] 35 | trim_trailing_whitespace = false 36 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true 5 | }, 6 | extends: [ 7 | 'standard' 8 | ], 9 | parser: '@typescript-eslint/parser', 10 | parserOptions: { 11 | ecmaVersion: 13, 12 | sourceType: 'module' 13 | }, 14 | plugins: [ 15 | '@typescript-eslint' 16 | ], 17 | rules: { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | name: Test 4 | 5 | on: [push, pull_request] 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | 11 | strategy: 12 | matrix: 13 | node-version: [16.x] 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v2 18 | 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v1 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | 24 | - name: Install dependencies 25 | run: npm ci 26 | 27 | - name: Run test 28 | run: npm test 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # IDEs. 64 | .idea/ 65 | .vscode/ 66 | 67 | # Sourcemaps. 68 | *.map 69 | 70 | # Dist. 71 | dist/ 72 | 73 | # Coverage report. 74 | .coverage-report/ 75 | 76 | # RollUp. 77 | .rpt2_cache/ 78 | -------------------------------------------------------------------------------- /.jest/setup.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata' 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 LancerComet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | --- 24 | 25 | Copyright (c) 2019 LancerComet 26 | 27 | Anti 996 License Version 1.0 (Draft) 28 | 29 | Permission is hereby granted to any individual or legal entity 30 | obtaining a copy of this licensed work (including the source code, 31 | documentation and/or related items, hereinafter collectively referred 32 | to as the "licensed work"), free of charge, to deal with the licensed 33 | work for any purpose, including without limitation, the rights to use, 34 | reproduce, modify, prepare derivative works of, distribute, publish 35 | and sublicense the licensed work, subject to the following conditions: 36 | 37 | 1. The individual or the legal entity must conspicuously display, 38 | without modification, this License and the notice on each redistributed 39 | or derivative copy of the Licensed Work. 40 | 41 | 2. The individual or the legal entity must strictly comply with all 42 | applicable laws, regulations, rules and standards of the jurisdiction 43 | relating to labor and employment where the individual is physically 44 | located or where the individual was born or naturalized; or where the 45 | legal entity is registered or is operating (whichever is stricter). In 46 | case that the jurisdiction has no such laws, regulations, rules and 47 | standards or its laws, regulations, rules and standards are 48 | unenforceable, the individual or the legal entity are required to 49 | comply with Core International Labor Standards. 50 | 51 | 3. The individual or the legal entity shall not induce or force its 52 | employee(s), whether full-time or part-time, or its independent 53 | contractor(s), in any methods, to agree in oral or written form, to 54 | directly or indirectly restrict, weaken or relinquish his or her 55 | rights or remedies under such laws, regulations, rules and standards 56 | relating to labor and employment as mentioned above, no matter whether 57 | such written or oral agreement are enforceable under the laws of the 58 | said jurisdiction, nor shall such individual or the legal entity 59 | limit, in any methods, the rights of its employee(s) or independent 60 | contractor(s) from reporting or complaining to the copyright holder or 61 | relevant authorities monitoring the compliance of the license about 62 | its violation(s) of the said license. 63 | 64 | THE LICENSED WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 65 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 66 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 67 | IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, 68 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 69 | OTHERWISE, ARISING FROM, OUT OF OR IN ANY WAY CONNECTION WITH THE 70 | LICENSED WORK OR THE USE OR OTHER DEALINGS IN THE LICENSED WORK. 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vert 2 | 3 | Vert is the service container for Vue to build Vue applications in OOP. 4 | 5 | [![npm version](https://badge.fury.io/js/%40vert%2Fcore.svg)](https://badge.fury.io/js/%40vert%2Fcore) 6 | [![test status](https://github.com/vuevert/Vert-Core/workflows/Test/badge.svg)](https://github.com/LancerComet/vue-jsonp/actions) 7 | 8 | ![vert-logo](https://raw.githubusercontent.com/LancerComet/Vert-Core/master/logo.png) 9 | 10 | ## Notice 11 | 12 | This project is only available for Vue 2. For Vue 3, please consider using Composition API instead of OOP-like component. 13 | 14 | ## Introduction 15 | 16 | Vert is a library which is designed for building applications that are based on Vue in OOP. It provides some function and decorators to help you to achieve that goal. In General, it's a service container for Vue and makes your project Angular-like. 17 | 18 | ## Features 19 | 20 | - Build OOP Vue apps by using Angular-like grammar. 21 | - Pure TypeScript Experience. 22 | - Service container for Vue. 23 | - Inject dependencies into Vue components directly. 24 | - Built-in service ([@vert/services](https://github.com/LancerComet/Vert-Services)). 25 | - Available for both [Vue-SSR](https://ssr.vuejs.org) and [Nuxt.js](https://github.com/nuxt/nuxt.js). 26 | 27 | ## Documents 28 | 29 | Please check all documents [here](https://github.com/vuevert/Vert-Core/tree/master/doc). 30 | 31 | ## Demo 32 | 33 | - [vert-vue-ssr-template](https://github.com/LancerComet/vert-vue-ssr-template) 34 | - [vert-demo](https://github.com/LancerComet/Vert-Demo) 35 | 36 | ## Take care of yourself 37 | 38 | I have developed several projects with this tool however please take care of yourself, leave me a issue if there is a bug. 39 | 40 | ## Which projects? 41 | 42 | Some projects for https://live.bilibili.com/ and https://manga.bilibili.com/ 43 | 44 | ## License 45 | 46 | MIT 47 | -------------------------------------------------------------------------------- /doc/00.quick-start.md: -------------------------------------------------------------------------------- 1 | # Quick start 2 | 3 | ## Setup. 4 | 5 | Install the necessaries: 6 | 7 | ```bash 8 | npm install @vert/core reflect-metadata --save 9 | ``` 10 | 11 | Turn on `emitDecoratorMetadata` in your `tsconfig.json`: 12 | 13 | ```json 14 | { 15 | "compilerOptions": { 16 | "emitDecoratorMetadata": true, 17 | "experimentalDecorators": true 18 | } 19 | } 20 | ``` 21 | 22 | Set alias for Vue in webpack if you have multiple Vue packages in your node_modules: 23 | 24 | ```js 25 | const path = require('path') 26 | 27 | { 28 | resolve: { 29 | alias: { 30 | // Absolute path. 31 | 'vue$': path.resolve(__dirname, './node_modulesvue/dist/vue.esm.js') 32 | } 33 | } 34 | } 35 | ``` 36 | 37 | Done! 38 | 39 | ## Now let's code. 40 | 41 | First, let's build a vue component and make it be our root component: 42 | 43 | ```html 44 | 45 | 46 | 51 | 52 | 53 | ``` 54 | 55 | ```typescript 56 | // root-component.ts 57 | 58 | import { Component } from '@vert/core' 59 | import Vue from 'vue' 60 | 61 | // Use @Component to make a class into a vue component. 62 | @Component 63 | export default class RootComponent extends Vue { 64 | name: string = '' 65 | } 66 | ``` 67 | 68 | Now in the entry file (such as `index.ts`, `main.ts` or `app.ts`), we need to create an instance of `App`: 69 | 70 | ```typescript 71 | // index.ts 72 | 73 | import 'reflect-metadata' // You should import 'reflect-metadata' first. 74 | 75 | import { App } from '@vert/core' 76 | import RootComponent from './root-component.vue' // Import root-component.vue 77 | 78 | const app = new App({ 79 | element: '#web-app', 80 | RootComponent 81 | }) 82 | 83 | app.start() 84 | ``` 85 | 86 | Now, let's create a service to provide `name` for `RootComponent`: 87 | 88 | ```typescript 89 | // service.employee.ts 90 | 91 | import { Injectable } from '@vert/core' 92 | 93 | /** 94 | * A simple http service. 95 | */ 96 | @Injectable() 97 | class Http { 98 | async get (url) {} 99 | } 100 | 101 | /** 102 | * Employee service. 103 | * 104 | * @class EmployeeService 105 | */ 106 | @Injectable() 107 | class EmployeeService { 108 | async getEmployee (id: number): Promise<{ data: IEmployee, error: Error }> { 109 | let data: IEmployee = null 110 | let error: Error = null 111 | 112 | try { 113 | data = await this.http.get('/employee/v1/' + id) 114 | } catch (err) { 115 | error = err 116 | } 117 | 118 | return { 119 | data, error 120 | } 121 | } 122 | 123 | constructor ( 124 | private http: Http 125 | ) {} 126 | } 127 | 128 | export { 129 | EmployeeService 130 | } 131 | 132 | /** 133 | * An employee. 134 | * 135 | * @interface IEmployee 136 | */ 137 | interface IEmployee { 138 | id: number 139 | name: string 140 | } 141 | ``` 142 | 143 | And update `root-component.ts`: 144 | 145 | ```typescript 146 | // root-component.ts 147 | 148 | import Vue from 'vue' 149 | import { Component } from '@vert/core' 150 | import { EmployeeService } from './service.employee' 151 | 152 | @Component 153 | export default class RootComponent extends Vue { 154 | name: string = '' 155 | 156 | private async getUserData () { 157 | const id = 10 158 | const { data, error } = await this.employeeSrv.getEmployee(id) 159 | if (!error) { 160 | this.name = data.name 161 | } else { 162 | // Handle error... 163 | } 164 | } 165 | 166 | created () { 167 | this.getUserData() 168 | } 169 | 170 | // Inject EmployeeService into component. 171 | constructor ( 172 | private employeeSrv: EmployeeService 173 | ) { 174 | super() 175 | } 176 | } 177 | ``` 178 | 179 | Finally, let's register our service to our app: 180 | 181 | ```typescript 182 | // Entry file. 183 | import 'reflect-metadata' // You should import 'reflect-metadata' first. 184 | 185 | import { App } from '@vert/core' 186 | import { EmployeeService, Http } from './service.employee' // Import services. 187 | import RootComponent from './root-component.vue' // Import root-component.vue 188 | 189 | // Register services for the whole app. 190 | App.addTransient(EmployeeService, Http) 191 | 192 | const app = new App({ 193 | element: '#web-app', 194 | RootComponent 195 | }) 196 | 197 | app.start() 198 | ``` 199 | 200 | That's it! An oop-vue-app has been built, have a nice try! 201 | -------------------------------------------------------------------------------- /doc/01.register-service.md: -------------------------------------------------------------------------------- 1 | # Register service 2 | 3 | Before injecting a service into vue component, you should register it into your app. 4 | 5 | ```typescript 6 | // Your services. 7 | 8 | import { Injectable } from '@vert/core' 9 | 10 | @Injectable() 11 | class HttpService { 12 | } 13 | 14 | @Injectable() 15 | class BookshelfService { 16 | constructor ( 17 | private httpSrv: HttpService 18 | ) {} 19 | } 20 | 21 | @Injectable() 22 | class UserService { 23 | constructor ( 24 | private httpSrv: HttpService 25 | ) {} 26 | } 27 | 28 | export { 29 | BookshelfService, 30 | UserService 31 | } 32 | ``` 33 | 34 | Registration: 35 | 36 | ```typescript 37 | import { App } from '@vert/core' 38 | 39 | // You have these two services and you should register theme for your app. 40 | import { BookshelfService, HttpService, UserService } from './services' 41 | 42 | // Registration. 43 | // You will get different instances of these services in every single initialization. 44 | App.addTransient(BookshelfService, HttpService, UserService) 45 | 46 | // You will get the same instance of these every single service. 47 | App.addSingleton(BookshelfService, HttpService, UserService) 48 | 49 | // For more detail about this behavior, please check "05.use-injector.md". 50 | ``` 51 | 52 | ## API 53 | 54 | ```typescript 55 | /** 56 | * App is the basic unit for a project. 57 | * 58 | * @class App 59 | */ 60 | class App { 61 | /** 62 | * Register target as a singleton provider in global. 63 | * 64 | * @static 65 | * @template T 66 | * @param {TConstructor[]} Providers 67 | */ 68 | static addSingleton (...Providers: TConstructor[]): typeof App 69 | 70 | /** 71 | * Register target as a transient provider in global. 72 | * 73 | * @static 74 | * @template T 75 | * @param {TConstructor[]} Providers 76 | */ 77 | static addTransient (...Providers: TConstructor[]): typeof App 78 | } 79 | ``` 80 | -------------------------------------------------------------------------------- /doc/02.create-app.md: -------------------------------------------------------------------------------- 1 | # Create app. 2 | 3 | App is the entry for an app. 4 | 5 | ```typescript 6 | import { App } from '@vert/core' 7 | import RootComponent from './layout/default.vue' 8 | 9 | const app = new App({ 10 | element: '#my-app', 11 | RootComponent 12 | }) 13 | 14 | app.start() 15 | ``` 16 | 17 | If you want to destroy it: 18 | 19 | ```typescript 20 | app.destroy() 21 | ``` 22 | 23 | ## API 24 | 25 | ```typescript 26 | /** 27 | * App is the basic unit for a project. 28 | * 29 | * @class App 30 | */ 31 | class App { 32 | /** 33 | * Register target as a singleton provider in global. 34 | * 35 | * @static 36 | * @template T 37 | * @param {TConstructor[]} Providers 38 | */ 39 | static addSingleton (...Providers: TConstructor[]): typeof App 40 | 41 | /** 42 | * Register target as a transient provider in global. 43 | * 44 | * @static 45 | * @template T 46 | * @param {TConstructor[]} Providers 47 | */ 48 | static addTransient (...Providers: TConstructor[]): typeof App 49 | 50 | /** 51 | * Name of this app instance. 52 | * 53 | * @type {string} 54 | * @readonly 55 | * @memberof App 56 | */ 57 | get name (): string 58 | 59 | /** 60 | * Vue store object of this app, 61 | * 62 | * @type {*} 63 | * @readonly 64 | * @memberof App 65 | */ 66 | get store (): any 67 | 68 | /** 69 | * View model of this app, 70 | * 71 | * @type {Vue} 72 | * @readonly 73 | * @memberof App 74 | */ 75 | get viewModel (): Vue 76 | 77 | /** 78 | * Root component constructor. 79 | * 80 | * @readonly 81 | * @type {TRootComponent} 82 | * @memberof App 83 | */ 84 | get RootComponent (): TRootComponent 85 | 86 | /** 87 | * Start up this app. 88 | * 89 | * @memberof App 90 | */ 91 | start (): void 92 | 93 | constructor (option: IAppOption) 94 | } 95 | 96 | /** 97 | * Constructor param of AppPage. 98 | * 99 | * @interface IAppPage 100 | */ 101 | interface IAppOption { 102 | /** 103 | * HTML element to mount. 104 | * 105 | * @type {(string | HTMLElement)} 106 | * @memberof IAppOption 107 | */ 108 | element?: string | HTMLElement 109 | 110 | /** 111 | * You can specify a name for this app instance. 112 | * 113 | * @type {string} 114 | * @memberof IAppOption 115 | */ 116 | name?: string 117 | 118 | /** 119 | * Root component the root vue component. 120 | * 121 | * @type {TRootComponent} 122 | * @memberof IAppOption 123 | */ 124 | RootComponent: TRootComponent 125 | 126 | /** 127 | * Vue router instance for this app. 128 | * 129 | * @type {*} 130 | * @memberof IAppOption 131 | */ 132 | router?: any 133 | 134 | /** 135 | * Vuex instance for this app. 136 | * 137 | * @type {*} 138 | * @memberof IAppOption 139 | */ 140 | store?: any 141 | 142 | /** 143 | * Created hook. 144 | * 145 | * @type {THookFunction} 146 | * @memberof IAppOption 147 | */ 148 | created?: THookFunction 149 | 150 | /** 151 | * Mounted hook. 152 | * 153 | * @type {THookFunction} 154 | * @memberof IAppOption 155 | */ 156 | mounted?: THookFunction 157 | 158 | /** 159 | * Before destroy hook. 160 | * 161 | * @type {THookFunction} 162 | * @memberof IAppOption 163 | */ 164 | beforeDestroy?: THookFunction 165 | } 166 | ``` 167 | -------------------------------------------------------------------------------- /doc/03.create-vue-component.md: -------------------------------------------------------------------------------- 1 | # Create vue component 2 | 3 | No more talking. 4 | 5 | ```html 6 | 7 | 8 | 13 | 14 | 18 | 19 | 20 | ``` 21 | 22 | ```typescript 23 | // my-component.ts 24 | 25 | import Vue from 'vue' 26 | import { Component, Prop, Watch } from '@vert/core' 27 | 28 | import DataTable from './components/data-table.vue' 29 | 30 | @Component({ 31 | components: { 32 | DataTable, 33 | ImageUploader: () => import('./image-uploader.vue') 34 | } 35 | }) 36 | export default class MyComponent extends Vue { 37 | @Prop({ 38 | type: String, 39 | default: '--' 40 | }) 41 | private readonly name: string 42 | 43 | @Watch('name') 44 | private onWatchName (value: string, oldValue: string) { 45 | // ... 46 | } 47 | 48 | private age: number = 0 49 | private hobbies: string[] = [] 50 | 51 | private currentPage: number = 1 52 | private totalPage: number = 1 53 | 54 | private get computedAge() { 55 | return this.age 56 | } 57 | private set computedAge(age) { 58 | this.age = age / 2 59 | } 60 | 61 | created () { 62 | // ... 63 | } 64 | 65 | mounted () { 66 | // ... 67 | } 68 | 69 | beforeDestroy () { 70 | // ... 71 | } 72 | } 73 | ``` 74 | -------------------------------------------------------------------------------- /doc/04.inject-service-into-vue-component.md: -------------------------------------------------------------------------------- 1 | # Inject service into vue component. 2 | 3 | Let's say we have these services: 4 | 5 | ```typescript 6 | // Your services. 7 | 8 | import { Injectable } from '@vert/core' 9 | 10 | @Injectable() 11 | class Http { 12 | async get () { 13 | // ... 14 | } 15 | 16 | async post () { 17 | // ... 18 | } 19 | } 20 | 21 | @Injectable() 22 | class BookshelfService { 23 | async getBookshelfData () { 24 | } 25 | 26 | constructor ( 27 | private http: Http 28 | ) {} 29 | } 30 | 31 | @Injectable() 32 | class UserService { 33 | async getUserData (id: number) { 34 | let data: Account = null 35 | let error: Error = null 36 | 37 | try { 38 | data = await this.http.get(...) 39 | } catch (exception) { 40 | error = exception 41 | } 42 | 43 | return { 44 | data, error 45 | } 46 | } 47 | 48 | constructor ( 49 | private http: Http 50 | ) {} 51 | } 52 | 53 | @Injectable() 54 | class Hinter { 55 | info (message: string) {} 56 | warn (message: string) {} 57 | success (message: string) {} 58 | error (message: string) {} 59 | } 60 | 61 | export { 62 | BookshelfService, 63 | Hinter, 64 | UserService 65 | } 66 | ``` 67 | 68 | And here is our vue component: 69 | 70 | ```vue 71 | 74 | 75 | 78 | 79 | 80 | ``` 81 | 82 | We are going to inject these services to this component: 83 | 84 | ```typescript 85 | import Vue from 'vue' 86 | import { Component } from '@vert/core' 87 | 88 | import { BookshelfService, Hinter, UserService } from './services' 89 | 90 | @Component 91 | export default class MyComponent extends Vue { 92 | private userData: Account = null 93 | private bookshelfData: BookShelf = null 94 | private userId: number = 0 95 | 96 | private async getUserData () { 97 | const { data, error } = await this.userSrv.getUserData(this.userId) 98 | if (error) { 99 | this.hinter.error(error.message) 100 | return 101 | } 102 | 103 | this.userData = data 104 | } 105 | 106 | private async getBookshelfInfo () { 107 | const { data, error } = await this.bookshelfSrv.getBookshelfData() 108 | if (error) { 109 | this.hinter.error(error.message) 110 | return 111 | } 112 | 113 | this.bookshelfData = data 114 | } 115 | 116 | created () { 117 | this.getUserData() 118 | this.getBookshelfInfo() 119 | } 120 | 121 | // Service Injection. 122 | constructor ( 123 | private bookshelfSrv: BookshelfService, 124 | private userSrv: UserService, 125 | private hinter: Hinter 126 | ) { 127 | super() 128 | } 129 | } 130 | ``` 131 | -------------------------------------------------------------------------------- /doc/05.use-injector.md: -------------------------------------------------------------------------------- 1 | # Use injector. 2 | 3 | Injector is something to create instance of your classes. 4 | 5 | Classes in same injector can reference each other by using injection. 6 | 7 | ```typescript 8 | // Your services. 9 | 10 | import { Injectable } from '@vert/core' 11 | 12 | @Injectable() 13 | class Logger { 14 | // ... 15 | } 16 | 17 | @Injectable() 18 | class Http { 19 | // ... 20 | constructor ( 21 | private logger: Logger 22 | ) {} 23 | } 24 | 25 | @Injectable() 26 | class BookshelfService { 27 | // ... 28 | constructor ( 29 | private http: Http, 30 | private logger: Logger 31 | ) {} 32 | } 33 | 34 | @Injectable() 35 | class UserService { 36 | // ... 37 | constructor ( 38 | private http: Http, 39 | private logger: Logger 40 | ) {} 41 | } 42 | 43 | export { 44 | BookshelfService, 45 | Http, 46 | Logger, 47 | UserService 48 | } 49 | ``` 50 | 51 | ```typescript 52 | // Using injector. 53 | 54 | import { Injector } from '@vert/core' 55 | import { BookshelfService, Http, Logger, UserService } from './services' 56 | 57 | // Create an injector. 58 | const injector = Injector.create() 59 | injector.addTransient(BookshelfService, Http, Logger, UserService) 60 | 61 | // Get instances. 62 | const bookshelfSrv = injector.get(BookshelfService) 63 | const userSrv = injector.get(UserService) 64 | ``` 65 | 66 | You can register your service as transient-service or singleton-service: 67 | 68 | ```typescript 69 | injector 70 | .addTransient(BookshelfService) 71 | .addSingleton(UserService) 72 | 73 | const bookshelfSrv1 = injector.get(BookshelfService) 74 | const bookshelfSrv2 = injector.get(BookshelfService) 75 | bookshelfSrv1 === bookshelfSrv2 // false 76 | 77 | const userSrv1 = injector.get(UserService) 78 | const userSrv2 = injector.get(UserService) 79 | userSrv1 === userSrv2 // true 80 | ``` 81 | 82 | ## API 83 | 84 | ```typescript 85 | /** 86 | * Standalone injector class. 87 | */ 88 | class Injector { 89 | /** 90 | * Create a new class injector. 91 | * 92 | * @return {Injector} 93 | */ 94 | static create (): Injector 95 | 96 | /** 97 | * Register target as a singleton provider. 98 | * You will get the same instance in every single initialization. 99 | * 100 | * @param {TConstructor} Provider 101 | */ 102 | addSingleton (Provider: TConstructor): this 103 | 104 | /** 105 | * Register target as transient provider. 106 | * You will get different instances in every single initialization. 107 | * 108 | * @param {TConstructor} Provider 109 | */ 110 | addTransient (Provider: TConstructor): this 111 | 112 | /** 113 | * Get target instance from injector by providing provider. 114 | * 115 | * @param {{new(...args): T}} Provider 116 | * @return {T} 117 | */ 118 | get (Provider: new (...args: any[]) => T): T 119 | 120 | /** 121 | * Check whether injector has registered this provider. 122 | * 123 | * @param target 124 | */ 125 | has (target: TConstructor): boolean 126 | 127 | private constructor () 128 | } 129 | ``` 130 | -------------------------------------------------------------------------------- /doc/06.faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | **Q: "Unknown custom element: - did you register the component correctly?" / Vue plugin not working ?** 4 | 5 | A: Set the alias for Vue in Webpack config `resolve.alias`, using absolute path. 6 | 7 | ```js 8 | const path = require('path') 9 | 10 | { 11 | resolve: { 12 | alias: { 13 | // Absolute path. 14 | 'vue$': path.resolve(__dirname, './node_modulesvue/dist/vue.esm.js') 15 | } 16 | } 17 | } 18 | ``` 19 | 20 | **Q: I can't get any instance from both injector and component, what's wrong?** 21 | 22 | A: Make sure these three things: 23 | - Options `emitDecoratorMetadata` and `experimentalDecorators` must be to `true` in `tsconfig.json`. 24 | - You should add `import 'reflect-metadata'` in the top to your entry file. 25 | - If there are multiple vues in your node_modules, make sure you have set `resolve.alias.vue` in your webpack configuration. 26 | 27 | **Q: Can I use it in browser directly without webpack or something else?** 28 | 29 | A: No, because you have to use TypeScript and Reflect-Metadata. 30 | 31 | **Q: What does it for?** 32 | 33 | A: For people who like oop 34 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | verbose: true, 4 | collectCoverage: true, 5 | testEnvironment: 'jsdom', 6 | setupFiles: [ 7 | '/.jest/setup.ts' 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /lib/app-component/component-decorator.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-redeclare */ 2 | 3 | import Vue, { Component, ComponentOptions } from 'vue' 4 | import { Emit, Inject as VueInject, Model, Prop, Provide as VueProvide, Watch } from 'vue-property-decorator' 5 | import { componentFactory, registerHooks } from '../libs/vue-class-component' 6 | 7 | import { TConstructor } from '../types' 8 | import { InjectionUtils } from '../utils/injection-utils' 9 | import { ReflectionUtils } from '../utils/reflection-utils' 10 | 11 | type VueClass = new (...args: any[]) => T & Vue 12 | 13 | let componentId = 1 14 | 15 | // Nuxt support. 16 | registerHooks([ 17 | 'beforeRouteEnter', 18 | 'beforeRouteUpdate', 19 | 'beforeRouteLeave', 20 | 'asyncData', 21 | 'fetch', 22 | 'head', 23 | 'layout', 24 | 'meta', 25 | 'middleware', 26 | 'title', 27 | 'transition', 28 | 'scrollToTop', 29 | 'validate' 30 | ]) 31 | 32 | /** 33 | * Decorate a class into the component. 34 | */ 35 | function Component (options: ComponentOptions & ThisType) 36 | : >(target: VC) => VC 37 | function Component> (targetClass: VC): VC 38 | function Component (param: any): any { 39 | if (typeof param === 'function') { 40 | const Providers = ReflectionUtils.getProvidersFromParams(param) 41 | const Constructor = InjectionUtils.createInjectedConstructor(param, Providers) 42 | 43 | // Keep targetClass.__decorators__. 44 | // "__decorators__" is defined in vue-class-component, and it holds all customized decorators' data 45 | // such as @Prop, @Watch, .ect. 46 | keepDecorators(param, Constructor) 47 | 48 | return componentFactory(Constructor, {}) 49 | } 50 | 51 | return function (targetClass: TConstructor) { 52 | param = param || {} 53 | 54 | const componentName = targetClass.prototype.constructor.name || 55 | 'AppComponent-' + componentId++ 56 | 57 | param = Object.assign({ 58 | name: componentName 59 | }, param) 60 | 61 | const Providers = ReflectionUtils.getProvidersFromParams(targetClass) 62 | const Constructor = InjectionUtils.createInjectedConstructor(targetClass, Providers) 63 | 64 | keepDecorators(targetClass, Constructor) 65 | 66 | const ComponentConstructor = componentFactory(Constructor, param) 67 | return ComponentConstructor 68 | } 69 | } 70 | 71 | /** 72 | * Function to keep targetClass.__decorators__. 73 | * "__decorators__" is defined in vue-class-component, and it holds all customized decorators' data 74 | * such as @Prop, @Watch, .ect. 75 | * 76 | * @link https://github.com/vuejs/vue-class-component/blob/master/src/component.ts#L59 77 | * 78 | * @param {*} targetClass 79 | * @param {*} Constructor 80 | */ 81 | function keepDecorators (targetClass: any, Constructor: any) { 82 | if (targetClass.__decorators__) { 83 | Constructor.__decorators__ = targetClass.__decorators__ 84 | } 85 | } 86 | 87 | export { 88 | Component, 89 | Prop, 90 | VueInject, 91 | VueProvide, 92 | Watch, 93 | Emit, 94 | Model 95 | } 96 | -------------------------------------------------------------------------------- /lib/core/index.ts: -------------------------------------------------------------------------------- 1 | import Vue, { ComponentOptions } from 'vue' 2 | 3 | import { GlobalInjector } from '../internal-injectors/global' 4 | import { TConstructor, THookFunction, TRootComponent } from '../types' 5 | import { TypeUtils } from '../utils/type-utils' 6 | 7 | let appId = 1 8 | 9 | /** 10 | * Constructor param of AppPage. 11 | * 12 | * @interface IAppPage 13 | */ 14 | export interface IAppOption { 15 | element?: string | HTMLElement 16 | name?: string 17 | RootComponent: TRootComponent 18 | router?: any 19 | store?: any 20 | 21 | created?: THookFunction 22 | mounted?: THookFunction 23 | beforeDestroy?: THookFunction 24 | } 25 | 26 | /** 27 | * App is the basic unit for a project. 28 | * 29 | * @description 30 | * Page is the root member for an app. Create an instance to initialize your app. 31 | * 32 | * @class App 33 | */ 34 | export class App { 35 | /** 36 | * Register instance as a singleton provider in global. 37 | * 38 | * @static 39 | * @template T 40 | * @param {TConstructor} type - The type to register 41 | * @param {T} instance - The instance to register for the type 42 | */ 43 | static addSingletonInstance (type: TConstructor, instance: T): typeof App { 44 | GlobalInjector.addSingletonInstance(type, instance) 45 | return App 46 | } 47 | 48 | /** 49 | * Register target as a singleton provider in global. 50 | * 51 | * @static 52 | * @param {TConstructor[]} Providers 53 | */ 54 | static addSingleton (...Providers: TConstructor[]): typeof App { 55 | GlobalInjector.addSingleton(...Providers) 56 | return App 57 | } 58 | 59 | /** 60 | * Register target as a transient provider in global. 61 | * 62 | * @static 63 | * @template T 64 | * @param {TConstructor[]} Providers 65 | */ 66 | static addTransient (...Providers: TConstructor[]): typeof App { 67 | GlobalInjector.addTransient(...Providers) 68 | return App 69 | } 70 | 71 | private _element?: string | HTMLElement 72 | private _name: string 73 | private _store?: any 74 | private _router?: any 75 | private _viewModel: Vue 76 | private _rootComponent: TRootComponent 77 | 78 | get name (): string { return this._name } 79 | get store () { return this._store } 80 | get viewModel (): Vue { return this._viewModel } 81 | get RootComponent (): TRootComponent { return this._rootComponent } 82 | 83 | private initViewModel ( 84 | RootComponent: TRootComponent, 85 | created?: THookFunction, 86 | mounted?: THookFunction, 87 | beforeDestroy?: THookFunction 88 | ) { 89 | const option: ComponentOptions = { 90 | name: this.name, 91 | render: h => h(RootComponent), 92 | created () { 93 | TypeUtils.isFunction(created) && created(this as Vue) 94 | }, 95 | mounted () { 96 | TypeUtils.isFunction(mounted) && mounted(this as Vue) 97 | }, 98 | beforeDestroy () { 99 | TypeUtils.isFunction(beforeDestroy) && beforeDestroy(this as Vue) 100 | } 101 | } 102 | 103 | if (TypeUtils.isDefined(this._router)) { 104 | Object.assign(option, { router: this._router }) 105 | } 106 | 107 | if (TypeUtils.isDefined(this._store)) { 108 | Object.assign(option, { store: this._store }) 109 | } 110 | 111 | this._viewModel = new Vue(option) 112 | } 113 | 114 | /** 115 | * Start up this app. 116 | * 117 | * @memberof App 118 | */ 119 | start () { 120 | if (this._element) { 121 | this._viewModel.$mount(this._element) 122 | } 123 | } 124 | 125 | /** 126 | * Destroy app. 127 | * 128 | * @memberof App 129 | */ 130 | destroy () { 131 | this._viewModel.$destroy() 132 | } 133 | 134 | constructor (option: IAppOption) { 135 | this._element = option.element 136 | this._name = option.name || 'DefaultApp-' + appId++ 137 | this._router = option.router 138 | this._store = option.store 139 | this._rootComponent = option.RootComponent 140 | 141 | this.initViewModel( 142 | option.RootComponent, 143 | option.created, 144 | option.mounted, 145 | option.beforeDestroy 146 | ) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /lib/data/index.ts: -------------------------------------------------------------------------------- 1 | import { TConstructor } from '../types' 2 | 3 | let isSupportProxy = true 4 | 5 | try { 6 | // eslint-disable-next-line no-unused-vars 7 | const proxy = new Proxy({}, { 8 | set () { 9 | return true 10 | } 11 | }) 12 | } catch (error) { 13 | isSupportProxy = false 14 | if (process.env.NODE_ENV === 'development') { 15 | console.warn('[@Vert/core] Your browser doesn\'t support Proxy.') 16 | } 17 | } 18 | 19 | class Data { 20 | static createTypeSafetyInstance (Constructor: TConstructor, ...args: any[]): T { 21 | const obj = new Constructor(...args) 22 | 23 | if (!isSupportProxy) { 24 | return obj 25 | } 26 | 27 | return new Proxy(obj, { 28 | set (target, keyName, value, proxy) { 29 | const newType = getType(value) 30 | const correctType = getType(target[keyName]) 31 | if (newType === correctType) { 32 | Reflect.set(target, keyName, value) 33 | } else { 34 | console.warn( 35 | `[Warn] Incorrect data type was given to property "${String(keyName)}" on "${Constructor.name}":\n` + 36 | ` "${value}" (${getTypeText(newType)}) was given, but should be a ${getTypeText(correctType)}.` 37 | ) 38 | } 39 | // Always return true to avoid error throwing. 40 | return true 41 | } 42 | }) 43 | } 44 | } 45 | 46 | function getType (target: any) { 47 | return Object.prototype.toString.call(target) 48 | } 49 | 50 | function getTypeText (fullTypeString: string) { 51 | return fullTypeString.replace(/\[object |\]/g, '') 52 | } 53 | 54 | export { 55 | Data 56 | } 57 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Vert is the service container which is designed 3 | * to build OOP applications which are based on Vue. 4 | * 5 | * # Carry Your World # 6 | * 7 | * @author LancerComet 8 | * @copyright LancerComet 9 | * @license MIT 10 | * @link https://github.com/vuevert/Vert-Core 11 | */ 12 | 13 | export { Component, Prop, VueInject, VueProvide, Watch, Emit, Model } from './app-component/component-decorator' 14 | export { App } from './core' 15 | export { Data } from './data' 16 | export { Injector } from './injection/injector' 17 | export { Injectable } from './injection/injectable' 18 | -------------------------------------------------------------------------------- /lib/injection/injectable.ts: -------------------------------------------------------------------------------- 1 | import { TConstructor } from '../types' 2 | 3 | const INJECTED_FLAG = 'Vert:Injected' 4 | const INJECTED_PARAMS_METADATA_KEY = 'Vert:ParamTypes' 5 | 6 | /** 7 | * Injectable decorator. 8 | */ 9 | function Injectable (): any { 10 | return function (Provider: TConstructor) { 11 | const types = Reflect.getMetadata('design:paramtypes', Provider) 12 | Reflect.defineMetadata(INJECTED_FLAG, true, Provider) 13 | Reflect.defineMetadata(INJECTED_PARAMS_METADATA_KEY, types, Provider) 14 | } 15 | } 16 | 17 | /** 18 | * Check whether a class is injected. 19 | * 20 | * @param target 21 | */ 22 | function checkIsInjected (target: TConstructor): boolean { 23 | return Reflect.getMetadata(INJECTED_FLAG, target) === true 24 | } 25 | 26 | export { 27 | Injectable, 28 | checkIsInjected 29 | } 30 | -------------------------------------------------------------------------------- /lib/injection/injector.ts: -------------------------------------------------------------------------------- 1 | import { TConstructor } from '../types' 2 | import { ReflectionUtils } from '../utils/reflection-utils' 3 | import { checkIsInjected } from './injectable' 4 | 5 | /** 6 | * Standalone injector class. 7 | */ 8 | class Injector { 9 | /** 10 | * Create a new class injector. 11 | * 12 | * @return {Injector} 13 | */ 14 | static create (): Injector { 15 | return new Injector() 16 | } 17 | 18 | /** 19 | * Check whether a class is injected. 20 | * 21 | * @param Provider 22 | */ 23 | private static checkIsInjected (Provider: TConstructor) { 24 | if (!checkIsInjected(Provider)) { 25 | throw new Error(`[@ver/core] "${Provider.name}" is not an injectable class.`) 26 | } 27 | } 28 | 29 | /** 30 | * This map keeps singleton provider and its instance. 31 | */ 32 | private readonly singletonMap = new WeakMap() 33 | 34 | /** 35 | * This map keeps transient provider. 36 | */ 37 | private readonly transient = new WeakMap() 38 | 39 | /** 40 | * Register instance as a singleton provider in global. 41 | * 42 | * @static 43 | * @template T 44 | * @param {TConstructor} type - The type to register 45 | * @param {T} instance - The instance to register for the type 46 | */ 47 | addSingletonInstance (type: TConstructor, instance: T) { 48 | if (this.transient.has(type)) { 49 | throw new Error(`[@vert/core] "${type.name}" has been registered as transient provider.`) 50 | } 51 | 52 | this.singletonMap.set(type, instance) 53 | 54 | return this 55 | } 56 | 57 | /** 58 | * Register target as a singleton provider. 59 | * You will get the same instance in every single initialization. 60 | * 61 | * @param {TConstructor[]} Providers 62 | */ 63 | addSingleton (...Providers: TConstructor[]): this { 64 | Providers.forEach(Provider => { 65 | Injector.checkIsInjected(Provider) 66 | 67 | if (this.transient.has(Provider)) { 68 | throw new Error(`[@vert/core] "${Provider.name}" has been registered as transient provider.`) 69 | } 70 | 71 | this.singletonMap.set(Provider, null) 72 | }) 73 | 74 | return this 75 | } 76 | 77 | /** 78 | * Register target as a transient provider. 79 | * You will get different instances in every single initialization. 80 | * 81 | * @param {TConstructor[]} Providers 82 | */ 83 | addTransient (...Providers: TConstructor[]): this { 84 | Providers.forEach(Provider => { 85 | Injector.checkIsInjected(Provider) 86 | 87 | if (this.singletonMap.has(Provider)) { 88 | throw new Error(`[@vert/core] "${Provider.name}" has been registered as singleton provider.`) 89 | } 90 | 91 | this.transient.set(Provider, null) 92 | }) 93 | 94 | return this 95 | } 96 | 97 | /** 98 | * Get target instance from injector by providing provider. 99 | * 100 | * @param {{new(...args): T}} Provider 101 | * @return {T} 102 | */ 103 | get (Provider: new (...args) => T): T { 104 | const isSingletonProvider = this.singletonMap.has(Provider) 105 | const isTransientProvider = this.transient.has(Provider) 106 | if (!isSingletonProvider && !isTransientProvider) { 107 | return null 108 | } 109 | 110 | switch (true) { 111 | case isSingletonProvider: { 112 | let instance = this.singletonMap.get(Provider) 113 | if (!instance) { 114 | const dependencyInstance = ReflectionUtils 115 | .getProvidersFromParams(Provider) 116 | .map(item => this.get(item)) 117 | instance = new Provider(...dependencyInstance) 118 | this.singletonMap.set(Provider, instance) 119 | } 120 | return instance 121 | } 122 | 123 | case isTransientProvider: { 124 | const dependencyInstance = ReflectionUtils 125 | .getProvidersFromParams(Provider) 126 | .map(item => this.get(item)) 127 | const instance = new Provider(...dependencyInstance) 128 | return instance 129 | } 130 | 131 | default: 132 | return null 133 | } 134 | } 135 | 136 | /** 137 | * Check whether injector has registered this provider. 138 | * 139 | * @param target 140 | */ 141 | has (target: TConstructor): boolean { 142 | return this.transient.has(target) || this.singletonMap.has(target) 143 | } 144 | } 145 | 146 | export { 147 | Injector 148 | } 149 | -------------------------------------------------------------------------------- /lib/internal-injectors/global.ts: -------------------------------------------------------------------------------- 1 | import { Injector } from '../injection/injector' 2 | import { TConstructor } from '../types' 3 | 4 | /** 5 | * Singleton injector holds all singleton instance. 6 | */ 7 | abstract class GlobalInjector { 8 | private static readonly injector = Injector.create() 9 | 10 | /** 11 | * Get target instance from injector by providing provider. 12 | * 13 | * @param {{new(...args): T}} Provider 14 | * @return {T} 15 | */ 16 | static get (Provider: new (...args) => T): T { 17 | return GlobalInjector.injector.get(Provider) 18 | } 19 | 20 | /** 21 | * Register instance as a singleton provider in global. 22 | * 23 | * @static 24 | * @template T 25 | * @param {TConstructor} type - The type to register 26 | * @param {T} instance - The instance to register for the type 27 | */ 28 | static addSingletonInstance (type: TConstructor, instance: T) { 29 | GlobalInjector.injector.addSingletonInstance(type, instance) 30 | } 31 | 32 | /** 33 | * Register target as singleton provider into global injector. 34 | * 35 | * @param Providers 36 | */ 37 | static addSingleton (...Providers: TConstructor[]) { 38 | GlobalInjector.injector.addSingleton(...Providers) 39 | } 40 | 41 | /** 42 | * Register target as transient provider into global injector. 43 | * 44 | * @param Providers 45 | */ 46 | static addTransient (...Providers: TConstructor[]) { 47 | GlobalInjector.injector.addTransient(...Providers) 48 | } 49 | 50 | /** 51 | * Check whether injector has registered this provider. 52 | * 53 | * @param Provider 54 | */ 55 | static has (Provider: TConstructor) { 56 | return GlobalInjector.injector.has(Provider) 57 | } 58 | } 59 | 60 | export { 61 | GlobalInjector 62 | } 63 | -------------------------------------------------------------------------------- /lib/libs/vue-class-component/component.ts: -------------------------------------------------------------------------------- 1 | import Vue, { ComponentOptions } from 'vue' 2 | import { collectDataFromConstructor } from './data' 3 | import { DecoratedClass, VueClass } from './declarations' 4 | import { copyReflectionMetadata, reflectionIsSupported } from './reflect' 5 | import { hasProto, isPrimitive, warn } from './util' 6 | 7 | const $internalHooks = [ 8 | 'data', 9 | 'beforeCreate', 10 | 'created', 11 | 'beforeMount', 12 | 'mounted', 13 | 'beforeDestroy', 14 | 'destroyed', 15 | 'beforeUpdate', 16 | 'updated', 17 | 'activated', 18 | 'deactivated', 19 | 'render', 20 | 'errorCaptured', // 2.5 21 | 'serverPrefetch' // 2.6 22 | ] 23 | 24 | function componentFactory ( 25 | Component: VueClass, 26 | options: ComponentOptions = {} 27 | ): VueClass { 28 | options.name = options.name || (Component as any)._componentTag || (Component as any).name 29 | // prototype props. 30 | const proto = Component.prototype 31 | Object.getOwnPropertyNames(proto).forEach(function (key) { 32 | if (key === 'constructor') { 33 | return 34 | } 35 | 36 | // hooks 37 | if ($internalHooks.indexOf(key) > -1) { 38 | options[key] = proto[key] 39 | return 40 | } 41 | const descriptor = Object.getOwnPropertyDescriptor(proto, key)! 42 | if (descriptor.value !== undefined) { 43 | // methods 44 | if (typeof descriptor.value === 'function') { 45 | (options.methods || (options.methods = {}))[key] = descriptor.value 46 | } else { 47 | // typescript decorated data 48 | (options.mixins || (options.mixins = [])).push({ 49 | data (this: Vue) { 50 | return { [key]: descriptor.value } 51 | } 52 | }) 53 | } 54 | } else if (descriptor.get || descriptor.set) { 55 | // computed properties 56 | (options.computed || (options.computed = {}))[key] = { 57 | get: descriptor.get, 58 | set: descriptor.set 59 | } 60 | } 61 | }) 62 | 63 | // add data hook to collect class properties as Vue instance's data 64 | ; (options.mixins || (options.mixins = [])).push({ 65 | data (this: Vue) { 66 | return collectDataFromConstructor(this, Component) 67 | } 68 | }) 69 | 70 | // decorate options 71 | const decorators = (Component as DecoratedClass).__decorators__ 72 | if (decorators) { 73 | decorators.forEach(fn => fn(options)) 74 | delete (Component as DecoratedClass).__decorators__ 75 | } 76 | 77 | // find super 78 | const superProto = Object.getPrototypeOf(Component.prototype) 79 | const Super = superProto instanceof Vue 80 | ? superProto.constructor as VueClass 81 | : Vue 82 | const Extended = Super.extend(options) 83 | 84 | forwardStaticMembers(Extended, Component, Super) 85 | 86 | if (reflectionIsSupported) { 87 | copyReflectionMetadata(Extended, Component) 88 | } 89 | 90 | return Extended 91 | } 92 | 93 | const reservedPropertyNames = [ 94 | // Unique id 95 | 'cid', 96 | 97 | // Super Vue constructor 98 | 'super', 99 | 100 | // Component options that will be used by the component 101 | 'options', 102 | 'superOptions', 103 | 'extendOptions', 104 | 'sealedOptions', 105 | 106 | // Private assets 107 | 'component', 108 | 'directive', 109 | 'filter' 110 | ] 111 | 112 | function forwardStaticMembers ( 113 | Extended: typeof Vue, 114 | Original: typeof Vue, 115 | Super: typeof Vue 116 | ): void { 117 | // We have to use getOwnPropertyNames since Babel registers methods as non-enumerable 118 | Object.getOwnPropertyNames(Original).forEach(key => { 119 | // `prototype` should not be overwritten 120 | if (key === 'prototype') { 121 | return 122 | } 123 | 124 | // Some browsers does not allow reconfigure built-in properties 125 | const extendedDescriptor = Object.getOwnPropertyDescriptor(Extended, key) 126 | if (extendedDescriptor && !extendedDescriptor.configurable) { 127 | return 128 | } 129 | 130 | const descriptor = Object.getOwnPropertyDescriptor(Original, key)! 131 | 132 | // If the user agent does not support `__proto__` or its family (IE <= 10), 133 | // the sub class properties may be inherited properties from the super class in TypeScript. 134 | // We need to exclude such properties to prevent to overwrite 135 | // the component options object which stored on the extended constructor (See #192). 136 | // If the value is a referenced value (object or function), 137 | // we can check equality of them and exclude it if they have the same reference. 138 | // If it is a primitive value, it will be forwarded for safety. 139 | if (!hasProto) { 140 | // Only `cid` is explicitly exluded from property forwarding 141 | // because we cannot detect whether it is a inherited property or not 142 | // on the no `__proto__` environment even though the property is reserved. 143 | if (key === 'cid') { 144 | return 145 | } 146 | 147 | const superDescriptor = Object.getOwnPropertyDescriptor(Super, key) 148 | 149 | if ( 150 | !isPrimitive(descriptor.value) && 151 | superDescriptor && 152 | superDescriptor.value === descriptor.value 153 | ) { 154 | return 155 | } 156 | } 157 | 158 | // Warn if the users manually declare reserved properties 159 | if ( 160 | process.env.NODE_ENV !== 'production' && 161 | reservedPropertyNames.indexOf(key) >= 0 162 | ) { 163 | warn( 164 | `Static property name '${key}' declared on class '${Original.name}' ` + 165 | 'conflicts with reserved property name of Vue internal. ' + 166 | 'It may cause unexpected behavior of the component. Consider renaming the property.' 167 | ) 168 | } 169 | 170 | Object.defineProperty(Extended, key, descriptor) 171 | }) 172 | } 173 | 174 | export { 175 | $internalHooks, 176 | componentFactory 177 | } 178 | -------------------------------------------------------------------------------- /lib/libs/vue-class-component/data.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { VueClass } from './declarations' 3 | import { warn } from './util' 4 | 5 | export function collectDataFromConstructor (vm: Vue, Component: VueClass) { 6 | // override _init to prevent to init as Vue instance 7 | const originalInit = Component.prototype._init 8 | Component.prototype._init = function (this: Vue) { 9 | // proxy to actual vm 10 | const keys = Object.getOwnPropertyNames(vm) 11 | // 2.2.0 compat (props are no longer exposed as self properties) 12 | if (vm.$options.props) { 13 | for (const key in vm.$options.props) { 14 | // eslint-disable-next-line no-prototype-builtins 15 | if (!vm.hasOwnProperty(key)) { 16 | keys.push(key) 17 | } 18 | } 19 | } 20 | keys.forEach(key => { 21 | if (key.charAt(0) !== '_') { 22 | Object.defineProperty(this, key, { 23 | get: () => vm[key], 24 | set: value => { vm[key] = value }, 25 | configurable: true 26 | }) 27 | } 28 | }) 29 | } 30 | 31 | // should be acquired class property values 32 | const data = new Component() 33 | 34 | // restore original _init to avoid memory leak (#209) 35 | Component.prototype._init = originalInit 36 | 37 | // create plain data object 38 | const plainData = {} 39 | Object.keys(data).forEach(key => { 40 | if (data[key] !== undefined) { 41 | plainData[key] = data[key] 42 | } 43 | }) 44 | 45 | if (process.env.NODE_ENV !== 'production') { 46 | if (!(Component.prototype instanceof Vue) && Object.keys(plainData).length > 0) { 47 | warn( 48 | 'Component class must inherit Vue or its descendant class ' + 49 | 'when class property is used.' 50 | ) 51 | } 52 | } 53 | 54 | return plainData 55 | } 56 | -------------------------------------------------------------------------------- /lib/libs/vue-class-component/declarations.ts: -------------------------------------------------------------------------------- 1 | import Vue, { ComponentOptions } from 'vue' 2 | 3 | export type VueClass = { new (...args: any[]): V & Vue } & typeof Vue 4 | 5 | export type DecoratedClass = VueClass & { 6 | // Property, method and parameter decorators created by `createDecorator` helper 7 | // will enqueue functions that update component options for lazy processing. 8 | // They will be executed just before creating component constructor. 9 | __decorators__?: ((options: ComponentOptions) => void)[] 10 | } 11 | -------------------------------------------------------------------------------- /lib/libs/vue-class-component/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-redeclare */ 2 | import Vue, { ComponentOptions } from 'vue' 3 | import { $internalHooks, componentFactory } from './component' 4 | import { VueClass } from './declarations' 5 | 6 | function Component (options: ComponentOptions & ThisType): >(target: VC) => VC 7 | function Component > (target: VC): VC 8 | function Component (options: ComponentOptions | VueClass): any { 9 | if (typeof options === 'function') { 10 | return componentFactory(options) 11 | } 12 | return function (Component: VueClass) { 13 | return componentFactory(Component, options) 14 | } 15 | } 16 | 17 | function registerHooks (keys: string[]): void { 18 | $internalHooks.push(...keys) 19 | } 20 | 21 | export { createDecorator, VueDecorator, mixins } from './util' 22 | export { 23 | Component, 24 | componentFactory, 25 | registerHooks 26 | } 27 | -------------------------------------------------------------------------------- /lib/libs/vue-class-component/reflect.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VueConstructor } from 'vue' 2 | import { VueClass } from './declarations' 3 | 4 | // The rational behind the verbose Reflect-feature check below is the fact that there are polyfills 5 | // which add an implementation for Reflect.defineMetadata but not for Reflect.getOwnMetadataKeys. 6 | // Without this check consumers will encounter hard to track down runtime errors. 7 | const reflectionIsSupported = typeof Reflect !== 'undefined' && Reflect.defineMetadata && Reflect.getOwnMetadataKeys 8 | 9 | export function copyReflectionMetadata ( 10 | to: VueConstructor, 11 | from: VueClass 12 | ) { 13 | forwardMetadata(to, from) 14 | 15 | Object.getOwnPropertyNames(from.prototype).forEach(key => { 16 | forwardMetadata(to.prototype, from.prototype, key) 17 | }) 18 | 19 | Object.getOwnPropertyNames(from).forEach(key => { 20 | forwardMetadata(to, from, key) 21 | }) 22 | } 23 | 24 | function forwardMetadata (to: object, from: object, propertyKey?: string): void { 25 | const metaKeys = propertyKey ? Reflect.getOwnMetadataKeys(from, propertyKey) : Reflect.getOwnMetadataKeys(from) 26 | metaKeys.forEach(metaKey => { 27 | const metadata = propertyKey ? Reflect.getOwnMetadata(metaKey, from, propertyKey) : Reflect.getOwnMetadata(metaKey, from) 28 | 29 | if (propertyKey) { 30 | Reflect.defineMetadata(metaKey, metadata, to, propertyKey) 31 | } else { 32 | Reflect.defineMetadata(metaKey, metadata, to) 33 | } 34 | }) 35 | } 36 | 37 | export { 38 | reflectionIsSupported 39 | } 40 | -------------------------------------------------------------------------------- /lib/libs/vue-class-component/util.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-redeclare */ 2 | 3 | import Vue, { ComponentOptions } from 'vue' 4 | import { DecoratedClass, VueClass } from './declarations' 5 | 6 | export const noop = () => {} 7 | 8 | const fakeArray = { __proto__: [] } 9 | export const hasProto = fakeArray instanceof Array 10 | 11 | export interface VueDecorator { 12 | // Class decorator 13 | (Ctor: typeof Vue): void 14 | 15 | // Property decorator 16 | (target: Vue, key: string): void 17 | 18 | // Parameter decorator 19 | (target: Vue, key: string, index: number): void 20 | } 21 | 22 | export function createDecorator (factory: (options: ComponentOptions, key: string, index: number) => void): VueDecorator { 23 | return (target: Vue | typeof Vue, key?: any, index?: any) => { 24 | const Ctor = typeof target === 'function' 25 | ? target as DecoratedClass 26 | : target.constructor as DecoratedClass 27 | if (!Ctor.__decorators__) { 28 | Ctor.__decorators__ = [] 29 | } 30 | if (typeof index !== 'number') { 31 | index = undefined 32 | } 33 | Ctor.__decorators__.push(options => factory(options, key, index)) 34 | } 35 | } 36 | 37 | export function mixins (CtorA: VueClass): VueClass 38 | export function mixins (CtorA: VueClass, CtorB: VueClass): VueClass 39 | export function mixins (CtorA: VueClass, CtorB: VueClass, CtorC: VueClass): VueClass 40 | export function mixins (CtorA: VueClass, CtorB: VueClass, CtorC: VueClass, CtorD: VueClass): VueClass 41 | export function mixins (CtorA: VueClass, CtorB: VueClass, CtorC: VueClass, CtorD: VueClass, CtorE: VueClass): VueClass 42 | export function mixins (...Ctors: VueClass[]): VueClass 43 | export function mixins (...Ctors: VueClass[]): VueClass { 44 | return Vue.extend({ mixins: Ctors }) 45 | } 46 | 47 | export function isPrimitive (value: any): boolean { 48 | const type = typeof value 49 | return value == null || (type !== 'object' && type !== 'function') 50 | } 51 | 52 | export function warn (message: string): void { 53 | if (typeof console !== 'undefined') { 54 | console.warn('[vue-class-component] ' + message) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/libs/vue-property-decorator.ts: -------------------------------------------------------------------------------- 1 | /** vue-property-decorator verson 8.0.0 MIT LICENSE copyright 2018 kaorun343 */ 2 | 3 | 'use strict' 4 | import Vue, { PropOptions, WatchOptions } from 'vue' 5 | import { InjectKey } from 'vue/types/options' 6 | import { Component, createDecorator, mixins } from './vue-class-component' 7 | 8 | export interface Constructor { 9 | new(...args: any[]): any 10 | } 11 | 12 | export { Component, Vue, mixins as Mixins } 13 | 14 | /** 15 | * decorator of an inject 16 | * @param from key 17 | * @return PropertyDecorator 18 | */ 19 | export function Inject (options?: { from?: InjectKey, default?: any } | InjectKey): PropertyDecorator { 20 | return createDecorator((componentOptions, key) => { 21 | if (typeof componentOptions.inject === 'undefined') { 22 | componentOptions.inject = {} 23 | } 24 | if (!Array.isArray(componentOptions.inject)) { 25 | componentOptions.inject[key] = options || key 26 | } 27 | }) 28 | } 29 | 30 | /** 31 | * decorator of a provide 32 | * @param key key 33 | * @return PropertyDecorator | void 34 | */ 35 | export function Provide (key?: string | symbol): PropertyDecorator { 36 | return createDecorator((componentOptions, k) => { 37 | let provide: any = componentOptions.provide 38 | if (typeof provide !== 'function' || !provide.managed) { 39 | const original = componentOptions.provide 40 | provide = componentOptions.provide = function (this: any) { 41 | const rv = Object.create((typeof original === 'function' ? original.call(this) : original) || null) 42 | for (const i in provide.managed) { rv[provide.managed[i]] = this[i] } 43 | return rv 44 | } 45 | provide.managed = {} 46 | } 47 | provide.managed[k] = key || k 48 | }) 49 | } 50 | 51 | /** 52 | * decorator of model 53 | * @param event event name 54 | * @param options options 55 | * @return PropertyDecorator 56 | */ 57 | export function Model (event?: string, options: (PropOptions | Constructor[] | Constructor) = {}): PropertyDecorator { 58 | return createDecorator((componentOptions, k) => { 59 | (componentOptions.props || (componentOptions.props = {}) as any)[k] = options 60 | componentOptions.model = { prop: k, event: event || k } 61 | }) 62 | } 63 | 64 | /** 65 | * decorator of a prop 66 | * @param options the options for the prop 67 | * @return PropertyDecorator | void 68 | */ 69 | export function Prop (options: (PropOptions | Constructor[] | Constructor) = {}): PropertyDecorator { 70 | return createDecorator((componentOptions, k) => { 71 | (componentOptions.props || (componentOptions.props = {}) as any)[k] = options 72 | }) 73 | } 74 | 75 | /** 76 | * decorator of a watch function 77 | * @param path the path or the expression to observe 78 | * @param WatchOption 79 | * @return MethodDecorator 80 | */ 81 | export function Watch (path: string, options: WatchOptions = {}): MethodDecorator { 82 | const { deep = false, immediate = false } = options 83 | 84 | return createDecorator((componentOptions, handler) => { 85 | if (typeof componentOptions.watch !== 'object') { 86 | componentOptions.watch = Object.create(null) 87 | } 88 | 89 | const watch: any = componentOptions.watch 90 | 91 | if (typeof watch[path] === 'object' && !Array.isArray(watch[path])) { 92 | watch[path] = [watch[path]] 93 | } else if (typeof watch[path] === 'undefined') { 94 | watch[path] = [] 95 | } 96 | 97 | watch[path].push({ handler, deep, immediate }) 98 | }) 99 | } 100 | 101 | // Code copied from Vue/src/shared/util.js 102 | const hyphenateRE = /\B([A-Z])/g 103 | const hyphenate = (str: string) => str.replace(hyphenateRE, '-$1').toLowerCase() 104 | 105 | /** 106 | * decorator of an event-emitter function 107 | * @param event The name of the event 108 | * @return MethodDecorator 109 | */ 110 | export function Emit (event?: string): MethodDecorator { 111 | return function (_target: Vue, key: string, descriptor: any) { 112 | key = hyphenate(key) 113 | const original = descriptor.value 114 | descriptor.value = function emitter (...args: any[]) { 115 | const emit = (returnValue: any) => { 116 | if (returnValue !== undefined) { args.unshift(returnValue) } 117 | this.$emit(event || key, ...args) 118 | } 119 | 120 | const returnValue: any = original.apply(this, args) 121 | 122 | if (isPromise(returnValue)) { 123 | returnValue.then(returnValue => { 124 | emit(returnValue) 125 | }) 126 | } else { 127 | emit(returnValue) 128 | } 129 | } 130 | } 131 | } 132 | 133 | function isPromise (obj: any): obj is Promise { 134 | return obj instanceof Promise || (obj && typeof obj.then === 'function') 135 | } 136 | -------------------------------------------------------------------------------- /lib/shim.d.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata' 2 | -------------------------------------------------------------------------------- /lib/types/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { CombinedVueInstance } from 'vue/types/vue' 3 | 4 | /** 5 | * Root component type. 6 | */ 7 | type TRootComponent 8 | = CombinedVueInstance 9 | 10 | /** 11 | * Hook function type. 12 | */ 13 | type THookFunction = (viewModel?: Vue) => void 14 | 15 | /** 16 | * Constructor type. 17 | */ 18 | type TConstructor = new (...args: any[]) => T 19 | 20 | /** 21 | * Provider type. 22 | */ 23 | type TProviders = TConstructor[] 24 | 25 | export { 26 | TConstructor, 27 | THookFunction, 28 | TRootComponent, 29 | TProviders 30 | } 31 | -------------------------------------------------------------------------------- /lib/utils/injection-utils.ts: -------------------------------------------------------------------------------- 1 | import { GlobalInjector } from '../internal-injectors/global' 2 | import { TConstructor, TProviders } from '../types' 3 | import { Logger } from './logger' 4 | 5 | abstract class InjectionUtils { 6 | /** 7 | * Class a class that has already been injected. 8 | * 9 | * @param {*} TargetClass 10 | * @param {TProviders} Providers 11 | * @return {*} 12 | */ 13 | static createInjectedConstructor (TargetClass: TConstructor, Providers: TProviders): any { 14 | // Return a new constructor. 15 | // This new constructor has no params so you can not get any info by using 'design:paramtypes'. 16 | const Constructor: any = function () { 17 | const providers = [] 18 | Providers.forEach(Provider => { 19 | if (!GlobalInjector.has(Provider)) { 20 | Logger.warn(`Provider "${Provider.name}" hasn't been registered in global.`) 21 | providers.push(undefined) 22 | return 23 | } 24 | 25 | const instance = GlobalInjector.get(Provider) 26 | providers.push(instance) 27 | }) 28 | 29 | return new TargetClass(...providers) 30 | } 31 | 32 | Constructor.prototype = TargetClass.prototype 33 | 34 | try { 35 | Object.defineProperty(Constructor, 'name', { 36 | writable: true, 37 | configurable: true, 38 | value: TargetClass.name 39 | }) 40 | } catch (error) { 41 | console.warn('[@Vert/Core] This browser can not redefine property name for injected constructor:', error.message) 42 | } 43 | 44 | return Constructor 45 | } 46 | } 47 | 48 | export { 49 | InjectionUtils 50 | } 51 | -------------------------------------------------------------------------------- /lib/utils/logger.ts: -------------------------------------------------------------------------------- 1 | abstract class Logger { 2 | static warn (...args) { 3 | console.warn('[@vert/core]', ...args) 4 | } 5 | 6 | static error (...args) { 7 | console.error('[@vert/core]', ...args) 8 | } 9 | } 10 | 11 | export { 12 | Logger 13 | } 14 | -------------------------------------------------------------------------------- /lib/utils/reflection-utils.ts: -------------------------------------------------------------------------------- 1 | import { TProviders } from '../types' 2 | 3 | abstract class ReflectionUtils { 4 | static getProvidersFromParams (target: any): TProviders { 5 | const result = Reflect.getMetadata('design:paramtypes', target) 6 | return (result || []).filter((item: any) => { 7 | return typeof item === 'function' && 8 | item !== Object && 9 | item !== Function 10 | }) 11 | } 12 | } 13 | 14 | export { 15 | ReflectionUtils 16 | } 17 | -------------------------------------------------------------------------------- /lib/utils/type-utils.ts: -------------------------------------------------------------------------------- 1 | class TypeUtils { 2 | static isUndefined (target: any): boolean { 3 | return typeof target === 'undefined' 4 | } 5 | 6 | static isDefined (target: any): boolean { 7 | return !TypeUtils.isUndefined(target) 8 | } 9 | 10 | static isFunction (target: any): boolean { 11 | return typeof target === 'function' 12 | } 13 | } 14 | 15 | export { 16 | TypeUtils 17 | } 18 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuevert/Vert-Core/3228428770c1e9bef581a4c139fed1e1e90bccaf/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vert/core", 3 | "version": "1.2.1", 4 | "description": "Library to build OOP applications which are based on Vue.", 5 | "author": { 6 | "name": "LancerComet", 7 | "email": "chw644@hotmail.com" 8 | }, 9 | "repository": { 10 | "url": "https://github.com/vuevert/Vert-Core.git", 11 | "type": "git" 12 | }, 13 | "files": [ 14 | "dist/", 15 | "doc/", 16 | "README.md" 17 | ], 18 | "main": "./dist/index.js", 19 | "module": "./dist/index.esm.js", 20 | "license": "MIT", 21 | "scripts": { 22 | "build": "rollup -c", 23 | "test": "jest", 24 | "predev": "npm run build", 25 | "preversion": "npm test" 26 | }, 27 | "peerDependencies": { 28 | "reflect-metadata": "^0.1.13", 29 | "vue": "^2.0.0", 30 | "vue-property-decorator": "^8.0.0" 31 | }, 32 | "devDependencies": { 33 | "@types/node": "^16.0.0", 34 | "@types/reflect-metadata": "^0.1.0", 35 | "typescript": "^4.6.4", 36 | "@types/jest": "^27.5.0", 37 | "@typescript-eslint/eslint-plugin": "^5.7.0", 38 | "@typescript-eslint/parser": "^5.7.0", 39 | "eslint": "^7.32.0", 40 | "eslint-config-standard": "^16.0.3", 41 | "eslint-plugin-import": "^2.25.3", 42 | "eslint-plugin-node": "^11.1.0", 43 | "eslint-plugin-promise": "^5.2.0", 44 | "jest": "^27.4.5", 45 | "jest-useragent-mock": "^0.1.1", 46 | "reflect-metadata": "^0.1.13", 47 | "rollup": "^2.61.1", 48 | "rollup-plugin-commonjs": "^10.1.0", 49 | "rollup-plugin-delete": "^2.0.0", 50 | "rollup-plugin-typescript2": "^0.31.1", 51 | "ts-jest": "^27.1.2", 52 | "tslib": "^2.4.0", 53 | "typescript-formatter": "^7.2.2", 54 | "vue": "^2.0.0", 55 | "vue-property-decorator": "^8.0.0" 56 | }, 57 | "keywords": [ 58 | "Framework", 59 | "Vue", 60 | "Vert", 61 | "ServiceContainer", 62 | "OOP", 63 | "LancerComet" 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from 'rollup-plugin-typescript2' 2 | 3 | export default { 4 | input: './lib/index.ts', 5 | 6 | output: [ 7 | { 8 | file: './dist/index.js', 9 | format: 'commonjs' 10 | }, 11 | { 12 | file: './dist/index.esm.js', 13 | format: 'es' 14 | } 15 | ], 16 | 17 | plugins: [ 18 | typescript({ 19 | tsconfigOverride: { 20 | compilerOptions: { 21 | target: 'es5' 22 | }, 23 | include: [ 24 | 'lib/**/*' 25 | ] 26 | } 27 | }) 28 | ], 29 | 30 | external: [ 31 | 'vue', 32 | 'vue-class-component', 33 | 'reflect-metadata', 34 | 'vue-property-decorator' 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /test/app.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | import { App } from '../lib' 3 | 4 | describe('App testing.', () => { 5 | const RootComponent = { 6 | data () { 7 | return { 8 | name: 'LancerComet' 9 | } 10 | } 11 | } 12 | 13 | it('Should emit created hook.', done => { 14 | const app = new App({ 15 | RootComponent, 16 | created () { 17 | done() 18 | } 19 | }) 20 | 21 | app.start() 22 | }) 23 | 24 | it('Should emit mounted hook.', done => { 25 | const div = document.createElement('div') 26 | document.body.appendChild(div) 27 | 28 | const app = new App({ 29 | element: div, 30 | RootComponent, 31 | mounted () { 32 | done() 33 | } 34 | }) 35 | 36 | app.start() 37 | }) 38 | 39 | it('Should emit beforeDestroy hook.', done => { 40 | const div = document.createElement('div') 41 | document.body.appendChild(div) 42 | 43 | const app = new App({ 44 | element: div, 45 | RootComponent, 46 | beforeDestroy () { 47 | done() 48 | } 49 | }) 50 | 51 | app.start() 52 | app.destroy() 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /test/component.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-useless-constructor */ 2 | /* eslint-disable no-undef */ 3 | 4 | import Vue from 'vue' 5 | import { App, Component, Injectable } from '../lib' 6 | 7 | @Injectable() 8 | class HttpService { 9 | getData (data: T): T { 10 | return data 11 | } 12 | } 13 | 14 | @Injectable() 15 | class MyService { 16 | getName () { 17 | return this.httpSrv.getData('John Smith') 18 | } 19 | 20 | getAge () { 21 | return this.httpSrv.getData(100) 22 | } 23 | 24 | constructor ( 25 | private httpSrv: HttpService 26 | ) {} 27 | } 28 | 29 | @Component({ 30 | name: 'some-component', 31 | template: '
{{greeting}}
' 32 | }) 33 | class SomeComponent extends Vue { 34 | name: string = 'LancerComet' 35 | age: number = 99 36 | 37 | get greeting (): string { 38 | return `Hello! My name is ${this.name} and I'm ${this.age} now.` 39 | } 40 | 41 | updateName () { 42 | this.name = this.mySrv.getName() 43 | } 44 | 45 | updateAge () { 46 | this.age = this.mySrv.getAge() 47 | } 48 | 49 | constructor ( 50 | private mySrv: MyService 51 | ) { 52 | super() 53 | } 54 | } 55 | 56 | describe('Component testing.', () => { 57 | let someComponent: SomeComponent = null 58 | 59 | beforeAll(() => { 60 | App.addTransient(HttpService, MyService) 61 | 62 | const div = document.createElement('div') 63 | document.body.appendChild(div) 64 | 65 | const app = new App({ 66 | element: div, 67 | RootComponent: SomeComponent 68 | }) 69 | app.start() 70 | 71 | someComponent = app.viewModel.$children[0] as SomeComponent 72 | }) 73 | 74 | it('Should convert class SomeComponent into a vue component.', () => { 75 | // @ts-ignore 76 | expect(someComponent._isVue).toEqual(true) 77 | expect(someComponent.$options.template).toEqual('
{{greeting}}
') 78 | expect(someComponent.$options.computed.greeting).toBeDefined() 79 | expect(typeof someComponent.$mount).toEqual('function') 80 | 81 | expect(someComponent.name).toEqual('LancerComet') 82 | expect(someComponent.age).toEqual(99) 83 | expect(someComponent.greeting).toEqual('Hello! My name is LancerComet and I\'m 99 now.') 84 | 85 | someComponent.updateName() 86 | someComponent.updateAge() 87 | expect(someComponent.name).toEqual('John Smith') 88 | expect(someComponent.age).toEqual(100) 89 | }) 90 | }) 91 | -------------------------------------------------------------------------------- /test/core.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | 3 | import { App } from '../lib' 4 | 5 | describe('Core Nodule test.', () => { 6 | it('Should create an app without error.', () => { 7 | const app = new App({ 8 | name: 'demo-app', 9 | RootComponent: { 10 | data () { 11 | return { 12 | name: 'vert' 13 | } 14 | } 15 | } 16 | }) 17 | 18 | expect(app.name).toEqual('demo-app') 19 | expect(app.viewModel).toBeDefined() 20 | expect(app.start).toBeDefined() 21 | 22 | try { 23 | app.start() 24 | } catch (e) { 25 | expect(false).toEqual(true) 26 | } 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /test/data.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | 3 | import { Data } from '../lib' 4 | 5 | interface IStudent { 6 | name: string 7 | age: any 8 | } 9 | 10 | describe('Data testing.', () => { 11 | it('Type-Secured data should work.', () => { 12 | const lancerComet = Student.create({ 13 | name: 'LancerComet', age: 27 14 | }) 15 | 16 | expect(lancerComet.name).toEqual('LancerComet') 17 | expect(lancerComet.age).toEqual(27) 18 | 19 | lancerComet.name = 'Wch' 20 | expect(lancerComet.name).toEqual('Wch') 21 | 22 | lancerComet.age = 'Wrong type' as any 23 | expect(lancerComet.age).toEqual(27) 24 | }) 25 | 26 | class Student { 27 | static create (param?: IStudent): Student { 28 | return Data.createTypeSafetyInstance(Student, param) 29 | } 30 | 31 | name: string = '' 32 | age: number = 0 33 | 34 | constructor (param?: IStudent) { 35 | if (param) { 36 | this.name = param.name 37 | this.age = param.age 38 | } 39 | } 40 | } 41 | }) 42 | 43 | // function Type (target: any, key: string) { 44 | // const typeMeta = Reflect.getMetadata('design:type', target, key) 45 | // const targetType = typeMeta.name 46 | // console.log(key, 'should be a', targetType) 47 | // } 48 | -------------------------------------------------------------------------------- /test/injector.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-useless-constructor */ 2 | /* eslint-disable no-undef */ 3 | 4 | import { Injectable, Injector } from '../lib' 5 | 6 | describe('Injector test.', () => { 7 | @Injectable() 8 | class A { 9 | name = 'a' 10 | } 11 | 12 | @Injectable() 13 | class B { 14 | name = 'b' 15 | constructor ( 16 | public a: A 17 | ) {} 18 | } 19 | 20 | @Injectable() 21 | class C { 22 | name = 'c' 23 | constructor ( 24 | public a: A, 25 | public b: B 26 | ) {} 27 | } 28 | 29 | it('Transient injection should work.', () => { 30 | const injector = Injector.create() 31 | .addTransient(A, B, C) 32 | 33 | const a1 = injector.get(A) 34 | const b1 = injector.get(B) 35 | const c1 = injector.get(C) 36 | 37 | expect(a1.name).toEqual('a') 38 | expect(b1.name).toEqual('b') 39 | expect(b1.a.name).toEqual('a') 40 | expect(c1.name).toEqual('c') 41 | expect(c1.a.name).toEqual('a') 42 | expect(c1.b.name).toEqual('b') 43 | 44 | a1.name = 'John Smith' 45 | expect(b1.a.name).toEqual('a') 46 | expect(c1.a.name).toEqual('a') 47 | 48 | const a2 = injector.get(A) 49 | const b2 = injector.get(B) 50 | const c2 = injector.get(C) 51 | 52 | expect(a1 === a2).toEqual(false) 53 | expect(b1 === b2).toEqual(false) 54 | expect(b1.a === b2.a).toEqual(false) 55 | expect(b1.a === a1).toEqual(false) 56 | expect(b2.a === a2).toEqual(false) 57 | expect(c1 === c2).toEqual(false) 58 | expect(c1.a === c2.a).toEqual(false) 59 | expect(c1.b === c2.b).toEqual(false) 60 | expect(c1.a === a1).toEqual(false) 61 | expect(c1.b === b2).toEqual(false) 62 | expect(c2.a === a1).toEqual(false) 63 | expect(c2.b === b2).toEqual(false) 64 | }) 65 | 66 | it('Singleton injection should work.', () => { 67 | const injector = Injector.create() 68 | .addSingleton(A, B, C) 69 | 70 | const a1 = injector.get(A) 71 | const b1 = injector.get(B) 72 | const c1 = injector.get(C) 73 | 74 | expect(a1.name).toEqual('a') 75 | expect(b1.name).toEqual('b') 76 | expect(b1.a.name).toEqual('a') 77 | expect(c1.name).toEqual('c') 78 | expect(c1.a.name).toEqual('a') 79 | expect(c1.b.name).toEqual('b') 80 | 81 | a1.name = 'John Smith' 82 | expect(b1.a.name).toEqual('John Smith') 83 | expect(c1.a.name).toEqual('John Smith') 84 | 85 | const a2 = injector.get(A) 86 | const b2 = injector.get(B) 87 | const c2 = injector.get(C) 88 | 89 | expect(a1 === a2).toEqual(true) 90 | expect(b1 === b2).toEqual(true) 91 | expect(b1.a === b2.a).toEqual(true) 92 | expect(b1.a === a1).toEqual(true) 93 | expect(b2.a === a2).toEqual(true) 94 | expect(c1 === c2).toEqual(true) 95 | expect(c1.a === c2.a).toEqual(true) 96 | expect(c1.b === c2.b).toEqual(true) 97 | expect(c1.a === a1).toEqual(true) 98 | expect(c1.b === b2).toEqual(true) 99 | expect(c2.a === a1).toEqual(true) 100 | expect(c2.b === b2).toEqual(true) 101 | }) 102 | 103 | it('Mixing usage should work.', () => { 104 | const injector = Injector.create() 105 | .addSingleton(A) 106 | .addTransient(B, C) 107 | 108 | const a1 = injector.get(A) 109 | const b1 = injector.get(B) 110 | const c1 = injector.get(C) 111 | const a2 = injector.get(A) 112 | const b2 = injector.get(B) 113 | const c2 = injector.get(C) 114 | 115 | expect(a1 === a2).toEqual(true) 116 | expect(b1 === b2).toEqual(false) 117 | expect(c1.b === b2).toEqual(false) 118 | expect(c1 === c2).toEqual(false) 119 | expect(c2.b === b2).toEqual(false) 120 | 121 | a1.name = 'LancerComet' 122 | expect(a1.name).toEqual('LancerComet') 123 | expect(a2.name).toEqual('LancerComet') 124 | expect(b1.a.name).toEqual('LancerComet') 125 | expect(b2.a.name).toEqual('LancerComet') 126 | expect(c1.a.name).toEqual('LancerComet') 127 | expect(c2.a.name).toEqual('LancerComet') 128 | 129 | b1.name = 'Tom' 130 | expect(b1.name).toEqual('Tom') 131 | expect(c1.b.name).toEqual('b') 132 | }) 133 | 134 | it('Singleton instance injection should work.', () => { 135 | const a = new A() 136 | a.name = 'foo' 137 | 138 | const injector = Injector.create() 139 | .addSingletonInstance(A, a) 140 | 141 | injector.addSingleton(B, C) 142 | 143 | const a1 = injector.get(A) 144 | const b1 = injector.get(B) 145 | const c1 = injector.get(C) 146 | 147 | expect(a1.name).toEqual('foo') 148 | expect(b1.name).toEqual('b') 149 | expect(b1.a.name).toEqual('foo') 150 | expect(c1.name).toEqual('c') 151 | expect(c1.a.name).toEqual('foo') 152 | expect(c1.b.name).toEqual('b') 153 | 154 | a1.name = 'John Smith' 155 | expect(b1.a.name).toEqual('John Smith') 156 | expect(c1.a.name).toEqual('John Smith') 157 | 158 | const a2 = injector.get(A) 159 | const b2 = injector.get(B) 160 | const c2 = injector.get(C) 161 | 162 | expect(a === a1).toEqual(true) 163 | expect(a1 === a2).toEqual(true) 164 | expect(b1 === b2).toEqual(true) 165 | expect(b1.a === b2.a).toEqual(true) 166 | expect(b1.a === a1).toEqual(true) 167 | expect(b2.a === a2).toEqual(true) 168 | expect(c1 === c2).toEqual(true) 169 | expect(c1.a === c2.a).toEqual(true) 170 | expect(c1.b === c2.b).toEqual(true) 171 | expect(c1.a === a1).toEqual(true) 172 | expect(c1.b === b2).toEqual(true) 173 | expect(c2.a === a1).toEqual(true) 174 | expect(c2.b === b2).toEqual(true) 175 | }) 176 | }) 177 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "experimentalDecorators": true, 5 | "emitDecoratorMetadata": true, 6 | "moduleResolution": "node", 7 | "module": "ESNext", 8 | "target": "ES5", 9 | "declaration": true, 10 | "esModuleInterop": true, 11 | "lib": [ 12 | "DOM", 13 | "ES5", 14 | "ES2015", 15 | "ES2016", 16 | "ES2017", 17 | "ES2018" 18 | ] 19 | }, 20 | "exclude": [ 21 | "dist/**/*", 22 | "node_modules" 23 | ] 24 | } 25 | --------------------------------------------------------------------------------