From 3ace88f14f262748814c0a861ef3f49125a67873 Mon Sep 17 00:00:00 2001 From: Andre Wiggins Date: Tue, 14 Nov 2023 20:17:35 -0800 Subject: [PATCH 1/4] On exported useSignals, only run logic if auto is not installed --- packages/react/runtime/src/auto.ts | 8 ++++-- packages/react/runtime/src/index.ts | 39 +++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/packages/react/runtime/src/auto.ts b/packages/react/runtime/src/auto.ts index b5d505ec7..90dcda22f 100644 --- a/packages/react/runtime/src/auto.ts +++ b/packages/react/runtime/src/auto.ts @@ -6,7 +6,7 @@ import { import React from "react"; import jsxRuntime from "react/jsx-runtime"; import jsxRuntimeDev from "react/jsx-dev-runtime"; -import { EffectStore, useSignals, wrapJsx } from "./index"; +import { EffectStore, wrapJsx, _useSignalsImplementation } from "./index"; export interface ReactDispatcher { useRef: typeof React.useRef; @@ -146,11 +146,15 @@ const dispatcherMachinePROD = createMachine({ ``` */ +export let isAutoSignalTrackingInstalled = false; + let store: EffectStore | null = null; let lock = false; let currentDispatcher: ReactDispatcher | null = null; function installCurrentDispatcherHook() { + isAutoSignalTrackingInstalled = true; + Object.defineProperty(ReactInternals.ReactCurrentDispatcher, "current", { get() { return currentDispatcher; @@ -171,7 +175,7 @@ function installCurrentDispatcherHook() { isEnteringComponentRender(currentDispatcherType, nextDispatcherType) ) { lock = true; - store = useSignals(); + store = _useSignalsImplementation(); lock = false; } else if ( isExitingComponentRender(currentDispatcherType, nextDispatcherType) diff --git a/packages/react/runtime/src/index.ts b/packages/react/runtime/src/index.ts index 8a81cdb87..e23da8d4a 100644 --- a/packages/react/runtime/src/index.ts +++ b/packages/react/runtime/src/index.ts @@ -1,11 +1,13 @@ import { signal, computed, effect, Signal } from "@preact/signals-core"; import { useRef, useMemo, useEffect } from "react"; import { useSyncExternalStore } from "use-sync-external-store/shim/index.js"; +import { isAutoSignalTrackingInstalled } from "./auto"; export { installAutoSignalTracking } from "./auto"; const Empty = [] as const; const ReactElemType = Symbol.for("react.element"); // https://github.com/facebook/react/blob/346c7d4c43a0717302d446da9e7423a8e28d8996/packages/shared/ReactSymbols.js#L15 +const noop = () => {}; export function wrapJsx(jsx: T): T { if (typeof jsx !== "function") return jsx; @@ -113,6 +115,29 @@ function createEffectStore(): EffectStore { }; } +function createEmptyEffectStore(): EffectStore { + return { + effect: { + _sources: undefined, + _callback() {}, + _start() { + return noop; + }, + _dispose() {}, + }, + subscribe() { + return noop; + }, + getSnapshot() { + return 0; + }, + f() {}, + [symDispose]() {}, + }; +} + +const emptyEffectStore = createEmptyEffectStore(); + let finalCleanup: Promise | undefined; const _queueMicroTask = Promise.prototype.then.bind(Promise.resolve()); @@ -120,7 +145,7 @@ const _queueMicroTask = Promise.prototype.then.bind(Promise.resolve()); * Custom hook to create the effect to track signals used during render and * subscribe to changes to rerender the component when the signals change. */ -export function useSignals(): EffectStore { +export function _useSignalsImplementation(): EffectStore { clearCurrentStore(); if (!finalCleanup) { finalCleanup = _queueMicroTask(() => { @@ -145,7 +170,12 @@ export function useSignals(): EffectStore { * A wrapper component that renders a Signal's value directly as a Text node or JSX. */ function SignalValue({ data }: { data: Signal }) { - return data.value; + const store = useSignals(); + try { + return data.value; + } finally { + store.f(); + } } // Decorate Signals so React renders them as components. @@ -161,6 +191,11 @@ Object.defineProperties(Signal.prototype, { ref: { configurable: true, value: null }, }); +export function useSignals() { + if (isAutoSignalTrackingInstalled) return emptyEffectStore; + return _useSignalsImplementation(); +} + export function useSignal(value: T) { return useMemo(() => signal(value), Empty); } From 818ee986bbe72059f8e33f9b4826d8ff0c1f0cd5 Mon Sep 17 00:00:00 2001 From: Andre Wiggins Date: Tue, 14 Nov 2023 20:52:51 -0800 Subject: [PATCH 2/4] Remove top-level requirement from react-transform --- packages/react-transform/src/index.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/react-transform/src/index.ts b/packages/react-transform/src/index.ts index 5c83fa936..779bd642f 100644 --- a/packages/react-transform/src/index.ts +++ b/packages/react-transform/src/index.ts @@ -145,16 +145,12 @@ function isOptedOutOfSignalTracking(path: NodePath | null): boolean { function isComponentFunction(path: NodePath): boolean { return ( fnNameStartsWithCapital(path) && // Function name indicates it's a component - getData(path.scope, containsJSX) === true && // Function contains JSX - path.scope.parent === path.scope.getProgramParent() // Function is top-level + getData(path.scope, containsJSX) === true // Function contains JSX ); } function isCustomHook(path: NodePath): boolean { - return ( - fnNameStartsWithUse(path) && // Function name indicates it's a hook - path.scope.parent === path.scope.getProgramParent() - ); // Function is top-level + return fnNameStartsWithUse(path); // Function name indicates it's a hook } function shouldTransform( From 2f17eb8da9638e2c0feaf8c5c430ac8227fce665 Mon Sep 17 00:00:00 2001 From: Andre Wiggins Date: Tue, 14 Nov 2023 20:53:58 -0800 Subject: [PATCH 3/4] Add changesets --- .changeset/fresh-panthers-explain.md | 5 +++++ .changeset/lovely-moons-beam.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/fresh-panthers-explain.md create mode 100644 .changeset/lovely-moons-beam.md diff --git a/.changeset/fresh-panthers-explain.md b/.changeset/fresh-panthers-explain.md new file mode 100644 index 000000000..f8f4f7f44 --- /dev/null +++ b/.changeset/fresh-panthers-explain.md @@ -0,0 +1,5 @@ +--- +"@preact/signals-react-transform": patch +--- + +Remove top-level requirement from react-transform diff --git a/.changeset/lovely-moons-beam.md b/.changeset/lovely-moons-beam.md new file mode 100644 index 000000000..b45318a57 --- /dev/null +++ b/.changeset/lovely-moons-beam.md @@ -0,0 +1,5 @@ +--- +"@preact/signals-react": patch +--- + +Fix rendering signals as text when using react-transform From edf4fae4f0551ffa057eaf7094e72b858d7f8588 Mon Sep 17 00:00:00 2001 From: Andre Wiggins Date: Tue, 14 Nov 2023 21:18:02 -0800 Subject: [PATCH 4/4] Add return types to react runtime exports --- packages/react/runtime/src/index.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/react/runtime/src/index.ts b/packages/react/runtime/src/index.ts index e23da8d4a..b5945d5e1 100644 --- a/packages/react/runtime/src/index.ts +++ b/packages/react/runtime/src/index.ts @@ -1,4 +1,10 @@ -import { signal, computed, effect, Signal } from "@preact/signals-core"; +import { + signal, + computed, + effect, + Signal, + ReadonlySignal, +} from "@preact/signals-core"; import { useRef, useMemo, useEffect } from "react"; import { useSyncExternalStore } from "use-sync-external-store/shim/index.js"; import { isAutoSignalTrackingInstalled } from "./auto"; @@ -191,22 +197,22 @@ Object.defineProperties(Signal.prototype, { ref: { configurable: true, value: null }, }); -export function useSignals() { +export function useSignals(): EffectStore { if (isAutoSignalTrackingInstalled) return emptyEffectStore; return _useSignalsImplementation(); } -export function useSignal(value: T) { +export function useSignal(value: T): Signal { return useMemo(() => signal(value), Empty); } -export function useComputed(compute: () => T) { +export function useComputed(compute: () => T): ReadonlySignal { const $compute = useRef(compute); $compute.current = compute; return useMemo(() => computed(() => $compute.current()), Empty); } -export function useSignalEffect(cb: () => void | (() => void)) { +export function useSignalEffect(cb: () => void | (() => void)): void { const callback = useRef(cb); callback.current = cb;