summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorGravatar Phil <me@ph1p.dev> 2024-08-28 12:41:35 +0200
committerGravatar GitHub <noreply@github.com> 2024-08-28 11:41:35 +0100
commit5f2536b51df93bfd51098c48220d647e7ad3954c (patch)
tree056a91c16866655154ede8173ffebbed022ea46e /packages
parent26c63a2b07e6298dfb59c0c3178348cbd02c8a73 (diff)
downloadastro-5f2536b51df93bfd51098c48220d647e7ad3954c.tar.gz
astro-5f2536b51df93bfd51098c48220d647e7ad3954c.tar.zst
astro-5f2536b51df93bfd51098c48220d647e7ad3954c.zip
fix: handle preact signals in array correctly (#11834)
* fix: handle preact signals in array correctly * feat: serialize signals in object
Diffstat (limited to 'packages')
-rw-r--r--packages/astro/test/fixtures/preact-component/src/components/SignalsInArray.jsx8
-rw-r--r--packages/astro/test/fixtures/preact-component/src/components/SignalsInObject.jsx8
-rw-r--r--packages/astro/test/fixtures/preact-component/src/pages/signals.astro5
-rw-r--r--packages/astro/test/preact-component.test.js40
-rw-r--r--packages/integrations/preact/src/client.ts31
-rw-r--r--packages/integrations/preact/src/signals.ts55
-rw-r--r--packages/integrations/preact/src/types.ts6
7 files changed, 136 insertions, 17 deletions
diff --git a/packages/astro/test/fixtures/preact-component/src/components/SignalsInArray.jsx b/packages/astro/test/fixtures/preact-component/src/components/SignalsInArray.jsx
new file mode 100644
index 000000000..69940f730
--- /dev/null
+++ b/packages/astro/test/fixtures/preact-component/src/components/SignalsInArray.jsx
@@ -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>
+}
diff --git a/packages/astro/test/fixtures/preact-component/src/components/SignalsInObject.jsx b/packages/astro/test/fixtures/preact-component/src/components/SignalsInObject.jsx
new file mode 100644
index 000000000..6187ce8c5
--- /dev/null
+++ b/packages/astro/test/fixtures/preact-component/src/components/SignalsInObject.jsx
@@ -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>
+}
diff --git a/packages/astro/test/fixtures/preact-component/src/pages/signals.astro b/packages/astro/test/fixtures/preact-component/src/pages/signals.astro
index b68fde36d..37b43a73c 100644
--- a/packages/astro/test/fixtures/preact-component/src/pages/signals.astro
+++ b/packages/astro/test/fixtures/preact-component/src/pages/signals.astro
@@ -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>
diff --git a/packages/astro/test/preact-component.test.js b/packages/astro/test/preact-component.test.js
index e8c89d5bf..f5b5c7233 100644
--- a/packages/astro/test/preact-component.test.js
+++ b/packages/astro/test/preact-component.test.js
@@ -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');
+ });
});
diff --git a/packages/integrations/preact/src/client.ts b/packages/integrations/preact/src/client.ts
index 3fb36d22a..fa20a9928 100644
--- a/packages/integrations/preact/src/client.ts
+++ b/packages/integrations/preact/src/client.ts
@@ -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);
}
}
diff --git a/packages/integrations/preact/src/signals.ts b/packages/integrations/preact/src/signals.ts
index ea022b422..f8bae590f 100644
--- a/packages/integrations/preact/src/signals.ts
+++ b/packages/integrations/preact/src/signals.ts
@@ -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;
+}
diff --git a/packages/integrations/preact/src/types.ts b/packages/integrations/preact/src/types.ts
index 93f65bbc2..e1c56ca30 100644
--- a/packages/integrations/preact/src/types.ts
+++ b/packages/integrations/preact/src/types.ts
@@ -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;