Skip to content

Commit 7fe902f

Browse files
authored
Merge branch 'main' into watched
2 parents ee34c3c + ad95a9b commit 7fe902f

26 files changed

+5580
-4922
lines changed

.github/workflows/compressed-size.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
- name: Install pnpm
1515
uses: pnpm/action-setup@v3
1616
with:
17-
version: 8
17+
version: 9.12
1818

1919
- name: compressed-size-action
2020
uses: preactjs/compressed-size-action@v2

.github/workflows/main.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
- name: Install pnpm
1919
uses: pnpm/action-setup@v3
2020
with:
21-
version: 8
21+
version: 9.12
2222

2323
- name: Install Node.js
2424
uses: actions/setup-node@v4

.github/workflows/release.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
- name: Install pnpm
2323
uses: pnpm/action-setup@v3
2424
with:
25-
version: 8
25+
version: 9.12
2626

2727
- name: Install Node.js
2828
uses: actions/setup-node@v4

karma.conf.js

+15-14
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,6 @@ const pkgList = {
224224
core: "@preact/signals-core",
225225
preact: "@preact/signals",
226226
react: "@preact/signals-react",
227-
"react/auto": "@preact/signals-react/auto",
228227
"react/runtime": "@preact/signals-react/runtime",
229228
"react-transform": "@preact/signals-react-transform",
230229
};
@@ -304,19 +303,21 @@ module.exports = function (config) {
304303
customLaunchers: localLaunchers,
305304

306305
files: [
307-
...filteredPkgList.some(i => /^react/.test(i)) ? [
308-
{
309-
// Provide some NodeJS globals to run babel in a browser environment
310-
pattern: "test/browser/nodeGlobals.js",
311-
watched: false,
312-
type: "js",
313-
},
314-
{
315-
pattern: "test/browser/babel.js",
316-
watched: false,
317-
type: "js",
318-
},
319-
] : [],
306+
...(filteredPkgList.some(i => /^react/.test(i))
307+
? [
308+
{
309+
// Provide some NodeJS globals to run babel in a browser environment
310+
pattern: "test/browser/nodeGlobals.js",
311+
watched: false,
312+
type: "js",
313+
},
314+
{
315+
pattern: "test/browser/babel.js",
316+
watched: false,
317+
type: "js",
318+
},
319+
]
320+
: []),
320321
{
321322
pattern:
322323
process.env.TESTS ||

mangle.json

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
}
2020
},
2121
"compress": {
22+
"pure_getters": false,
2223
"conditionals": false,
2324
"loops": false,
2425
"sequences": false

package.json

+1-3
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,16 @@
33
"private": true,
44
"scripts": {
55
"prebuild": "shx rm -rf packages/*/dist/",
6-
"build": "pnpm build:core && pnpm build:preact && pnpm build:react-runtime && pnpm build:react-auto && pnpm build:react && pnpm build:react-transform",
6+
"build": "pnpm build:core && pnpm build:preact && pnpm build:react-runtime && pnpm build:react && pnpm build:react-transform",
77
"_build": "microbundle --raw --globals @preact/signals-core=preactSignalsCore,preact/hooks=preactHooks,@preact/signals-react/runtime=reactSignalsRuntime",
88
"build:core": "pnpm _build --cwd packages/core && pnpm postbuild:core",
99
"build:preact": "pnpm _build --cwd packages/preact && pnpm postbuild:preact",
1010
"build:react": "pnpm _build --cwd packages/react --external \"react,@preact/signals-react/runtime,@preact/signals-core\" && pnpm postbuild:react",
11-
"build:react-auto": "pnpm _build --cwd packages/react/auto && pnpm postbuild:react-auto",
1211
"build:react-runtime": "pnpm _build --cwd packages/react/runtime && pnpm postbuild:react-runtime",
1312
"build:react-transform": "pnpm _build --no-compress --cwd packages/react-transform",
1413
"postbuild:core": "cd packages/core/dist && shx mv -f index.d.ts signals-core.d.ts",
1514
"postbuild:preact": "cd packages/preact/dist && shx mv -f preact/src/index.d.ts signals.d.ts && shx rm -rf preact",
1615
"postbuild:react": "cd packages/react/dist && shx mv -f react/src/index.d.ts signals.d.ts && shx rm -rf react",
17-
"postbuild:react-auto": "cd packages/react/auto/dist && shx mv -f react/auto/src/*.d.ts . && shx rm -rf react",
1816
"postbuild:react-runtime": "cd packages/react/runtime/dist && shx mv -f react/runtime/src/*.d.ts . && shx rm -rf react",
1917
"lint": "pnpm lint:eslint && pnpm lint:tsc",
2018
"lint:eslint": "eslint 'packages/**/*.{ts,tsx,js,jsx}'",

packages/preact/CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# @preact/signals
22

3+
## 2.0.1
4+
5+
### Patch Changes
6+
7+
- [#647](https://github.com/preactjs/signals/pull/647) [`655905b`](https://github.com/preactjs/signals/commit/655905bc6e5ee8ba30d578e2a7bf02a9c83ee38c) Thanks [@jviide](https://github.com/jviide)! - Ensure that text effects get disposed
8+
9+
- [#630](https://github.com/preactjs/signals/pull/630) [`4b9144f`](https://github.com/preactjs/signals/commit/4b9144f7f13815013f78299dd487344d3750fd8f) Thanks [@JoviDeCroock](https://github.com/JoviDeCroock)! - Change the way we deal with state settling hooks, when we know we are dealing with hooks that can settle their A -> B -> A state (and wind up at the same value). We should not verbatim rerender in our custom shouldComponentUpdate. Instead we should trust that hooks have handled their own state settling.
10+
311
## 2.0.0
412

513
### Major Changes

packages/preact/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@preact/signals",
3-
"version": "2.0.0",
3+
"version": "2.0.1",
44
"license": "MIT",
55
"description": "Manage state with style in Preact",
66
"keywords": [],

packages/preact/src/index.ts

+66-58
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@ const HAS_PENDING_UPDATE = 1 << 0;
3434
const HAS_HOOK_STATE = 1 << 1;
3535
const HAS_COMPUTEDS = 1 << 2;
3636

37+
let oldNotify: (this: Effect) => void,
38+
effectsQueue: Array<Effect> = [],
39+
domQueue: Array<Effect> = [];
40+
41+
// Capture the original `Effect.prototype._notify` method so that we can install
42+
// custom `._notify`s for each different use-case but still call the original
43+
// implementation in the end. Dispose the temporary effect immediately afterwards.
44+
effect(function (this: Effect) {
45+
oldNotify = this._notify;
46+
})();
47+
3748
// Install a Preact options hook
3849
function hook<T extends OptionsTypes>(hookName: T, hookFn: HookFn<T>) {
3950
// @ts-ignore-next-line private options hooks usage
@@ -80,7 +91,7 @@ function SignalValue(this: AugmentedComponent, { data }: { data: Signal }) {
8091
const currentSignal = useSignal(data);
8192
currentSignal.value = data;
8293

83-
const s = useMemo(() => {
94+
const [isText, s] = useMemo(() => {
8495
let self = this;
8596
// mark the parent component as having computeds so it gets optimized
8697
let v = this.__v;
@@ -91,38 +102,51 @@ function SignalValue(this: AugmentedComponent, { data }: { data: Signal }) {
91102
}
92103
}
93104

94-
const wrappedSignal = computed(function (this: Effect) {
95-
let data = currentSignal.value;
96-
let s = data.value;
105+
const wrappedSignal = computed(() => {
106+
let s = currentSignal.value.value;
97107
return s === 0 ? 0 : s === true ? "" : s || "";
98108
});
99109

100-
const isText = computed(
101-
() => isValidElement(wrappedSignal.value) || this.base?.nodeType !== 3
102-
);
110+
const isText = computed(() => !isValidElement(wrappedSignal.value));
103111

104-
this._updater!._callback = () => {
105-
if (isValidElement(s.peek()) || this.base?.nodeType !== 3) {
106-
this._updateFlags |= HAS_PENDING_UPDATE;
107-
this.setState({});
108-
return;
109-
}
110-
(this.base as Text).data = s.peek();
111-
};
112-
113-
effect(function (this: Effect) {
114-
if (!oldNotify) oldNotify = this._notify;
112+
// Update text nodes directly without rerendering when the new value
113+
// is also text.
114+
const dispose = effect(function (this: Effect) {
115115
this._notify = notifyDomUpdates;
116-
const val = wrappedSignal.value;
117-
if (isText.value && self.base) {
118-
(self.base as Text).data = val;
116+
117+
// Subscribe to wrappedSignal updates only when its values are text...
118+
if (isText.value) {
119+
// ...but regardless of `self.base`'s current value, as it can be
120+
// undefined before mounting or a non-text node. In both of those cases
121+
// the update gets handled by a full rerender.
122+
const value = wrappedSignal.value;
123+
if (self.base && self.base.nodeType === 3) {
124+
(self.base as Text).data = value;
125+
}
119126
}
120127
});
121128

122-
return wrappedSignal;
129+
// Piggyback this._updater's disposal to ensure that the text updater effect
130+
// above also gets disposed on unmount.
131+
const oldDispose = this._updater!._dispose;
132+
this._updater!._dispose = function () {
133+
dispose();
134+
oldDispose.call(this);
135+
};
136+
137+
return [isText, wrappedSignal];
123138
}, []);
124139

125-
return s.value;
140+
// Rerender the component whenever `data.value` changes from a VNode
141+
// to another VNode, from text to a VNode, or from a VNode to text.
142+
// That is, everything else except text-to-text updates.
143+
//
144+
// This also ensures that the backing DOM node types gets updated to
145+
// text nodes and back when needed.
146+
//
147+
// For text-to-text updates, `.peek()` is used to skip full rerenders,
148+
// leaving them to the optimized path above.
149+
return isText.value ? s.peek() : s.value;
126150
}
127151
SignalValue.displayName = "_st";
128152

@@ -255,7 +279,6 @@ function createPropUpdater(
255279
props = newProps;
256280
},
257281
_dispose: effect(function (this: Effect) {
258-
if (!oldNotify) oldNotify = this._notify;
259282
this._notify = notifyDomUpdates;
260283
const value = changeSignal.value.value;
261284
// If Preact just rendered this value, don't render it again:
@@ -321,38 +344,28 @@ Component.prototype.shouldComponentUpdate = function (
321344
const updater = this._updater;
322345
const hasSignals = updater && updater._sources !== undefined;
323346

324-
// let reason;
325-
// if (!hasSignals && !hasComputeds.has(this)) {
326-
// reason = "no signals or computeds";
327-
// } else if (hasPendingUpdate.has(this)) {
328-
// reason = "has pending update";
329-
// } else if (hasHookState.has(this)) {
330-
// reason = "has hook state";
331-
// }
332-
// if (reason) {
333-
// if (!this) reason += " (`this` bug)";
334-
// console.log("not optimizing", this?.constructor?.name, ": ", reason, {
335-
// details: {
336-
// hasSignals,
337-
// hasComputeds: hasComputeds.has(this),
338-
// hasPendingUpdate: hasPendingUpdate.has(this),
339-
// hasHookState: hasHookState.has(this),
340-
// deps: Array.from(updater._deps),
341-
// updater,
342-
// },
343-
// });
344-
// }
345-
346-
// if this component used no signals or computeds, update:
347-
if (!hasSignals && !(this._updateFlags & HAS_COMPUTEDS)) return true;
348-
349-
// if there is a pending re-render triggered from Signals,
350-
// or if there is hook or class state, update:
351-
if (this._updateFlags & (HAS_PENDING_UPDATE | HAS_HOOK_STATE)) return true;
352-
347+
// If this is a component using state, rerender
353348
// @ts-ignore
354349
for (let i in state) return true;
355350

351+
if (this.__f || (typeof this.u == "boolean" && this.u === true)) {
352+
const hasHooksState = this._updateFlags & HAS_HOOK_STATE;
353+
// if this component used no signals or computeds and no hooks state, update:
354+
if (!hasSignals && !hasHooksState && !(this._updateFlags & HAS_COMPUTEDS))
355+
return true;
356+
357+
// if there is a pending re-render triggered from Signals,
358+
// or if there is hooks state, update:
359+
if (this._updateFlags & HAS_PENDING_UPDATE) return true;
360+
} else {
361+
// if this component used no signals or computeds, update:
362+
if (!hasSignals && !(this._updateFlags & HAS_COMPUTEDS)) return true;
363+
364+
// if there is a pending re-render triggered from Signals,
365+
// or if there is hooks state, update:
366+
if (this._updateFlags & (HAS_PENDING_UPDATE | HAS_HOOK_STATE)) return true;
367+
}
368+
356369
// if any non-Signal props changed, update:
357370
for (let i in props) {
358371
if (i !== "__source" && props[i] !== this.props[i]) return true;
@@ -379,10 +392,6 @@ export function useComputed<T>(compute: () => T, options?: SignalOptions<T>) {
379392
return useMemo(() => computed<T>(() => $compute.current(), options), []);
380393
}
381394

382-
let oldNotify: (this: Effect) => void,
383-
effectsQueue: Array<Effect> = [],
384-
domQueue: Array<Effect> = [];
385-
386395
const deferEffects =
387396
typeof requestAnimationFrame === "undefined"
388397
? setTimeout
@@ -430,7 +439,6 @@ export function useSignalEffect(cb: () => void | (() => void)) {
430439

431440
useEffect(() => {
432441
return effect(function (this: Effect) {
433-
if (!oldNotify) oldNotify = this._notify;
434442
this._notify = notifyEffects;
435443
return callback.current();
436444
});

packages/preact/src/internal.d.ts

+5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ export interface AugmentedElement extends HTMLElement {
1919
}
2020

2121
export interface AugmentedComponent extends Component<any, any> {
22+
// hasScuFromHooks
23+
// Preact 10.12 - Preact 10.25
24+
u?: boolean;
25+
// Preact 10.26 and onwards
26+
__f?: boolean;
2227
__v: VNode;
2328
_updater?: Effect;
2429
_updateFlags: number;

0 commit comments

Comments
 (0)