@yak/solid 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal.js +56 -86
- package/dist/internal.js.map +1 -1
- package/dist/loaders/vite-plugin.js +18 -5
- package/dist/loaders/vite-plugin.js.map +1 -1
- package/package.json +7 -7
- package/runtime/__tests__/reactivity.test.tsx +36 -0
- package/runtime/styled.ts +109 -118
package/runtime/styled.ts
CHANGED
|
@@ -43,7 +43,7 @@ import type {
|
|
|
43
43
|
StaticStyleProcessor,
|
|
44
44
|
StyleObject,
|
|
45
45
|
} from "./publicStyledApi.js";
|
|
46
|
-
import { $PROXY, createMemo } from "solid-js";
|
|
46
|
+
import { $PROXY, createMemo, merge, omit } from "solid-js";
|
|
47
47
|
// the helpers solid's compiled jsx calls; this file hand-writes what the compiler would emit for a tag
|
|
48
48
|
import {
|
|
49
49
|
ChildProperties,
|
|
@@ -339,8 +339,8 @@ const combineProps = (props: Props, newProps: Props | null | undefined): Props =
|
|
|
339
339
|
// descriptors, not values: a spread would run every author getter here,
|
|
340
340
|
// and a children getter renders (twice, and with the wrong hydration keys)
|
|
341
341
|
const descriptors = {
|
|
342
|
-
...
|
|
343
|
-
...
|
|
342
|
+
...rehomedDescriptors(props),
|
|
343
|
+
...rehomedDescriptors(newProps),
|
|
344
344
|
};
|
|
345
345
|
// an equal class counts as nothing: own attrs get the combined props and
|
|
346
346
|
// may hand the same class back, merging it again would duplicate it
|
|
@@ -363,6 +363,35 @@ const valueDescriptor = (value: unknown): PropertyDescriptor => ({
|
|
|
363
363
|
writable: true,
|
|
364
364
|
});
|
|
365
365
|
|
|
366
|
+
/**
|
|
367
|
+
* a getter is only defined for a read through its own object: solid's
|
|
368
|
+
* compiled props getters (2.0.0-rc.10, `hoistProps`) reach their captured
|
|
369
|
+
* values through `this`, so a descriptor moved to another object reads
|
|
370
|
+
* nothing. re-homing keeps the laziness (nothing is read here) and reads
|
|
371
|
+
* the source when the target is read; a data descriptor passes as it is
|
|
372
|
+
*/
|
|
373
|
+
const rehome = (
|
|
374
|
+
source: object,
|
|
375
|
+
key: PropertyKey,
|
|
376
|
+
descriptor: PropertyDescriptor,
|
|
377
|
+
): PropertyDescriptor =>
|
|
378
|
+
descriptor.get || descriptor.set
|
|
379
|
+
? {
|
|
380
|
+
get: () => (source as any)[key],
|
|
381
|
+
set: descriptor.set && ((value: unknown) => ((source as any)[key] = value)),
|
|
382
|
+
enumerable: descriptor.enumerable,
|
|
383
|
+
configurable: true,
|
|
384
|
+
}
|
|
385
|
+
: descriptor;
|
|
386
|
+
|
|
387
|
+
const rehomedDescriptors = (source: object): PropertyDescriptorMap => {
|
|
388
|
+
const descriptors = Object.getOwnPropertyDescriptors(source) as PropertyDescriptorMap;
|
|
389
|
+
for (const key of Reflect.ownKeys(descriptors)) {
|
|
390
|
+
descriptors[key as string] = rehome(source, key, descriptors[key as string]);
|
|
391
|
+
}
|
|
392
|
+
return descriptors;
|
|
393
|
+
};
|
|
394
|
+
|
|
366
395
|
const hasGetter = (object: object): boolean => {
|
|
367
396
|
for (const key of Object.getOwnPropertyNames(object)) {
|
|
368
397
|
if (!("value" in Object.getOwnPropertyDescriptor(object, key)!)) return true;
|
|
@@ -754,140 +783,102 @@ const createElementRenderer = (tag: string, attrString: string): TargetRenderer
|
|
|
754
783
|
};
|
|
755
784
|
|
|
756
785
|
const bindElement = (el: Element, props: Props, meta: RenderMeta): Element => {
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
786
|
+
// no untrack anywhere below: a component body runs untracked
|
|
787
|
+
if (!meta.attrsAddKeys && !($PROXY in props)) {
|
|
788
|
+
// a fixed key set read by one spread: copying the few descriptors is
|
|
789
|
+
// cheaper than a view. measured on the client with omit() + the source
|
|
790
|
+
// list instead: +12–15% hydrate and mount on dynamic-prop tags (an
|
|
791
|
+
// omit view, spread()'s source walk and their garbage, per element)
|
|
792
|
+
const bound = copyProps(props, meta);
|
|
793
|
+
spread(el, bound, !("children" in bound));
|
|
794
|
+
} else {
|
|
795
|
+
// attrs or a reactive spread can add keys — children too — so the target
|
|
796
|
+
// reads views. the sources as an array, like a compiled <tag {...a} {...b}>:
|
|
797
|
+
// spread() reads a function source inline, no memo and no hydration id.
|
|
798
|
+
// that matters here: the server writer for a tag builds none of this, so
|
|
799
|
+
// an owner created only on the client would shift every later element's id
|
|
800
|
+
spread(el, targetSources(props, meta), false);
|
|
801
|
+
}
|
|
762
802
|
// replay events once the element's bindings are ready
|
|
763
803
|
runHydrationEvents();
|
|
764
804
|
return el;
|
|
765
805
|
};
|
|
766
806
|
|
|
767
|
-
/**
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
* names and $-props are open-ended, so the filter is hand-written
|
|
771
|
-
*
|
|
772
|
-
* plain copy when the keys can't change, proxy when attrs or a reactive
|
|
773
|
-
* spread can add keys and downstream omit() has to notice
|
|
774
|
-
* the copy stays unmarked on purpose, a $PROXY mark makes omit()/merge()
|
|
775
|
-
* wrap it again and every read gets slower
|
|
776
|
-
*/
|
|
777
|
-
const targetProps = (props: Props, meta: RenderMeta): Record<PropertyKey, unknown> => {
|
|
778
|
-
if (!meta.attrsAddKeys && !($PROXY in props)) return copyProps(props, meta);
|
|
779
|
-
return proxyProps(props, meta);
|
|
780
|
-
};
|
|
807
|
+
/** the props a component target receives: the sources as one merge() view */
|
|
808
|
+
const targetProps = (props: Props, meta: RenderMeta): Props =>
|
|
809
|
+
merge(...targetSources(props, meta)) as Props;
|
|
781
810
|
|
|
782
811
|
/**
|
|
783
|
-
*
|
|
784
|
-
*
|
|
785
|
-
*
|
|
812
|
+
* the client tag path when the keys cannot change: the computed class/style
|
|
813
|
+
* getters, then the author's props copied in without reading them — a
|
|
814
|
+
* getter like icon={<Icon />} renders a child and takes hydration keys, so it
|
|
815
|
+
* is re-homed (see `rehome`) and read by the spread in its own order
|
|
786
816
|
*/
|
|
787
|
-
const copyProps = (props: Props, meta: RenderMeta):
|
|
788
|
-
const
|
|
789
|
-
|
|
790
|
-
//
|
|
817
|
+
const copyProps = (props: Props, meta: RenderMeta): Props => {
|
|
818
|
+
const out = clientExtras(props, meta);
|
|
819
|
+
// string keys only, like solid's omit(): a symbol never reaches the target.
|
|
820
|
+
// skip drops class (and style with a memo), the keys the extras own
|
|
791
821
|
for (const key of Object.getOwnPropertyNames(props)) {
|
|
792
822
|
if (meta.skip(key)) continue;
|
|
793
823
|
const descriptor = Reflect.getOwnPropertyDescriptor(props, key)!;
|
|
794
|
-
if ("value" in descriptor && descriptor.enumerable)
|
|
795
|
-
|
|
796
|
-
} else {
|
|
797
|
-
Object.defineProperty(out, key, descriptor);
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
if (isServer) {
|
|
801
|
-
// the values are final on the server: plain values take the value path
|
|
802
|
-
// in solid's omit() and merge() instead of a getter per read
|
|
803
|
-
if (compute) {
|
|
804
|
-
const computed = compute();
|
|
805
|
-
out.class = computed.class;
|
|
806
|
-
if (computed.style !== undefined) out.style = computed.style;
|
|
807
|
-
} else out.class = classOf(props);
|
|
808
|
-
return out;
|
|
809
|
-
}
|
|
810
|
-
const classFn = compute ? () => compute().class : () => classOf(props);
|
|
811
|
-
const styleFn = styleGetter(compute);
|
|
812
|
-
Object.defineProperty(out, "class", {
|
|
813
|
-
get: classFn,
|
|
814
|
-
enumerable: true,
|
|
815
|
-
configurable: true,
|
|
816
|
-
});
|
|
817
|
-
if (styleFn) {
|
|
818
|
-
Object.defineProperty(out, "style", { get: styleFn, enumerable: true, configurable: true });
|
|
824
|
+
if ("value" in descriptor && descriptor.enumerable) out[key] = descriptor.value;
|
|
825
|
+
else Object.defineProperty(out, key, rehome(props, key, descriptor));
|
|
819
826
|
}
|
|
820
827
|
return out;
|
|
821
828
|
};
|
|
822
829
|
|
|
823
830
|
/**
|
|
824
|
-
* the
|
|
825
|
-
*
|
|
826
|
-
*
|
|
827
|
-
*
|
|
831
|
+
* what the target sees, as the sources solid's own primitives take: omit()
|
|
832
|
+
* with the skip predicate over the author's props ($-props, theme, and the
|
|
833
|
+
* keys attrs bake), the attrs the memo produced as a function source when
|
|
834
|
+
* they can add keys, and the computed class/style last. views, not a copy:
|
|
835
|
+
* a getter like icon={<Icon />} renders a child and takes hydration keys,
|
|
836
|
+
* so it is read by the target in its own order, once. on the server the
|
|
837
|
+
* values are final and go in as data properties; on the client as getters
|
|
838
|
+
* over the memo
|
|
828
839
|
*/
|
|
829
|
-
const
|
|
830
|
-
compute
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
if (isServer
|
|
834
|
-
|
|
840
|
+
const targetSources = (props: Props, meta: RenderMeta): [Props, TargetAttrs, Props] => {
|
|
841
|
+
const { skip, compute, classOf } = meta;
|
|
842
|
+
const rest = omit(props, skip as (key: string | symbol) => boolean);
|
|
843
|
+
let extras: Props;
|
|
844
|
+
if (isServer) {
|
|
845
|
+
extras = {};
|
|
846
|
+
if (compute) {
|
|
847
|
+
const computed = compute();
|
|
848
|
+
extras.class = computed.class;
|
|
849
|
+
if (computed.style !== undefined) extras.style = computed.style;
|
|
850
|
+
} else extras.class = classOf(props);
|
|
851
|
+
} else extras = clientExtras(props, meta);
|
|
852
|
+
if (!meta.attrsAddKeys) return [rest, undefined, extras];
|
|
853
|
+
// meta.attrsAddKeys, not the destructured copy: the read narrows meta.compute
|
|
854
|
+
const attrsSource = () => {
|
|
855
|
+
const attrs = meta.compute().attrs;
|
|
856
|
+
return attrs ? omit(attrs, skip as (key: string | symbol) => boolean) : undefined;
|
|
857
|
+
};
|
|
858
|
+
return [rest, attrsSource, extras];
|
|
835
859
|
};
|
|
836
860
|
|
|
861
|
+
/** the attrs source, or none when attrs cannot add keys (baked, or no attrs) */
|
|
862
|
+
type TargetAttrs = (() => Props | undefined) | undefined;
|
|
863
|
+
|
|
837
864
|
/**
|
|
838
|
-
*
|
|
839
|
-
*
|
|
865
|
+
* client only: the computed class and style as getters over the memo. object
|
|
866
|
+
* literals, two shapes: a getter defined on a literal takes V8's boilerplate
|
|
867
|
+
* path
|
|
840
868
|
*/
|
|
841
|
-
const
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
key === "class" ? classFn : key === "style" ? styleFn : undefined;
|
|
852
|
-
const fromAttrs = (key: PropertyKey) => {
|
|
853
|
-
const attrs = attrsProps();
|
|
854
|
-
return attrs && key in attrs ? attrs : undefined;
|
|
855
|
-
};
|
|
856
|
-
return new Proxy(props, {
|
|
857
|
-
get(target, key, receiver) {
|
|
858
|
-
// the receiver, like solid's own props proxies: solid's spread walks
|
|
859
|
-
// a proxy's keys through its traps only when props[$PROXY] === props
|
|
860
|
-
if (key === $PROXY) return receiver;
|
|
861
|
-
const getter = contributed(key);
|
|
862
|
-
if (getter) return getter();
|
|
863
|
-
if (skip(key)) return undefined;
|
|
864
|
-
const attrs = fromAttrs(key);
|
|
865
|
-
return attrs ? attrs[key] : Reflect.get(target, key);
|
|
866
|
-
},
|
|
867
|
-
has(target, key) {
|
|
868
|
-
if (key === $PROXY) return true;
|
|
869
|
-
if (contributed(key)) return true;
|
|
870
|
-
if (skip(key)) return false;
|
|
871
|
-
return Reflect.has(target, key) || fromAttrs(key) !== undefined;
|
|
872
|
-
},
|
|
873
|
-
ownKeys(target) {
|
|
874
|
-
const keys = new Set<string | symbol>();
|
|
875
|
-
for (const key of Reflect.ownKeys(target)) if (!skip(key)) keys.add(key);
|
|
876
|
-
for (const key of Object.keys(attrsProps() ?? {})) if (!skip(key)) keys.add(key);
|
|
877
|
-
// class and style are always own keys, listed after the author keys and attrs
|
|
878
|
-
keys.add("class");
|
|
879
|
-
if (styleFn) keys.add("style");
|
|
880
|
-
return [...keys];
|
|
881
|
-
},
|
|
882
|
-
getOwnPropertyDescriptor(target, key) {
|
|
883
|
-
const getter = contributed(key);
|
|
884
|
-
if (getter) return { enumerable: true, configurable: true, get: getter };
|
|
885
|
-
if (skip(key)) return undefined;
|
|
886
|
-
const attrs = fromAttrs(key);
|
|
887
|
-
if (attrs) {
|
|
888
|
-
return { enumerable: true, configurable: true, get: () => attrsProps()?.[key] };
|
|
869
|
+
const clientExtras = (props: Props, { compute, classOf }: RenderMeta): Props =>
|
|
870
|
+
compute
|
|
871
|
+
? {
|
|
872
|
+
get class() {
|
|
873
|
+
// compute().class is accessed lazily and includes props.class in its results
|
|
874
|
+
return compute().class;
|
|
875
|
+
},
|
|
876
|
+
get style() {
|
|
877
|
+
return compute().style;
|
|
878
|
+
},
|
|
889
879
|
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
}
|
|
880
|
+
: {
|
|
881
|
+
get class() {
|
|
882
|
+
return classOf(props);
|
|
883
|
+
},
|
|
884
|
+
};
|