├── .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 | [![Build Status](https://github.com/mobxjs/mobx-vue/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/stylus/stylus/actions?query=branch%3Amaster) 4 | [![npm version](https://img.shields.io/npm/v/mobx-vue.svg)](https://www.npmjs.com/package/mobx-vue) 5 | [![coverage](https://img.shields.io/codecov/c/github/mobxjs/mobx-vue.svg)](https://codecov.io/gh/mobxjs/mobx-vue) 6 | [![npm downloads](https://img.shields.io/npm/dt/mobx-vue.svg)](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 | ![logo](https://github.com/mobxjs/mobx-vue/blob/master/logo.png?raw=true) 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 | 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 | 7 | 8 | 26 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/ClassBase.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 26 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/Conditional.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 75 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/DecoratedClassBase.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 41 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/ModelClassBase.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 39 | -------------------------------------------------------------------------------- /src/__tests__/fixtures2/Conditional.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 64 | -------------------------------------------------------------------------------- /src/__tests__/fixtures2/DecoratedClassBase.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 38 | -------------------------------------------------------------------------------- /src/__tests__/fixtures2/ModelClassBase.vue: -------------------------------------------------------------------------------- 1 | 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 | --------------------------------------------------------------------------------