├── .eslintignore ├── .github ├── FUNDING.yml └── workflows │ └── test.yml ├── .eslintrc ├── jest.config.js ├── tsconfig.json ├── LICENSE ├── test └── index.test.ts ├── README.md ├── .gitignore ├── package.json └── src └── index.ts /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | typings 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: antfu 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "es6": true, 5 | "browser": true, 6 | "node": true, 7 | "jest": true 8 | }, 9 | "extends": ["@antfu/eslint-config-ts"], 10 | "plugins": ["jest"], 11 | "rules": {} 12 | } 13 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testMatch: [ 3 | '**/__tests__/**/*.+(ts|tsx|js)', 4 | '**/?(*.)+(spec|test).+(ts|tsx|js)', 5 | ], 6 | transform: { 7 | '^.+\\.(ts|tsx)$': 'ts-jest', 8 | }, 9 | collectCoverage: true, 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "module": "esnext", 5 | "lib": ["esnext"], 6 | "moduleResolution": "node", 7 | "esModuleInterop": true, 8 | "strict": true, 9 | "strictNullChecks": true, 10 | "resolveJsonModule": true, 11 | "skipLibCheck": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [12.x] 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | 17 | - name: Install 18 | run: yarn 19 | 20 | - name: Lint 21 | run: npm run lint 22 | 23 | - name: Test 24 | run: npm test 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Anthony Fu 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 | -------------------------------------------------------------------------------- /test/index.test.ts: -------------------------------------------------------------------------------- 1 | import { isRef } from 'vue-demi' 2 | import { $ } from '../src' 3 | 4 | describe('$', () => { 5 | it('ref', () => { 6 | const a = $(1) 7 | 8 | expect(isRef(a)).toBeTruthy() 9 | expect(a.value).toBe(1) 10 | }) 11 | 12 | it('ref + object', () => { 13 | const a = $({ foo: 'bar' }) 14 | 15 | expect(isRef(a)).toBeTruthy() 16 | expect(JSON.stringify(a.value)).toBe(JSON.stringify({ foo: 'bar' })) 17 | }) 18 | 19 | it('unref', () => { 20 | const a = $(42) 21 | 22 | expect(isRef(a)).toBeTruthy() 23 | expect($(a)).toBe(42) 24 | }) 25 | 26 | it('computed & set', () => { 27 | const a = $(42) 28 | const doubled = $(() => $(a) * 2) 29 | 30 | expect($(doubled)).toBe(84) 31 | 32 | $(a, 10) 33 | 34 | expect($(doubled)).toBe(20) 35 | }) 36 | 37 | it('set ref to ref', () => { 38 | const a = $(42) 39 | const b = $(10) 40 | 41 | expect($(a)).toBe(42) 42 | expect($(b)).toBe(10) 43 | 44 | $(a, b) 45 | 46 | expect($(a)).toBe(10) 47 | expect($(b)).toBe(10) 48 | }) 49 | 50 | it('watch', () => { 51 | const a = $(42) 52 | let count = 0 53 | 54 | const stop = $(a, () => count += 1, { immediate: true, flush: 'sync' }) 55 | 56 | expect(count).toBe(1) 57 | 58 | $(a, 10) 59 | 60 | expect(count).toBe(2) 61 | 62 | stop() 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # v-dollar 2 | 3 | jQuery-like Vue Reactivity API , it's well-typed! 4 | 5 | ```bash 6 | npm i v-dollar 7 | ``` 8 | 9 | `ref` => `$`
10 | `computed` => `$`
11 | `watch` => `$`
12 | `unref` => `$`
13 | `set` => `$`
14 | 15 | You are welcome. 16 | 17 | ## Usages 18 | 19 | with `v-dollar` 20 | 21 | ```ts 22 | import { $ } from 'v-dollar' 23 | 24 | const counter = $(0) 25 | const doubled = $(() => $(counter) * 2) 26 | 27 | const reset = () => $(counter, 0) 28 | const double = () => $(counter, doubled) 29 | 30 | $(counter, (value) => console.log(value), { flush: 'post' }) 31 | ``` 32 | 33 | w/o `v-dollar` 34 | 35 | ```ts 36 | import { ref, computed, unref, watch } from 'vue' 37 | 38 | const counter = ref(0) 39 | const doubled = computed(() => counter.value * 2) 40 | 41 | const reset = () => counter.value = 0 42 | const double = () => counter.value = unref(doubled) 43 | 44 | watch(counter, (value) => console.log(value), { flush: 'post' }) 45 | ``` 46 | 47 | Or if you prefer this 😈: 48 | 49 | ```ts 50 | import { $ as _ } from 'v-dollar' 51 | 52 | const counter = _(0) 53 | const doubled = _(() => _(counter) * 2) 54 | 55 | const reset = () => _(counter, 0) 56 | const double = () => _(counter, doubled) 57 | 58 | _(counter, (value) => console.log(value), { flush: 'post' }) 59 | ``` 60 | 61 | ## Why? 62 | 63 | FUN. 64 | 65 | Don't take it seriously, it's just a toy. But yeah, you can use it in prod if you like, we have nearly 100% test coverage :p 66 | 67 | ## License 68 | 69 | MIT 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # TypeScript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # parcel-bundler cache (https://parceljs.org/) 63 | .cache 64 | 65 | # next.js build output 66 | .next 67 | 68 | # nuxt.js build output 69 | .nuxt 70 | 71 | # Nuxt generate 72 | dist 73 | 74 | # vuepress build output 75 | .vuepress/dist 76 | 77 | # Serverless directories 78 | .serverless 79 | 80 | # IDE 81 | .idea 82 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "v-dollar", 3 | "version": "0.1.0", 4 | "description": "jQuery-like Vue Reactivity API", 5 | "main": "dist/index.js", 6 | "module": "dist/index.mjs", 7 | "types": "dist/index.d.ts", 8 | "author": "Anthony Fu ", 9 | "license": "MIT", 10 | "sideEffects": false, 11 | "bugs": { 12 | "url": "https://github.com/antfu/v-dollar/issues" 13 | }, 14 | "homepage": "https://github.com/antfu/v-dollar#readme", 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/antfu/v-dollar.git" 18 | }, 19 | "scripts": { 20 | "prepublishOnly": "npm run build", 21 | "watch": "npm run build -- --watch", 22 | "dev": "esno src/index.ts", 23 | "build": "tsup src/index.ts --format cjs,esm --dts", 24 | "publish:ci": "npm publish --access public", 25 | "release": "npx bumpp --commit --push --tag && npm run publish:ci", 26 | "lint": "eslint \"{src,test}/**/*.ts\"", 27 | "lint:fix": "npm run lint -- --fix", 28 | "test": "jest" 29 | }, 30 | "husky": { 31 | "hooks": { 32 | "pre-commit": "lint-staged" 33 | } 34 | }, 35 | "lint-staged": { 36 | "*.{ts,js}": [ 37 | "eslint --fix", 38 | "git add" 39 | ] 40 | }, 41 | "files": [ 42 | "dist" 43 | ], 44 | "devDependencies": { 45 | "@antfu/eslint-config-ts": "^0.4.3", 46 | "@types/jest": "^26.0.15", 47 | "eslint": "^7.12.1", 48 | "eslint-plugin-jest": "^24.1.0", 49 | "esno": "^0.2.4", 50 | "husky": "^4.3.0", 51 | "jest": "^26.6.2", 52 | "lint-staged": "^10.5.1", 53 | "ts-jest": "^26.4.3", 54 | "tsup": "^3.7.1", 55 | "typescript": "4.0.2", 56 | "vue": "^3.0.2" 57 | }, 58 | "dependencies": { 59 | "vue-demi": "^0.4.1" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { ref, isRef, unref, computed, WatchOptions, watch, WatchSource, WatchCallback, Ref, ComputedRef, WatchStopHandle } from 'vue-demi' 2 | 3 | declare type MapSources = { 4 | [K in keyof T]: T[K] extends WatchSource ? Immediate extends true ? (V | undefined) : V : T[K] extends object ? Immediate extends true ? (T[K] | undefined) : T[K] : never; 5 | } 6 | 7 | // Overload 1: unref 8 | export function $(ref: Ref): T 9 | 10 | // Overload 2: set value to ref 11 | export function $(ref: Ref, value: T): void 12 | 13 | // Overload 3: set ref to ref 14 | export function $(ref: Ref, source: Ref): void 15 | 16 | // Overload 4: computed 17 | export function $(r: ((...args: any) => T)): ComputedRef 18 | 19 | // Overload 5: watch array source 20 | export function $ | object>>, Immediate extends Readonly = false>(sources: T, cb: WatchCallback, MapSources>, options?: WatchOptions): WatchStopHandle 21 | 22 | // Overload 6: watch ref 23 | export function $ = false>(source: WatchSource, cb: WatchCallback, options?: WatchOptions): WatchStopHandle 24 | 25 | // Overload 7: ref 26 | export function $(r: T): Ref 27 | 28 | // Implementation 29 | export function $(r: any, v?: any, options?: any): any { 30 | // watch 31 | if (arguments.length >= 2 && typeof v === 'function') { 32 | return watch(r, v, options) 33 | } 34 | // set / copy 35 | else if (arguments.length === 2 && isRef(r)) { 36 | r.value = unref(v) 37 | return 38 | } 39 | else if (arguments.length === 1) { 40 | // computed 41 | if (typeof r === 'function') 42 | return computed(r) 43 | 44 | // unref 45 | else if (isRef(r)) 46 | return r.value 47 | 48 | // ref 49 | else 50 | return ref(r) 51 | } 52 | throw new Error('unexpected') 53 | } 54 | --------------------------------------------------------------------------------