Skip to content

Commit

Permalink
Merge pull request #431 from preactjs/signals-2
Browse files Browse the repository at this point in the history
  • Loading branch information
marvinhagemeister authored Oct 1, 2022
2 parents 7c90833 + a86f6d7 commit 5445e3b
Show file tree
Hide file tree
Showing 16 changed files with 159 additions and 26 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"@parcel/css": "^1.12.2",
"@playwright/test": "^1.25.1",
"@preact/preset-vite": "^2.3.0",
"@preact/signals": "^1.0.1",
"@preact/signals": "^1.1.1",
"@testing-library/preact": "^2.0.0",
"@types/archiver": "^5.3.1",
"@types/babel__core": "^7.1.19",
Expand Down
8 changes: 8 additions & 0 deletions src/adapter/adapter/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export interface InspectData {
hooks: PropData[] | null;
props: Record<string, any> | null;
state: Record<string, any> | null;
signals: Record<string, any> | null;
canSuspend: boolean;
/** Only Suspense components have this */
suspended: boolean;
Expand Down Expand Up @@ -148,6 +149,13 @@ export function createAdapter(
listen("update-prop", data => update({ ...data, type: "props" }));
listen("update-state", data => update({ ...data, type: "state" }));
listen("update-context", data => update({ ...data, type: "context" }));
listen("update-signal", data => {
getRendererByVNodeId(renderers, data.id)?.updateSignal?.(
data.id,
+data.path.replace("root.", "").replace(".value", ""),
data.value,
);
});
listen("update-hook", data => {
if (!data.meta) return;

Expand Down
1 change: 1 addition & 0 deletions src/adapter/hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface DevtoolEvents {
"update-prop": { id: ID; path: string; value: any };
"update-state": { id: ID; path: string; value: any };
"update-context": { id: ID; path: string; value: any };
"update-signal": { id: ID; path: string; value: any };
"update-hook": { id: ID; value: any; meta: any };
/**
* @deprecated
Expand Down
2 changes: 2 additions & 0 deletions src/adapter/protocol/events.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ describe("applyEvent", () => {
name: "Foo",
props: null,
state: null,
signals: null,
type: "asd",
canSuspend: false,
suspended: false,
Expand Down Expand Up @@ -257,6 +258,7 @@ describe("applyEvent", () => {
name: "Foo",
props: null,
state: null,
signals: null,
type: "asd",
canSuspend: false,
suspended: false,
Expand Down
2 changes: 2 additions & 0 deletions src/adapter/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export interface Renderer<T extends SharedVNode = SharedVNode> {

// Hooks
updateHook?(id: ID, index: number, value: any): void; // V3
// signals
updateSignal?(id: ID, index: number, value: any): void; // V5

// Component actions
suspend?(id: ID, active: boolean): void; // V4
Expand Down
24 changes: 24 additions & 0 deletions src/adapter/shared/inspectVNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ export function inspectVNode<T extends SharedVNode>(
? serialize(config, bindings, cleanProps(vnode.props))
: null;
const state = hasState ? serialize(config, bindings, c!.state) : null;
const signals =
c != null && "__$u" in c
? inspectSignalSubscriptions(config, bindings, c.__$u.s)
: null;

let suspended = false;
let canSuspend = false;
Expand Down Expand Up @@ -77,9 +81,29 @@ export function inspectVNode<T extends SharedVNode>(
name: getSignalTextName(bindings.getDisplayName(vnode, config)),
props,
state,
signals,
// TODO: We're not using this information anywhere yet
type: getDevtoolsType(vnode, bindings),
suspended,
version,
};
}

function inspectSignalSubscriptions<T extends SharedVNode>(
config: RendererConfig,
bindings: PreactBindings<T>,
node: any,
) {
const out: Record<string, any> = {};
let i = 0;

const seen = new Set();
while (node !== null && node !== undefined && !seen.has(node)) {
seen.add(node);
out[i] = serialize(config, bindings, node.S);
node = node.n;
i++;
}

return i > 0 ? out : null;
}
22 changes: 22 additions & 0 deletions src/adapter/shared/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,28 @@ export function createRenderer<T extends SharedVNode>(
}
},

updateSignal(id, index, value) {
const vnode = getVNodeById(ids, id);
if (vnode !== null && bindings.isComponent(vnode)) {
const c = bindings.getComponent(vnode);

if (c !== null && "__$u" in c) {
let node = c.__$u.s;
let i = 0;
const seen = new Set();
while (node !== null && node !== undefined && !seen.has(node)) {
if (i === index) {
node.S.value = value;
return;
}
seen.add(node);
node = node.n;
i++;
}
}
}
},

suspend(id, active) {
let vnode = getVNodeById(ids, id);
while (vnode !== null) {
Expand Down
7 changes: 6 additions & 1 deletion src/adapter/shared/serialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ export function isSignal(x: any): x is Signal {
}

export function isReadOnlySignal(signal: Signal): boolean {
return (signal as any)._r === true;
return (
// Signals <1.2.0
(signal as any)._r === true ||
// Signals >=1.2.0
("g" in signal && typeof (signal as any).x === "function")
);
}

export function jsonify(
Expand Down
1 change: 1 addition & 0 deletions src/adapter/store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ describe("Store", () => {
name: "Foo",
props: null,
state: null,
signals: null,
suspended: false,
type: 1,
version: "",
Expand Down
16 changes: 15 additions & 1 deletion src/view/components/sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function Sidebar() {
}
return null;
}).value;
const { props: propData, state, context, hooks } = store.sidebar;
const { props: propData, state, context, hooks, signals } = store.sidebar;
const { emit } = store;

return (
Expand All @@ -42,6 +42,20 @@ export function Sidebar() {
onCopy={() => inspect && emit("copy", serializeProps(inspect.props))}
canAddNew
/>
{inspect && inspect.signals !== null && (
<PropsPanel
label="Signals"
items={signals.items}
uncollapsed={signals.uncollapsed}
onChange={(value, path) => {
emit("update-signal", {
id: inspect!.id,
path,
value,
});
}}
/>
)}
{inspect && inspect.hooks !== null && (
<PropsPanel
label="Hooks"
Expand Down
28 changes: 18 additions & 10 deletions src/view/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,30 +64,38 @@ export function createStore(): Store {
uncollapsed: signal<string[]>([]),
items: signal<PropData[]>([]),
},
signals: {
uncollapsed: signal<string[]>([]),
items: signal<PropData[]>([]),
},
};

const inspectData = signal<InspectData | null>(null);

effect(() => {
const data = inspectData.value ? inspectData.value.props : null;
const props = inspectData.value ? inspectData.value.props : null;
sidebar.props.items.value = parseObjectState(
data,
props,
sidebar.props.uncollapsed.value,
);
});
effect(() => {
const data = inspectData.value ? inspectData.value.state : null;

const state = inspectData.value ? inspectData.value.state : null;
sidebar.state.items.value = parseObjectState(
data,
state,
sidebar.state.uncollapsed.value,
);
});
effect(() => {
const data = inspectData.value ? inspectData.value.context : null;

const context = inspectData.value ? inspectData.value.context : null;
sidebar.context.items.value = parseObjectState(
data,
context,
sidebar.context.uncollapsed.value,
);

const signals = inspectData.value ? inspectData.value.signals : null;
sidebar.signals.items.value = parseObjectState(
signals,
sidebar.signals.uncollapsed.value,
);
});

const supportsHooks = signal(false);
Expand Down
4 changes: 4 additions & 0 deletions src/view/store/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ export interface Store {
uncollapsed: Signal<string[]>;
items: Signal<PropData[]>;
};
signals: {
uncollapsed: Signal<string[]>;
items: Signal<PropData[]>;
};
context: {
uncollapsed: Signal<string[]>;
items: Signal<PropData[]>;
Expand Down
17 changes: 17 additions & 0 deletions test-e2e/fixtures/apps/signals-subscribe.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { h, render } from "preact";
import { signal, computed } from "@preact/signals";

const count = signal(1);
const double = computed(() => count.value * 2);

function App() {
const source = count.value;
const value = double.value;
return (
<p>
count: {source}, double: {value}
</p>
);
}

render(<App />, document.getElementById("app"));
6 changes: 3 additions & 3 deletions test-e2e/fixtures/rewrite-preact-version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ export function rewritePreactVersion(): Plugin {
}

if (id === `@preact/signals@${preactVersion}`) {
return `@fixture-signals`;
return `@fixture-signals:${preactVersion}`;
}
},
load(id) {
if (id.startsWith(PREFIX)) {
const newId = id.slice(PREFIX.length, id.indexOf("?"));
const filePath = path.join(__dirname, newId);
return fs.readFileSync(filePath, "utf-8");
} else if (id === "@fixture-signals") {
} else if (id.startsWith("@fixture-signals")) {
const filePath = path.join(
__dirname,
"..",
Expand All @@ -60,7 +60,7 @@ export function rewritePreactVersion(): Plugin {
}
},
transform(code, id) {
if (id === "@fixture-signals") {
if (id.startsWith("@fixture-signals")) {
const res = transformSync(code, {
plugins: [[rewriteImportPlugin, { version: preactVersion }]],
})!;
Expand Down
27 changes: 26 additions & 1 deletion test-e2e/tests/inspect-signal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ test("Show signals in hooks", async ({ page }) => {
await devtools.waitForSelector('[data-testid="props-row"]');
await devtools
.locator(
'[data-testid="props-row"] [data-testid="prop-name"]:has-text("w")',
'[data-testid="props-row"] [data-testid="prop-name"]:has-text("_")',
)
.click();
await devtools
Expand All @@ -72,3 +72,28 @@ test("Show signals in hooks", async ({ page }) => {
),
).toHaveCount(1);
});

test("Dectect signal subscriptions", async ({ page }) => {
const { devtools } = await gotoTest(page, "signals-subscribe");

await devtools.click(locateTreeItem("App"));

await devtools.waitForSelector('[data-testid="Signals"]');
await devtools.waitForSelector('[data-testid="props-row"]');
await expect(
devtools.locator('[data-testid="Signals"] [data-testid="props-row"]'),
).toHaveCount(2);

await devtools
.locator('[data-testid="Signals"] [data-testid="prop-name"]:has-text("0")')
.click();
await devtools
.locator('[data-testid="Signals"] [data-testid="prop-value"] input')
.fill("");
await devtools
.locator('[data-testid="Signals"] [data-testid="prop-value"] input')
.type("10");
await page.keyboard.press("Enter");

await page.locator("p:has-text('count: 10, double: 20')").waitFor();
});
18 changes: 9 additions & 9 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -750,17 +750,17 @@
kolorist "^1.2.10"
resolve "^1.20.0"

"@preact/signals-core@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@preact/signals-core/-/signals-core-1.0.0.tgz#ccdfb40e9ec97668c74aecbe91192c7422636836"
integrity sha512-Gx5zCzjJLnI7ZeS4Z5TKu48vBGe/fqNONxXNmoIToOw0lOz3dN32/Lmfcg408Mf52kwyqWmMRjPJo1paPYhLOg==
"@preact/signals-core@^1.2.0":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@preact/signals-core/-/signals-core-1.2.1.tgz#ef3ef1a1d02381b1227d80ead7688f381264891d"
integrity sha512-aqzRMNFU1hoQyP4Kb1ldJrUTCnA9vqPDa7qHEQzHJ3upnBOcC2pjmvjAuTqGuY4AVTtUkCQV0FvOCuIQQ2hSdA==

"@preact/signals@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@preact/signals/-/signals-1.0.1.tgz#9f427abf5eea09f0163e05e4f3b58b00dff3111d"
integrity sha512-G1SCckKl1V0AGJ6Ulf8nqLPxyiReoXLqFWAYKMkH4YyUlLQJP+qq8/qcjw0Iqx92f8fyTRnuMLYKSdiqemPeRQ==
"@preact/signals@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@preact/signals/-/signals-1.1.1.tgz#a6071312f6b926598eecd81503e7dad66e2d23d2"
integrity sha512-I1DhYo2d1t9qDkEq1jYDVTQdBGmo4NlqatNEtulsS/87kVdwhZluP6TTDS4/5sc2h86TlBF6UA6LO+tDpIt/Gw==
dependencies:
"@preact/signals-core" "^1.0.0"
"@preact/signals-core" "^1.2.0"

"@prefresh/[email protected]":
version "0.4.3"
Expand Down

0 comments on commit 5445e3b

Please sign in to comment.