Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
under_moon committed Sep 18, 2023
1 parent 99d4ed0 commit d38782e
Show file tree
Hide file tree
Showing 19 changed files with 2,536 additions and 0 deletions.
45 changes: 45 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: ci

on:
push:
tags:
- 'v*'

jobs:
unit-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: pnpm setup
uses: pnpm/[email protected]
with:
version: 7.33.6
- name: node setup
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'pnpm'
- run: pnpm install
- run: pnpm build
- run: pnpm test

release:
needs: unit-test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: pnpm setup
uses: pnpm/[email protected]
with:
version: 7.33.6
- name: node setup
uses: actions/setup-node@v3
with:
node-version: 18
registry-url: 'https://registry.npmjs.org'
cache: 'pnpm'
- run: pnpm install
- run: pnpm build
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
//npm.pkg.github.com/:_authToken=NODE_AUTH_TOKEN
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist
coverage
8 changes: 8 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none"
}
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"editor.formatOnSave": true
}
66 changes: 66 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# object-shake

### Use Cases

#### - `Vanilla`

```js
import { shake } from '@object-shake/core'

const target = {
a: {
b: { c: 1, d: 2 },
e: 3
},
f: 4
}

const [proxyTarget, shakedTarget] = shake(target)

proxyTarget.a.b.c
console.log(shakedTarget) // { a: { b: { c: 1 } } }

proxyTarget.f
console.log(shakedTarget) // { a: { b: { c: 1 } }, f: 4 }
```

#### - `Vue3`

```html
<script setup>
import { ref, onMounted } from 'vue'
import { reactiveShake } from '@object-shake/vue'
const target = ref({
a: {
b: { c: 1, d: 2 },
e: 3
},
f: 4
})
const [proxyTarget, shakedTarget] = shake(target)
onMounted(() => {
console.log(shakedTarget) // { a: { b: { c: 1 } }, f: 4 }
})
</script>

<template>
<div>
<span>{{ proxyTarget.a.b.c }}</span>
<span>{{ proxyTarget.f }}</span>
</div>
</template>
```

### Perf

- performance Test in Nuxt3: `shake state` vs `state` -> https://github.com/undermoonn/vue-reactive-shake-perf

### Roadmap

#### `@object-shake/vue`

- [ ] nested ref
- [ ] vue2 support
24 changes: 24 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"private": true,
"version": "1.0.0-alpha.1",
"type": "module",
"scripts": {
"prepare": "husky install",
"format": "prettier --write --cache \"**/*.[tj]s?(x)\"",
"test": "vitest run --coverage",
"build": "turbo run build",
"clean": "find . \\( -name 'node_modules' -o -name 'dist' \\) -type d -exec rm -rf {} +"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@vitest/coverage-v8": "^0.34.4",
"husky": "^8.0.3",
"prettier": "^3.0.3",
"turbo": "^1.10.14",
"typescript": "^5.2.2",
"unbuild": "^2.0.0",
"vitest": "^0.34.4"
}
}
11 changes: 11 additions & 0 deletions packages/core/build.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
entries: ['./src/index.ts'],
declaration: true,
rollup: {
esbuild: {
minify: true
}
}
})
20 changes: 20 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"private": false,
"name": "@object-shake/core",
"version": "1.0.0-alpha.1",
"main": "src/index.ts",
"type": "module",
"repository": {
"type": "git",
"url": "https://github.com/undermoonn/object-shake"
},
"scripts": {
"build": "unbuild"
},
"files": [
"dist"
],
"keywords": [],
"author": "",
"license": "ISC"
}
65 changes: 65 additions & 0 deletions packages/core/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { test, expect, describe } from 'vitest'
import { shake } from './index'

describe('[core] test', () => {
test('base', () => {
const [p, s] = shake({ a: { b: 1 }, c: 2 })
expect(s).toEqual({})
p.a.b
expect(s).toEqual({ a: { b: 1 } })
})
})

