Skip to content

Commit 1915f23

Browse files
committed
Refactor signal logic into its own module
1 parent 64053dc commit 1915f23

File tree

6 files changed

+86
-37
lines changed

6 files changed

+86
-37
lines changed

packages/integrations/preact/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@
3535
"@babel/core": ">=7.0.0-0 <8.0.0",
3636
"@babel/plugin-transform-react-jsx": "^7.17.12",
3737
"babel-plugin-module-resolver": "^4.1.0",
38-
"preact-render-to-string": "^5.2.0",
39-
"@preact/signals": "1.0.3"
38+
"preact-render-to-string": "^5.2.4",
39+
"@preact/signals": "^1.1.0"
4040
},
4141
"devDependencies": {
4242
"astro": "workspace:*",

packages/integrations/preact/src/context.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import type { RendererContext, SignalLike } from './types';
1+
import type { RendererContext, SignalLike, PropNameToSignalMap } from './types';
22

3-
type Context = {
3+
export type Context = {
44
id: string;
55
c: number;
66
signals: Map<SignalLike, string>;
7+
propsToSignals: Map<Record<string, any>, PropNameToSignalMap>;
78
};
89

910
const contexts = new WeakMap<RendererContext['result'], Context>();
@@ -17,7 +18,8 @@ export function getContext(result: RendererContext['result']): Context {
1718
get id() {
1819
return 'p' + this.c.toString();
1920
},
20-
signals: new Map()
21+
signals: new Map(),
22+
propsToSignals: new Map()
2123
};
2224
contexts.set(result, ctx);
2325
return ctx;

packages/integrations/preact/src/server.ts

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import type { RendererContext, SignalLike } from './types';
1+
import type { AstroPreactAttrs, RendererContext } from './types';
22
import { h, Component as BaseComponent } from 'preact';
33
import render from 'preact-render-to-string';
44
import StaticHtml from './static-html.js';
5-
import { getContext, incrementId } from './context.js';
5+
import { getContext } from './context.js';
6+
import { restoreSignalsOnProps, serializeSignals } from './signals.js';
67

78
const slotName = (str: string) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase());
89

@@ -37,10 +38,6 @@ function check(this: RendererContext, Component: any, props: Record<string, any>
3738
}
3839
}
3940

40-
function isSignal(x: any): x is SignalLike {
41-
return x != null && typeof x === 'object' && typeof x.peek === 'function' && 'value' in x;
42-
}
43-
4441
function renderToStaticMarkup(this: RendererContext, Component: any, props: Record<string, any>, { default: children, ...slotted }: Record<string, any>) {
4542
const ctx = getContext(this.result);
4643

@@ -49,30 +46,14 @@ function renderToStaticMarkup(this: RendererContext, Component: any, props: Reco
4946
const name = slotName(key);
5047
slots[name] = h(StaticHtml, { value, name });
5148
}
52-
// Note: create newProps to avoid mutating `props` before they are serialized
49+
50+
// Restore signals back onto props so that they will be passed as-is to components
51+
restoreSignalsOnProps(ctx, props);
52+
5353
const newProps = { ...props, ...slots };
5454

55-
// Check for signals
56-
const signals: Record<string, string> = {};
57-
for(const [key, value] of Object.entries(props)) {
58-
if(isSignal(value)) {
59-
// Set the value to the current signal value
60-
newProps[key] = value.peek();
61-
62-
let id: string;
63-
if(ctx.signals.has(value)) {
64-
id = ctx.signals.get(value)!;
65-
} else {
66-
id = incrementId(ctx);
67-
ctx.signals.set(value, id);
68-
}
69-
signals[key] = id;
70-
}
71-
}
72-
const attrs: { ['data-preact-signals']?: string } = {};
73-
if(Object.keys(signals).length) {
74-
attrs['data-preact-signals'] = JSON.stringify(signals);
75-
}
55+
const attrs: AstroPreactAttrs = {};
56+
serializeSignals(ctx, props, attrs);
7657

7758
const html = render(
7859
h(Component, newProps, children != null ? h(StaticHtml, { value: children }) : children)
@@ -83,6 +64,7 @@ function renderToStaticMarkup(this: RendererContext, Component: any, props: Reco
8364
};
8465
}
8566

67+
8668
/**
8769
* Reduces console noise by filtering known non-problematic errors.
8870
*
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import type { AstroPreactAttrs, PropNameToSignalMap, SignalLike } from './types';
2+
import type { Context } from './context';
3+
import { incrementId } from './context.js';
4+
5+
function isSignal(x: any): x is SignalLike {
6+
return x != null && typeof x === 'object' && typeof x.peek === 'function' && 'value' in x;
7+
}
8+
9+
export function restoreSignalsOnProps(ctx: Context, props: Record<string, any>) {
10+
// Restore signal props that were mutated for serialization
11+
let propMap: PropNameToSignalMap;
12+
if(ctx.propsToSignals.has(props)) {
13+
propMap = ctx.propsToSignals.get(props)!
14+
} else {
15+
propMap = new Map();
16+
ctx.propsToSignals.set(props, propMap);
17+
}
18+
for(const [key, signal] of propMap) {
19+
props[key] = signal;
20+
}
21+
}
22+
23+
export function serializeSignals(ctx: Context, props: Record<string, any>, attrs: AstroPreactAttrs){
24+
// Check for signals
25+
const signals: Record<string, string> = {};
26+
for(const [key, value] of Object.entries(props)) {
27+
if(isSignal(value)) {
28+
// Set the value to the current signal value
29+
// This mutates the props on purpose, so that it will be serialized correct.
30+
props[key] = value.peek();
31+
32+
let id: string;
33+
if(ctx.signals.has(value)) {
34+
id = ctx.signals.get(value)!;
35+
} else {
36+
id = incrementId(ctx);
37+
ctx.signals.set(value, id);
38+
}
39+
signals[key] = id;
40+
}
41+
}
42+
43+
if(Object.keys(signals).length) {
44+
attrs['data-preact-signals'] = JSON.stringify(signals);
45+
}
46+
}

packages/integrations/preact/src/types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,10 @@ export type RendererContext = {
55

66
export type SignalLike = {
77
peek(): any;
8-
}
8+
};
9+
10+
export type PropNameToSignalMap = Map<string, SignalLike>;
11+
12+
export type AstroPreactAttrs = {
13+
['data-preact-signals']?: string
14+
};

pnpm-lock.yaml

Lines changed: 16 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)