Skip to content

Commit

Permalink
Merge 81b53e3 into 5088504
Browse files Browse the repository at this point in the history
  • Loading branch information
juanrgm committed May 3, 2023
2 parents 5088504 + 81b53e3 commit 782561e
Show file tree
Hide file tree
Showing 3 changed files with 324 additions and 39 deletions.
110 changes: 71 additions & 39 deletions packages/solid/src/render/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,15 @@ function resolveSource(s: any) {
return !(s = typeof s === "function" ? s() : s) ? {} : s;
}

function resolveSources(this: (() => any)[]) {
for (let i = 0, length = this.length; i < length; ++i) {
const v = this[i]();
if (v !== undefined) return v;
}
}

export function mergeProps<T extends unknown[]>(...sources: T): MergeProps<T> {
if (sources.length === 1) return sources[0] as any;
let proxy = false;
for (let i = 0; i < sources.length; i++) {
const s = sources[i];
Expand Down Expand Up @@ -213,25 +221,45 @@ export function mergeProps<T extends unknown[]>(...sources: T): MergeProps<T> {
propTraps
) as unknown as MergeProps<T>;
}
const target = {} as MergeProps<T>;
const target: Record<string, any> = {};
const sourcesMap: Record<string, any[]> = {};
let someNonTargetKey = false;

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;
}
const source = sources[i] as Record<string, any>;
if (!source) continue;
const sourceKeys = Object.getOwnPropertyNames(source);
someNonTargetKey = someNonTargetKey || (i !== 0 && !!sourceKeys.length);
for (let i = 0, length = sourceKeys.length; i < length; i++) {
const key = sourceKeys[i];
if (!(key in target)) {
const desc = Object.getOwnPropertyDescriptor(source, key)!;
if (desc.get) {
Object.defineProperty(target, key, {
enumerable: true,
get: resolveSources.bind(
(sourcesMap[key] = [desc.get.bind(source)])
),
});
} else {
target[key] = desc.value;
}
} else {
const sources = sourcesMap[key];
const desc = Object.getOwnPropertyDescriptor(source, key)!;
if (sources) {
if (desc.get) {
sources.push(desc.get.bind(source));
} else if (desc.value !== undefined) {
sources.push(() => desc.value);
}
});
} else if (target[key] === undefined) {
target[key] = desc.value;
}
}
}
}
return target;
return (someNonTargetKey ? target : sources[0]) as any;
}

export type SplitProps<T, K extends (readonly (keyof T)[])[]> = [
Expand All @@ -247,8 +275,8 @@ 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 blocked = new Set<keyof T>(keys.length > 1 ? keys.flat() : keys[0]);
const res = keys.map(k => {
return new Proxy(
{
Expand Down Expand Up @@ -283,31 +311,35 @@ export function splitProps<
);
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
}
);
const otherObject: Record<string, any> = {};
const objects: Record<string, any>[] = keys.map(() => ({}));

for (const propName of Object.getOwnPropertyNames(props)) {
const desc = Object.getOwnPropertyDescriptor(props, propName)!;
const isDefaultDesc =
!desc.get &&
!desc.set &&
desc.enumerable &&
desc.writable &&
desc.configurable;
let blocked = false;
let objectIndex = 0;
for (const k of keys) {
if (k.includes(propName)) {
blocked = true;
isDefaultDesc
? (objects[objectIndex][propName] = desc.value)
: Object.defineProperty(objects[objectIndex], propName, desc)
}
++objectIndex;
}
return clone;
}) as SplitProps<T, K>;
if (!blocked) {
isDefaultDesc
? (otherObject[propName] = desc.value)
: Object.defineProperty(otherObject, propName, desc);
}
}
return [...objects, otherObject] as any;
}

// lazy load a function component asynchronously
Expand Down
92 changes: 92 additions & 0 deletions packages/solid/test/component.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { bench } from "vitest";
import { mergeProps, splitProps } from "../src";
import * as old from "./component.old"

const createObject = (prefix: string, amount: number, descriptor: any) => {
const proto: Record<string, any> = {}
for (let index = 0; index < amount; ++index)
proto[`${prefix}${index}`] = descriptor
return Object.defineProperties({}, proto)
}

function pickProps<T extends number>(inProps: Record<string, any>, ...amounts: T[]): {
[K in T]: { names: string[], props: Record<string, any> }
} {
const result: Record<number, { names: string[], props: Record<string, any> }> = {}
const desc = Object.getOwnPropertyDescriptors(inProps)
const inPropNames = Object.keys(inProps)
for (const amount of amounts) {
const names = inPropNames.slice(0, amount)
const props: Record<string, any> = {}
for (const name of inPropNames.slice(0, amount)) {
Object.defineProperty(props, name, desc[name])
}
result[amount] = { names, props }
}
return result
}

const mergePropsItems: {
name: string
func: Function
}[] = [{
name: "mergeProps",
func: mergeProps
}, {
name: "oldMergeProps",
func: old.mergeProps
}]

const splitPropsItems: {
name: string
func: Function
}[] = [{
name: "splitProps",
func: splitProps
}, {
name: "oldSplitProps",
func: old.splitProps
}]


const staticDesc = { value: 1, writable: true, configurable: true, enumerable: true }
const signalDesc = { get() { return 1 }, configurable: true, enumerable: true }
const staticObject = createObject("signal", 100, signalDesc)
const staticProps = pickProps(staticObject, 0, 5, 15, 25, 50, 100)
const iterations = 1000
const inputs = [
[0, 15],
[0, 100],
[25, 100],
[50, 100],
[100, 25]
] as const

for (const input of inputs) {
const [a, b] = input
describe(`splitProps(${a}, ${b})`, () => {
for (const { name, func } of splitPropsItems)
bench(`${name}(${a}, ${b})`, () => {
func(staticProps[a].props, staticProps[b].names)
}, {
iterations
})
});
describe(`splitProps(${a}, ${b}, ${b})`, () => {
for (const { name, func } of splitPropsItems)
bench(`${name}(${a}, ${b}, ${b})`, () => {
func(staticProps[a].props, staticProps[b].names, staticProps[b].names)
}, {
iterations
})
});
describe(`mergeProps(${a}, ${b})`, () => {
for (const { name, func } of mergePropsItems)
bench(`${name}(${a}, ${b})`, () => {
func(staticProps[a].props, staticProps[b].props)
}, {
iterations: 1000
})
})
}

Loading

0 comments on commit 782561e

Please sign in to comment.