mirror of
https://github.com/withastro/astro.git
synced 2025-03-31 23:31:30 -05:00
fix: handle preact signals in array correctly (#11834)
* fix: handle preact signals in array correctly * feat: serialize signals in object
This commit is contained in:
parent
26c63a2b07
commit
5f2536b51d
8 changed files with 141 additions and 17 deletions
5
.changeset/eleven-cameras-sing.md
Normal file
5
.changeset/eleven-cameras-sing.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@astrojs/preact': patch
|
||||
---
|
||||
|
||||
Preact signals are now serialized correctly in arrays when they are given to components.
|
8
packages/astro/test/fixtures/preact-component/src/components/SignalsInArray.jsx
vendored
Normal file
8
packages/astro/test/fixtures/preact-component/src/components/SignalsInArray.jsx
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
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>
|
||||
</div>
|
||||
}
|
8
packages/astro/test/fixtures/preact-component/src/components/SignalsInObject.jsx
vendored
Normal file
8
packages/astro/test/fixtures/preact-component/src/components/SignalsInObject.jsx
vendored
Normal file
|
@ -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>
|
||||
}
|
|
@ -1,7 +1,10 @@
|
|||
---
|
||||
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);
|
||||
---
|
||||
<html>
|
||||
<head>
|
||||
|
@ -10,5 +13,7 @@ const count = signal(1);
|
|||
<body>
|
||||
<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>
|
||||
|
|
|
@ -100,4 +100,44 @@ describe('Preact component', () => {
|
|||
assert.notEqual(sigs1.count, undefined);
|
||||
assert.equal(sigs1.count, sigs2.count);
|
||||
});
|
||||
|
||||
it('Can use signals in array', async () => {
|
||||
const html = await fixture.readFile('/signals/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
const element = $('.preact-signal-array');
|
||||
assert.equal(element.length, 1);
|
||||
|
||||
const sigs1Raw = $($('astro-island')[2]).attr('data-preact-signals');
|
||||
|
||||
const sigs1 = JSON.parse(sigs1Raw);
|
||||
|
||||
assert.deepEqual(sigs1, {
|
||||
signalsArray: [
|
||||
['p0', 1],
|
||||
['p0', 2],
|
||||
['p1', 4],
|
||||
],
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,13 +18,34 @@ export default (element: HTMLElement) =>
|
|||
let signalsRaw = element.dataset.preactSignals;
|
||||
if (signalsRaw) {
|
||||
const { signal } = await import('@preact/signals');
|
||||
let signals: Record<string, string> = JSON.parse(element.dataset.preactSignals!);
|
||||
let signals: Record<string, string | [string, number][]> = JSON.parse(
|
||||
element.dataset.preactSignals!,
|
||||
);
|
||||
for (const [propName, signalId] of Object.entries(signals)) {
|
||||
if (!sharedSignalMap.has(signalId)) {
|
||||
const signalValue = signal(props[propName]);
|
||||
sharedSignalMap.set(signalId, signalValue);
|
||||
if (Array.isArray(signalId)) {
|
||||
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][indexOrKeyInProps] = sharedSignalMap.get(id);
|
||||
});
|
||||
} else {
|
||||
if (!sharedSignalMap.has(signalId)) {
|
||||
const signalValue = signal(props[propName]);
|
||||
sharedSignalMap.set(signalId, signalValue);
|
||||
}
|
||||
props[propName] = sharedSignalMap.get(signalId);
|
||||
}
|
||||
props[propName] = sharedSignalMap.get(signalId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -28,22 +35,38 @@ export function serializeSignals(
|
|||
map: PropNameToSignalMap,
|
||||
) {
|
||||
// Check for signals
|
||||
const signals: Record<string, string> = {};
|
||||
const signals: Signals = {};
|
||||
for (const [key, value] of Object.entries(props)) {
|
||||
if (isSignal(value)) {
|
||||
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)) {
|
||||
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]]);
|
||||
|
||||
const currentSignals = (signals[key] || []) as ArrayObjectMapping;
|
||||
signals[key] = [...currentSignals, [getSignalId(ctx, signal), keyOrIndex]];
|
||||
}
|
||||
});
|
||||
} else if (isSignal(value)) {
|
||||
// Set the value to the current signal value
|
||||
// This mutates the props on purpose, so that it will be serialized correct.
|
||||
props[key] = value.peek();
|
||||
map.set(key, value);
|
||||
|
||||
let id: string;
|
||||
if (ctx.signals.has(value)) {
|
||||
id = ctx.signals.get(value)!;
|
||||
} else {
|
||||
id = incrementId(ctx);
|
||||
ctx.signals.set(value, id);
|
||||
}
|
||||
signals[key] = id;
|
||||
signals[key] = getSignalId(ctx, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,3 +74,13 @@ export function serializeSignals(
|
|||
attrs['data-preact-signals'] = JSON.stringify(signals);
|
||||
}
|
||||
}
|
||||
|
||||
function getSignalId(ctx: Context, item: SignalLike) {
|
||||
let id = ctx.signals.get(item);
|
||||
if (!id) {
|
||||
id = incrementId(ctx);
|
||||
ctx.signals.set(item, id);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,11 @@ export type SignalLike = {
|
|||
peek(): any;
|
||||
};
|
||||
|
||||
export type PropNameToSignalMap = Map<string, SignalLike>;
|
||||
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;
|
||||
|
|
Loading…
Add table
Reference in a new issue