describe('[core] options test', () => {
test('default onKeySet', () => {
const [p, s] = shake(
{ a: { b: 1 }, c: 2 },
{
walkOptions: {
onKeySet: (keyPath) => keyPath
}
}
)
expect(s).toEqual({})
p.a.b
expect(s).toEqual({ a: { b: 1 } })
})
})

describe('[core] key test', () => {
test('symbol key', () => {
const key = Symbol()
const [p, s] = shake({ a: { [key]: 1 }, c: 2 })
p.a[key]
expect(s).toEqual({})
})

test('symbol key with collectSymbolKey enable', () => {
const key = Symbol()
const [p, s] = shake({ a: { [key]: 1 }, c: 2 }, { walkOptions: { collectSymbolKey: true } })
expect(s).toEqual({})
p.a[key]
expect(s).toEqual({ a: { [key]: 1 } })
})
})

describe('[core] value test', () => {
test('null value', () => {
const [p, s] = shake({ a: null })
p.a
expect(s).toEqual({ a: null })
})

test('function value', () => {
const a = () => {}
const [p, s] = shake({ a })
p.a()
expect(s).toEqual({ a })
})

test('undefined value', () => {
const [p, s] = shake({ a: undefined })
p.a
expect(s).toEqual({ a: undefined })
})
})
108 changes: 108 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
export interface PublicWalkOptions<T extends object> {
onKeySet?: (parentDeepKeyPath: KeyPath) => KeyPath | false

collectSymbolKey?: boolean
}

interface PrivateWalkOptions<T extends object> extends PublicWalkOptions<T> {
/** ProxyHandler target */
target: object

/** ProxyHandler key */
key: string | symbol

/** ProxyHandler receiver */
receiver: object

parentDeepKeyPath: KeyPath

rootReceiver: T
}

export interface ShakeOptions<T extends object> {
walkOptions?: PublicWalkOptions<T>
}

type Key = string | symbol
type KeyPath = Key[]

export function shake<T extends object>(target: T, options?: ShakeOptions<T>): [T, T] {
const shaked: T = Object.create(null)

const proxy = new Proxy(target, {
get(target, key, receiver) {
return walk({
target,
key,
receiver,

parentDeepKeyPath: [],
rootReceiver: shaked,

...options?.walkOptions
})
}
})

return [proxy, shaked]
}

function walk<T extends object>(
options: PrivateWalkOptions<T>
): object | string | null | undefined {
const { target, key, receiver, parentDeepKeyPath, rootReceiver, ...otherOptions } = options

if (typeof key === 'symbol') {
if (options.collectSymbolKey) {
//
} else {
return undefined
}
}

const currentDeepKeyPath = [...parentDeepKeyPath, key]
const value = Reflect.get(target, key, receiver)

if (typeof value === 'object' && value !== null) {
return new Proxy(value, {
get(target, key, receiver) {
return walk<T>({
target,
key,
receiver,
parentDeepKeyPath: currentDeepKeyPath,
rootReceiver,
...otherOptions
})
}
})
}

if (options.onKeySet) {
const filteredKeyPath = options.onKeySet(currentDeepKeyPath)
if (filteredKeyPath) {
deepSet(rootReceiver, filteredKeyPath, value)
}
} else {
deepSet(rootReceiver, currentDeepKeyPath, value)
}

return value
}

function deepSet<T extends object>(target: T, keyPath: KeyPath, value: any): T {
let t: any = target

keyPath.forEach((key, idx) => {
if (idx === keyPath.length - 1) {
Reflect.set(t, key, value)
return
}
if (typeof t[key] === 'undefined') {
Reflect.set(t, key, {})
}
t = t[key]
})

return target
}
11 changes: 11 additions & 0 deletions packages/vue/build.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
entries: ['./src/index.ts'],
declaration: true,
rollup: {
esbuild: {
minify: true
}
}
})
Loading

0 comments on commit d38782e

Please sign in to comment.