{"id":13657475,"url":"https://github.com/WebReflection/usignal","last_synced_at":"2025-04-24T01:32:17.380Z","repository":{"id":59145145,"uuid":"534546174","full_name":"WebReflection/usignal","owner":"WebReflection","description":"A blend of @preact/signals-core and solid-js basic reactivity API","archived":false,"fork":false,"pushed_at":"2024-10-21T07:32:30.000Z","size":482,"stargazers_count":216,"open_issues_count":1,"forks_count":15,"subscribers_count":10,"default_branch":"main","last_synced_at":"2024-10-21T10:38:23.808Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/WebReflection.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-09-09T07:37:44.000Z","updated_at":"2024-10-21T07:32:34.000Z","dependencies_parsed_at":"2024-06-18T18:38:52.786Z","dependency_job_id":"0ac86c05-5f91-4048-aa07-ee601b0cafc4","html_url":"https://github.com/WebReflection/usignal","commit_stats":null,"previous_names":[],"tags_count":67,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2Fusignal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2Fusignal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2Fusignal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2Fusignal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/WebReflection","download_url":"https://codeload.github.com/WebReflection/usignal/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223941053,"owners_count":17228945,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T05:00:43.527Z","updated_at":"2024-11-10T10:31:31.130Z","avatar_url":"https://github.com/WebReflection.png","language":"JavaScript","readme":"# \u003cem\u003eµ\u003c/em\u003esignal\n\n[![Downloads](https://img.shields.io/npm/dm/usignal.svg)](https://www.npmjs.com/package/usignal) [![Coverage Status](https://coveralls.io/repos/github/WebReflection/usignal/badge.svg?branch=main)](https://coveralls.io/github/WebReflection/usignal?branch=main) [![build status](https://github.com/WebReflection/usignal/actions/workflows/node.js.yml/badge.svg)](https://github.com/WebReflection/usignal/actions)\n\n\u003csup\u003e**Social Media Photo by [Carlos Alberto Gómez Iñiguez](https://unsplash.com/@iniguez) on [Unsplash](https://unsplash.com/)**\u003c/sup\u003e\n\nA blend of [@preact/signals-core](https://github.com/preactjs/signals) and [solid-js basic reactivity API](https://www.solidjs.com/docs/latest), with API and DX mostly identical to *@preact/signals-core* but extra goodness inspired by *solid-js*, 803 bytes minified with brotli.\n\n```js\nimport {signal, computed, effect, batch, Signal} from 'usignal';\n// const {signal, computed, effect, batch, Signal} = require('usignal');\n\nsignal(0) instanceof Signal;          // true\ncomputed(() =\u003e {}) instanceof Signal; // true\n\neffect(\n  () =\u003e { console.log('fx') },\n  void 0,       // optional value to pass along the callback as initial/prev value\n  {async: true} // optionally make the effect async: false by default\n);\n\n// try every example shown in\n// https://github.com/preactjs/signals\n// or see test/index.js file to see it in action\n```\n\n#### Exports\n\nThis is a *dual module* so it works in either *CommonJS* or *ECMAScript* module systems.\n\n  * `usignal/sync` exports with an enforced *sync* effect\n  * `usignal/async` exports with an enforced *async* effect\n  * `usignal` in *browsers* exports `usignal/async` and `usignal/sync` in *servers* or by *default*\n  * `usignal/core` just exports the *effect* as callback that accepts an effect and an optionally asynchronous `true` flag, used by all other exports by default, but you decide if a specific effect should sync or async.\n  * the [unpkg/usignal](https://unpkg.com/usignal) default export points at the pre minified [es.js](./es.js) file without any enforcement around *effect*, like `usignal/core`, so that all effects are *sync* by default but can be *async* passing `true` as second parameter\n\nCurrent exports are exactly these:\n\n```js\nimport {\n  signal,\n  computed,\n  effect,\n  batch,\n  Signal\n} from 'usignal';\n```\n\nThe `Signal` export is useful only as brand check for either *computed* or *signal* references, but it cannot be used as constructor right away.\n\n\n#### Exports - Extra\n\nTo allow developers to try and use different patterns there are a few variants of this module, still based on the very same core primitives:\n\n  * `usignal/fn`, with its `*/sync` and `*/async` variants, where signals are callbacks so that `signal()` returns a its value, and `signal(value)` updates its value and return the new one, [inspired by S](https://github.com/adamhaile/S). Computeds do not update anything so `computed()` returns values. This is a variant around the `.value` accessor pattern I don't necessarily disike, specially when we'd like to *signal* that a signal is being observed: `effect(() =\u003e { mySignal(); })`\n  * `usignal/solid`, with its `*/sync` and `*/async` variants, where the module exports [createEffect](https://www.solidjs.com/docs/latest#createeffect), [createMemo](https://www.solidjs.com/docs/latest#creatememo), and [createSignal](https://www.solidjs.com/docs/latest#createsignal), mimicking the behavior (and returned values) as [solid-js basic reactivity API](https://www.solidjs.com/docs/latest/api). This is handy to compare the two or drop-in usignal in solid-js already based code.\n\n---\n\n## Differently thought ...\n\n  * the default comparison for equality is not based on `===` but on [Object.is](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is). This might be a tiny, hopefully irrelevant, performance penalty, but I feel like guarding *NaN* cases in reactivity is a step forward to avoid infinite loops out of *NaN* poisoning some computation. *+0* and *-0* are less interesting cases to tackle, still these might be fundamental in some case, hence preserved in this moudle.\n\n  * this library has lazy, non side-effecting, computed values, something [@preact/signals-core](https://github.com/preactjs/signals) recently introduced and [Solid 2.0 is planning to improve](https://twitter.com/RyanCarniato/status/1569815024964706304).\n\n  * computed accepts an initial value otherwise passed as previous one by default, mimicking *solid-js* `useMemo(fn[, value[, options]])` signature.\n\n  * effect passes along its initial value or the previoulsy returned one. If this is a function though, it runs it before re-executing, passing along its returned value, if any.\n\n  * both `signal(value[, options])` and `computed(fn[, value[, options]])` accept an optionally *options* argument, currently implementing [equals](https://www.solidjs.com/docs/latest#options) as explained in *solid-js* documentation.\n\n  * both *signal* and *computed* also have a `toJSON` and a `valueOf()` allowing to implicitly use their values, e.g.\n\n```js\nconst one = signal(1);\nconst two = signal(2);\nconst three = computed(() =\u003e one + two);\n\nthree.value;  // 3 indeed!\n```\n\n---\n\n\n## Benchmark\n\nThe benchmark currently compares *S*, *solid*, *preact/signals*, and *cellx* against *usignal*.\n\nPlease note *preact* is currently not able to solve nested effects so its logic might be simpler than other libraries.\n\n```sh\nnpm run benchmark\n```\n\n![current status](./test/benchmark.png)\n\n\n## Tests\n\nThis module is 100% code covered, including ~~the *WeakRef*~~ possible leaks which is tested through the [test/leak.js](./test/leak.js) file, which is part of the *build* script process.\n\nTo use other libraries as reference, I have also added *preact/signals-core* and *solid-js* dev-dependencies within the test folder.\n\nPlease note *preact* is currently not able to solve nested effects so its logic might be simpler than other libraries.\n\nThe following instructions are needed to test other libraries too:\n\n```sh\ncd usignal\nnpm i\ncd test\nnpm i\ncd ..\n\n# normal tests\nnpm test usignal      # shows also code-coverage\nnpm test solid\nnpm test preact\n\n# leak test\nnpm run leak usignal  # calculate leaks via internals\nnpm run leak solid\nnpm run leak preact\n```\n\n#### About the leak test\n\nThis file is not meant at all as meaningful benchmark against other libraries, it's simply there to allow me to spot regressions on future updates of the library:\n  * ~~there should be zero leaks on signals when a computed reference is garbage collected~~ v0.5.0 removed the *WeakRef*, computeds go when signals go ... [but why?!](https://twitter.com/WebReflection/status/1570380914613694466)\n  * the amount of used memory should always be lower than the initial one\n  * the performance should be competitive compared to others\n\n## How to integrate with Lit\n\nYou create a following [mixin](https://lit.dev/docs/composition/mixins/) function. Your class inherits from Mixin. Please see the [demo](https://lit.dev/playground/#project=W3sibmFtZSI6InNpZ25hbC1leGFtcGxlLmpzIiwiY29udGVudCI6ImltcG9ydCB7IGh0bWwsIGNzcywgTGl0RWxlbWVudCB9IGZyb20gJ2xpdCc7XG5pbXBvcnQgeyBXaXRoVXNpZ25hbCB9IGZyb20gJy4vd2l0aC11c2lnbmFsLmpzJztcbmltcG9ydCB7IHNpZ25hbCB9IGZyb20gJ3VzaWduYWwnO1xuXG5jb25zdCBjb3VudGVyID0gc2lnbmFsKDEpO1xuY29uc3QgY291bnRlcjIgPSBzaWduYWwoMSk7XG5cbmNsYXNzIFNpZ25hbEV4YW1wbGUgZXh0ZW5kcyBXaXRoVXNpZ25hbChMaXRFbGVtZW50KSB7XG4gIHN0YXRpYyBzdHlsZXMgPSBjc3NgXG4gICAgOmhvc3Qge1xuICAgICAgZGlzcGxheTogYmxvY2s7XG4gICAgICBib3JkZXI6IHNvbGlkIDFweCBibHVlO1xuICAgICAgcGFkZGluZzogOHB4O1xuICAgICAgbWFyZ2luOiA0cHg7XG4gICAgfVxuICBgO1xuXG4gIHN0YXRpYyBwcm9wZXJ0aWVzID0ge1xuICAgIGNvdW50OiB7IHN0YXRlOiB0cnVlIH0sXG4gICAgbmFtZToge31cbiAgfVxuXG4gIGNvbnN0cnVjdG9yKCkge1xuICAgIHN1cGVyKCk7XG4gICAgdGhpcy5jb3VudCA9IDE7XG4gIH1cblxuICByZW5kZXIoKSB7XG4gICAgcmV0dXJuIGh0bWxgXG4gICAgICA8aDQ-JHt0aGlzLm5hbWV9PC9oND5cbiAgICAgIDxwPlxuICAgICAgICBTaWduYWwgY291bnRlcjogJHtjb3VudGVyfVxuICAgICAgICA8YnV0dG9uIEBjbGljaz0keygpID0-IGNvdW50ZXIudmFsdWUrK30-Kys8L2J1dHRvbj5cbiAgICAgIDwvcD5cbiAgICAgIDxwPlxuICAgICAgICBSZWFjdGl2ZSBwcm9wZXJ0eSBjb3VudGVyOiAke3RoaXMuY291bnR9XG4gICAgICAgIDxidXR0b24gQGNsaWNrPSR7KCkgPT4gdGhpcy5jb3VudCsrfT4rKzwvYnV0dG9uPlxuICAgICAgPC9wPlxuICAgICAgJHsgdGhpcy5jb3VudCA-IDIgPyBodG1sYFxuICAgICAgIDxwPlxuICAgICAgICBTaWduYWwgY291bnRlcjogJHtjb3VudGVyMn1cbiAgICAgICAgPGJ1dHRvbiBAY2xpY2s9JHsoKSA9PiBjb3VudGVyMi52YWx1ZSsrfT4rKzwvYnV0dG9uPlxuICAgICAgIDwvcD5gIDogbnVsbH0gICAgICAgIFxuICAgIGA7XG4gIH1cbn1cblxuY3VzdG9tRWxlbWVudHMuZGVmaW5lKCdzaWduYWwtZXhhbXBsZScsIFNpZ25hbEV4YW1wbGUpOyJ9LHsibmFtZSI6ImluZGV4Lmh0bWwiLCJjb250ZW50IjoiPCFET0NUWVBFIGh0bWw-XG48aHRtbD5cbiAgPGhlYWQ-XG4gICAgPHNjcmlwdCB0eXBlPVwibW9kdWxlXCIgc3JjPVwiLi9zaWduYWwtZXhhbXBsZS5qc1wiPjwvc2NyaXB0PlxuICAgIDxzdHlsZT5cbiAgICAgIGJvZHkge1xuICAgICAgICBmb250LXNpemU6IDEuMjVyZW07XG4gICAgICAgIGZvbnQtZmFtaWx5OiBzYW5zLXNlcmlmO1xuICAgICAgfVxuICAgIDwvc3R5bGU-XG4gIDwvaGVhZD5cbiAgPGJvZHk-XG4gICAgPHNpZ25hbC1leGFtcGxlIG5hbWU9XCJJbnN0YW5jZSAxXCI-PC9zaWduYWwtZXhhbXBsZT5cbiAgICA8c2lnbmFsLWV4YW1wbGUgbmFtZT1cIkluc3RhbmNlIDJcIj48L3NpZ25hbC1leGFtcGxlPlxuICA8L2JvZHk-XG48L2h0bWw-In0seyJuYW1lIjoicGFja2FnZS5qc29uIiwiY29udGVudCI6IntcbiAgXCJkZXBlbmRlbmNpZXNcIjoge1xuICAgIFwibGl0XCI6IFwiXjIuMC4wXCIsXG4gICAgXCJAbGl0L3JlYWN0aXZlLWVsZW1lbnRcIjogXCJeMS4wLjBcIixcbiAgICBcImxpdC1lbGVtZW50XCI6IFwiXjMuMC4wXCIsXG4gICAgXCJsaXQtaHRtbFwiOiBcIl4yLjAuMFwiXG4gIH1cbn0iLCJoaWRkZW4iOnRydWV9LHsibmFtZSI6IndpdGgtdXNpZ25hbC5qcyIsImNvbnRlbnQiOiJpbXBvcnQgeyBlZmZlY3QsIHNpZ25hbCB9IGZyb20gJ3VzaWduYWwnO1xuXG5leHBvcnQgZnVuY3Rpb24gV2l0aFVzaWduYWwoQmFzZSl7XG4gIHJldHVybiBjbGFzcyBXaXRoVXNpZ25hbCBleHRlbmRzIEJhc2Uge1xuICAgICNkaXNwb3NlRWZmZWN0XG4gICAgI3JlYWN0aXZlUHJvcFVwZGF0ZSA9IHNpZ25hbCgwKTtcblxuICAgIGRpc2Nvbm5lY3RlZENhbGxiYWNrKCkge1xuICAgICAgdGhpcy4jZGlzcG9zZUVmZmVjdD8uKCk7XG4gICAgfVxuXG4gICAgcGVyZm9ybVVwZGF0ZSgpIHtcbiAgICAgIGlmICghdGhpcy5pc1VwZGF0ZVBlbmRpbmcpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuXG4gICAgICBpZiAodGhpcy4jZGlzcG9zZUVmZmVjdCkge1xuICAgICAgICB0aGlzLiNyZWFjdGl2ZVByb3BVcGRhdGUudmFsdWUrKztcbiAgICAgIH1cblxuICAgICAgdGhpcy4jZGlzcG9zZUVmZmVjdCA9IGVmZmVjdCgoKSA9PiB7ICAgICAgXG4gICAgICAgIHRoaXMuaXNVcGRhdGVQZW5kaW5nID0gdHJ1ZTtcbiAgICAgICAgdGhpcy4jcmVhY3RpdmVQcm9wVXBkYXRlLnZhbHVlO1xuICAgICAgICBzdXBlci5wZXJmb3JtVXBkYXRlKCk7XG4gICAgICB9KTtcbiAgICB9XG4gIH07XG59In1d) for details.\n\n```js\nimport { effect, signal } from 'usignal';\n\nexport function WithUsignal(Base){\n  return class WithUsignal extends Base {\n    #disposeEffect\n    #reactivePropUpdate = signal(0);\n\n    disconnectedCallback() {\n      this.#disposeEffect?.();\n    }\n\n    performUpdate() {\n      if (!this.isUpdatePending) {\n        return;\n      }\n\n      if (this.#disposeEffect) {\n        this.#reactivePropUpdate.value++;\n        return;\n      }\n\n      this.#disposeEffect = effect(() =\u003e {      \n        this.isUpdatePending = true;\n        this.#reactivePropUpdate.value;\n        super.performUpdate();\n      });\n    }\n  };\n}\n```\n","funding_links":[],"categories":["State Managers","Uncategorized","JavaScript"],"sub_categories":["Signals","Uncategorized"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FWebReflection%2Fusignal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FWebReflection%2Fusignal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FWebReflection%2Fusignal/lists"}