├── .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 | [](https://badge.fury.io/js/%40vert%2Fcore)
6 | [](https://github.com/LancerComet/vue-jsonp/actions)
7 |
8 | 
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 |
47 |
48 |
Hello, {{name}}!
49 |
50 |
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 |
9 | div.my-component
10 | data-table
11 | image-uploader
12 |
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 |
72 | // ...
73 |
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 |
--------------------------------------------------------------------------------