Skip to content

Commit

Permalink
feat: serialize signals in object
Browse files Browse the repository at this point in the history
  • Loading branch information
ph1p committed Aug 25, 2024
1 parent 0f4f08e commit adb61ab
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import { h } from 'preact';
export default ({ signalsArray }) => {
return <div class="preact-signal-array">
<h1>{signalsArray[0]} {signalsArray[3]}</h1>
<p>{signalsArray[1].value}{signalsArray[2].value}{signalsArray[4].value}</p>
<p>{signalsArray[1].value}-{signalsArray[2].value}-{signalsArray[4].value}</p>
</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { h } from 'preact';

export default ({ signalsObject }) => {
return <div class="preact-signal-object">
<h1>{signalsObject.title}</h1>
<p>{signalsObject.counter.value}</p>
</div>
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { signal } from '@preact/signals';
import Signals from '../components/Signals';
import SignalsInArray from '../components/SignalsInArray';
import SignalsInObject from '../components/SignalsInObject';
const count = signal(1);
const secondCount = signal(2);
---
Expand All @@ -13,5 +14,6 @@ const secondCount = signal(2);
<Signals client:load count={count} />
<Signals client:load count={count} />
<SignalsInArray client:load signalsArray={["I'm not a signal", count, count, 12345, secondCount]} />
<SignalsInObject client:load signalsObject={{title:'I am a title', counter: count}} />
</body>
</html>
22 changes: 20 additions & 2 deletions packages/astro/test/preact-component.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,25 @@ describe('Preact component', () => {
],
});

assert.equal($('.preact-signal-array h1').text(), "I'm not a signal 12345");
assert.equal($('.preact-signal-array p').text(), '112');
assert.equal(element.find('h1').text(), "I'm not a signal 12345");
assert.equal(element.find('p').text(), '1-1-2');
});

it('Can use signals in object', async () => {
const html = await fixture.readFile('/signals/index.html');
const $ = cheerio.load(html);
const element = $('.preact-signal-object');
assert.equal(element.length, 1);

const sigs1Raw = $($('astro-island')[3]).attr('data-preact-signals');

const sigs1 = JSON.parse(sigs1Raw);

assert.deepEqual(sigs1, {
signalsObject: [['p0', 'counter']],
});

assert.equal(element.find('h1').text(), "I am a title");
assert.equal(element.find('p').text(), '1');
});
});
14 changes: 11 additions & 3 deletions packages/integrations/preact/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,21 @@ export default (element: HTMLElement) =>
);
for (const [propName, signalId] of Object.entries(signals)) {
if (Array.isArray(signalId)) {
signalId.forEach(([id, indexInPropsArray]) => {
const [valueOfSignal, indexInProps] = props[propName][indexInPropsArray];
signalId.forEach(([id, indexOrKeyInProps]) => {
const mapValue = props[propName][indexOrKeyInProps];
let valueOfSignal = mapValue;

// not an property key
if(typeof indexOrKeyInProps !== 'string') {
valueOfSignal = mapValue[0];
indexOrKeyInProps = mapValue[1];
}

if (!sharedSignalMap.has(id)) {
const signalValue = signal(valueOfSignal);
sharedSignalMap.set(id, signalValue);
}
props[propName][indexInProps] = sharedSignalMap.get(id);
props[propName][indexOrKeyInProps] = sharedSignalMap.get(id);
});
} else {
if (!sharedSignalMap.has(signalId)) {
Expand Down
38 changes: 28 additions & 10 deletions packages/integrations/preact/src/signals.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import type { Context } from './context.js';
import { incrementId } from './context.js';
import type { AstroPreactAttrs, PropNameToSignalMap, SignalLike } from './types.js';
import type {
ArrayObjectMapping,
AstroPreactAttrs,
PropNameToSignalMap,
SignalLike,
Signals,
SignalToKeyOrIndexMap,
} from './types.js';

function isSignal(x: any): x is SignalLike {
return x != null && typeof x === 'object' && typeof x.peek === 'function' && 'value' in x;
Expand Down Expand Up @@ -28,18 +35,29 @@ export function serializeSignals(
map: PropNameToSignalMap,
) {
// Check for signals
const signals: Record<string, string | [string, number][]> = {};
const signals: Signals = {};
for (const [key, value] of Object.entries(props)) {
if (Array.isArray(value)) {
value.forEach((signal, index) => {
// find signals in array. The index is important!
const isPropArray = Array.isArray(value);
const isPropObject = !isSignal(value) && typeof props[key] === 'object' && !isPropArray;

if (isPropObject || isPropArray) {
const values = isPropObject ? Object.keys(props[key]) : value;
values.forEach((valueKey: number | string, valueIndex: number) => {
const signal = isPropObject ? props[key][valueKey] : valueKey;
if (isSignal(signal)) {
props[key] = props[key].map((v: SignalLike, i: number) =>
i === index ? [signal.peek(), i] : v,
);
map.set(key, [...((map.get(key) || []) as []), [signal, index]]);
const keyOrIndex = isPropObject ? valueKey.toString() : valueIndex;

props[key] = isPropObject
? Object.assign({}, props[key], { [keyOrIndex]: signal.peek() })
: props[key].map((v: SignalLike, i: number) =>
i === valueIndex ? [signal.peek(), i] : v,
);

const currentMap = (map.get(key) || []) as SignalToKeyOrIndexMap;
map.set(key, [...currentMap, [signal, keyOrIndex]]);

signals[key] = [...((signals[key] || []) as []), [getSignalId(ctx, signal), index]];
const currentSignals = (signals[key] || []) as ArrayObjectMapping;
signals[key] = [...currentSignals, [getSignalId(ctx, signal), keyOrIndex]];
}
});
} else if (isSignal(value)) {
Expand Down
6 changes: 5 additions & 1 deletion packages/integrations/preact/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ export type SignalLike = {
peek(): any;
};

export type PropNameToSignalMap = Map<string, SignalLike | [SignalLike, number][]>;
export type ArrayObjectMapping = [string, number | string][];
export type Signals = Record<string, string | ArrayObjectMapping>;

export type SignalToKeyOrIndexMap = [SignalLike, number | string][];
export type PropNameToSignalMap = Map<string, SignalLike | SignalToKeyOrIndexMap>;

export type AstroPreactAttrs = {
['data-preact-signals']?: string;
Expand Down

0 comments on commit adb61ab

Please sign in to comment.