├── .editorconfig
├── .github
└── workflows
│ ├── ci.yml
│ ├── release-alpha.yml
│ └── release.yml
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── example
├── .babelrc
├── .editorconfig
├── .gitignore
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── src
│ ├── App.vue
│ ├── Component.vue
│ ├── assets
│ │ └── logo.png
│ └── main.ts
├── tsconfig.json
└── webpack.config.js
├── logo.png
├── package-lock.json
├── package.json
├── src
├── __tests__
│ ├── fixtures
│ │ ├── Base.vue
│ │ ├── ClassBase.vue
│ │ ├── Conditional.vue
│ │ ├── DecoratedClassBase.vue
│ │ └── ModelClassBase.vue
│ ├── fixtures2
│ │ ├── Conditional.vue
│ │ ├── DecoratedClassBase.vue
│ │ └── ModelClassBase.vue
│ ├── makeAutoObservable.spec.ts
│ ├── makeObservable.spec.ts
│ └── observer.spec.ts
├── collectData.ts
├── index.ts
├── observer.ts
└── typings.d.ts
├── tsconfig.cjs.json
├── tsconfig.json
└── tslint.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [**]
5 | # Unix-style newlines with a newline ending every file
6 | end_of_line = lf
7 | insert_final_newline = true
8 | trim_trailing_whitespace = true
9 | charset = utf-8
10 | indent_style = tab
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [{package.json,.babelrc,.yml,.eslintrc,.esdocrc}]
16 | indent_style = space
17 | indent_size = 2
18 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Node.js CI
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 | - name: Use Node.js v12.x
17 | uses: actions/setup-node@v1
18 | with:
19 | node-version: '12.x'
20 | - name: Cache Node.js modules
21 | uses: actions/cache@v2
22 | with:
23 | # npm cache files are stored in `~/.npm` on Linux/macOS
24 | path: ~/.npm
25 | key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}
26 | restore-keys: |
27 | ${{ runner.OS }}-node-
28 | ${{ runner.OS }}-
29 | - name: Install dependencies
30 | run: npm ci
31 | - name: Run unit test
32 | run: npm run lint
33 | - name: Codecov
34 | uses: codecov/codecov-action@v1
35 | with:
36 | fail_ci_if_error: true
37 |
--------------------------------------------------------------------------------
/.github/workflows/release-alpha.yml:
--------------------------------------------------------------------------------
1 | name: NPM Release Alpha
2 |
3 | on:
4 | push:
5 | tags:
6 | - v2.1.0-alpha.*
7 |
8 | jobs:
9 | Release:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v2
13 | - name: Use Node.js v12.x
14 | uses: actions/setup-node@v2
15 | with:
16 | node-version: '12.x'
17 | registry-url: 'https://registry.npmjs.org'
18 | - name: Install dependencies
19 | run: npm install
20 | - name: build package
21 | run: npm run build
22 | - name: cp file
23 | run: cp {package.json,README.md,LICENSE,.npmignore} dist
24 | - name: Npm publish
25 | run: npm publish ./dist --tag next
26 | env:
27 | NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
28 |
29 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: NPM Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - v2.*.*
7 |
8 | jobs:
9 | Release:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v2
13 | - name: Use Node.js v12.x
14 | uses: actions/setup-node@v2
15 | with:
16 | node-version: '12.x'
17 | registry-url: 'https://registry.npmjs.org'
18 | - name: Install dependencies
19 | run: npm install
20 | - name: build package
21 | run: npm run build
22 | - name: cp file
23 | run: cp {package.json,README.md,LICENSE,.npmignore} dist
24 | - name: Npm publish
25 | run: npm publish ./dist
26 | env:
27 | NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
28 |
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .idea/
3 | coverage/
4 | dist/
5 | .DS_Store
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | __tests__
2 | node_modules
3 | *.js.map
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 mobx-vue
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mobx-vue
2 |
3 | [](https://github.com/stylus/stylus/actions?query=branch%3Amaster)
4 | [](https://www.npmjs.com/package/mobx-vue)
5 | [](https://codecov.io/gh/mobxjs/mobx-vue)
6 | [](https://www.npmjs.com/package/mobx-vue)
7 |
8 | Vue2 bindings for MobX, inspired by [mobx-react](https://github.com/mobxjs/mobx-react). Looking for Vue3 support? Please look here [mobx-vue-lite](https://github.com/mobxjs/mobx-vue-lite)
9 |
10 | 
11 |
12 | ## Support Table
13 | | package | mobx v6 | mobx v2/v3/v4/v5 | vue2 | vue3 |
14 | | ------ | ------- | ---------------- | ---- | ---- |
15 | | [mobx-vue](https://github.com/mobxjs/mobx-vue) | >= v2.1.0 | * (exclude v2.1.0) | * | - |
16 | | [mobx-vue-lite](https://github.com/mobxjs/mobx-vue-lite) | * | - | - | * |
17 | > `*` means `all` and `-` means `none`
18 |
19 | ## Installation
20 |
21 | ```bash
22 | npm i mobx-vue -S
23 | ```
24 |
25 | or
26 |
27 | ```bash
28 | yarn add mobx-vue
29 | ```
30 |
31 | ## Why mobx-vue
32 |
33 | MobX is an unopinionated, scalable state management, which can make our programming more intuitive.
34 |
35 | Unlike the other vue-rx-inspired libraries which based on the plugin mechanism of vue, mobx-vue will be the simplest you ever meet. What you all need is to bind your state in component definition and observe it just like [mobx-react](https://github.com/mobxjs/mobx-react) does, then your component will react to your state changes automatically which managed by mobx.
36 |
37 | And, the most important is that you can build a view-library-free application, if you wanna migrate to another view library(React/Angular) someday, rewrite the template and switch to the relevant mobx bindings([mobx-react](https://github.com/mobxjs/mobx-react),[mobx-angular](https://github.com/mobxjs/mobx-angular),[mobx-angularjs](https://github.com/mobxjs/mobx-angularjs)) is all you have to do.
38 |
39 | ### Articles:
40 |
41 | * [Build A View-Framework-Free Data Layer Based on MobX — Integration With Vue](https://medium.com/@kuitos/build-a-view-framework-free-data-layer-based-on-mobx-integration-with-vue-1-8b524b86c7b8)
42 |
43 | * [Why MobX + movue, instead of Vuex?](https://github.com/nighca/movue/issues/8)
44 | * [基于 MobX 构建视图框架无关的数据层-与 Vue 的结合(1)](https://zhuanlan.zhihu.com/p/37736470)
45 |
46 | ## Usage
47 |
48 | We highly recommend using the bindings with [vue-class-component](https://github.com/vuejs/vue-class-component) decorator, and define the Store/ViewModel independently.
49 |
50 | ```ts
51 | import { action, computed, observable } from "mobx";
52 | export default class ViewModel {
53 | @observable age = 10;
54 | @observable users = [];
55 |
56 | @computed get computedAge() {
57 | return this.age + 1;
58 | }
59 |
60 | @action.bound setAge() {
61 | this.age++;
62 | }
63 |
64 | @action.bound async fetchUsers() {
65 | this.users = await http.get('/users')
66 | }
67 | }
68 | ```
69 |
70 | ```vue
71 |
72 |
73 |
74 |
75 | {{user.name}}
76 |
77 |
78 |
79 |
80 |
95 | ```
96 |
97 | Or used with the traditional way:
98 |
99 | ```vue
100 |
113 | ```
114 |
115 | All you need is to bind your state to component and observe it. No more reactive data definitions in component.
116 |
117 | *Tips: If you're tired of instantiating instance manually every time, you might wanna try the [mmlpx](https://github.com/mmlpxjs/mmlpx) library which leveraged a dependency injection system.*
118 |
119 | ## API
120 |
121 | * observer((VueComponent | options): ExtendedVueComponent
122 |
--------------------------------------------------------------------------------
/example/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "env",
5 | {
6 | "modules": false
7 | }
8 | ],
9 | "stage-0"
10 | ],
11 | "plugins": [
12 | "transform-decorators-legacy",
13 | "transform-vue-jsx"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/example/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | npm-debug.log
5 | yarn-error.log
6 |
7 | # Editor directories and files
8 | .idea
9 | *.suo
10 | *.ntvs*
11 | *.njsproj
12 | *.sln
13 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # example
2 |
3 | > A Vue.js project
4 |
5 | ## Build Setup
6 |
7 | ``` bash
8 | # install dependencies
9 | npm install
10 |
11 | # serve with hot reload at localhost:8080
12 | npm run dev
13 |
14 | # build for production with minification
15 | npm run build
16 | ```
17 |
18 | For detailed explanation on how things work, consult the [docs for vue-loader](http://vuejs.github.io/vue-loader).
19 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "description": "A Vue.js project",
4 | "version": "2.0.0",
5 | "author": "Kuitos ",
6 | "license": "MIT",
7 | "private": true,
8 | "scripts": {
9 | "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
10 | "build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
11 | },
12 | "dependencies": {
13 | "mmlpx": "^3.0.0",
14 | "mobx": "^5.0.3",
15 | "mobx-state-tree": "^2.2.0",
16 | "mobx-vue": "^2.0.6",
17 | "vue": "^2.5.11",
18 | "vue-property-decorator": "^6.1.0",
19 | "vue-router": "^3.0.1"
20 | },
21 | "browserslist": [
22 | "> 1%",
23 | "last 2 versions",
24 | "not ie <= 8"
25 | ],
26 | "devDependencies": {
27 | "@vue/test-utils": "^1.0.0-beta.18",
28 | "babel-core": "^6.26.0",
29 | "babel-helper-vue-jsx-merge-props": "^2.0.3",
30 | "babel-loader": "^7.1.2",
31 | "babel-plugin-syntax-jsx": "^6.18.0",
32 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
33 | "babel-plugin-transform-vue-jsx": "^3.7.0",
34 | "babel-preset-env": "^1.6.0",
35 | "babel-preset-stage-0": "^6.24.1",
36 | "cross-env": "^5.0.5",
37 | "css-loader": "^0.28.7",
38 | "file-loader": "^1.1.4",
39 | "ts-loader": "^3.5.0",
40 | "typescript": "^2.9.1",
41 | "vue-class-component": "^6.2.0",
42 | "vue-loader": "^13.0.5",
43 | "vue-template-compiler": "^2.4.4",
44 | "webpack": "^3.6.0",
45 | "webpack-dev-server": "^3.1.11"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/example/src/App.vue:
--------------------------------------------------------------------------------
1 |
101 |
102 |
130 |
--------------------------------------------------------------------------------
/example/src/Component.vue:
--------------------------------------------------------------------------------
1 |
34 |
35 |
38 |
--------------------------------------------------------------------------------
/example/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobxjs/mobx-vue/4c2825e0731f14a4fa8b81b13eac319514377344/example/src/assets/logo.png
--------------------------------------------------------------------------------
/example/src/main.ts:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import Router from 'vue-router';
4 |
5 | Vue.use(Router);
6 |
7 | const router = new Router({
8 | routes: [
9 | {
10 | path: "/",
11 | component: App
12 | }
13 | ]
14 | });
15 |
16 | new Vue({
17 | el: '#app',
18 | router,
19 | render: h => {
20 | return h(App)
21 | }
22 | })
23 |
--------------------------------------------------------------------------------
/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "module": "es2015",
5 | "moduleResolution": "node",
6 | "target": "es5",
7 | // "strict": true,
8 | "experimentalDecorators": true,
9 | "emitDecoratorMetadata": true,
10 | "jsx": "preserve",
11 | "jsxFactory": "h"
12 | },
13 | "include": [
14 | "./src/**/*"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/example/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var webpack = require('webpack')
3 |
4 | module.exports = {
5 | entry: './src/main.ts',
6 | output: {
7 | path: path.resolve(__dirname, './dist'),
8 | publicPath: '/dist/',
9 | filename: 'build.js'
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.css$/,
15 | use: [
16 | 'vue-style-loader',
17 | 'css-loader'
18 | ],
19 | }, {
20 | test: /\.vue$/,
21 | loader: 'vue-loader',
22 | options: {
23 | loaders: {
24 | ts: 'ts-loader',
25 | tsx: 'babel-loader!ts-loader'
26 | }
27 | // other vue-loader options go here
28 | }
29 | },
30 | {
31 | test: /\.tsx?$/,
32 | exclude: /node_modules/,
33 | use: [
34 | 'babel-loader',
35 | {
36 | loader: 'ts-loader',
37 | options: { appendTsxSuffixTo: [/\.vue$/] }
38 | }
39 | ]
40 | },
41 | {
42 | test: /\.js$/,
43 | loader: 'babel-loader',
44 | exclude: /node_modules/
45 | },
46 | {
47 | test: /\.(png|jpg|gif|svg)$/,
48 | loader: 'file-loader',
49 | options: {
50 | name: '[name].[ext]?[hash]'
51 | }
52 | }
53 | ]
54 | },
55 | resolve: {
56 | alias: {
57 | 'vue$': 'vue/dist/vue.esm.js'
58 | },
59 | extensions: ['*', '.ts', 'tsx', '.js', '.vue', '.json']
60 | },
61 | devServer: {
62 | historyApiFallback: true,
63 | noInfo: true,
64 | overlay: true
65 | },
66 | performance: {
67 | hints: false
68 | },
69 | devtool: '#eval-source-map',
70 | }
71 |
72 | if (process.env.NODE_ENV === 'production') {
73 | module.exports.devtool = '#source-map'
74 | // http://vue-loader.vuejs.org/en/workflow/production.html
75 | module.exports.plugins = (module.exports.plugins || []).concat([
76 | new webpack.DefinePlugin({
77 | 'process.env': {
78 | NODE_ENV: '"production"'
79 | }
80 | }),
81 | // new webpack.optimize.UglifyJsPlugin({
82 | // sourceMap: true,
83 | // compress: {
84 | // warnings: false
85 | // }
86 | // }),
87 | new webpack.LoaderOptionsPlugin({
88 | minimize: true
89 | })
90 | ])
91 | }
92 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobxjs/mobx-vue/4c2825e0731f14a4fa8b81b13eac319514377344/logo.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mobx-vue",
3 | "version": "2.2.0",
4 | "description": "Vue bindings for MobX",
5 | "main": "./lib/index.js",
6 | "module": "./esm/index.js",
7 | "types": "./esm/index.d.ts",
8 | "sideEffects": false,
9 | "scripts": {
10 | "start": "jest -o --watch --coverage false",
11 | "build": "npm run build:esm & npm run build:cjs",
12 | "build:esm": "rm -fr dist/esm && tsc --skipLibCheck",
13 | "build:cjs": "rm -fr dist/lib && tsc --skipLibCheck -p tsconfig.cjs.json",
14 | "lint": "tslint 'src/**/*.ts' && npm test",
15 | "prepush": "npm run lint",
16 | "test": "jest"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/mobxjs/mobx-vue.git"
21 | },
22 | "keywords": [
23 | "mobx",
24 | "vue",
25 | "mobx-vue",
26 | "bindings",
27 | "connector"
28 | ],
29 | "author": "Kuitos",
30 | "license": "MIT",
31 | "bugs": {
32 | "url": "https://github.com/mobxjs/mobx-vue/issues"
33 | },
34 | "homepage": "https://github.com/mobxjs/mobx-vue#readme",
35 | "peerDependencies": {
36 | "mobx": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0",
37 | "vue": "^2.0.0"
38 | },
39 | "devDependencies": {
40 | "@types/jest": "^22.2.3",
41 | "@vue/test-utils": "1.0.0-beta.22",
42 | "husky": "^0.14.3",
43 | "jest": "^23.0.0",
44 | "mobx": "^6.0.0",
45 | "ts-jest": "^22.4.6",
46 | "tslint": "^5.10.0",
47 | "typescript": "^3.0.0",
48 | "vue": "^2.6.14",
49 | "vue-class-component": ">=7.2.6",
50 | "vue-jest": "^2.6.0",
51 | "vue-template-compiler": "^2.6.14"
52 | },
53 | "jest": {
54 | "globals": {
55 | "vue-jest": {
56 | "tsConfigFile": "tsconfig.json"
57 | }
58 | },
59 | "moduleFileExtensions": [
60 | "js",
61 | "ts",
62 | "vue"
63 | ],
64 | "testMatch": [
65 | "/src/**/__tests__/**/*.spec.ts"
66 | ],
67 | "transform": {
68 | "^.+\\.ts$": "/node_modules/ts-jest",
69 | ".*\\.(vue)$": "/node_modules/vue-jest"
70 | },
71 | "collectCoverage": true,
72 | "coveragePathIgnorePatterns": [
73 | "/node_modules/",
74 | "/__tests__/",
75 | "/lib/"
76 | ]
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/__tests__/fixtures/Base.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
26 |
--------------------------------------------------------------------------------
/src/__tests__/fixtures/ClassBase.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
26 |
--------------------------------------------------------------------------------
/src/__tests__/fixtures/Conditional.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
75 |
--------------------------------------------------------------------------------
/src/__tests__/fixtures/DecoratedClassBase.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
41 |
--------------------------------------------------------------------------------
/src/__tests__/fixtures/ModelClassBase.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
39 |
--------------------------------------------------------------------------------
/src/__tests__/fixtures2/Conditional.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
64 |
--------------------------------------------------------------------------------
/src/__tests__/fixtures2/DecoratedClassBase.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
38 |
--------------------------------------------------------------------------------
/src/__tests__/fixtures2/ModelClassBase.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
36 |
--------------------------------------------------------------------------------
/src/__tests__/makeAutoObservable.spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author iChenLei
3 | * @homepage https://github.com/iChenLei/
4 | * @since 2021-06-17 17:42
5 | */
6 | import { shallowMount } from '@vue/test-utils';
7 | import { makeAutoObservable, observable } from 'mobx';
8 | import Vue, { CreateElement } from 'vue';
9 | import Component from 'vue-class-component';
10 | import { Observer, observer } from '../observer';
11 | import Conditional from './fixtures2/Conditional.vue';
12 | import DecoratedClassBase from './fixtures2/DecoratedClassBase.vue';
13 | import ModelClassBase from './fixtures2/ModelClassBase.vue';
14 |
15 | class Model {
16 | age = 10;
17 |
18 | constructor () {
19 | makeAutoObservable(this, { }, { autoBind: true });
20 | }
21 |
22 | get computedAge() {
23 | return this.age + 1;
24 | }
25 |
26 | setAge () {
27 | this.age++;
28 | }
29 | }
30 |
31 | test('use observer function with class component and observable model constructed by class', () => {
32 |
33 | const Component = observer(ModelClassBase);
34 |
35 | const wrapper = shallowMount(Component);
36 | expect(wrapper.name()).toBe(ModelClassBase.name);
37 | expect(wrapper.find('[role="age"]').text()).toBe('10');
38 | expect(wrapper.find('[role="computed-age"]').text()).toBe('11');
39 |
40 | wrapper.find('button').trigger('click');
41 | expect(wrapper.find('[role=age]').text()).toBe('11');
42 | expect(wrapper.find('[role="computed-age"]').text()).toBe('12');
43 |
44 | wrapper.destroy();
45 | });
46 |
47 | describe('use observer decorator with class component and observable model constructed by class', () => {
48 |
49 | const wrapper = shallowMount(DecoratedClassBase);
50 | test('component should be reactive', () => {
51 |
52 | expect(wrapper.name()).toBe((DecoratedClassBase as any).name);
53 | expect(wrapper.find('[role="age"]').text()).toBe('10');
54 | expect(wrapper.find('[role="computed-age"]').text()).toBe('11');
55 |
56 | wrapper.find('button').trigger('click');
57 | wrapper.find('button').trigger('click');
58 | expect(wrapper.find('[role=age]').text()).toBe('12');
59 | expect(wrapper.find('[role="computed-age"]').text()).toBe('13');
60 |
61 | wrapper.destroy();
62 |
63 | });
64 |
65 | test('mobx reaction should be disposed while component destroyed', () => {
66 |
67 | const spy = jest.fn();
68 | const $destroy = DecoratedClassBase.prototype.$destroy;
69 | DecoratedClassBase.prototype.$destroy = function (this: any, ...args: any[]) {
70 | $destroy.apply(this, args);
71 | spy();
72 | };
73 |
74 | wrapper.destroy();
75 | expect(spy.mock.calls.length).toBe(1);
76 | });
77 |
78 | });
79 | //
80 | test('compatible with traditional component definition', () => {
81 |
82 | const Component = observer({
83 | name: 'HelloWorld',
84 | data() {
85 | return { name: 'kuitos', model: new Model() };
86 | },
87 | methods: {
88 | setName(this: any) {
89 | this.name = 'lk';
90 | },
91 | },
92 | render(this: any, h: CreateElement) {
93 | return h('button', {
94 | on: { click: this.model.setAge, focus: this.setName }, domProps: { textContent: `${this.model.age} ${this.name}` },
95 | });
96 | },
97 | });
98 | const wrapper = shallowMount(Component);
99 | wrapper.trigger('click');
100 | expect(wrapper.find('button').text()).toBe('11 kuitos');
101 | wrapper.trigger('focus');
102 | expect(wrapper.find('button').text()).toBe('11 lk');
103 | });
104 |
105 | test('component lifecycle should worked well', () => {
106 |
107 | const Component = observer({
108 | name: 'HelloWorld',
109 | data() {
110 | return { model: new Model() };
111 | },
112 | beforeCreate(this: any) {
113 | expect(this.model).toBeUndefined();
114 | },
115 | created(this: any) {
116 | expect(this.model.age).toBe(10);
117 | },
118 | beforeUpdate(this: any) {
119 | expect(this.model.age).toBe(11);
120 | },
121 | updated(this: any) {
122 | expect(this.model.age).toBe(11);
123 | },
124 | render(this: any, h: CreateElement) {
125 | return h('button', {
126 | on: { click: this.model.setAge }, domProps: { textContent: this.model.age },
127 | });
128 | },
129 | });
130 |
131 | const wrapper = shallowMount(Component);
132 | wrapper.trigger('click');
133 |
134 | });
135 |
136 | test('conditional render should be re tracked', () => {
137 |
138 | const wrapper = shallowMount(Conditional);
139 | expect(wrapper.find('[role=age]').text()).toBe('10');
140 | wrapper.find('[role=toggle]').trigger('click');
141 | expect(wrapper.find('[role=count]').text()).toBe('0');
142 | wrapper.find('[role=increase]').trigger('click');
143 | expect(wrapper.find('[role=count]').text()).toBe('1');
144 |
145 | wrapper.find('[role=native-toggle]').trigger('click');
146 | expect(wrapper.find('[role=native-count]').text()).toBe('0');
147 | wrapper.find('[role=native-increase]').trigger('click');
148 | expect(wrapper.find('[role=native-count]').text()).toBe('1');
149 | });
150 |
151 | test('mobx state should not be collect by vue', () => {
152 |
153 | class ObservableModel {
154 | name = '';
155 | constructor () {
156 | makeAutoObservable(this);
157 | }
158 | }
159 |
160 | const prop = (value: string): any => () => {
161 | return {
162 | configurable: true,
163 | get() {
164 | return value;
165 | },
166 | };
167 | };
168 |
169 | const model1 = observable({ xx: 10 });
170 |
171 | class Model {
172 | }
173 |
174 | @Observer
175 | @Component
176 | class App extends Vue {
177 |
178 | @prop('kuitos')
179 | name!: string;
180 |
181 | model = new Model();
182 | om = new ObservableModel();
183 | om1 = model1;
184 | age = 10;
185 |
186 | render(h: CreateElement) {
187 | return h('div');
188 | }
189 | }
190 |
191 | const vm = shallowMount(App).vm;
192 |
193 | expect(vm.$data.hasOwnProperty('om')).toBeFalsy();
194 | expect(vm.$data.hasOwnProperty('om1')).toBeFalsy();
195 | expect(vm.$data.hasOwnProperty('age')).toBeTruthy();
196 | expect(vm.$data.hasOwnProperty('model')).toBeTruthy();
197 |
198 | expect(vm.name).toBe('kuitos');
199 | expect(vm.$data.hasOwnProperty('name')).toBeFalsy();
200 | });
201 |
--------------------------------------------------------------------------------
/src/__tests__/makeObservable.spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author iChenLei
3 | * @homepage https://github.com/iChenLei/
4 | * @since 2021-06-17 18:49
5 | */
6 | import { shallowMount } from '@vue/test-utils';
7 | import { action, computed, makeObservable, observable } from 'mobx';
8 | import Vue, { CreateElement } from 'vue';
9 | import Component from 'vue-class-component';
10 | import { Observer, observer } from '../observer';
11 |
12 | class Model {
13 | age = 10;
14 |
15 | constructor () {
16 | makeObservable(this, {
17 | age: observable,
18 | computedAge: computed,
19 | setAge: action.bound,
20 | });
21 | }
22 |
23 | get computedAge() {
24 | return this.age + 1;
25 | }
26 |
27 | setAge () {
28 | this.age++;
29 | }
30 | }
31 |
32 | test('component lifecycle should worked well', () => {
33 |
34 | const Component = observer({
35 | name: 'HelloWorld',
36 | data() {
37 | return { model: new Model() };
38 | },
39 | beforeCreate(this: any) {
40 | expect(this.model).toBeUndefined();
41 | },
42 | created(this: any) {
43 | expect(this.model.age).toBe(10);
44 | },
45 | beforeUpdate(this: any) {
46 | expect(this.model.age).toBe(11);
47 | },
48 | updated(this: any) {
49 | expect(this.model.age).toBe(11);
50 | },
51 | render(this: any, h: CreateElement) {
52 | return h('button', {
53 | on: { click: this.model.setAge }, domProps: { textContent: this.model.age },
54 | });
55 | },
56 | });
57 |
58 | const wrapper = shallowMount(Component);
59 | wrapper.trigger('click');
60 |
61 | });
62 |
63 | test('mobx state should not be collect by vue', () => {
64 |
65 | class ObservableModel {
66 | name = '';
67 | constructor () {
68 | makeObservable(this, {
69 | name: observable,
70 | });
71 | }
72 | }
73 |
74 | const prop = (value: string): any => () => {
75 | return {
76 | configurable: true,
77 | get() {
78 | return value;
79 | },
80 | };
81 | };
82 |
83 | const model1 = observable({ xx: 10 });
84 |
85 | class Model {
86 | }
87 |
88 | @Observer
89 | @Component
90 | class App extends Vue {
91 |
92 | @prop('kuitos')
93 | name!: string;
94 |
95 | model = new Model();
96 | om = new ObservableModel();
97 | om1 = model1;
98 | age = 10;
99 |
100 | render(h: CreateElement) {
101 | return h('div');
102 | }
103 | }
104 |
105 | const vm = shallowMount(App).vm;
106 |
107 | expect(vm.$data.hasOwnProperty('om')).toBeFalsy();
108 | expect(vm.$data.hasOwnProperty('om1')).toBeFalsy();
109 | expect(vm.$data.hasOwnProperty('age')).toBeTruthy();
110 | expect(vm.$data.hasOwnProperty('model')).toBeTruthy();
111 |
112 | expect(vm.name).toBe('kuitos');
113 | expect(vm.$data.hasOwnProperty('name')).toBeFalsy();
114 | });
115 |
--------------------------------------------------------------------------------
/src/__tests__/observer.spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Kuitos
3 | * @homepage https://github.com/kuitos/
4 | * @since 2018-05-24 13:36
5 | */
6 | import { shallowMount } from '@vue/test-utils';
7 | import { action, computed, makeObservable, observable } from 'mobx';
8 | import Vue, { CreateElement } from 'vue';
9 | import Component from 'vue-class-component';
10 | import { Observer, observer } from '../observer';
11 | import Base from './fixtures/Base.vue';
12 | import ClassBase from './fixtures/ClassBase.vue';
13 | import Conditional from './fixtures/Conditional.vue';
14 | import DecoratedClassBase from './fixtures/DecoratedClassBase.vue';
15 | import ModelClassBase from './fixtures/ModelClassBase.vue';
16 |
17 | class Model {
18 | @observable
19 | age = 10;
20 |
21 | constructor () {
22 | makeObservable(this);
23 | }
24 |
25 | @computed
26 | get computedAge() {
27 | return this.age + 1;
28 | }
29 |
30 | @action.bound
31 | setAge() {
32 | this.age++;
33 | }
34 | }
35 |
36 | test('observer with a object literal component', () => {
37 |
38 | const model = observable({
39 | age: 10,
40 | setAge() {
41 | model.age++;
42 | },
43 | });
44 |
45 | const Component = observer({
46 | data() {
47 | return {
48 | model,
49 | };
50 | },
51 | name: 'HelloWorld',
52 | render(this: any, h: CreateElement) {
53 | return h('button', {
54 | on: { click: this.model.setAge }, domProps: { textContent: this.model.age },
55 | });
56 | },
57 | });
58 |
59 | const wrapper = shallowMount(Component);
60 | expect(wrapper.name()).toBe('HelloWorld');
61 | expect(wrapper.find('button').text()).toBe('10');
62 |
63 | wrapper.find('button').trigger('click');
64 | expect(wrapper.find('button').text()).toBe('11');
65 |
66 | wrapper.destroy();
67 | });
68 |
69 | test('observer with a base component', () => {
70 |
71 | const Component = observer(Base);
72 |
73 | const wrapper = shallowMount(Component);
74 | expect(wrapper.name()).toBe(Base.name);
75 | expect(wrapper.find('p').text()).toBe('10');
76 |
77 | wrapper.find('button').trigger('click');
78 | expect(wrapper.find('p').text()).toBe('11');
79 |
80 | wrapper.destroy();
81 |
82 | });
83 |
84 | test('observer with a class component', () => {
85 |
86 | const Component = observer(ClassBase);
87 |
88 | const wrapper = shallowMount(Component);
89 | expect(wrapper.name()).toBe(ClassBase.name);
90 | expect(wrapper.find('[role="age"]').text()).toBe('10');
91 |
92 | wrapper.find('button').trigger('click');
93 | expect(wrapper.find('[role="age"]').text()).toBe('11');
94 |
95 | wrapper.destroy();
96 | });
97 | //
98 | test('use observer function with class component and observable model constructed by class', () => {
99 |
100 | const Component = observer(ModelClassBase);
101 |
102 | const wrapper = shallowMount(Component);
103 | expect(wrapper.name()).toBe(ModelClassBase.name);
104 | expect(wrapper.find('[role="age"]').text()).toBe('10');
105 | expect(wrapper.find('[role="computed-age"]').text()).toBe('11');
106 |
107 | wrapper.find('button').trigger('click');
108 | expect(wrapper.find('[role=age]').text()).toBe('11');
109 | expect(wrapper.find('[role="computed-age"]').text()).toBe('12');
110 |
111 | wrapper.destroy();
112 | });
113 | //
114 | describe('use observer decorator with class component and observable model constructed by class', () => {
115 |
116 | const wrapper = shallowMount(DecoratedClassBase);
117 | test('component should be reactive', () => {
118 |
119 | expect(wrapper.name()).toBe((DecoratedClassBase as any).name);
120 | expect(wrapper.find('[role="age"]').text()).toBe('10');
121 | expect(wrapper.find('[role="computed-age"]').text()).toBe('11');
122 |
123 | wrapper.find('button').trigger('click');
124 | wrapper.find('button').trigger('click');
125 | expect(wrapper.find('[role=age]').text()).toBe('12');
126 | expect(wrapper.find('[role="computed-age"]').text()).toBe('13');
127 |
128 | wrapper.destroy();
129 |
130 | });
131 |
132 | test('mobx reaction should be disposed while component destroyed', () => {
133 |
134 | const spy = jest.fn();
135 | const $destroy = DecoratedClassBase.prototype.$destroy;
136 | DecoratedClassBase.prototype.$destroy = function (this: any, ...args: any[]) {
137 | $destroy.apply(this, args);
138 | spy();
139 | };
140 |
141 | wrapper.destroy();
142 | expect(spy.mock.calls.length).toBe(1);
143 | });
144 |
145 | });
146 | //
147 | test('compatible with traditional component definition', () => {
148 |
149 | const Component = observer({
150 | name: 'HelloWorld',
151 | data() {
152 | return { name: 'kuitos', model: new Model() };
153 | },
154 | methods: {
155 | setName(this: any) {
156 | this.name = 'lk';
157 | },
158 | },
159 | render(this: any, h: CreateElement) {
160 | return h('button', {
161 | on: { click: this.model.setAge, focus: this.setName }, domProps: { textContent: `${this.model.age} ${this.name}` },
162 | });
163 | },
164 | });
165 | const wrapper = shallowMount(Component);
166 | wrapper.trigger('click');
167 | expect(wrapper.find('button').text()).toBe('11 kuitos');
168 | wrapper.trigger('focus');
169 | expect(wrapper.find('button').text()).toBe('11 lk');
170 | });
171 |
172 | test('component lifecycle should worked well', () => {
173 |
174 | const Component = observer({
175 | name: 'HelloWorld',
176 | data() {
177 | return { model: new Model() };
178 | },
179 | beforeCreate(this: any) {
180 | expect(this.model).toBeUndefined();
181 | },
182 | created(this: any) {
183 | expect(this.model.age).toBe(10);
184 | },
185 | beforeUpdate(this: any) {
186 | expect(this.model.age).toBe(11);
187 | },
188 | updated(this: any) {
189 | expect(this.model.age).toBe(11);
190 | },
191 | render(this: any, h: CreateElement) {
192 | return h('button', {
193 | on: { click: this.model.setAge }, domProps: { textContent: this.model.age },
194 | });
195 | },
196 | });
197 |
198 | const wrapper = shallowMount(Component);
199 | wrapper.trigger('click');
200 |
201 | });
202 |
203 | test('conditional render should be re tracked', () => {
204 |
205 | const wrapper = shallowMount(Conditional);
206 | expect(wrapper.find('[role=age]').text()).toBe('10');
207 | wrapper.find('[role=toggle]').trigger('click');
208 | expect(wrapper.find('[role=count]').text()).toBe('0');
209 | wrapper.find('[role=increase]').trigger('click');
210 | expect(wrapper.find('[role=count]').text()).toBe('1');
211 |
212 | wrapper.find('[role=native-toggle]').trigger('click');
213 | expect(wrapper.find('[role=native-count]').text()).toBe('0');
214 | wrapper.find('[role=native-increase]').trigger('click');
215 | expect(wrapper.find('[role=native-count]').text()).toBe('1');
216 | });
217 |
218 | test('mobx state should not be collect by vue', () => {
219 |
220 | class ObservableModel {
221 | @observable name = '';
222 |
223 | constructor () {
224 | makeObservable(this);
225 | }
226 | }
227 |
228 | const prop = (value: string): any => () => {
229 | return {
230 | configurable: true,
231 | get() {
232 | return value;
233 | },
234 | };
235 | };
236 |
237 | const model1 = observable({ xx: 10 });
238 |
239 | class Model {
240 | }
241 |
242 | @Observer
243 | @Component
244 | class App extends Vue {
245 |
246 | @prop('kuitos')
247 | name!: string;
248 |
249 | model = new Model();
250 | om = new ObservableModel();
251 | om1 = model1;
252 | age = 10;
253 |
254 | render(h: CreateElement) {
255 | return h('div');
256 | }
257 | }
258 |
259 | const vm = shallowMount(App).vm;
260 |
261 | expect(vm.$data.hasOwnProperty('om')).toBeFalsy();
262 | expect(vm.$data.hasOwnProperty('om1')).toBeFalsy();
263 | expect(vm.$data.hasOwnProperty('age')).toBeTruthy();
264 | expect(vm.$data.hasOwnProperty('model')).toBeTruthy();
265 |
266 | expect(vm.name).toBe('kuitos');
267 | expect(vm.$data.hasOwnProperty('name')).toBeFalsy();
268 | });
269 |
--------------------------------------------------------------------------------
/src/collectData.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Kuitos
3 | * @homepage https://github.com/kuitos/
4 | * @since 2018-06-08 10:16
5 | */
6 |
7 | import { isObservable } from 'mobx';
8 | import Vue from 'vue';
9 | import { DefaultData } from 'vue/types/options';
10 |
11 | /**
12 | * collect the data which defined for vue
13 | * and filter the mobx data to avoid duplicated watching by vue
14 | * @param {Vue} vm
15 | * @param {DefaultData} data
16 | * @returns {any} filtered data for vue definition
17 | */
18 | export default function collectData(vm: Vue, data?: DefaultData) {
19 |
20 | // @ts-expect-error
21 | const dataDefinition = typeof data === 'function' ? data.call(vm, vm) : (data || {});
22 | const filteredData = Object.keys(dataDefinition).reduce((result: any, field) => {
23 |
24 | const value = dataDefinition[field];
25 | if (isObservable(value)) {
26 | Object.defineProperty(vm, field, {
27 | configurable: true,
28 | get() {
29 | return value;
30 | },
31 | // @formatter:off
32 | // tslint:disable-next-line
33 | set() {}
34 | // @formatter:on
35 | });
36 | } else {
37 | result[field] = value;
38 | }
39 |
40 | return result;
41 |
42 | }, {});
43 |
44 | return filteredData;
45 | }
46 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Kuitos
3 | * @homepage https://github.com/kuitos/
4 | * @since 2018-05-24 13:22
5 | */
6 |
7 | export * from './observer';
8 |
--------------------------------------------------------------------------------
/src/observer.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Kuitos
3 | * @homepage https://github.com/kuitos/
4 | * @since 2018-05-22 16:39
5 | */
6 | import { Reaction } from 'mobx';
7 | import Vue, { ComponentOptions } from 'vue';
8 | import collectDataForVue from './collectData';
9 |
10 | export type VueClass = (new(...args: any[]) => V & Vue) & typeof Vue;
11 |
12 | // @formatter:off
13 | // tslint:disable-next-line
14 | const noop = () => {};
15 | const disposerSymbol = Symbol('disposerSymbol');
16 | // @formatter:on
17 | function observer>(Component: VC | ComponentOptions): VC;
18 | function observer>(Component: VC | ComponentOptions) {
19 |
20 | const name = (Component as any).name || (Component as any)._componentTag || (Component.constructor && Component.constructor.name) || '';
21 |
22 | const originalOptions = typeof Component === 'object' ? Component : (Component as any).options;
23 | // To not mutate the original component options, we need to construct a new one
24 | const dataDefinition = originalOptions.data;
25 | const options = {
26 | name,
27 | ...originalOptions,
28 | data(vm: Vue) {
29 | return collectDataForVue(vm || this, dataDefinition);
30 | },
31 | // overrider the cached constructor to avoid extending skip
32 | // @see https://github.com/vuejs/vue/blob/6cc070063bd211229dff5108c99f7d11b6778550/src/core/global-api/extend.js#L24
33 | _Ctor: {},
34 | };
35 |
36 | // we couldn't use the Component as super class when Component was a VueClass, that will invoke the lifecycle twice after we called Component.extend
37 | const superProto = typeof Component === 'function' && Object.getPrototypeOf(Component.prototype);
38 | const Super = superProto instanceof Vue ? superProto.constructor : Vue;
39 | const ExtendedComponent = Super.extend(options);
40 |
41 | const { $mount, $destroy } = ExtendedComponent.prototype;
42 |
43 | ExtendedComponent.prototype.$mount = function (this: any, ...args: any[]) {
44 |
45 | let mounted = false;
46 | this[disposerSymbol] = noop;
47 |
48 | let nativeRenderOfVue: any;
49 | const reactiveRender = () => {
50 | reaction.track(() => {
51 | if (!mounted) {
52 | $mount.apply(this, args);
53 | mounted = true;
54 | nativeRenderOfVue = this._watcher.getter;
55 | // rewrite the native render method of vue with our reactive tracker render
56 | // thus if component updated by vue watcher, we could re track and collect dependencies by mobx
57 | this._watcher.getter = reactiveRender;
58 | } else {
59 | nativeRenderOfVue.call(this, this);
60 | }
61 | });
62 |
63 | return this;
64 | };
65 |
66 | const reaction = new Reaction(`${name}.render()`, reactiveRender);
67 |
68 | // @ts-expect-error
69 | this[disposerSymbol] = reaction.getDisposer_?.() || reaction.getDisposer?.();
70 |
71 | return reactiveRender();
72 | };
73 |
74 | ExtendedComponent.prototype.$destroy = function (this: Vue) {
75 | (this as any)[disposerSymbol]();
76 | $destroy.apply(this);
77 | };
78 |
79 | const extendedComponentNamePropertyDescriptor = Object.getOwnPropertyDescriptor(ExtendedComponent, 'name') || {};
80 | if (extendedComponentNamePropertyDescriptor.configurable === true) {
81 | Object.defineProperty(ExtendedComponent, 'name', {
82 | writable: false,
83 | value: name,
84 | enumerable: false,
85 | configurable: false,
86 | });
87 | }
88 |
89 | return ExtendedComponent;
90 | }
91 |
92 | export {
93 | observer,
94 | observer as Observer,
95 | };
96 |
--------------------------------------------------------------------------------
/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Kuitos
3 | * @homepage https://github.com/kuitos/
4 | * @since 2018-05-24 17:52
5 | */
6 |
7 | declare module '*.vue' {
8 | import Vue from 'vue';
9 | export default Vue;
10 | }
11 |
--------------------------------------------------------------------------------
/tsconfig.cjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig",
3 | "compilerOptions": {
4 | "module": "commonjs",
5 | "outDir": "dist/lib",
6 | "declaration": false
7 | },
8 | "exclude": [
9 | "**/__tests__/**"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Basic Options */
4 | "target": "es5",
5 | /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */
6 | "module": "es2015",
7 | /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
8 | "lib": [
9 | "es2015",
10 | "dom"
11 | ],
12 | /* Specify library files to be included in the compilation: */
13 | // "allowJs": true, /* Allow javascript files to be compiled. */
14 | // "checkJs": true, /* Report errors in .js files. */
15 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
16 | "declaration": true,
17 | /* Generates corresponding '.d.ts' file. */
18 | // "sourceMap": true,
19 | /* Generates corresponding '.map' file. */
20 | // "outFile": "./", /* Concatenate and emit output to single file. */
21 | "outDir": "dist/esm",
22 | /* Redirect output structure to the directory. */
23 | /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
24 | // "removeComments": true, /* Do not emit comments to output. */
25 | // "noEmit": true, /* Do not emit outputs. */
26 | "importHelpers": true,
27 | /* Import emit helpers from 'tslib'. */
28 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
29 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
30 |
31 | /* Strict Type-Checking Options */
32 | "strict": true,
33 | /* Enable all strict type-checking options. */
34 | "noImplicitAny": true,
35 | /* Raise error on expressions and declarations with an implied 'any' type. */
36 | "strictNullChecks": true,
37 | /* Enable strict null checks. */
38 | "noImplicitThis": true,
39 | /* Raise error on 'this' expressions with an implied 'any' type. */
40 | "alwaysStrict": true,
41 | /* Parse in strict mode and emit "use strict" for each source file. */
42 |
43 | /* Additional Checks */
44 | "noUnusedLocals": true,
45 | /* Report errors on unused locals. */
46 | "noUnusedParameters": true,
47 | /* Report errors on unused parameters. */
48 | "noImplicitReturns": true,
49 | /* Report error when not all code paths in function return a value. */
50 | "noFallthroughCasesInSwitch": true,
51 | "strictPropertyInitialization": true,
52 | "allowSyntheticDefaultImports": true,
53 | "esModuleInterop": true,
54 | "pretty": true,
55 | "noEmitOnError": false,
56 | /* Report errors for fallthrough cases in switch statement. */
57 | /* Module Resolution Options */
58 | "moduleResolution": "node",
59 | /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
60 | "baseUrl": "./",
61 | /* Base directory to resolve non-absolute module names. */
62 | /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
63 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
64 | // "typeRoots": [], /* List of folders to include type definitions from. */
65 | // "types": [], /* Type declaration files to be included in compilation. */
66 | // "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
67 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
68 |
69 | /* Source Map Options */
70 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
71 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
72 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
73 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
74 |
75 | "skipLibCheck": true,
76 | /* Experimental Options */
77 | "experimentalDecorators": true
78 | /* Enables experimental support for ES7 framework. */
79 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for framework. */
80 | },
81 | "include": [
82 | "./src/**/*"
83 | ],
84 | "exclude": [
85 | "./node_modules/**/*"
86 | ]
87 | }
88 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": [
4 | "tslint:recommended"
5 | ],
6 |
7 | "jsRules": {},
8 | "rules": {
9 | "indent": [
10 | "tabs"
11 | ],
12 | "quotemark": [
13 | true,
14 | "single"
15 | ],
16 | "no-console": false,
17 | "arrow-parens": [
18 | "ban-single-arg-parens"
19 | ],
20 | "object-literal-sort-keys": false,
21 | "object-curly-spacing": [
22 | true,
23 | "always"
24 | ],
25 | "member-access": false,
26 | "member-ordering": [
27 | true,
28 | {
29 | "order": [
30 | "private-static-field",
31 | "public-static-field",
32 | "private-instance-field",
33 | "public-instance-field",
34 | "private-constructor",
35 | "public-constructor",
36 | "private-instance-method",
37 | "protected-instance-method",
38 | "public-static-method",
39 | "public-instance-method"
40 | ]
41 | }
42 | ],
43 | "variable-name": false,
44 | "object-literal-key-quotes": false,
45 | "max-classes-per-file": [
46 | true,
47 | 5,
48 | "exclude-class-expressions"
49 | ],
50 | "no-shadowed-variable": false,
51 | "no-unused-expression": true,
52 | "max-line-length": [
53 | true,
54 | 180
55 | ],
56 | "interface-over-type-literal": false,
57 | "space-before-function-paren": [true, {"anonymous": "always", "named": "never", "asyncArrow": "always"}]
58 | }
59 | }
60 |
--------------------------------------------------------------------------------