From 3dc7a65c4d1aeb32e241f5dc2d6f8825cf235667 Mon Sep 17 00:00:00 2001 From: Justin Fagnani Date: Tue, 8 Oct 2024 10:00:59 -0700 Subject: [PATCH] [docs] Add data/signals docs (#1372) --- package-lock.json | 169 ++++--- packages/lit-dev-content/package.json | 1 + .../samples/examples/signals/index.html | 10 + .../samples/examples/signals/project.json | 10 + .../examples/signals/shared-counter.ts | 34 ++ .../site/blog/2024-10-08-signals.md | 195 ++++++++ packages/lit-dev-content/site/docs/README.md | 8 + .../site/docs/v3/data/signals.md | 433 ++++++++++++++++++ .../site/docs/v3/libraries/labs.md | 14 + .../lit-dev-content/site/home/1-splash.html | 10 +- 10 files changed, 807 insertions(+), 77 deletions(-) create mode 100644 packages/lit-dev-content/samples/examples/signals/index.html create mode 100644 packages/lit-dev-content/samples/examples/signals/project.json create mode 100644 packages/lit-dev-content/samples/examples/signals/shared-counter.ts create mode 100644 packages/lit-dev-content/site/blog/2024-10-08-signals.md create mode 100644 packages/lit-dev-content/site/docs/README.md create mode 100644 packages/lit-dev-content/site/docs/v3/data/signals.md diff --git a/package-lock.json b/package-lock.json index dab105cb8..b5b4e3367 100644 --- a/package-lock.json +++ b/package-lock.json @@ -118,10 +118,11 @@ } }, "node_modules/@11ty/eleventy-dev-server/node_modules/ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -217,10 +218,11 @@ } }, "node_modules/@11ty/eleventy/node_modules/luxon": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", - "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz", + "integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" } @@ -250,13 +252,11 @@ } }, "node_modules/@11ty/eleventy/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -597,6 +597,16 @@ "version": "1.1.0", "license": "BSD-3-Clause" }, + "node_modules/@lit-labs/signals": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@lit-labs/signals/-/signals-0.1.1.tgz", + "integrity": "sha512-QQYp2CyXzxaYho4EHKLLOHLMX0Jz8HWUfGGiCKyei54Vol0DqiPggbA1z3t53oYJ3mqU/NTWF+flfcY7Tfpozg==", + "license": "BSD-3-Clause", + "dependencies": { + "lit": "^2.0.0 || ^3.0.0", + "signal-polyfill": "^0.2.0" + } + }, "node_modules/@lit-labs/ssr": { "version": "3.1.9", "resolved": "https://registry.npmjs.org/@lit-labs/ssr/-/ssr-3.1.9.tgz", @@ -631,9 +641,10 @@ } }, "node_modules/@lit-labs/ssr-dom-shim": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.2.tgz", - "integrity": "sha512-jnOD+/+dSrfTWYfSXBXlo5l5f0q1UuJo3tkbMDCYA2lKUYq79jaxqtGEvnRoh049nt1vdo1+45RinipU6FGY2g==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.1.tgz", + "integrity": "sha512-wx4aBmgeGvFmOKucFKY+8VFJSYZxs9poN3SDNQFF6lT6NrQUnHiPB2PWz2sc4ieEcAaYYzN+1uWahEeTq2aRIQ==", + "license": "BSD-3-Clause" }, "node_modules/@lit-labs/ssr/node_modules/parse5": { "version": "7.1.2", @@ -692,11 +703,12 @@ } }, "node_modules/@lit/task/node_modules/@lit/reactive-element": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.2.tgz", - "integrity": "sha512-SVOwLAWUQg3Ji1egtOt1UiFe4zdDpnWHyc5qctSceJ5XIu0Uc76YmGpIjZgx9YJ0XtdW0Jm507sDvjOu+HnB8w==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz", + "integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==", + "license": "BSD-3-Clause", "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.1.2" + "@lit-labs/ssr-dom-shim": "^1.2.0" } }, "node_modules/@lit/ts-transformers": { @@ -1829,10 +1841,11 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" }, "node_modules/@types/etag": { "version": "1.8.1", @@ -2108,12 +2121,10 @@ } }, "node_modules/@web/config-loader/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -2240,9 +2251,10 @@ } }, "node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -2421,10 +2433,11 @@ "license": "MIT" }, "node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" }, "node_modules/asynckit": { "version": "0.4.0", @@ -2964,10 +2977,11 @@ } }, "node_modules/clean-css": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz", - "integrity": "sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", "dev": true, + "license": "MIT", "dependencies": { "source-map": "~0.6.0" }, @@ -3306,10 +3320,12 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.3.4", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -3454,17 +3470,19 @@ } }, "node_modules/discord.js/node_modules/@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", + "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/discord.js/node_modules/ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -5265,9 +5283,10 @@ } }, "node_modules/jsdom/node_modules/tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "license": "BSD-3-Clause", "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", @@ -5300,9 +5319,10 @@ } }, "node_modules/jsdom/node_modules/ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -5362,12 +5382,10 @@ } }, "node_modules/jsonwebtoken/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -5983,7 +6001,9 @@ } }, "node_modules/ms": { - "version": "2.1.2", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/multimatch": { @@ -7329,6 +7349,12 @@ "dev": true, "license": "ISC" }, + "node_modules/signal-polyfill": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/signal-polyfill/-/signal-polyfill-0.2.0.tgz", + "integrity": "sha512-EbZ5L6pm0LPVZECfzfDLQ+2tWd2xQ+iG+HfuvqWjF+pl0yvdT1rsL/YgfeBakubLbnwAd1zRvap1rYqPBG8prA==", + "license": "Apache-2.0" + }, "node_modules/slash": { "version": "1.0.0", "dev": true, @@ -7832,9 +7858,10 @@ } }, "node_modules/typedoc/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -8254,9 +8281,10 @@ "license": "ISC" }, "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", "engines": { "node": ">=8.3.0" }, @@ -8343,6 +8371,7 @@ "@lit-labs/context": "^0.4.1", "@lit-labs/motion": "^1.0.1", "@lit-labs/react": "^1.0.8", + "@lit-labs/signals": "^0.1.1", "@lit-labs/task": "^3.0.2", "@lit/context": "^1.1.0", "@lit/localize": "^0.10.0", @@ -8395,15 +8424,15 @@ } }, "packages/lit-dev-content/node_modules/@rollup/plugin-node-resolve": { - "version": "15.2.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", - "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.0.tgz", + "integrity": "sha512-9eO5McEICxMzJpDW9OnMYSv4Sta3hmt7VtBFz5zR9273suNOydOyq/FrGeGy+KsTRFm8w0SLVhzig2ILFT63Ag==", "dev": true, + "license": "MIT", "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", - "is-builtin-module": "^3.2.1", "is-module": "^1.0.0", "resolve": "^1.22.1" }, diff --git a/packages/lit-dev-content/package.json b/packages/lit-dev-content/package.json index 6e921c939..5bf2871dd 100644 --- a/packages/lit-dev-content/package.json +++ b/packages/lit-dev-content/package.json @@ -187,6 +187,7 @@ "@lit-labs/motion": "^1.0.1", "@lit-labs/react": "^1.0.8", "@lit-labs/task": "^3.0.2", + "@lit-labs/signals": "^0.1.1", "@lit/context": "^1.1.0", "@lit/localize": "^0.10.0", "@lit/react": "^1.0.0 || 1.0.0-pre.0", diff --git a/packages/lit-dev-content/samples/examples/signals/index.html b/packages/lit-dev-content/samples/examples/signals/index.html new file mode 100644 index 000000000..a7fe990bf --- /dev/null +++ b/packages/lit-dev-content/samples/examples/signals/index.html @@ -0,0 +1,10 @@ + + + + + +

Instance 1

+

Instance 2

+ diff --git a/packages/lit-dev-content/samples/examples/signals/project.json b/packages/lit-dev-content/samples/examples/signals/project.json new file mode 100644 index 000000000..918a4019f --- /dev/null +++ b/packages/lit-dev-content/samples/examples/signals/project.json @@ -0,0 +1,10 @@ +{ + "extends": "/samples/base.json", + "title": "Signals", + "description": "An example of using Signals with Lit with the @lit-labs/signals package.", + "section": "Managing Data", + "files": { + "shared-counter.ts": {}, + "index.html": {} + } +} diff --git a/packages/lit-dev-content/samples/examples/signals/shared-counter.ts b/packages/lit-dev-content/samples/examples/signals/shared-counter.ts new file mode 100644 index 000000000..28c8affa4 --- /dev/null +++ b/packages/lit-dev-content/samples/examples/signals/shared-counter.ts @@ -0,0 +1,34 @@ +import {LitElement, html, css} from 'lit'; +import {customElement} from 'lit/decorators.js'; +import {SignalWatcher, watch, signal} from '@lit-labs/signals'; + +// This signal is shared between all instances of the component +const count = signal(0); + +@customElement('shared-counter') +export class SharedCounterComponent extends SignalWatcher(LitElement) { + static styles = css` + :host { + display: block; + border: solid 1px lightgray; + margin: 8px; + padding: 8px; + } + + button { + background: lightgreen + } + `; + + render() { + return html` + +

count = ${watch(count)}

+ + `; + } + + #onClick() { + count.set(count.get() + 1); + } +} diff --git a/packages/lit-dev-content/site/blog/2024-10-08-signals.md b/packages/lit-dev-content/site/blog/2024-10-08-signals.md new file mode 100644 index 000000000..54661c947 --- /dev/null +++ b/packages/lit-dev-content/site/blog/2024-10-08-signals.md @@ -0,0 +1,195 @@ +--- +tags: blogPosts +layout: blog-post.html +title: 'Bringing Signals to Lit Labs' +summary: 'The new Signals package integrates the TC39 Signals proposal with Lit' +date: 2024-10-08 +author: + - lit-team +--- + +# Announcing @lit-labs/signals: Integrating the TC39 Signals Proposal with Lit + +We’re thrilled to announce the release of our newest Lit Labs package, +[`@lit-labs/signals`](https://www.npmjs.com/package/@lit-labs/signals), which integrates the +[polyfill](https://github.com/proposal-signals/signal-polyfill) for the +exciting new [TC39 Signals Proposal](https://github.com/tc39/proposal-signals) +directly with Lit. This package provides a powerful, reactive way to manage +state in your web applications by allowing you to use shared, observable +signals that automatically update components when their values change. + +Signals are quickly becoming a core feature in the JavaScript ecosystem, and the +TC39 proposal has the potential to unify signals and how we manage state and +reactivity across various libraries and frameworks. + +Though the proposal is in its early stages, you can start experimenting with +signals and `@lit-labs/signals` today to see how building components and apps on +a universal reactivity primitive might work for you. + +## What Are Signals? + +In simple terms, **signals** are observable data structures that hold values or +computations. When the value of a signal changes, all components or parts of +your app that depend on it are automatically notified and updated. This is +particularly useful in UIs where multiple components might need to share and +react to changes in state. + +### Key Benefits of Standards-Based Signals + +1. **Shared observable state**: Signals are great for managing state shared + across multiple components. If one component updates a signal, all others + using it will automatically update as well. +2. **Pinpoint updates**: Signals enable precisely targeted re-renders, + potentially improving performance by processing only bindings whose signal- + backed values have changed, skipping other bindings in the same templates. +3. **Interoperability**: The standardization of signals means different + libraries and frameworks can use signals interoperably, reducing the need for + complex adapters and improving compatibility. + +## Why We're Excited About `@lit-labs/signals` + +Lit is already known for its lightweight, performant, and declarative approach +to building web components. But Lit is tightly focused on helping you build +reusable, encapsulated _components_. Lit is not a framework and does not +precribe how to model your _data_ or make it observable. + +Lit's reactivity is by default relatively _shallow_. Components automatically +update when their own reactive properties change, but not when nested properties +change. Responding to deep property changes has either required manual update +requests, or the integration of a state management system like Redux or MobX. + +Signals give us many of the same deep observability abilities as these state +management systems, but with a smaller, simpler API, and the potential to be a +common standard across a large ecosystem of utilies, components, and frameworks. + +Signals aren't entirely new to Lit. We previously released the +`@lit-labs/preact-signals` package, but we were somewhat unsatisfied with the +need to build a Lit integration for a specific signals library, and potentially +every signals library that Lit developers might want to use. + +Standardized signals in JavaScript would let us build just one integration (and +eventually add signals support directly in Lit's core), and enable interop +between signal-using libraries in the same spirit of the interop that web +components enable. + +The new `@lit-labs/signals` package makes it super easy to use thw new +signals proposal from within your Lit components. + +Let’s dive into a few examples... + +### Example 1: A Shared Counter + +Here’s a simple example of a shared counter using `@lit-labs/signals`. To enable +signal-based reactivity in this component, we just use the `SignalWatcher` mixin +in our Custom Element definition; any signals we read from will automatically be +observed, triggering updates whenever their values change: + +```ts +import {LitElement, html} from 'lit'; +import {customElement} from 'lit/decorators.js'; +import {SignalWatcher, signal} from '@lit-labs/signals'; + +// This is a standard TC39 signal that uses the signals polyfill. +// The signal is shared across all component instances. +const count = signal(0); + +@customElement('shared-counter') +export class SharedCounterComponent extends SignalWatcher(LitElement) { + render() { + // Just by using the signal in your template, your component will update + // when the signal changes. + return html` +

The count is ${count.get()}

+ + `; + } + + increment() { + count.set(count.get() + 1); + } +} +``` + +With this approach, any number of `` components can be added to +the DOM, and all will reflect the same counter value, automatically updating +when the signal changes. + +You can also [see this example in the Lit +Playground](/playground/#sample=examples/signals). + +### Example 2: Pinpoint DOM Updates + +Using the `watch` directive, we can also achieve **pinpoint updates** targeting +individual bindings rather than an entire component: + +```ts +import {LitElement, html} from 'lit'; +import {customElement} from 'lit/decorators.js'; +import {SignalWatcher, watch, signal} from '@lit-labs/signals'; + +const count = signal(0); + +@customElement('pinpoint-counter') +export class PinpointCounter extends SignalWatcher(LitElement) { + render() { + return html` +

The count is ${watch(count)}

+ + `; + } + + increment() { + count.set(count.get() + 1); + } +} +``` + +Here, only the binding displaying the count is processed when the signal changes, +skipping unnecessary work and improving performance. + +## Future Work + +This is just the beginning or our exploration of signals integration for Lit. +Coming soon, we will add more utilities to `@lit-labs/signals` for incrementally +rendering changes from collections, easily running side-effects in components, +and using signals for reactive properties. + +Eventually—as the proposed standard progresses and we gain experience using +signals in Lit—signals will likely become part of the core library. + +The Lit team is working closely with the TC39 Signals Proposal champions to +ensure that signals work great for Lit, web components, and plain-DOM use +cases. We can't overstate how much potential we see in signals to form an +interoperable reactivity primitive that works across libraries and components +without requiring a centralized framework. + +## Try It Out and Give Feedback + +We’re eager to get feedback from the community as you experiment with this new +package. As part of the **Lit Labs** family, `@lit-labs/signals` is still +experimental and may undergo significant changes based on user feedback and +advancements in the TC39 Signals Proposal. + +To get started, simply install the package: + +```sh +npm i @lit-labs/signals +``` + +We’d love to hear your thoughts, so please try it out, and let us know how it +works for you. + +- Preliminary docs are available at + [lit.dev/docs/data/signals/](/docs/data/signals/) +- You can share feedback on the GitHub [feedback + discussion](https://github.com/lit/lit/discussions/4779) +- Report issues in the [Lit monorepo + issues](https://github.com/lit/lit/issues) +- Join us on our [Discord server](/discord/) to chat! + +We’re excited to see what you’ll build with signals and how they will shape +the future of state management in web applications! + +**Thanks!** + +**-The Lit Team** diff --git a/packages/lit-dev-content/site/docs/README.md b/packages/lit-dev-content/site/docs/README.md new file mode 100644 index 000000000..3cbfe5a78 --- /dev/null +++ b/packages/lit-dev-content/site/docs/README.md @@ -0,0 +1,8 @@ +With rare exceptions, edits should ONLY be made in the folder +representing the current (recommended) version of Lit. + +- Never edit files within the automatically generated + `unversioned` folder. + +- Make sure you don't accidentally edit an older version + when you're intending to update the latest docs. \ No newline at end of file diff --git a/packages/lit-dev-content/site/docs/v3/data/signals.md b/packages/lit-dev-content/site/docs/v3/data/signals.md new file mode 100644 index 000000000..5b0bc616b --- /dev/null +++ b/packages/lit-dev-content/site/docs/v3/data/signals.md @@ -0,0 +1,433 @@ +--- +title: Signals +eleventyNavigation: + key: Signals + parent: Managing Data + order: 3 + labs: true +--- + +{% labs-disclaimer %} + +## Overview + +### What are Signals? + +Signals are data structures for managing observable state. + +A signal can hold either a single value or a computed value that depends on other +signals. Signals are observable, so that a consumer can be notified when they +change. Because they form a dependency graph, computed signals will re-compute and +notify consumers when their dependencies change. + +Signals are very useful for modeling and managing **shared observable state**—state +that many different components may access and/or modify. When a signal is updated, +every component that uses and watches that signal, or any signals that depend on +it, will update. + +Signals are a general concept, with many different implementations and variations +found in JavaScript libraries and frameworks. There is also now a +[TC39 proposal](https://github.com/tc39/proposal-signals) to standardize signals +as part of JavaScript. + +Signal APIs typically have three main concepts: + +- State signals, which hold a single value +- Computed signals, which wrap a computation that may depend on other signals +- Watchers or effects, which run side-effectful code when signal values change + +### Example + +Here is an example of signals with the proposed standard JavaScript signals API: + +```ts +// +// Code developers might write to build their signals-based state... +// + +// State signals hold values: +const count = new Signal.State(0); + +// Computed signals wrap computations that use other signals: +const doubleCount = new Signal.Computed(() => count.get() * 2); + +// +// Lower-level code of the sort that will typically be inside frameworks and +// signal-consuming libraries... +// + +// Watchers are notified when signals that they watch change: +const watcher = new Signal.subtle.Watcher(async () => { + // Notify callbacks are not allowed to access signals synchronously + await 0; + console.log('doubleCount is', doubleCount); + // Watchers have to be re-enabled after they run: + watcher.watch(); +}); +watcher.watch(doubleCount); + +// Computed signals are lazy, so we need to read it to run the computation and +// potentially notify watchers: +doubleCount.get(); +``` + +### Signal Libraries + +There are many signal implementations built in JavaScript. Many are tightly +integrated into frameworks and only usable from within those frameworks, and +some are standalone libraries that are usable from any other code. + +While there are some differences in the specific signals APIs, they are quite +similar. + +Preact's signal library, +[`@preact/signals`](https://preactjs.com/guide/v10/signals/), is a standalone +library that is relatively fast and small, so we built our first Lit Labs +signals integration package around it: +[`@lit-labs/preact-signals`](https://www.npmjs.com/package/@lit-labs/preact-signals). + +### Signals Proposal for JavaScript + +Because of the strong similarities between signal APIs, the increasing use of +signals to implement reactivity in frameworks, and the desire for +interoperability between signal-using systems, a proposal for standardizing +signals is now underway in TC39 at https://github.com/tc39/proposal-signals. + +Lit provides the +[`@lit-labs/signals`](https://www.npmjs.com/package/@lit-labs/signals) package +to integrate with the official polyfill for this proposal. + +This proposal is very exciting for the web components ecosystem. Because all +libraries and frameworks that adpot the standard will produce compatible signals, +different web components won't have to use the same library to interoperably +consume and produce signals. + +What's more, signals have the potential to become the foundation for a wide range +of state management systems and observability libraries, new or existing. Each of +these libraries, like MobX or Redux, currently requires a specific adapter to +ergonomically integrate with the Lit lifecycle. Signals standardization could +mean we eventually need only one Lit adapter (or no adapter at all, when support +for signals is built into the core Lit library). + +## Signals and Lit + +Lit currently provides two signals integration packages: +[`@lit-labs/signals`](https://www.npmjs.com/package/@lit-labs/signals) for +integration with the TC39 Signals Proposal, and +[`@lit-labs/preact-signals`](https://www.npmjs.com/package/@lit-labs/preact-signals) +for integration with Preact Signals. + +Because the TC39 Signals Proposal promises to be the one signal API that +JavaScript systems converge on, we recommend using it, and will focus on its +usage in this document. + +### Installation + +Install `@lit-labs/signals` from npm: + +```sh +npm i @lit-labs/signals +``` + +### Usage + +`@lit-labs/signals` provides three main exports: + +- The `SignalWatcher` mixin to apply to all classes using signals +- The `watch()` template directive to watch individual signals with pinpoint + updates +- The `html` template tag to apply the watch directive automatically to template + bindings + +Import these like so: + +```ts +import {SignalWatcher, watch, signal} from '@lit-labs/signals'; +``` + +
+ +`@lit-labs/signals` also exports some of the polyfilled signals API for +convenience, and a `withWatch()` template tag factory so that developers who need +custom template tags can easily add signal-watching functionality. + +
+ + +#### Auto-watching with SignalWatcher + +This simplest way to use signals is to apply the `SignalWatcher` mixin when +defining your Custom Element class. With the mixin applied, you can read +signals in Lit lifecycle methods (like `render()`); any changes to the +values of those signals will automatically initiate an update. You can write +signals wherever it makes sense—for example, in event handlers. + +In this example, the `SharedCounterComponent` reads and writes to a shared +signal. Every instance of the component will show the same value, and they will +all update when the value changes. + + + +```ts +import {LitElement, html} from 'lit'; +import {customElement} from 'lit/decorators.js'; +import {SignalWatcher, signal} from '@lit-labs/signals'; + +const count = signal(0); + +@customElement('shared-counter') +export class SharedCounterComponent extends SignalWatcher(LitElement) { + static styles = css` + :host { + display: block; + } + `; + + render() { + return html` +

The count is ${count.get()}

+ + `; + } + + #onClick() { + count.set(count.get() + 1); + } +} +``` + +```html + + + +``` + +#### Pinpoint Updates with `watch()` + +Signals can also be used to achieve "pinpoint" DOM updates targeting individual +bindings rather than an entire component. To do this, we need to watch signals +individually with the `watch()` directive. + +For coordination purposes, updates triggered by the `watch()` directive are +batched and still participate in the Lit reactive update lifecycle. However, +when a given Lit update has been triggered purely by `watch()` directives, the +only bindings updated are those with changed signals; the rest of the bindings +in the template are skipped. + +This example is the same as the previous, but only the `${watch(count)}` binding +is updated when the `count` signal changes: + +```ts +import {LitElement, html} from 'lit'; +import {customElement} from 'lit/decorators.js'; +import {SignalWatcher, watch, signal} from '@lit-labs/signals'; + +const count = signal(0); + +@customElement('shared-counter') +export class SharedCounterComponent extends SignalWatcher(LitElement) { + static styles = css` + :host { + display: block; + } + `; + + render() { + return html` +

The count is ${watch(count)}

+ + `; + } + + #onClick() { + count.set(count.get() + 1); + } +} +``` + +Note that the work avoided by this pinpoint update is actually very little: +the only things skipped are the identity check for the template returned by +`render()` and the value check for the `@click` binding, both of which are cheap. + +In fact, in most cases `watch()` will _not_ result in a significant performance +improvement over "plain" Lit template renders. This is because Lit already only +updates the DOM for bindings that have changed values. + +The performance savings of `watch()` will tend to scale with the amount of +template logic and the number of bindings that can be skipped in an update, so +savings will be more significant in templates with lots of logic and bindings. + +
+ +`@lit-labs/signals` does not yet contain a signal-aware `repeat()` directive. +Changes to the contents of arrays will perform full renders until then. + +
+ +#### Auto-pinpoint updates with the signals `html` template tag + +`@lit-labs/signals` also exports a special version of Lit's `html` template tag +that automatically applies the `watch()` directive to any signal value passed +to a binding. + +This can be convenient to avoid the extra characters of the `watch()` directive +or the `signal.get()` calls required without `watch()`. + +If you import `html` from `@lit-labs/signals` instead of from `lit`, you will +get the auto-watching feature: + +```ts +import {LitElement} from 'lit'; +import {SignalWatcher, html, signal} from '@lit-labs/signals'; + +// SharedCounterComponent ... + render() { + return html` +

The count is ${count}

+ + `; + } +``` + +
+ +The signals `html` tag doesn't yet work well with lit-analyzer. The analyzer +will report type errors on bindings that use signals becuase it sees an +assigment of `Signal` to `T`. + +
+ +## Ensuring proper polyfill installation + +`@lit-labs/signals` includes the `signal-polyfill` package as a dependency, so +you don't need to explicitly install anything else to start using signals. + +But since signals rely on a shared global data structure (the signal dependency +graph), it's critically important that the polyfill is installed properly: +there can be only one copy of the polyfill package in any page or app. + +If more than one copy of the polyfill is installed (either because of +incompatible versions or other npm mishaps) then it's possible to _partition_ +the signal graph so that some watchers will not work with some signals, or some +signals will not be tracked as dependencies of others. + +To prevent this, be sure to check that there's only one installation of +`signal-polyfill`, using the `npm ls` command: + +```sh +npm ls signal-polyfill +``` + +If you see more than one listing for `signal-polyfill` _without_ `deduped` next +to the line, then you have duplicate copies of the polyfill. + +You can usually fix this by running: + +```sh +npm dedupe +``` + +If that doesn't work, you may have to update dependencies until you get a single +compatible version of `signal-polyfill` across your package installation. + +## Missing Features + +`@lit-labs/signals` is not feature-complete. There are a few envisioned features +that will make working with signals in Lit more viable and performant: + +- [ ] A signal-aware `repeat()` directive. This will make incremental updates to + arrays more efficient. +- [ ] A `@property()` decorator that uses signals for storage, to unify reactive + properties and signals. This will make it easier to use generic signal + utilities with Lit reactive properties. +- [ ] A `@computed()` decorator for marking methods as computed signals. Since + computed signals are memoized, this can help with expensive computations. +- [ ] An `@effect()` decorator for marking methods as effects. This can be a + more ergonomic way of running effects than using a separate utility. + +## Useful Resources + +### `signal-utils` + +The `signal-utils` npm package contains a number of utilities for working with +the TC39 Signals Proposal, including: + +- Signal-backed, observable, collections like `Array`, `Map`, `Set`, `WeakMap`, + `WeakSet`, and `Object` +- Decorators for building classes with signal-backed fields +- Effects and reactions + +These collections and decorators are useful for building observable data models +from signals, where you will often need to manage values more complicated than a +primitive. + +#### Collections + +For instance, you can make an observable array: + +```ts +import {SignalArray} from 'signal-utils/array'; + +const numbers = new SignalArray([1, 2, 3]); +``` + +Reading from the array, like iterating over it or reading `.length` will be +tracked as signal access, and mutations of the array, like from `.push()` or +`.pop()`, will notify any watchers. + +#### Decorators + +The decorators let you model a class with observable fields, much like a +`LitElement`: + +```ts +import {signal} from 'signal-utils'; + +class GameState { + @signal + accessor playerOneTotal = 0; + + @signal + accessor playerTwoTotal = 0; + + @signal + accessor over = false; + + readonly rounds = new SignalArray(); + + recordRound(playerOneScore, playerTwoScore) { + this.playerOneTotal += playerOneScore; + this.playerTwoTotal += playerTwoScore; + this.rounds.push([playerOneScore, playerTwoScore]); + } +} +``` + +Instances of this `GameState` class will be tracked by SignalWatcher classes +that access it, and will update when the game state changes. + +## Status and Feedback + +This package is part of the Lit Labs family of experimental packages and under +active development. There may be missing features, serious bugs in the +implementation, and more frequent breaking changes than with the core Lit +libraries. + +This package also relies on a proposal and polyfill that themselves are not +stable. As the signals proproposal progresses, breaking changes may be made to +the proposed API, which will then be made to the polyfill. + +We encourage cautious use in order for us to gain experience with and get +feedback on the Lit integration layer, but please manage dependencies carefully +and test judiciously so that unexpected breaking changes are kept to a minimum. + +Please leave feedback on the [@lit-labs/signals feedback +discussion](https://github.com/lit/lit/discussions/4779), and [file any +issues](https://github.com/lit/lit/issues) you encounter. + +Feedback on the Signals proposal can be left on the [Signals proposal +repository](https://github.com/tc39/proposal-signals). Issues with the polyfill +can be filed [here](https://github.com/proposal-signals/signal-polyfill). diff --git a/packages/lit-dev-content/site/docs/v3/libraries/labs.md b/packages/lit-dev-content/site/docs/v3/libraries/labs.md index 3f959e5c8..eb380933c 100644 --- a/packages/lit-dev-content/site/docs/v3/libraries/labs.md +++ b/packages/lit-dev-content/site/docs/v3/libraries/labs.md @@ -100,6 +100,20 @@ A plugin for [Eleventy](https://www.11ty.dev) that pre-renders Lit components at +[signals](https://www.npmjs.com/package/@lit-labs/signals) + + +TC39 Signals Proposal polyfill integration for Lit. + + +[📄 Docs](/docs/data/signals/ "Docs")
[💬 Feedback](https://github.com/lit/lit/discussions/4779 "Feedback")
[🐞 Issues](https://github.com/lit/lit/issues?q=is%3Aissue+is%3Aopen+in%3Atitle+%5Blabs%2Fsignals%5D "Issues") + + + + + + + [ssr](https://www.npmjs.com/package/@lit-labs/ssr) diff --git a/packages/lit-dev-content/site/home/1-splash.html b/packages/lit-dev-content/site/home/1-splash.html index 40648a276..1f73354aa 100644 --- a/packages/lit-dev-content/site/home/1-splash.html +++ b/packages/lit-dev-content/site/home/1-splash.html @@ -1,12 +1,8 @@

- 💡 - Lit.dev now supports dark mode. Try it out by toggling it in the top menu. - - Feedback welcome! - + 🚦 + Our newest Lit Labs package @lit-labs/signals is now available! + Check out the announcement here.