From 4a5bf32f615ebfe89cdb7be67fca20008f56e3df Mon Sep 17 00:00:00 2001 From: David Di Biase <1168397+davedbase@users.noreply.github.com> Date: Mon, 20 Apr 2026 21:32:52 -0400 Subject: [PATCH 1/3] Deep changes amongst a number of pritmivies --- .changeset/media-solid2-migration.md | 45 +++++++ packages/event-listener/package.json | 6 +- packages/event-listener/src/components.ts | 2 +- packages/event-listener/src/eventListener.ts | 35 ++++-- .../event-listener/src/eventListenerMap.ts | 2 +- .../event-listener/src/eventListenerStack.ts | 2 +- packages/media/README.md | 41 ++++--- packages/media/package.json | 8 +- packages/media/src/index.ts | 2 +- packages/media/test/index.test.ts | 16 +-- packages/rootless/package.json | 6 +- packages/rootless/src/index.ts | 11 +- packages/static-store/package.json | 6 +- packages/static-store/src/index.ts | 19 ++- packages/utils/package.json | 6 +- packages/utils/src/index.ts | 23 ++-- pnpm-lock.yaml | 116 ++++++++++++++---- 17 files changed, 235 insertions(+), 111 deletions(-) create mode 100644 .changeset/media-solid2-migration.md diff --git a/.changeset/media-solid2-migration.md b/.changeset/media-solid2-migration.md new file mode 100644 index 000000000..bec101809 --- /dev/null +++ b/.changeset/media-solid2-migration.md @@ -0,0 +1,45 @@ +--- +"@solid-primitives/media": major +"@solid-primitives/event-listener": major +"@solid-primitives/rootless": major +"@solid-primitives/static-store": major +"@solid-primitives/utils": major +--- + +Migrate to Solid.js v2.0 (beta.7) + +## Breaking Changes + +**Peer dependency**: `solid-js@^2.0.0-beta.7` and `@solidjs/web@^2.0.0-beta.7` are now required. + +### `@solid-primitives/media` + +- `isServer` now imported from `@solidjs/web` (not `solid-js/web`) +- Requires Solid.js v2 — `classList` is replaced by `class` with object/array forms in consuming code + +### `@solid-primitives/utils` + +- `isServer` import moved from `solid-js/web` to `@solidjs/web` +- `createHydratableSignal`: uses `onSettled` (was `onMount`) and `sharedConfig.hydrating` (was `sharedConfig.context`) for hydration detection +- `INTERNAL_OPTIONS`: `{ internal: true }` changed to `{ pureWrite: true }` to match Solid 2.0 `SignalOptions` +- `defaultEquals` now aliases `isEqual` (was `equalFn`) +- `defer`: `AccessorArray` type replaced with `Accessor[]` (type was removed in Solid 2.0) + +### `@solid-primitives/static-store` + +- `isServer` import moved from `solid-js/web` to `@solidjs/web` +- `createStaticStore`: uses `getObserver` (was `getListener`) and `{ pureWrite: true }` (was `{ internal: true }`) +- Removed explicit `batch()` calls — updates are automatically batched in Solid 2.0 +- `createHydratableStaticStore`: uses `onSettled` (was `onMount`) and `sharedConfig.hydrating` (was `sharedConfig.context`) + +### `@solid-primitives/rootless` + +- `isServer` import moved from `solid-js/web` to `@solidjs/web` +- `createHydratableSingletonRoot`: uses `sharedConfig.hydrating` (was `sharedConfig.context`) +- `createRootPool`: removed `batch()` calls — Solid 2.0 auto-batches on microtasks + +### `@solid-primitives/event-listener` + +- `isServer` import moved from `solid-js/web` to `@solidjs/web` across all source files +- `createEventListener` and `createRenderEffect` converted to split compute/apply effect pattern required by Solid 2.0 +- `eventListener` directive converted to split effect pattern; cleanup is returned from apply phase instead of using `onCleanup` diff --git a/packages/event-listener/package.json b/packages/event-listener/package.json index 516416b21..ef03fe1b3 100644 --- a/packages/event-listener/package.json +++ b/packages/event-listener/package.json @@ -57,10 +57,12 @@ "@solid-primitives/utils": "workspace:^" }, "peerDependencies": { - "solid-js": "^1.6.12" + "@solidjs/web": "^2.0.0-beta.7", + "solid-js": "^2.0.0-beta.7" }, "typesVersions": {}, "devDependencies": { - "solid-js": "^1.9.7" + "@solidjs/web": "2.0.0-beta.7", + "solid-js": "2.0.0-beta.7" } } diff --git a/packages/event-listener/src/components.ts b/packages/event-listener/src/components.ts index 72c541fb2..69b18727c 100644 --- a/packages/event-listener/src/components.ts +++ b/packages/event-listener/src/components.ts @@ -1,4 +1,4 @@ -import { isServer } from "solid-js/web"; +import { isServer } from "@solidjs/web"; import { keys } from "@solid-primitives/utils"; import { type Component } from "solid-js"; import { makeEventListener } from "./eventListener.js"; diff --git a/packages/event-listener/src/eventListener.ts b/packages/event-listener/src/eventListener.ts index 5fa8674ea..7c19fc933 100644 --- a/packages/event-listener/src/eventListener.ts +++ b/packages/event-listener/src/eventListener.ts @@ -7,7 +7,7 @@ import { tryOnCleanup, } from "@solid-primitives/utils"; import { type Accessor, createEffect, createRenderEffect, createSignal } from "solid-js"; -import { isServer } from "solid-js/web"; +import { isServer } from "@solidjs/web"; import type { EventListenerDirectiveProps, EventMapOf, @@ -110,17 +110,28 @@ export function createEventListener( ): void { if (isServer) return; - const attachListeners = () => { - asArray(access(targets)).forEach(el => { - if (el) asArray(access(type)).forEach(type => makeEventListener(el, type, handler, options)); - }); + type State = { els: EventTarget[]; types: string[] }; + + const compute = (): State => ({ + els: asArray(access(targets)).filter(Boolean) as EventTarget[], + types: asArray(access(type)) as string[], + }); + + const apply = ({ els, types }: State) => { + const cleanups: VoidFunction[] = []; + for (const el of els) + for (const t of types) { + el.addEventListener(t, handler, options); + cleanups.push(el.removeEventListener.bind(el, t, handler, options)); + } + return () => cleanups.forEach(c => c()); }; - // if the target is an accessor the listeners will be added on the first effect (onMount) - // so that when passed a jsx ref it will be availabe - if (typeof targets === "function") createEffect(attachListeners); + // if the target is an accessor the listeners will be added on the first effect (after mount) + // so that when passed a jsx ref it will be available + if (typeof targets === "function") createEffect(compute, apply); // if the target prop is NOT an accessor, the event listeners can be added right away - else createRenderEffect(attachListeners); + else createRenderEffect(compute, apply); } // Possible targets prop shapes: @@ -192,9 +203,9 @@ export function createEventSignal( * */ export const eventListener: Directive = (target, props) => { - createEffect(() => { - const [type, handler, options] = props(); - makeEventListener(target, type, handler, options); + createEffect(props, ([type, handler, options]) => { + target.addEventListener(type, handler, options); + return () => target.removeEventListener(type, handler, options); }); }; diff --git a/packages/event-listener/src/eventListenerMap.ts b/packages/event-listener/src/eventListenerMap.ts index 69450e8d8..a5e312de1 100644 --- a/packages/event-listener/src/eventListenerMap.ts +++ b/packages/event-listener/src/eventListenerMap.ts @@ -1,7 +1,7 @@ import { type AnyFunction, entries, type Many, type MaybeAccessor } from "@solid-primitives/utils"; import { createEventListener } from "./eventListener.js"; import type { EventMapOf, TargetWithEventMap, EventListenerOptions } from "./types.js"; -import { isServer } from "solid-js/web"; +import { isServer } from "@solidjs/web"; export type EventHandlersMap = { [EventName in keyof EventMap]: (event: EventMap[EventName]) => void; diff --git a/packages/event-listener/src/eventListenerStack.ts b/packages/event-listener/src/eventListenerStack.ts index 140a51ca1..c1b29a411 100644 --- a/packages/event-listener/src/eventListenerStack.ts +++ b/packages/event-listener/src/eventListenerStack.ts @@ -2,7 +2,7 @@ import { createCallbackStack } from "@solid-primitives/utils"; import { onCleanup } from "solid-js"; import { makeEventListener } from "./eventListener.js"; import type { EventMapOf, TargetWithEventMap, EventListenerOptions } from "./types.js"; -import { isServer } from "solid-js/web"; +import { isServer } from "@solidjs/web"; export type EventListenerStackOn> = { ( diff --git a/packages/media/README.md b/packages/media/README.md index 968ea767a..f6dfa8a96 100644 --- a/packages/media/README.md +++ b/packages/media/README.md @@ -23,6 +23,8 @@ npm install @solid-primitives/media yarn add @solid-primitives/media ``` +> **Requires Solid.js v2.0 (beta.7+)** + ## `makeMediaQueryListener` Attaches a MediaQuery listener to window, listeneing to changes to provided query @@ -77,20 +79,23 @@ const breakpoints = { const Example: Component = () => { const matches = createBreakpoints(breakpoints); - createEffect(() => { - console.log(matches.sm); // true when screen width >= 640px - console.log(matches.lg); // true when screen width >= 1024px - console.log(matches.xl); // true when screen width >= 1280px - }); + createEffect( + () => [matches.sm, matches.lg, matches.xl], + ([sm, lg, xl]) => { + console.log(sm); // true when screen width >= 640px + console.log(lg); // true when screen width >= 1024px + console.log(xl); // true when screen width >= 1280px + } + ); return (
Smallest
}> Extra Large @@ -169,9 +174,10 @@ Provides a signal indicating if the user has requested dark color theme. The set import { createPrefersDark } from "@solid-primitives/media"; const prefersDark = createPrefersDark(); -createEffect(() => { - prefersDark(); // => boolean -}); +createEffect( + prefersDark, + dark => console.log("prefers dark:", dark) +); ``` ### Server fallback @@ -192,9 +198,10 @@ This primitive provides a [singleton root](https://github.com/solidjs-community/ import { usePrefersDark } from "@solid-primitives/media"; const prefersDark = usePrefersDark(); -createEffect(() => { - prefersDark(); // => boolean -}); +createEffect( + prefersDark, + dark => console.log("prefers dark:", dark) +); ``` > Note: `usePrefersDark` will deopt to `createPrefersDark` if used during hydration. (see issue [#310](https://github.com/solidjs-community/solid-primitives/issues/310)) diff --git a/packages/media/package.json b/packages/media/package.json index a729055b8..79a3cd4d3 100644 --- a/packages/media/package.json +++ b/packages/media/package.json @@ -1,6 +1,6 @@ { "name": "@solid-primitives/media", - "version": "2.3.5", + "version": "3.0.0", "description": "Primitives for media query and device features", "author": "David Di Biase ", "contributors": [ @@ -68,10 +68,12 @@ "@solid-primitives/utils": "workspace:^" }, "peerDependencies": { - "solid-js": "^1.6.12" + "@solidjs/web": "^2.0.0-beta.7", + "solid-js": "^2.0.0-beta.7" }, "typesVersions": {}, "devDependencies": { - "solid-js": "^1.9.7" + "@solidjs/web": "2.0.0-beta.7", + "solid-js": "2.0.0-beta.7" } } diff --git a/packages/media/src/index.ts b/packages/media/src/index.ts index 6234f607d..0923bcd0a 100644 --- a/packages/media/src/index.ts +++ b/packages/media/src/index.ts @@ -1,5 +1,5 @@ import { type Accessor } from "solid-js"; -import { isServer } from "solid-js/web"; +import { isServer } from "@solidjs/web"; import { makeEventListener } from "@solid-primitives/event-listener"; import { entries, noop, createHydratableSignal } from "@solid-primitives/utils"; import { createHydratableStaticStore } from "@solid-primitives/static-store"; diff --git a/packages/media/test/index.test.ts b/packages/media/test/index.test.ts index 6d7411b69..7306a433a 100644 --- a/packages/media/test/index.test.ts +++ b/packages/media/test/index.test.ts @@ -1,5 +1,5 @@ import { describe, test, expect, vi, beforeEach, beforeAll, afterAll } from "vitest"; -import { createRoot, onMount } from "solid-js"; +import { createRoot } from "solid-js"; import { createBreakpoints, sortBreakpoints } from "../src/index.js"; describe("createBreakpoints", () => { @@ -217,11 +217,8 @@ describe("createBreakpoints", () => { createBreakpoints(breakpoints, { watchChange: false, }); - - onMount(() => { - expect(addListenerMock).not.toBeCalled(); - dispose(); - }); + expect(addListenerMock).not.toBeCalled(); + dispose(); }); expect(removeListenerMock).not.toBeCalled(); }); @@ -230,11 +227,8 @@ describe("createBreakpoints", () => { window.matchMedia = undefined as unknown as typeof window.matchMedia; createRoot(dispose => { createBreakpoints(breakpoints); - - onMount(() => { - expect(addListenerMock).not.toBeCalled(); - dispose(); - }); + expect(addListenerMock).not.toBeCalled(); + dispose(); }); expect(removeListenerMock).not.toBeCalled(); }); diff --git a/packages/rootless/package.json b/packages/rootless/package.json index 2cb9e390a..0bb4329e2 100644 --- a/packages/rootless/package.json +++ b/packages/rootless/package.json @@ -56,9 +56,11 @@ "@solid-primitives/utils": "workspace:^" }, "peerDependencies": { - "solid-js": "^1.6.12" + "@solidjs/web": "^2.0.0-beta.7", + "solid-js": "^2.0.0-beta.7" }, "devDependencies": { - "solid-js": "^1.9.7" + "@solidjs/web": "2.0.0-beta.7", + "solid-js": "2.0.0-beta.7" } } diff --git a/packages/rootless/src/index.ts b/packages/rootless/src/index.ts index 29d2dd439..bb95bfcfe 100644 --- a/packages/rootless/src/index.ts +++ b/packages/rootless/src/index.ts @@ -8,10 +8,9 @@ import { type Accessor, createSignal, type Signal, - batch, type Setter, } from "solid-js"; -import { isServer } from "solid-js/web"; +import { isServer } from "@solidjs/web"; import { type AnyFunction, asArray, @@ -161,7 +160,7 @@ export const createSharedRoot = createSingletonRoot; export function createHydratableSingletonRoot(factory: (dispose: VoidFunction) => T): () => T { const owner = getOwner(); const singleton = createSingletonRoot(factory, owner); - return () => (isServer || sharedConfig.context ? createRoot(factory, owner) : singleton()); + return () => (isServer || sharedConfig.hydrating ? createRoot(factory, owner) : singleton()); } /** @@ -310,10 +309,8 @@ export function createRootPool( if (length) { root = pool[--length]!; pool[length] = undefined!; - batch(() => { - root.set(() => arg); - root.setA(true); - }); + root.set(() => arg); + root.setA(true); } else root = createRoot(dispose => mapRoot(dispose, createSignal(arg)), owner); onCleanup(() => cleanupRoot(root)); diff --git a/packages/static-store/package.json b/packages/static-store/package.json index b348a17e2..181e2de4d 100644 --- a/packages/static-store/package.json +++ b/packages/static-store/package.json @@ -51,12 +51,14 @@ "test:ssr": "pnpm run vitest --mode ssr" }, "peerDependencies": { - "solid-js": "^1.6.12" + "@solidjs/web": "^2.0.0-beta.7", + "solid-js": "^2.0.0-beta.7" }, "dependencies": { "@solid-primitives/utils": "workspace:^" }, "devDependencies": { - "solid-js": "^1.9.7" + "@solidjs/web": "2.0.0-beta.7", + "solid-js": "2.0.0-beta.7" } } diff --git a/packages/static-store/src/index.ts b/packages/static-store/src/index.ts index 4e49612bc..28cd6b67b 100644 --- a/packages/static-store/src/index.ts +++ b/packages/static-store/src/index.ts @@ -1,21 +1,20 @@ import { accessWith, isObject, type SetterParam } from "@solid-primitives/utils"; import { type Accessor, - batch, createMemo, createSignal, type EffectFunction, - getListener, + getObserver, getOwner, type MemoOptions, type NoInfer, - onMount, + onSettled, runWithOwner, sharedConfig, type Signal, untrack, } from "solid-js"; -import { isServer } from "solid-js/web"; +import { isServer } from "@solidjs/web"; export type StaticStoreSetter = { (setter: (prev: T) => Partial): T; @@ -54,8 +53,8 @@ export function createStaticStore( const getValue = (key: keyof T): T[keyof T] => { let signal = cache[key]; if (!signal) { - if (!getListener()) return copy[key]; - cache[key] = signal = createSignal(copy[key], { internal: true }); + if (!getObserver()) return copy[key]; + cache[key] = signal = createSignal(copy[key], { pureWrite: true }); delete copy[key]; } return signal[0](); @@ -78,9 +77,7 @@ export function createStaticStore( const entries = untrack( () => Object.entries(accessWith(a, store) as Partial) as [any, any][], ); - batch(() => { - for (const [key, value] of entries) setValue(key, () => value); - }); + for (const [key, value] of entries) setValue(key, () => value); } else setValue(a, b); return store; }, @@ -104,9 +101,9 @@ export function createHydratableStaticStore( ): ReturnType> { if (isServer) return createStaticStore(serverValue); - if (sharedConfig.context) { + if (sharedConfig.hydrating) { const [state, setState] = createStaticStore(serverValue); - onMount(() => setState(update())); + onSettled(() => setState(update())); return [state, setState]; } diff --git a/packages/utils/package.json b/packages/utils/package.json index d01e18508..4aeea1e36 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -61,9 +61,11 @@ "primitives" ], "peerDependencies": { - "solid-js": "^1.6.12" + "@solidjs/web": "^2.0.0-beta.7", + "solid-js": "^2.0.0-beta.7" }, "devDependencies": { - "solid-js": "^1.9.7" + "@solidjs/web": "2.0.0-beta.7", + "solid-js": "2.0.0-beta.7" } } diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 5ba5b7e24..143364460 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -4,16 +4,15 @@ import { createSignal, type Accessor, untrack, - type AccessorArray, type EffectFunction, type NoInfer, type SignalOptions, sharedConfig, - onMount, + onSettled, DEV, - equalFn, + isEqual, } from "solid-js"; -import { isServer } from "solid-js/web"; +import { isServer } from "@solidjs/web"; import type { AnyClass, MaybeAccessor, @@ -39,11 +38,11 @@ export const noop = (() => void 0) as Noop; export const trueFn: () => boolean = () => true; export const falseFn: () => boolean = () => false; -/** @deprecated use {@link equalFn} from "solid-js" */ -export const defaultEquals = equalFn; +/** @deprecated use {@link isEqual} from "solid-js" */ +export const defaultEquals = isEqual; export const EQUALS_FALSE_OPTIONS = { equals: false } as const satisfies SignalOptions; -export const INTERNAL_OPTIONS = { internal: true } as const satisfies SignalOptions; +export const INTERNAL_OPTIONS = { pureWrite: true } as const satisfies SignalOptions; /** * Check if the value is an instance of ___ @@ -153,17 +152,17 @@ export function accessWith( * @param initialValue */ export function defer( - deps: AccessorArray | Accessor, + deps: Accessor[] | Accessor, fn: (input: S, prevInput: S, prev: undefined | NoInfer) => Next, initialValue: Next, ): EffectFunction, NoInfer>; export function defer( - deps: AccessorArray | Accessor, + deps: Accessor[] | Accessor, fn: (input: S, prevInput: S, prev: undefined | NoInfer) => Next, initialValue?: undefined, ): EffectFunction>; export function defer( - deps: AccessorArray | Accessor, + deps: Accessor[] | Accessor, fn: (input: S, prevInput: S, prev: undefined | NoInfer) => Next, initialValue?: Next, ): EffectFunction> { @@ -256,9 +255,9 @@ export function createHydratableSignal( if (isServer) { return createSignal(serverValue, options); } - if (sharedConfig.context) { + if (sharedConfig.hydrating) { const [state, setState] = createSignal(serverValue, options); - onMount(() => setState(() => update())); + onSettled(() => setState(() => update())); return [state, setState]; } return createSignal(update(), options); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ecadfdb95..ea563d3a1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -298,9 +298,12 @@ importers: specifier: workspace:^ version: link:../utils devDependencies: + '@solidjs/web': + specifier: 2.0.0-beta.7 + version: 2.0.0-beta.7(@solidjs/signals@2.0.0-beta.7)(solid-js@2.0.0-beta.7) solid-js: - specifier: ^1.9.7 - version: 1.9.7 + specifier: 2.0.0-beta.7 + version: 2.0.0-beta.7 packages/event-props: devDependencies: @@ -571,9 +574,12 @@ importers: specifier: workspace:^ version: link:../utils devDependencies: + '@solidjs/web': + specifier: 2.0.0-beta.7 + version: 2.0.0-beta.7(@solidjs/signals@2.0.0-beta.7)(solid-js@2.0.0-beta.7) solid-js: - specifier: ^1.9.7 - version: 1.9.7 + specifier: 2.0.0-beta.7 + version: 2.0.0-beta.7 packages/memo: dependencies: @@ -786,9 +792,12 @@ importers: specifier: workspace:^ version: link:../utils devDependencies: + '@solidjs/web': + specifier: 2.0.0-beta.7 + version: 2.0.0-beta.7(@solidjs/signals@2.0.0-beta.7)(solid-js@2.0.0-beta.7) solid-js: - specifier: ^1.9.7 - version: 1.9.7 + specifier: 2.0.0-beta.7 + version: 2.0.0-beta.7 packages/scheduled: devDependencies: @@ -884,9 +893,12 @@ importers: specifier: workspace:^ version: link:../utils devDependencies: + '@solidjs/web': + specifier: 2.0.0-beta.7 + version: 2.0.0-beta.7(@solidjs/signals@2.0.0-beta.7)(solid-js@2.0.0-beta.7) solid-js: - specifier: ^1.9.7 - version: 1.9.7 + specifier: 2.0.0-beta.7 + version: 2.0.0-beta.7 packages/storage: dependencies: @@ -970,9 +982,12 @@ importers: packages/utils: devDependencies: + '@solidjs/web': + specifier: 2.0.0-beta.7 + version: 2.0.0-beta.7(@solidjs/signals@2.0.0-beta.7)(solid-js@2.0.0-beta.7) solid-js: - specifier: ^1.9.7 - version: 1.9.7 + specifier: 2.0.0-beta.7 + version: 2.0.0-beta.7 packages/virtual: dependencies: @@ -1048,10 +1063,10 @@ importers: version: link:../packages/utils '@solidjs/meta': specifier: ^0.29.3 - version: 0.29.4(solid-js@1.9.7) + version: 0.29.4(solid-js@2.0.0-beta.7) '@solidjs/router': specifier: ^0.13.1 - version: 0.13.6(solid-js@1.9.7) + version: 0.13.6(solid-js@2.0.0-beta.7) clsx: specifier: ^2.0.0 version: 2.1.1 @@ -1078,13 +1093,13 @@ importers: version: 1.77.8 solid-dismiss: specifier: ^1.7.121 - version: 1.8.2(solid-js@1.9.7) + version: 1.8.2(solid-js@2.0.0-beta.7) solid-icons: specifier: ^1.1.0 - version: 1.1.0(solid-js@1.9.7) + version: 1.1.0(solid-js@2.0.0-beta.7) solid-tippy: specifier: ^0.2.1 - version: 0.2.1(solid-js@1.9.7)(tippy.js@6.3.7) + version: 0.2.1(solid-js@2.0.0-beta.7)(tippy.js@6.3.7) tippy.js: specifier: ^6.3.7 version: 6.3.7 @@ -2042,6 +2057,7 @@ packages: '@graphql-tools/prisma-loader@8.0.4': resolution: {integrity: sha512-hqKPlw8bOu/GRqtYr0+dINAI13HinTVYBDqhwGAPIFmLr5s+qKskzgCiwbsckdrb5LWVFmVZc+UXn80OGiyBzg==} engines: {node: '>=16.0.0'} + deprecated: 'This package was intended to be used with an older versions of Prisma.\nThe newer versions of Prisma has a different approach to GraphQL integration.\nTherefore, this package is no longer needed and has been deprecated and removed.\nLearn more: https://www.prisma.io/graphql' peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 @@ -2587,11 +2603,20 @@ packages: peerDependencies: solid-js: ^1.5.3 + '@solidjs/signals@2.0.0-beta.7': + resolution: {integrity: sha512-SgK6oQlQZofz82LiEJ2RzT3sbs1lWTqFEtLoWjLsUo/dk1v9EoIFpJJlmvgkXvNugASWG+l1yOHa1a8lPamxug==} + '@solidjs/start@1.1.4': resolution: {integrity: sha512-ma1TBYqoTju87tkqrHExMReM5Z/+DTXSmi30CCTavtwuR73Bsn4rVGqm528p4sL2koRMfAuBMkrhuttjzhL68g==} peerDependencies: vinxi: ^0.5.3 + '@solidjs/web@2.0.0-beta.7': + resolution: {integrity: sha512-m5VjmDBufrOX0ZKGbhvwkT0CPK0TbMxDbxVPDB1PH2evGbWXQZcUlrpFM1N8RBO5md3aR/T1PgMfnOjleJbrRg==} + peerDependencies: + '@solidjs/signals': ^2.0.0-beta.7 + solid-js: ^2.0.0-beta.7 + '@speed-highlight/core@1.2.7': resolution: {integrity: sha512-0dxmVj4gxg3Jg879kvFS/msl4s9F3T9UXC1InxgOf7t5NvcPD97u/WTA5vL/IxWHMn7qSxBozqrnnE2wvl1m8g==} @@ -3513,6 +3538,7 @@ packages: dax-sh@0.43.2: resolution: {integrity: sha512-uULa1sSIHgXKGCqJ/pA0zsnzbHlVnuq7g8O2fkHokWFNwEGIhh5lAJlxZa1POG5En5ba7AU4KcBAvGQWMMf8rg==} + deprecated: This package has moved to simply be 'dax' instead of 'dax-sh' db0@0.3.2: resolution: {integrity: sha512-xzWNQ6jk/+NtdfLyXEipbX55dmDSeteLFt/ayF+wZUU5bzKgmrDOxmInUTbyVRp46YwnJdkDA1KhB7WIXFofJw==} @@ -4164,11 +4190,12 @@ packages: glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} @@ -5892,10 +5919,20 @@ packages: peerDependencies: seroval: ^1.0 + seroval-plugins@1.5.2: + resolution: {integrity: sha512-qpY0Cl+fKYFn4GOf3cMiq6l72CpuVaawb6ILjubOQ+diJ54LfOWaSSPsaswN8DRPIPW4Yq+tE1k5aKd7ILyaFg==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + seroval@1.3.2: resolution: {integrity: sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==} engines: {node: '>=10'} + seroval@1.5.2: + resolution: {integrity: sha512-xcRN39BdsnO9Tf+VzsE7b3JyTJASItIV1FVFewJKCFcW4s4haIKS3e6vj8PGB9qBwC7tnuOywQMdv5N4qkzi7Q==} + engines: {node: '>=10'} + serve-placeholder@2.0.2: resolution: {integrity: sha512-/TMG8SboeiQbZJWRlfTCqMs2DD3SZgWp0kDQePz9yUuCnDfDh/92gf7/PxGhzXTKBIPASIHxFcZndoNbp6QOLQ==} @@ -6001,6 +6038,9 @@ packages: solid-js@1.9.7: resolution: {integrity: sha512-/saTKi8iWEM233n5OSi1YHCCuh66ZIQ7aK2hsToPe4tqGm7qAejU1SwNuTPivbWAYq7SjuHVVYxxuZQNRbICiw==} + solid-js@2.0.0-beta.7: + resolution: {integrity: sha512-7JHs+BhLeZXoU+u9dG+eKnyxxfZyGpOuJEBbN/1XbHKO/WhxecdplOAurlg/YDllNWPhsbXqmLR1H2paqSu62g==} + solid-refresh@0.6.3: resolution: {integrity: sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==} peerDependencies: @@ -6195,6 +6235,7 @@ packages: tar@7.4.3: resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} engines: {node: '>=18'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} @@ -6710,6 +6751,7 @@ packages: whatwg-encoding@3.1.1: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation whatwg-mimetype@4.0.0: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} @@ -8576,18 +8618,20 @@ snapshots: dependencies: solid-js: 1.9.7 - '@solidjs/meta@0.29.4(solid-js@1.9.7)': + '@solidjs/meta@0.29.4(solid-js@2.0.0-beta.7)': dependencies: - solid-js: 1.9.7 + solid-js: 2.0.0-beta.7 - '@solidjs/router@0.13.6(solid-js@1.9.7)': + '@solidjs/router@0.13.6(solid-js@2.0.0-beta.7)': dependencies: - solid-js: 1.9.7 + solid-js: 2.0.0-beta.7 '@solidjs/router@0.8.4(solid-js@1.9.7)': dependencies: solid-js: 1.9.7 + '@solidjs/signals@2.0.0-beta.7': {} + '@solidjs/start@1.1.4(solid-js@1.9.7)(vinxi@0.5.7(@types/node@22.15.31)(db0@0.3.2)(ioredis@5.6.1)(jiti@2.4.2)(sass@1.77.8)(terser@5.42.0)(tsx@4.20.2)(yaml@2.5.0))(vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(sass@1.77.8)(terser@5.42.0)(tsx@4.20.2)(yaml@2.5.0))': dependencies: '@tanstack/server-functions-plugin': 1.121.0(vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(sass@1.77.8)(terser@5.42.0)(tsx@4.20.2)(yaml@2.5.0)) @@ -8611,6 +8655,13 @@ snapshots: - supports-color - vite + '@solidjs/web@2.0.0-beta.7(@solidjs/signals@2.0.0-beta.7)(solid-js@2.0.0-beta.7)': + dependencies: + '@solidjs/signals': 2.0.0-beta.7 + seroval: 1.5.2 + seroval-plugins: 1.5.2(seroval@1.5.2) + solid-js: 2.0.0-beta.7 + '@speed-highlight/core@1.2.7': {} '@supabase/auth-js@2.67.3': @@ -12441,8 +12492,14 @@ snapshots: dependencies: seroval: 1.3.2 + seroval-plugins@1.5.2(seroval@1.5.2): + dependencies: + seroval: 1.5.2 + seroval@1.3.2: {} + seroval@1.5.2: {} + serve-placeholder@2.0.2: dependencies: defu: 6.1.4 @@ -12557,13 +12614,13 @@ snapshots: dot-case: 3.0.4 tslib: 2.8.1 - solid-dismiss@1.8.2(solid-js@1.9.7): + solid-dismiss@1.8.2(solid-js@2.0.0-beta.7): dependencies: - solid-js: 1.9.7 + solid-js: 2.0.0-beta.7 - solid-icons@1.1.0(solid-js@1.9.7): + solid-icons@1.1.0(solid-js@2.0.0-beta.7): dependencies: - solid-js: 1.9.7 + solid-js: 2.0.0-beta.7 solid-js@1.9.7: dependencies: @@ -12571,6 +12628,13 @@ snapshots: seroval: 1.3.2 seroval-plugins: 1.3.2(seroval@1.3.2) + solid-js@2.0.0-beta.7: + dependencies: + '@solidjs/signals': 2.0.0-beta.7 + csstype: 3.1.3 + seroval: 1.5.2 + seroval-plugins: 1.5.2(seroval@1.5.2) + solid-refresh@0.6.3(solid-js@1.9.7): dependencies: '@babel/generator': 7.27.5 @@ -12580,9 +12644,9 @@ snapshots: transitivePeerDependencies: - supports-color - solid-tippy@0.2.1(solid-js@1.9.7)(tippy.js@6.3.7): + solid-tippy@0.2.1(solid-js@2.0.0-beta.7)(tippy.js@6.3.7): dependencies: - solid-js: 1.9.7 + solid-js: 2.0.0-beta.7 tippy.js: 6.3.7 solid-transition-group@0.2.3(solid-js@1.9.7): From 4a4d61653fb2428a55aa9587a8a2298a399c21f6 Mon Sep 17 00:00:00 2001 From: David Di Biase <1168397+davedbase@users.noreply.github.com> Date: Tue, 21 Apr 2026 10:40:55 -0400 Subject: [PATCH 2/3] Added additional adjustments and migrations for rootless, static-store and event-listener --- .../test/event-listener-map.test.ts | 8 +- .../test/event-listener.test.ts | 25 +- packages/rootless/src/index.ts | 17 +- packages/rootless/test/index.test.ts | 253 ++++++++++-------- packages/rootless/test/root-pool.test.ts | 29 +- packages/static-store/src/index.ts | 2 +- packages/static-store/test/index.test.ts | 59 ++-- packages/utils/src/index.ts | 2 +- 8 files changed, 229 insertions(+), 166 deletions(-) diff --git a/packages/event-listener/test/event-listener-map.test.ts b/packages/event-listener/test/event-listener-map.test.ts index e5d5ba7f6..bec42c979 100644 --- a/packages/event-listener/test/event-listener-map.test.ts +++ b/packages/event-listener/test/event-listener-map.test.ts @@ -1,6 +1,6 @@ import { dispatchFakeEvent, event_target } from "./setup.js"; import { describe, test, expect } from "vitest"; -import { createRoot, onMount } from "solid-js"; +import { createRoot, onSettled } from "solid-js"; import { createEventListenerMap } from "../src/index.js"; describe("createEventListenerMap", () => { @@ -34,7 +34,7 @@ describe("createEventListenerMap", () => { map_1: e => (captured1 = e), }); - onMount(() => { + onSettled(() => { dispatchFakeEvent("map_0", testEvent); dispatchFakeEvent("map_1", testEvent1); @@ -55,7 +55,7 @@ describe("createEventListenerMap", () => { map_1: e => (captured1 = e), }); - onMount(() => { + onSettled(() => { dispose(); dispatchFakeEvent("map_0", testEvent); @@ -74,7 +74,7 @@ describe("createEventListenerMap", () => { map_0: e => (captured = e), }); - onMount(() => { + onSettled(() => { dispatchFakeEvent("map_0", testEvent); expect(captured).toBe(testEvent); dispose(); diff --git a/packages/event-listener/test/event-listener.test.ts b/packages/event-listener/test/event-listener.test.ts index 53c9826ca..6e8fefe44 100644 --- a/packages/event-listener/test/event-listener.test.ts +++ b/packages/event-listener/test/event-listener.test.ts @@ -1,6 +1,6 @@ import { dispatchFakeEvent, event_target } from "./setup.js"; import { describe, test, expect } from "vitest"; -import { createRoot, createSignal, onMount } from "solid-js"; +import { createRoot, createSignal, flush, onSettled } from "solid-js"; import { createEventListener, createEventSignal, @@ -124,14 +124,17 @@ describe("createEventListener", () => { return dispose; }); + flush(); dispatchFakeEvent("test", testEvent); expect(captured_times).toBe(1); - setTarget([]); + setTarget([]); + flush(); dispatchFakeEvent("test", testEvent); expect(captured_times).toBe(1); - setTarget(event_target); + setTarget(event_target); + flush(); dispatchFakeEvent("test", testEvent); expect(captured_times).toBe(2); dispose(); @@ -177,7 +180,7 @@ describe("createEventListener", () => { count++; }); - onMount(() => { + onSettled(() => { dispatchFakeEvent("test3", testEvent); expect(count, "captured count on mount should be 1").toBe(1); @@ -197,7 +200,7 @@ describe("createEventSignal", () => { expect(lastEvent, "returned value is an accessor").toBeTypeOf("function"); expect(lastEvent(), "returned value is undefined").toBeTypeOf("undefined"); - onMount(() => { + onSettled(() => { dispatchFakeEvent("sig_test", testEvent); expect(lastEvent()).toBe(testEvent); dispose(); @@ -221,16 +224,16 @@ describe("eventListener directive", () => { dispatchFakeEvent("load", testEvent); expect(captured.length, "event are not listened before the first effect").toBe(0); - onMount(() => { - dispatchFakeEvent("load", testEvent); - expect(captured.length, "one event after mounted should be captured").toBe(1); - expect(captured[0], "event after mounted should be captured").toBe(testEvent); - }); - return dispose; }); + flush(); // run effect phase → adds event listener + dispatchFakeEvent("load", testEvent); + expect(captured.length, "one event after mounted should be captured").toBe(1); + expect(captured[0], "event after mounted should be captured").toBe(testEvent); + setProps(["load", e => captured2.push(e)]); + flush(); // re-run effect → removes old listener, adds new one dispatchFakeEvent("load", testEvent); expect(captured.length, "events should no longer be captured by the previous handler").toBe(1); diff --git a/packages/rootless/src/index.ts b/packages/rootless/src/index.ts index bb95bfcfe..af83bfc82 100644 --- a/packages/rootless/src/index.ts +++ b/packages/rootless/src/index.ts @@ -18,6 +18,7 @@ import { noop, createMicrotask, trueFn, + INTERNAL_OPTIONS, } from "@solid-primitives/utils"; /** @@ -134,7 +135,9 @@ export function createSingletonRoot( }); if (!disposeRoot) { - createRoot(dispose => (value = factory((disposeRoot = dispose))), detachedOwner); + runWithOwner(detachedOwner, () => + createRoot(dispose => (value = factory((disposeRoot = dispose)))), + ); } return value!; @@ -255,7 +258,7 @@ export function createRootPool( mapRoot: (dispose: VoidFunction, signal: Signal) => Root = factory.length > 1 ? (dispose, [args, set]) => { - const [active, setA] = createSignal(true); + const [active, setA] = createSignal(true, INTERNAL_OPTIONS); const root: Root = { dispose, set, @@ -291,9 +294,10 @@ export function createRootPool( disposeRoot = (root: Root) => { root.dispose(); root.dispose = noop; - if (root.active()) root.setA(false); + const idx = pool.indexOf(root); + if (idx === -1) root.setA(false); else { - pool[pool.indexOf(root)] = pool[--length]!; + pool[idx] = pool[--length]!; pool[length] = undefined!; } }; @@ -311,7 +315,10 @@ export function createRootPool( pool[length] = undefined!; root.set(() => arg); root.setA(true); - } else root = createRoot(dispose => mapRoot(dispose, createSignal(arg)), owner); + } else + root = runWithOwner(owner, () => + createRoot(dispose => mapRoot(dispose, createSignal(arg, INTERNAL_OPTIONS))), + ); onCleanup(() => cleanupRoot(root)); diff --git a/packages/rootless/test/index.test.ts b/packages/rootless/test/index.test.ts index 96c4d7680..602d103a2 100644 --- a/packages/rootless/test/index.test.ts +++ b/packages/rootless/test/index.test.ts @@ -1,10 +1,10 @@ import { describe, test, expect } from "vitest"; import { - createComputed, - createEffect, createMemo, createRoot, createSignal, + createTrackedEffect, + flush, getOwner, onCleanup, } from "solid-js"; @@ -16,33 +16,48 @@ import { } from "../src/index.js"; describe("createSubRoot", () => { - test("behaves like a root", () => - createSubRoot(dispose => { - const captured: any[] = []; - const [count, setCount] = createSignal(0); - createComputed(() => captured.push(count())); - setCount(1); - expect(captured, "before dispose()").toEqual([0, 1]); - dispose(); - setCount(2); - expect(captured, "after dispose()").toEqual([0, 1]); - })); + test("behaves like a root", () => { + const captured: any[] = []; + const [count, setCount] = createSignal(0); + const dispose = createSubRoot(dispose => { + createTrackedEffect(() => { captured.push(count()); }); + return dispose; + }); + flush(); + expect(captured).toEqual([0]); + setCount(1); + flush(); + expect(captured, "before dispose()").toEqual([0, 1]); + dispose(); + setCount(2); + flush(); + expect(captured, "after dispose()").toEqual([0, 1]); + }); - test("disposes with owner", () => - createRoot(dispose => { + test("disposes with owner", () => { + const captured: any[] = []; + const [count, setCount] = createSignal(0); + const dispose = createRoot(dispose => { createSubRoot(() => { - const captured: any[] = []; - const [count, setCount] = createSignal(0); - createComputed(() => captured.push(count())); - setCount(1); - expect(captured, "before dispose()").toEqual([0, 1]); - dispose(); - setCount(2); - expect(captured, "after dispose()").toEqual([0, 1]); + createTrackedEffect(() => { captured.push(count()); }); }); - })); + return dispose; + }); + flush(); + expect(captured).toEqual([0]); + setCount(1); + flush(); + expect(captured, "before dispose()").toEqual([0, 1]); + dispose(); + setCount(2); + flush(); + expect(captured, "after dispose()").toEqual([0, 1]); + }); test("many parent owners", () => { + const captured: any[] = []; + const [count, setCount] = createSignal(0); + const [o1, o2, dispose1, dispose2] = createRoot(dispose1 => { const o1 = getOwner(); const [o2, dispose2] = createRoot(dispose2 => { @@ -53,18 +68,20 @@ describe("createSubRoot", () => { createSubRoot( () => { - const captured: any[] = []; - const [count, setCount] = createSignal(0); - createComputed(() => captured.push(count())); - setCount(1); - expect(captured, "before dispose()").toEqual([0, 1]); - dispose1(); - setCount(2); - expect(captured, "after dispose()").toEqual([0, 1]); + createTrackedEffect(() => { captured.push(count()); }); }, o1, o2, ); + flush(); + expect(captured).toEqual([0]); + setCount(1); + flush(); + expect(captured, "before dispose()").toEqual([0, 1]); + dispose1(); + setCount(2); + flush(); + expect(captured, "after dispose()").toEqual([0, 1]); dispose2(); }); }); @@ -89,32 +106,44 @@ describe("createCallback", () => { }); describe("createDisposable", () => { - test("working with createComputed", () => { + test("working with createTrackedEffect", () => { const [count, setCount] = createSignal(0); const captured: any[] = []; - const dispose = createDisposable(() => createComputed(() => captured.push(count()))); + const dispose = createDisposable(() => + createTrackedEffect(() => { captured.push(count()); }), + ); + flush(); expect(captured).toEqual([0]); setCount(1); + flush(); expect(captured, "before dispose()").toEqual([0, 1]); dispose(); + setCount(2); + flush(); expect(captured, "after disposing").toEqual([0, 1]); }); - test("disposes together with owner", () => - createRoot(dispose => { - const [count, setCount] = createSignal(0); - const captured: any[] = []; - createDisposable(() => createComputed(() => captured.push(count()))); - expect(captured).toEqual([0]); - setCount(1); - expect(captured, "before dispose()").toEqual([0, 1]); - dispose(); - expect(captured, "after disposing").toEqual([0, 1]); - })); + test("disposes together with owner", () => { + const [count, setCount] = createSignal(0); + const captured: any[] = []; + const dispose = createRoot(dispose => { + createDisposable(() => createTrackedEffect(() => { captured.push(count()); })); + return dispose; + }); + flush(); + expect(captured).toEqual([0]); + setCount(1); + flush(); + expect(captured, "before dispose()").toEqual([0, 1]); + dispose(); + setCount(2); + flush(); + expect(captured, "after disposing").toEqual([0, 1]); + }); }); describe("createSharedRoot", () => { - test("single root", () => { + test("single root", async () => { const [count, setCount] = createSignal(0); let runs = 0; @@ -127,23 +156,33 @@ describe("createSharedRoot", () => { }); }); - createRoot(dispose => { - expect(useMemo()()).toBe(0); - expect(disposes).toBe(0); - expect(runs).toBe(1); - setCount(1); - expect(runs).toBe(2); - dispose(); - queueMicrotask(() => { - expect(disposes).toBe(1); - expect(runs).toBe(2); - setCount(2); - expect(runs).toBe(2); - }); + let dispose!: VoidFunction; + createRoot(d => { + const memo = useMemo(); + createTrackedEffect(() => void memo()); + dispose = d; }); + + flush(); + expect(disposes).toBe(0); + expect(runs).toBe(1); + + setCount(1); + flush(); + expect(runs).toBe(2); + + dispose(); + + await Promise.resolve(); + expect(disposes).toBe(1); + expect(runs).toBe(2); + + setCount(2); + flush(); + expect(runs).toBe(2); }); - test("multiple roots", () => { + test("multiple roots", async () => { const [count, setCount] = createSignal(0); let runs = 0; @@ -156,66 +195,70 @@ describe("createSharedRoot", () => { }); }); - const d1 = createRoot(dispose => { - expect(useMemo()()).toBe(0); - return dispose; + let d1!: VoidFunction; + createRoot(d => { + const memo = useMemo(); + createTrackedEffect(() => void memo()); + d1 = d; }); - const d2 = createRoot(dispose => { - createEffect(() => useMemo()()); - return dispose; + let d2!: VoidFunction; + createRoot(d => { + const memo = useMemo(); + createTrackedEffect(() => void memo()); + d2 = d; }); + flush(); expect(runs).toBe(1); + setCount(1); + flush(); expect(runs).toBe(2); d1(); - queueMicrotask(() => { - expect(runs).toBe(2); - expect(disposes).toBe(0); - setCount(2); - expect(runs).toBe(3); + await Promise.resolve(); + expect(runs).toBe(2); + expect(disposes).toBe(0); - setTimeout(() => { - d2(); - - setTimeout(() => { - expect(runs).toBe(3); - expect(disposes).toBe(1); - setCount(3); - expect(runs).toBe(3); - }); - }); - }); + setCount(2); + flush(); + expect(runs).toBe(3); + + d2(); + + await Promise.resolve(); + expect(runs).toBe(3); + expect(disposes).toBe(1); + + setCount(3); + flush(); + expect(runs).toBe(3); }); - test("multiple dependents disposing in one tick", () => - createRoot(dispose => { - let alive = false; - const track = createSingletonRoot(() => { - alive = true; - onCleanup(() => (alive = false)); - }); + test("multiple dependents disposing in one tick", async () => { + let alive = false; + const track = createSingletonRoot(() => { + alive = true; + onCleanup(() => (alive = false)); + }); - const d1 = createRoot(d1 => { - track(); - return d1; - }); + const d1 = createRoot(d1 => { + track(); + return d1; + }); - const d2 = createRoot(d2 => { - track(); - return d2; - }); + const d2 = createRoot(d2 => { + track(); + return d2; + }); - expect(alive).toBe(true); - d1(); - d2(); + expect(alive).toBe(true); + d1(); + d2(); - queueMicrotask(() => { - expect(alive).toBe(false); - dispose(); - }); - })); + await Promise.resolve(); + expect(alive).toBe(false); + }); }); diff --git a/packages/rootless/test/root-pool.test.ts b/packages/rootless/test/root-pool.test.ts index 4a8bba9f0..078822c7c 100644 --- a/packages/rootless/test/root-pool.test.ts +++ b/packages/rootless/test/root-pool.test.ts @@ -7,9 +7,9 @@ import { useContext, onCleanup, Accessor, - createEffect, - createComputed, + createTrackedEffect, createSignal, + flush, runWithOwner, } from "solid-js"; @@ -40,7 +40,7 @@ describe("createRootPool", () => { const root = createRoot(dispose => { let pool!: ReturnType; - Ctx.Provider({ + const ctxChildren = Ctx({ value: "root", get children() { pool = createRootPool(() => { @@ -49,7 +49,8 @@ describe("createRootPool", () => { }); return ""; }, - }); + }) as unknown as () => unknown; + ctxChildren(); // force lazy children evaluation to initialize pool inside context scope return { dispose, pool }; }); @@ -127,7 +128,7 @@ describe("createRootPool", () => { const pool = createRootPool((n: Accessor) => { roots++; - createComputed(() => { + createTrackedEffect(() => { capturedArgs.push(n()); }); onCleanup(() => { @@ -142,6 +143,7 @@ describe("createRootPool", () => { d(); }); + flush(); expect(capturedArgs).toEqual([0, 1, 2]); expect(roots).toBe(3); expect(cleanups).toEqual([]); @@ -150,6 +152,7 @@ describe("createRootPool", () => { pool(4); pool(5); + flush(); expect(capturedArgs).toEqual([0, 1, 2, 3, 4, 5]); expect(roots).toBe(3); expect(cleanups).toEqual([]); @@ -158,7 +161,7 @@ describe("createRootPool", () => { expect(capturedArgs).toEqual([0, 1, 2, 3, 4, 5]); expect(roots).toBe(3); - expect(cleanups).toEqual([5, 4, 3]); + expect(cleanups).toEqual([3, 4, 5]); }); }); @@ -175,9 +178,9 @@ describe("createRootPool", () => { d(); }); - expect(pool()).toBe(0); - expect(pool()).toBe(1); expect(pool()).toBe(2); + expect(pool()).toBe(1); + expect(pool()).toBe(0); dispose(); }); @@ -192,7 +195,7 @@ describe("createRootPool", () => { const pool = createRootPool((arg, active) => { const index = i++; - createEffect(() => { + createTrackedEffect(() => { if (active()) captured[index] = count(); }); }); @@ -210,36 +213,44 @@ describe("createRootPool", () => { }); }); + flush(); await Promise.resolve(); expect(captured).toEqual([1, 1]); setCount(2); + flush(); await Promise.resolve(); expect(captured).toEqual([2, 2]); disposeRoot(); setCount(3); + flush(); await Promise.resolve(); expect(captured).toEqual([3, 2]); setCount(4); + flush(); await Promise.resolve(); expect(captured).toEqual([4, 2]); runWithOwner(owner, pool); + flush(); await Promise.resolve(); expect(captured).toEqual([4, 4]); setCount(5); + flush(); await Promise.resolve(); expect(captured).toEqual([5, 5]); dispose(); + flush(); await Promise.resolve(); expect(captured).toEqual([5, 5]); setCount(6); + flush(); await Promise.resolve(); expect(captured).toEqual([5, 5]); }); diff --git a/packages/static-store/src/index.ts b/packages/static-store/src/index.ts index 28cd6b67b..4a1f2b651 100644 --- a/packages/static-store/src/index.ts +++ b/packages/static-store/src/index.ts @@ -153,7 +153,7 @@ export function createDerivedStaticStore( get() { let keyMemo = cache[key]; if (!keyMemo) { - if (!getListener()) return fnMemo()[key]; + if (!getObserver()) return fnMemo()[key]; runWithOwner(o, () => (cache[key] = keyMemo = createMemo(() => fnMemo()[key]))); } return keyMemo!(); diff --git a/packages/static-store/test/index.test.ts b/packages/static-store/test/index.test.ts index 886dcc856..f725da71b 100644 --- a/packages/static-store/test/index.test.ts +++ b/packages/static-store/test/index.test.ts @@ -1,4 +1,4 @@ -import { createEffect, createRoot, createSignal } from "solid-js"; +import { createRoot, createSignal, createTrackedEffect, flush } from "solid-js"; import { describe, expect, test } from "vitest"; import { createDerivedStaticStore, @@ -35,7 +35,7 @@ describe("createStaticStore", () => { setState("a", prev => prev + 1); expect(state.a).toBe(10); - createEffect(() => { + createTrackedEffect(() => { state.a; aUpdates++; }); @@ -43,13 +43,14 @@ describe("createStaticStore", () => { return { dispose, setState }; }); + flush(); expect(aUpdates).toBe(0); - setState({ - b: 3, - }); + setState({ b: 3 }); + flush(); expect(aUpdates).toBe(0); setState("a", 4); + flush(); expect(aUpdates).toBe(1); dispose(); @@ -69,45 +70,43 @@ describe("createHydratableStaticStore", () => { describe("createDerivedStaticStore", () => { test("individual keys only update when changed", () => { let aUpdates = -1; + const _shape = { a: 1, b: 2, c: 3, d: [0, 1, 2] }; + const [s, set] = createSignal(_shape); - const { dispose, set } = createRoot(dispose => { - const _shape = { a: 1, b: 2, c: 3, d: [0, 1, 2] }; - const [s, set] = createSignal(_shape); + const { dispose, state } = createRoot(dispose => { const state = createDerivedStaticStore(s); - expect(state).toEqual(_shape); - expect(_shape, "original input shouldn't be mutated").toEqual({ - a: 1, - b: 2, - c: 3, - d: [0, 1, 2], - }); - - set(p => ({ ...p, a: 9, d: [3, 2, 1] })); - - expect(state).toEqual({ a: 9, b: 2, c: 3, d: [3, 2, 1] }); - expect(_shape, "original input shouldn't be mutated").toEqual({ - a: 1, - b: 2, - c: 3, - d: [0, 1, 2], - }); - - createEffect(() => { + createTrackedEffect(() => { state.a; aUpdates++; }); - return { dispose, set }; + return { dispose, state }; }); + flush(); + expect(state).toEqual(_shape); expect(aUpdates).toBe(0); + set(p => ({ ...p, a: 9, d: [3, 2, 1] })); + flush(); + expect(state).toEqual({ a: 9, b: 2, c: 3, d: [3, 2, 1] }); + expect(_shape, "original input shouldn't be mutated").toEqual({ + a: 1, + b: 2, + c: 3, + d: [0, 1, 2], + }); + expect(aUpdates).toBe(1); + set(p => ({ ...p, b: 3 })); - expect(aUpdates).toBe(0); - set(p => ({ ...p, a: 4 })); + flush(); expect(aUpdates).toBe(1); + set(p => ({ ...p, a: 4 })); + flush(); + expect(aUpdates).toBe(2); + dispose(); }); }); diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 143364460..7c5e33cb1 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -42,7 +42,7 @@ export const falseFn: () => boolean = () => false; export const defaultEquals = isEqual; export const EQUALS_FALSE_OPTIONS = { equals: false } as const satisfies SignalOptions; -export const INTERNAL_OPTIONS = { pureWrite: true } as const satisfies SignalOptions; +export const INTERNAL_OPTIONS = { ownedWrite: true } as const satisfies SignalOptions; /** * Check if the value is an instance of ___ From b4699b25ed1b3a1075e32044c6aeda377cc8626e Mon Sep 17 00:00:00 2001 From: David Di Biase <1168397+davedbase@users.noreply.github.com> Date: Tue, 21 Apr 2026 10:42:51 -0400 Subject: [PATCH 3/3] Remove deprecated HTMLFrameSetElement type --- packages/event-listener/src/types.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/event-listener/src/types.ts b/packages/event-listener/src/types.ts index 6f1aa5082..38b4feaf5 100644 --- a/packages/event-listener/src/types.ts +++ b/packages/event-listener/src/types.ts @@ -7,7 +7,6 @@ export type TargetWithEventMap = | Document | XMLDocument | HTMLBodyElement - | HTMLFrameSetElement | HTMLMediaElement | HTMLVideoElement | HTMLElement @@ -64,9 +63,7 @@ export type EventMapOf = Target extends Window ? DocumentEventMap : Target extends HTMLBodyElement ? HTMLBodyElementEventMap - : Target extends HTMLFrameSetElement - ? HTMLFrameSetElementEventMap - : Target extends HTMLMediaElement + : Target extends HTMLMediaElement ? HTMLMediaElementEventMap : Target extends HTMLVideoElement ? HTMLVideoElementEventMap