react-fine-grained-signals 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-ALIEN-SIGNALS.txt +21 -0
- package/README.ja.md +17 -10
- package/README.md +18 -10
- package/dist/base-WD_74mfC.js +55 -0
- package/dist/base-WD_74mfC.js.map +1 -0
- package/dist/core-runtime-2jSE9Tz5.js +1354 -0
- package/dist/core-runtime-2jSE9Tz5.js.map +1 -0
- package/dist/index.d.ts +20 -25
- package/dist/index.js +4 -4
- package/dist/{jsx-Xp5q2y97.d.ts → jsx-BQGrTir3.d.ts} +40 -5
- package/dist/jsx-P6volZdr.js +1833 -0
- package/dist/jsx-P6volZdr.js.map +1 -0
- package/dist/jsx-dev-runtime.d.ts +3 -3
- package/dist/jsx-dev-runtime.js +1 -1
- package/dist/jsx-runtime.d.ts +4 -4
- package/dist/jsx-runtime.js +1 -1
- package/dist/runtime.d.ts +2 -2
- package/dist/runtime.js +2 -2
- package/dist/use-signals-C1K8-vJX.js +169 -0
- package/dist/use-signals-C1K8-vJX.js.map +1 -0
- package/dist/use-signals-CJYcdFdL.d.ts +11 -0
- package/dist/utils.d.ts +14 -15
- package/dist/utils.js +6 -6
- package/dist/utils.js.map +1 -1
- package/package.json +22 -18
- package/dist/base-BZTnpU8U.js +0 -286
- package/dist/base-BZTnpU8U.js.map +0 -1
- package/dist/hooks-BFIWh7AD.js +0 -914
- package/dist/hooks-BFIWh7AD.js.map +0 -1
- package/dist/jsx-D9U6dSwL.js +0 -730
- package/dist/jsx-D9U6dSwL.js.map +0 -1
- package/dist/render-tracking-DCzw57BW.js +0 -96
- package/dist/render-tracking-DCzw57BW.js.map +0 -1
- package/dist/use-signals-8i2B-94o.js +0 -173
- package/dist/use-signals-8i2B-94o.js.map +0 -1
- package/dist/use-signals-CBDbbAbV.d.ts +0 -24
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-present Johnson Chu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.ja.md
CHANGED
|
@@ -8,25 +8,29 @@
|
|
|
8
8
|
|
|
9
9
|
## ドキュメント
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
ユーザー向けドキュメントの索引は[`docs/README.ja.md`](docs/README.ja.md)を参照してください。
|
|
12
|
+
v0.1.1からの移行については[v0.2移行ガイド](./docs/migration/v0.2.ja.md)を参照してください。
|
|
12
13
|
|
|
13
14
|
## インストール
|
|
14
15
|
|
|
15
16
|
```sh
|
|
16
|
-
pnpm add react-fine-grained-signals
|
|
17
|
+
pnpm add react-fine-grained-signals
|
|
17
18
|
```
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
React(19以降)はpeer dependencyで、Node.js 22以降が必要です。packageはESMのみで、
|
|
21
|
+
entry pointと型を `exports` で公開しているため、`react-fine-grained-signals/utils`
|
|
22
|
+
やJSXランタイムを解決するには、TypeScriptの `moduleResolution` を `bundler`、
|
|
23
|
+
`node16`、`nodenext` のいずれかにしてください。
|
|
20
24
|
|
|
21
25
|
## セットアップ
|
|
22
26
|
|
|
23
27
|
プリミティブとフックは、installした時点で動作します。buildツールやコンパイラの設定は不要です。
|
|
24
28
|
|
|
25
29
|
```tsx
|
|
26
|
-
import { useSignal,
|
|
30
|
+
import { useSignal, useSignalTracking } from "react-fine-grained-signals";
|
|
27
31
|
|
|
28
32
|
function Counter() {
|
|
29
|
-
|
|
33
|
+
useSignalTracking();
|
|
30
34
|
const count = useSignal(0);
|
|
31
35
|
|
|
32
36
|
return <button onClick={() => count.value++}>{count.value}</button>;
|
|
@@ -58,11 +62,11 @@ function Counter() {
|
|
|
58
62
|
|
|
59
63
|
Viteはこの2つを `tsconfig.json` から読むため、`vite.config.ts` 側にJSXの設定は不要です。既定の変換でも `@vitejs/plugin-react` を使う場合でも同じです。
|
|
60
64
|
|
|
61
|
-
このランタイムが対象とするのはネイティブ要素と、意図的に絞ったpropsだけです。Reactコンポーネントのpropsや子要素に渡したsignalはアンラップされません。許可リスト全体と、JSXをBabelで変換するツールチェーン(`tsconfig.json` を読みません)については[JSXのsignal子要素とhost binding](docs/jsx-bindings.ja.md)を参照してください。
|
|
65
|
+
このランタイムが対象とするのはネイティブ要素と、意図的に絞ったpropsだけです。Reactコンポーネントのpropsや子要素に渡したsignalはアンラップされません。許可リスト全体と、JSXをBabelで変換するツールチェーン(`tsconfig.json` を読みません)については[JSXのsignal子要素とhost binding](./docs/guides/jsx-bindings.ja.md)を参照してください。
|
|
62
66
|
|
|
63
|
-
### ビルドplugin — `
|
|
67
|
+
### ビルドplugin — `useSignalTracking()` の自動挿入
|
|
64
68
|
|
|
65
|
-
追跡境界をbuild時に挿入するため、コンポーネント側で `
|
|
69
|
+
追跡境界をbuild時に挿入するため、コンポーネント側で `useSignalTracking()` を手書きする必要がなくなります。別packageなので個別にinstallします。
|
|
66
70
|
|
|
67
71
|
```sh
|
|
68
72
|
pnpm add -D unplugin-react-fine-grained-signals
|
|
@@ -72,6 +76,7 @@ pnpm add -D unplugin-react-fine-grained-signals
|
|
|
72
76
|
|
|
73
77
|
```ts
|
|
74
78
|
// vite.config.ts
|
|
79
|
+
import { defineConfig } from "vite";
|
|
75
80
|
import signals from "unplugin-react-fine-grained-signals/vite";
|
|
76
81
|
|
|
77
82
|
export default defineConfig({
|
|
@@ -79,10 +84,12 @@ export default defineConfig({
|
|
|
79
84
|
});
|
|
80
85
|
```
|
|
81
86
|
|
|
82
|
-
オプションの詳細と、フックを手書きする場合との使い分けについては[描画最適化](docs/rendering-optimization.ja.md)を参照してください。
|
|
87
|
+
オプションの詳細と、フックを手書きする場合との使い分けについては[描画最適化](./docs/guides/rendering-optimization.ja.md)を参照してください。
|
|
83
88
|
|
|
84
89
|
## 開発
|
|
85
90
|
|
|
91
|
+
アーキテクチャ、設計、実装履歴は[`development/README.md`](development/README.md)にまとめています。
|
|
92
|
+
|
|
86
93
|
```sh
|
|
87
94
|
pnpm install --frozen-lockfile
|
|
88
95
|
pnpm typecheck
|
|
@@ -110,4 +117,4 @@ pnpm test:browser
|
|
|
110
117
|
|
|
111
118
|
- [alien-signals](https://www.npmjs.com/package/alien-signals) — 本パッケージが基盤とするシグナルエンジン。
|
|
112
119
|
- [@preact/signals-core](https://www.npmjs.com/package/@preact/signals-core) — ベンチマークの比較対象。
|
|
113
|
-
- [@preact/signals-react](https://www.npmjs.com/package/@preact/signals-react) — `useSignals()` boundaryのstore protocol設計における先行事例。詳細は[Prior art](
|
|
120
|
+
- [@preact/signals-react](https://www.npmjs.com/package/@preact/signals-react) — 外部API `useSignals()` boundaryのstore protocol設計における先行事例。詳細は[Prior art](./development/design/use-signals-boundary-design.ja.md#先行事例)を参照。
|
package/README.md
CHANGED
|
@@ -8,15 +8,19 @@ An experimental fine-grained rendering layer for React 19, built on [alien-signa
|
|
|
8
8
|
|
|
9
9
|
## Documentation
|
|
10
10
|
|
|
11
|
-
See [`docs/README.md`](docs/README.md) for
|
|
11
|
+
See [`docs/README.md`](docs/README.md) for the user documentation index.
|
|
12
|
+
For upgrades from v0.1.1, see the [v0.2 migration guide](./docs/migration/v0.2.md).
|
|
12
13
|
|
|
13
14
|
## Installation
|
|
14
15
|
|
|
15
16
|
```sh
|
|
16
|
-
pnpm add react-fine-grained-signals
|
|
17
|
+
pnpm add react-fine-grained-signals
|
|
17
18
|
```
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
React (19 or newer) is a peer dependency, and Node.js 22 or newer is required.
|
|
21
|
+
The package is ESM-only and publishes its entry points and types through
|
|
22
|
+
`exports`, so TypeScript needs `moduleResolution` set to `bundler`, `node16`, or
|
|
23
|
+
`nodenext` to resolve `react-fine-grained-signals/utils` and the JSX runtime.
|
|
20
24
|
|
|
21
25
|
## Setup
|
|
22
26
|
|
|
@@ -24,10 +28,10 @@ The primitives and hooks work as soon as the package is installed — no build
|
|
|
24
28
|
or compiler configuration is involved:
|
|
25
29
|
|
|
26
30
|
```tsx
|
|
27
|
-
import { useSignal,
|
|
31
|
+
import { useSignal, useSignalTracking } from "react-fine-grained-signals";
|
|
28
32
|
|
|
29
33
|
function Counter() {
|
|
30
|
-
|
|
34
|
+
useSignalTracking();
|
|
31
35
|
const count = useSignal(0);
|
|
32
36
|
|
|
33
37
|
return <button onClick={() => count.value++}>{count.value}</button>;
|
|
@@ -68,14 +72,14 @@ configuration of its own.
|
|
|
68
72
|
|
|
69
73
|
The runtime covers native elements only, and a deliberately narrow set of
|
|
70
74
|
props. Signals passed to a React component's props or children are not
|
|
71
|
-
unwrapped. See [JSX signal children and host bindings](docs/jsx-bindings.md)
|
|
75
|
+
unwrapped. See [JSX signal children and host bindings](./docs/guides/jsx-bindings.md)
|
|
72
76
|
for the full allow-list, and for toolchains that transform JSX through Babel,
|
|
73
77
|
which do not read `tsconfig.json`.
|
|
74
78
|
|
|
75
|
-
### Build plugin — automatic `
|
|
79
|
+
### Build plugin — automatic `useSignalTracking()`
|
|
76
80
|
|
|
77
81
|
Inserts the tracking boundary during the build, so components no longer call
|
|
78
|
-
`
|
|
82
|
+
`useSignalTracking()` by hand. It is a separate package:
|
|
79
83
|
|
|
80
84
|
```sh
|
|
81
85
|
pnpm add -D unplugin-react-fine-grained-signals
|
|
@@ -86,6 +90,7 @@ It then needs a bundler entry point — `/vite`, `/rollup`, `/webpack`,
|
|
|
86
90
|
|
|
87
91
|
```ts
|
|
88
92
|
// vite.config.ts
|
|
93
|
+
import { defineConfig } from "vite";
|
|
89
94
|
import signals from "unplugin-react-fine-grained-signals/vite";
|
|
90
95
|
|
|
91
96
|
export default defineConfig({
|
|
@@ -93,11 +98,14 @@ export default defineConfig({
|
|
|
93
98
|
});
|
|
94
99
|
```
|
|
95
100
|
|
|
96
|
-
See [Rendering optimization](docs/rendering-optimization.md) for the options
|
|
101
|
+
See [Rendering optimization](./docs/guides/rendering-optimization.md) for the options
|
|
97
102
|
and for the trade-offs against calling the hook by hand.
|
|
98
103
|
|
|
99
104
|
## Development
|
|
100
105
|
|
|
106
|
+
Architecture, design, and implementation history are indexed in
|
|
107
|
+
[`development/README.md`](development/README.md).
|
|
108
|
+
|
|
101
109
|
```sh
|
|
102
110
|
pnpm install --frozen-lockfile
|
|
103
111
|
pnpm typecheck
|
|
@@ -126,4 +134,4 @@ Run `pnpm dev:browser` to build the transform package and inspect the same examp
|
|
|
126
134
|
|
|
127
135
|
- [alien-signals](https://www.npmjs.com/package/alien-signals) — the signal engine this package builds on.
|
|
128
136
|
- [@preact/signals-core](https://www.npmjs.com/package/@preact/signals-core) — benchmark comparison target.
|
|
129
|
-
- [@preact/signals-react](https://www.npmjs.com/package/@preact/signals-react) — prior art for the `useSignals()` boundary's store protocol; see [Prior art](
|
|
137
|
+
- [@preact/signals-react](https://www.npmjs.com/package/@preact/signals-react) — prior art for the external `useSignals()` boundary's store protocol; see [Prior art](./development/design/use-signals-boundary-design.md#prior-art).
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { n as SIGNAL_BRAND, t as coreRuntime } from "./core-runtime-2jSE9Tz5.js";
|
|
2
|
+
//#region src/core/base.ts
|
|
3
|
+
const signalInstances = /* @__PURE__ */ new WeakSet();
|
|
4
|
+
/** Marks an internal signal implementation for the public identity guard. */
|
|
5
|
+
function registerSignal(value) {
|
|
6
|
+
signalInstances.add(value);
|
|
7
|
+
Object.defineProperty(value, SIGNAL_BRAND, {
|
|
8
|
+
value: 1,
|
|
9
|
+
enumerable: false,
|
|
10
|
+
writable: false,
|
|
11
|
+
configurable: false
|
|
12
|
+
});
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
/** Returns whether a value came from this package's signal APIs, any copy. */
|
|
16
|
+
function isSignal(value) {
|
|
17
|
+
if (typeof value !== "object" || value === null) return false;
|
|
18
|
+
if (signalInstances.has(value)) return true;
|
|
19
|
+
const brand = value[SIGNAL_BRAND];
|
|
20
|
+
if (typeof brand !== "number" || brand < 1) return false;
|
|
21
|
+
return typeof value.peek === "function";
|
|
22
|
+
}
|
|
23
|
+
/** Creates a writable reactive value. */
|
|
24
|
+
function signal(initialValue) {
|
|
25
|
+
return coreRuntime.signal(initialValue);
|
|
26
|
+
}
|
|
27
|
+
/** Creates a lazily evaluated reactive value. */
|
|
28
|
+
function computed(getter) {
|
|
29
|
+
return coreRuntime.computed(getter);
|
|
30
|
+
}
|
|
31
|
+
/** Runs a reactive side effect and returns a disposer. */
|
|
32
|
+
function effect(fn) {
|
|
33
|
+
return coreRuntime.effect(fn);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Package-internal: an effect that is never owned by the effect or computed
|
|
37
|
+
* running when it is created. Hooks and JSX bindings create theirs from React
|
|
38
|
+
* commits, which can run synchronously inside a user effect (`flushSync`), and
|
|
39
|
+
* must not be disposed when that unrelated effect next re-runs.
|
|
40
|
+
*/
|
|
41
|
+
function detachedEffect(fn) {
|
|
42
|
+
return coreRuntime.detachedEffect(fn);
|
|
43
|
+
}
|
|
44
|
+
/** Groups writes, deferring effect notifications until the callback completes. */
|
|
45
|
+
function batch(fn) {
|
|
46
|
+
return coreRuntime.batch(fn);
|
|
47
|
+
}
|
|
48
|
+
/** Runs a callback without collecting reactive dependencies. */
|
|
49
|
+
function untracked(fn) {
|
|
50
|
+
return coreRuntime.untracked(fn);
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
export { isSignal as a, untracked as c, effect as i, computed as n, registerSignal as o, detachedEffect as r, signal as s, batch as t };
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=base-WD_74mfC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-WD_74mfC.js","names":[],"sources":["../src/core/base.ts"],"sourcesContent":["import { coreRuntime } from \"./core-runtime.js\";\nimport { SIGNAL_BRAND, SIGNAL_BRAND_MIN_VERSION, SIGNAL_BRAND_VERSION } from \"./signal-brand.js\";\n\n/** A readable reactive value. */\nexport interface ReadonlySignal<T> {\n readonly value: T;\n peek(): T;\n}\n\n/** A readable and writable reactive value. */\nexport interface Signal<T> extends ReadonlySignal<T> {\n value: T;\n}\n\nconst signalInstances = new WeakSet<object>();\n\n// The brand carries a protocol version instead of `true` so a future instance\n// can tell which contract a foreign signal claims. See signal-brand.ts for why\n// v0.2 uses a different key from v0.1.x rather than a larger version number.\n\n/** Marks an internal signal implementation for the public identity guard. */\nexport function registerSignal<T extends object>(value: T): T {\n signalInstances.add(value);\n // Non-enumerable keeps the brand out of JSON, `Object.keys`, spread, and\n // React's prop diffing; non-writable and non-configurable keep it from being\n // retargeted or stripped once the value is public. The hardening is free:\n // measured against a writable/configurable descriptor the cost is identical,\n // because it is the `defineProperty` call itself, and V8 keeps the instance\n // in fast properties either way (checked with `%HasFastProperties`). Stamping\n // the prototype instead would be worse — that pushes the prototype into\n // dictionary mode, which every lookup through it then pays for.\n Object.defineProperty(value, SIGNAL_BRAND, {\n value: SIGNAL_BRAND_VERSION,\n enumerable: false,\n writable: false,\n configurable: false,\n });\n return value;\n}\n\n/** Returns whether a value came from this package's signal APIs, any copy. */\nexport function isSignal(value: unknown): value is ReadonlySignal<unknown> {\n if (typeof value !== \"object\" || value === null) return false;\n // The WeakSet is both the cheaper lookup and the authority for values created\n // by this instance; the brand is the fallback for every other instance.\n if (signalInstances.has(value)) return true;\n const brand = (value as { [SIGNAL_BRAND]?: unknown })[SIGNAL_BRAND];\n if (typeof brand !== \"number\" || brand < SIGNAL_BRAND_MIN_VERSION) return false;\n // A foreign brand is a claim, not a proof. Checking the cheap half of the\n // contract turns a malformed brand into `false` instead of a crash inside a\n // render, and costs nothing on the local-instance path above.\n return typeof (value as ReadonlySignal<unknown>).peek === \"function\";\n}\n\n/** Creates a writable reactive value. */\nexport function signal<T>(initialValue: T): Signal<T> {\n return coreRuntime.signal(initialValue);\n}\n\n/** Creates a lazily evaluated reactive value. */\nexport function computed<T>(getter: () => T): ReadonlySignal<T> {\n return coreRuntime.computed(getter);\n}\n\n/** Runs a reactive side effect and returns a disposer. */\nexport function effect(fn: () => void | (() => void)): () => void {\n return coreRuntime.effect(fn);\n}\n\n/**\n * Package-internal: an effect that is never owned by the effect or computed\n * running when it is created. Hooks and JSX bindings create theirs from React\n * commits, which can run synchronously inside a user effect (`flushSync`), and\n * must not be disposed when that unrelated effect next re-runs.\n */\nexport function detachedEffect(fn: () => void | (() => void)): () => void {\n return coreRuntime.detachedEffect(fn);\n}\n\n/** Groups writes, deferring effect notifications until the callback completes. */\nexport function batch<T>(fn: () => T): T {\n return coreRuntime.batch(fn);\n}\n\n/** Runs a callback without collecting reactive dependencies. */\nexport function untracked<T>(fn: () => T): T {\n return coreRuntime.untracked(fn);\n}\n"],"mappings":";;AAcA,MAAM,kCAAkB,IAAI,QAAgB;;AAO5C,SAAgB,eAAiC,OAAa;CAC5D,gBAAgB,IAAI,KAAK;CASzB,OAAO,eAAe,OAAO,cAAc;EACzC,OAAA;EACA,YAAY;EACZ,UAAU;EACV,cAAc;CAChB,CAAC;CACD,OAAO;AACT;;AAGA,SAAgB,SAAS,OAAkD;CACzE,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO;CAGxD,IAAI,gBAAgB,IAAI,KAAK,GAAG,OAAO;CACvC,MAAM,QAAS,MAAuC;CACtD,IAAI,OAAO,UAAU,YAAY,QAAA,GAAkC,OAAO;CAI1E,OAAO,OAAQ,MAAkC,SAAS;AAC5D;;AAGA,SAAgB,OAAU,cAA4B;CACpD,OAAO,YAAY,OAAO,YAAY;AACxC;;AAGA,SAAgB,SAAY,QAAoC;CAC9D,OAAO,YAAY,SAAS,MAAM;AACpC;;AAGA,SAAgB,OAAO,IAA2C;CAChE,OAAO,YAAY,OAAO,EAAE;AAC9B;;;;;;;AAQA,SAAgB,eAAe,IAA2C;CACxE,OAAO,YAAY,eAAe,EAAE;AACtC;;AAGA,SAAgB,MAAS,IAAgB;CACvC,OAAO,YAAY,MAAM,EAAE;AAC7B;;AAGA,SAAgB,UAAa,IAAgB;CAC3C,OAAO,YAAY,UAAU,EAAE;AACjC"}
|