Skip to content

Commit eb4fa82

Browse files
Merge pull request #706 from preactjs/resource-management
feat: dispose effect() with resource management
2 parents d0eb593 + f91e99b commit eb4fa82

File tree

7 files changed

+1137
-889
lines changed

7 files changed

+1137
-889
lines changed

.changeset/short-meals-fail.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
"@preact/signals-core": minor
3+
---
4+
5+
feat: support disposing `effect()` with resource management
6+
7+
This allows `effect()`'s to be disposed with the new `using` keyword from [the explicit resource management proposal](https://github.com/tc39/proposal-explicit-resource-management).
8+
9+
Whenever an effect goes out of scope the `Symbol.dispose` function is called automatically.
10+
11+
```js
12+
const count = signal(0);
13+
14+
function doSomething() {
15+
// The `using` keyword calls dispose at the end of
16+
// this function scope
17+
using _ = effect(() => {
18+
console.log(count.value);
19+
return () => console.log("disposed");
20+
});
21+
22+
console.log("hey");
23+
}
24+
25+
doSomething();
26+
// Logs:
27+
// 0
28+
// hey
29+
// disposed
30+
```

karma.conf.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,11 +184,16 @@ function createEsbuildPlugin(filteredPkgList) {
184184
},
185185
];
186186

187+
const explicitResourceManagement = [
188+
"@babel/plugin-proposal-explicit-resource-management",
189+
];
190+
187191
const tmp = await babel.transformAsync(result, {
188192
filename: args.path,
189193
sourceMaps: "inline",
190194
presets: downlevel ? [ts, jsx, downlevelPlugin] : [ts, jsx],
191195
plugins: [
196+
explicitResourceManagement,
192197
signalsTransform,
193198
coverage && coveragePlugin,
194199
minify && renamePlugin,

package.json

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,17 @@
4646
],
4747
"license": "MIT",
4848
"devDependencies": {
49-
"@babel/core": "^7.23.3",
50-
"@babel/plugin-proposal-explicit-resource-management": "^7.23.3",
51-
"@babel/plugin-syntax-jsx": "^7.23.3",
52-
"@babel/plugin-transform-modules-commonjs": "^7.23.3",
53-
"@babel/plugin-transform-react-jsx": "^7.23.4",
54-
"@babel/plugin-transform-typescript": "^7.19.1",
55-
"@babel/preset-env": "^7.23.3",
56-
"@babel/preset-react": "^7.23.3",
57-
"@babel/preset-typescript": "^7.23.3",
58-
"@babel/register": "^7.22.15",
59-
"@babel/standalone": "^7.23.4",
49+
"@babel/core": "^7.27.7",
50+
"@babel/plugin-proposal-explicit-resource-management": "^7.27.4",
51+
"@babel/plugin-syntax-jsx": "^7.27.1",
52+
"@babel/plugin-transform-modules-commonjs": "^7.27.1",
53+
"@babel/plugin-transform-react-jsx": "^7.27.1",
54+
"@babel/plugin-transform-typescript": "^7.27.1",
55+
"@babel/preset-env": "^7.27.2",
56+
"@babel/preset-react": "^7.27.1",
57+
"@babel/preset-typescript": "^7.27.1",
58+
"@babel/register": "^7.27.1",
59+
"@babel/standalone": "^7.27.7",
6060
"@changesets/changelog-github": "^0.5.0",
6161
"@changesets/cli": "^2.27.1",
6262
"@types/babel__traverse": "^7.18.5",
@@ -90,7 +90,7 @@
9090
"shx": "^0.3.4",
9191
"sinon": "^14.0.0",
9292
"sinon-chai": "^3.7.0",
93-
"typescript": "~5.1.6"
93+
"typescript": "~5.8.3"
9494
},
9595
"lint-staged": {
9696
"**/*.{js,jsx,ts,tsx,yml,json,md}": [

packages/core/src/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -858,7 +858,7 @@ Effect.prototype.dispose = function () {
858858
* @param fn The effect callback.
859859
* @returns A function for disposing the effect.
860860
*/
861-
function effect(fn: EffectFn): () => void {
861+
function effect(fn: EffectFn): { (): void; [Symbol.dispose](): void } {
862862
const effect = new Effect(fn);
863863
try {
864864
effect._callback();
@@ -868,7 +868,9 @@ function effect(fn: EffectFn): () => void {
868868
}
869869
// Return a bound function instead of a wrapper like `() => effect._dispose()`,
870870
// because bound functions seem to be just as fast and take up a lot less memory.
871-
return effect._dispose.bind(effect);
871+
const dispose = effect._dispose.bind(effect);
872+
(dispose as any)[Symbol.dispose] = dispose;
873+
return dispose as any;
872874
}
873875

874876
export { computed, effect, batch, untracked, Signal, ReadonlySignal };

packages/core/test/signal.test.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,18 @@ describe("effect()", () => {
786786
expect(() => dispose()).not.to.throw();
787787
});
788788

789+
it("should support resource management disposal", () => {
790+
const a = signal(0);
791+
const spy = sinon.spy();
792+
{
793+
using _dispose = effect(() => {
794+
a.value;
795+
return spy;
796+
});
797+
}
798+
expect(spy).to.be.calledOnce;
799+
});
800+
789801
it("should allow disposing a running effect", () => {
790802
const a = signal(0);
791803
const spy = sinon.spy();

0 commit comments

Comments
 (0)