-
-
Notifications
You must be signed in to change notification settings - Fork 943
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: add benchmark tests to
splitProps
and mergeProps
(WIP)
- Loading branch information
Showing
2 changed files
with
305 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
import { mergeProps, splitProps } from "../src"; | ||
import * as old from "./component.old"; | ||
import { bench } from "vitest"; | ||
|
||
const staticDesc = { | ||
value: 1, | ||
writable: true, | ||
configurable: true, | ||
enumerable: true, | ||
}; | ||
const signalDesc = { | ||
get() { | ||
return 1; | ||
}, | ||
configurable: true, | ||
enumerable: true, | ||
}; | ||
const createObject = ( | ||
prefix: string, | ||
amount: number, | ||
desc: (index: number) => PropertyDescriptor | ||
) => { | ||
const proto: Record<string, any> = {}; | ||
for (let index = 0; index < amount; ++index) | ||
proto[`${prefix}${index}`] = desc(index); | ||
return Object.defineProperties({}, proto) as Record<string, any>; | ||
}; | ||
|
||
const keys = (o: Record<string, any>) => Object.keys(o); | ||
|
||
type Test = { | ||
title: string; | ||
benchs: { title: string; func: any }[]; | ||
}; | ||
|
||
function createTest< | ||
T extends (...args: any[]) => any, | ||
G extends (...args: any[]) => any | ||
>(options: { | ||
subjects: { | ||
name: string; | ||
func: T; | ||
}[]; | ||
generator: Record<string, G>; | ||
inputs: (generator: G) => Record<string, Parameters<T>>; | ||
}) { | ||
const tests: Test[] = []; | ||
for (const generatorName in options.generator) { | ||
const generator = options.generator[generatorName]; | ||
const inputs = options.inputs(generator); | ||
for (const title in inputs) { | ||
const args = inputs[title]; | ||
const test: Test = { title: `${title} (${generatorName})`, benchs: [] }; | ||
for (const subject of options.subjects) { | ||
test.benchs.push({ | ||
title: subject.name, | ||
func: () => subject.func(...args), | ||
}); | ||
} | ||
tests.push(test); | ||
} | ||
} | ||
return tests; | ||
} | ||
|
||
type SplitProps = (...args: any[]) => Record<string, any>[] | ||
|
||
const generator = { | ||
static: (amount: number) => | ||
createObject("static", amount, () => staticDesc), | ||
dynamic: (amount: number) => | ||
createObject("dynamic", amount, () => signalDesc), | ||
mixed: (amount: number) => | ||
createObject("mixed", amount, (v) => (v % 2 ? staticDesc : signalDesc)), | ||
} as const | ||
|
||
const splitPropsTests = createTest({ | ||
subjects: [ | ||
{ | ||
name: "splitProps", | ||
func: splitProps as SplitProps, | ||
}, | ||
{ | ||
name: "oldSplitProps", | ||
func: old.splitProps as SplitProps, | ||
}, | ||
], | ||
generator, | ||
inputs: (g) => ({ | ||
"splitProps(5, 1)": [g(5), keys(g(1))], | ||
"splitProps(5, 1, 2)": [g(5), keys(g(1)), keys(g(2))], | ||
"splitProps(0, 15)": [g(0), keys(g(15))], | ||
"splitProps(0, 3, 2)": [g(0), keys(g(3)), keys(g(2))], | ||
"splitProps(0, 100)": [g(0), keys(g(100))], | ||
"splitProps(0, 100, 3, 2)": [g(0), keys(g(100)), keys(g(3)), keys(g(2))], | ||
"splitProps(25, 100)": [g(25), keys(g(100))], | ||
"splitProps(50, 100)": [g(50), keys(g(100))], | ||
"splitProps(100, 25)": [g(100), keys(g(25))], | ||
}), | ||
}); | ||
|
||
const mergePropsTest = createTest({ | ||
subjects: [ | ||
{ | ||
name: "mergeProps", | ||
func: mergeProps, | ||
}, | ||
{ | ||
name: "oldMergeProps", | ||
func: old.mergeProps , | ||
}, | ||
], | ||
generator, | ||
inputs: (g) => ({ | ||
"mergeProps(5, 1)": [g(5), (g(1))], | ||
"mergeProps(5, 1, 2)": [g(5), (g(1)), (g(2))], | ||
"mergeProps(0, 15)": [g(0), (g(15))], | ||
"mergeProps(0, 3, 2)": [g(0), (g(3)), (g(2))], | ||
"mergeProps(0, 100)": [g(0), (g(100))], | ||
"mergeProps(0, 100, 3, 2)": [g(0), (g(100)), (g(3)), (g(2))], | ||
"mergeProps(25, 100)": [g(25), (g(100))], | ||
"mergeProps(50, 100)": [g(50), (g(100))], | ||
"mergeProps(100, 25)": [g(100), (g(25))], | ||
}), | ||
}); | ||
|
||
const iterations = 100 | ||
|
||
for (const test of splitPropsTests) { | ||
describe(test.title, () => { | ||
for (const { title, func } of test.benchs) bench(title, func, { | ||
iterations | ||
}); | ||
}); | ||
} | ||
|
||
|
||
for (const test of mergePropsTest) { | ||
describe(test.title, () => { | ||
for (const { title, func } of test.benchs) bench(title, func, { | ||
iterations | ||
}); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import { $PROXY, MergeProps, SplitProps, createMemo } from "../src"; | ||
import { EffectFunction } from "../types"; | ||
|
||
function trueFn() { | ||
return true; | ||
} | ||
|
||
function resolveSource(s: any) { | ||
return !(s = typeof s === "function" ? s() : s) ? {} : s; | ||
} | ||
|
||
const propTraps: ProxyHandler<{ | ||
get: (k: string | number | symbol) => any; | ||
has: (k: string | number | symbol) => boolean; | ||
keys: () => string[]; | ||
}> = { | ||
get(_, property, receiver) { | ||
if (property === $PROXY) return receiver; | ||
return _.get(property); | ||
}, | ||
has(_, property) { | ||
if (property === $PROXY) return true; | ||
return _.has(property); | ||
}, | ||
set: trueFn, | ||
deleteProperty: trueFn, | ||
getOwnPropertyDescriptor(_, property) { | ||
return { | ||
configurable: true, | ||
enumerable: true, | ||
get() { | ||
return _.get(property); | ||
}, | ||
set: trueFn, | ||
deleteProperty: trueFn | ||
}; | ||
}, | ||
ownKeys(_) { | ||
return _.keys(); | ||
} | ||
}; | ||
export function splitProps< | ||
T extends Record<any, any>, | ||
K extends [readonly (keyof T)[], ...(readonly (keyof T)[])[]] | ||
>(props: T, ...keys: K): SplitProps<T, K> { | ||
const blocked = new Set<keyof T>(keys.length > 1 ? keys.flat() : keys[0]); | ||
if ($PROXY in props) { | ||
const res = keys.map(k => { | ||
return new Proxy( | ||
{ | ||
get(property) { | ||
return k.includes(property) ? props[property as any] : undefined; | ||
}, | ||
has(property) { | ||
return k.includes(property) && property in props; | ||
}, | ||
keys() { | ||
return k.filter(property => property in props); | ||
} | ||
}, | ||
propTraps | ||
); | ||
}); | ||
res.push( | ||
new Proxy( | ||
{ | ||
get(property) { | ||
return blocked.has(property) ? undefined : props[property as any]; | ||
}, | ||
has(property) { | ||
return blocked.has(property) ? false : property in props; | ||
}, | ||
keys() { | ||
return Object.keys(props).filter(k => !blocked.has(k)); | ||
} | ||
}, | ||
propTraps | ||
) | ||
); | ||
return res as SplitProps<T, K>; | ||
} | ||
const descriptors = Object.getOwnPropertyDescriptors(props); | ||
keys.push(Object.keys(descriptors).filter(k => !blocked.has(k as keyof T)) as (keyof T)[]); | ||
return keys.map(k => { | ||
const clone = {}; | ||
for (let i = 0; i < k.length; i++) { | ||
const key = k[i]; | ||
if (!(key in props)) continue; // skip defining keys that don't exist | ||
Object.defineProperty( | ||
clone, | ||
key, | ||
descriptors[key] | ||
? descriptors[key] | ||
: { | ||
get() { | ||
return props[key]; | ||
}, | ||
set() { | ||
return true; | ||
}, | ||
enumerable: true | ||
} | ||
); | ||
} | ||
return clone; | ||
}) as SplitProps<T, K>; | ||
} | ||
|
||
export function mergeProps<T extends unknown[]>(...sources: T): MergeProps<T> { | ||
let proxy = false; | ||
for (let i = 0; i < sources.length; i++) { | ||
const s = sources[i]; | ||
proxy = proxy || (!!s && $PROXY in (s as object)); | ||
sources[i] = | ||
typeof s === "function" ? ((proxy = true), createMemo(s as EffectFunction<unknown>)) : s; | ||
} | ||
if (proxy) { | ||
return new Proxy( | ||
{ | ||
get(property: string | number | symbol) { | ||
for (let i = sources.length - 1; i >= 0; i--) { | ||
const v = resolveSource(sources[i])[property]; | ||
if (v !== undefined) return v; | ||
} | ||
}, | ||
has(property: string | number | symbol) { | ||
for (let i = sources.length - 1; i >= 0; i--) { | ||
if (property in resolveSource(sources[i])) return true; | ||
} | ||
return false; | ||
}, | ||
keys() { | ||
const keys = []; | ||
for (let i = 0; i < sources.length; i++) | ||
keys.push(...Object.keys(resolveSource(sources[i]))); | ||
return [...new Set(keys)]; | ||
} | ||
}, | ||
propTraps | ||
) as unknown as MergeProps<T>; | ||
} | ||
const target = {} as MergeProps<T>; | ||
for (let i = sources.length - 1; i >= 0; i--) { | ||
if (sources[i]) { | ||
const descriptors = Object.getOwnPropertyDescriptors(sources[i]); | ||
for (const key in descriptors) { | ||
if (key in target) continue; | ||
Object.defineProperty(target, key, { | ||
enumerable: true, | ||
get() { | ||
for (let i = sources.length - 1; i >= 0; i--) { | ||
const v = ((sources[i] as any) || {})[key]; | ||
if (v !== undefined) return v; | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
} | ||
return target; | ||
} |