@papack/csr 0.0.2 → 1.0.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/dist/index.cjs +1 -1
- package/dist/index.d.cts +833 -127
- package/dist/index.d.mts +807 -130
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/licence.md +21 -0
- package/package.json +2 -2
- package/readme.md +108 -137
package/readme.md
CHANGED
|
@@ -1,72 +1,45 @@
|
|
|
1
1
|
# @papack/csr
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Instead: **real mutation**, **async components**, **deterministic structural updates**.
|
|
3
|
+
**Experimental synchronous UI DOM runtime.**
|
|
4
|
+
Designed for **simplicity, explicitness, and predictability**.
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
> **No feature is the feature.**
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- `signal` / `effect` as minimal reactivity primitives
|
|
14
|
-
- Explicit lifecycle (`mount`, `unmount`, `destroy`)
|
|
15
|
-
- Keyed `For` with stable DOM identity
|
|
16
|
-
- Structural conditional rendering (`Show`)
|
|
17
|
-
- Mutation is allowed (by design)
|
|
18
|
-
- Fully TypeScript
|
|
19
|
-
|
|
20
|
-
## Core Ideas
|
|
21
|
-
|
|
22
|
-
- **Async components are first-class**
|
|
8
|
+
No Virtual DOM.
|
|
9
|
+
No diffing.
|
|
10
|
+
No scheduler.
|
|
11
|
+
No hidden async.
|
|
23
12
|
|
|
24
|
-
|
|
25
|
-
async function User() {
|
|
26
|
-
const data = await fetch("/api/user").then((r) => r.json());
|
|
27
|
-
return <div>{data.name}</div>;
|
|
28
|
-
}
|
|
29
|
-
```
|
|
13
|
+
Just **real DOM**, **real mutation**, and **deterministic structure** with zero dependencies.
|
|
30
14
|
|
|
31
|
-
|
|
15
|
+
---
|
|
32
16
|
|
|
33
|
-
|
|
34
|
-
- mutation is allowed
|
|
35
|
-
- every `set()` reliably triggers effects
|
|
17
|
+
## Philosophy
|
|
36
18
|
|
|
37
|
-
|
|
19
|
+
This library is **simple by design**, not “easy”.
|
|
38
20
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
- removal = real `destroy`
|
|
21
|
+
- There is **one way** to do things
|
|
22
|
+
- Everything is **explicit**
|
|
42
23
|
|
|
43
|
-
|
|
24
|
+
It _is_ optimized for **clarity and long-term maintenance**.
|
|
44
25
|
|
|
45
|
-
|
|
46
|
-
import { jsx, render, signal } from "../core";
|
|
47
|
-
import { For } from "../core/for";
|
|
48
|
-
|
|
49
|
-
const [items, setItems] = signal([
|
|
50
|
-
{ uuid: "a", name: "A" },
|
|
51
|
-
{ uuid: "b", name: "B" },
|
|
52
|
-
]);
|
|
26
|
+
---
|
|
53
27
|
|
|
54
|
-
|
|
28
|
+
## Features
|
|
55
29
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
```
|
|
30
|
+
- Fully **synchronous rendering**
|
|
31
|
+
- No Virtual DOM, no reconciliation
|
|
32
|
+
- Minimal reactivity via `signal` / `effect`
|
|
33
|
+
- Explicit lifecycle (`mount`, `unmount`)
|
|
34
|
+
- Structural primitives (`Show`, `For`)
|
|
64
35
|
|
|
65
36
|
---
|
|
66
37
|
|
|
67
|
-
##
|
|
38
|
+
## Core Ideas
|
|
39
|
+
|
|
40
|
+
### Signals are active state
|
|
68
41
|
|
|
69
|
-
Signals are **
|
|
42
|
+
Signals are **sources**, not values.
|
|
70
43
|
|
|
71
44
|
```ts
|
|
72
45
|
const [count, setCount] = signal(0);
|
|
@@ -75,16 +48,12 @@ setCount((v) => v + 1);
|
|
|
75
48
|
setCount(() => 42);
|
|
76
49
|
```
|
|
77
50
|
|
|
78
|
-
### Properties
|
|
79
|
-
|
|
80
|
-
- `set()` **always triggers**
|
|
81
51
|
- no equality checks
|
|
52
|
+
- no dependency tracking
|
|
82
53
|
- mutation is allowed
|
|
83
|
-
-
|
|
84
|
-
|
|
85
|
-
## `effect(readFn, callback)`
|
|
54
|
+
- every write notifies every subscriber
|
|
86
55
|
|
|
87
|
-
|
|
56
|
+
### Effects are explicit reactions to signals
|
|
88
57
|
|
|
89
58
|
```ts
|
|
90
59
|
effect(count, (value) => {
|
|
@@ -92,132 +61,117 @@ effect(count, (value) => {
|
|
|
92
61
|
});
|
|
93
62
|
```
|
|
94
63
|
|
|
95
|
-
|
|
64
|
+
Characteristics:
|
|
96
65
|
|
|
97
66
|
- runs immediately with the current value
|
|
98
|
-
- runs on **every**
|
|
99
|
-
- no dependency
|
|
100
|
-
- async callbacks
|
|
67
|
+
- runs on **every** write
|
|
68
|
+
- only one singal, no dependency arrays
|
|
69
|
+
- async callbacks are allowed (fire-and-forget)
|
|
101
70
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const user = await fetch(`/api/user/${id}`).then((r) => r.json());
|
|
105
|
-
});
|
|
106
|
-
```
|
|
71
|
+
> Effects are **not awaited**.
|
|
72
|
+
> Concurrency is explicit and intentional.
|
|
107
73
|
|
|
108
|
-
|
|
109
|
-
## Context Injection
|
|
74
|
+
### Lifecycle is bound to real DOM
|
|
110
75
|
|
|
111
|
-
|
|
112
|
-
This context is automatically available in **every component** via `props.ctx`.
|
|
76
|
+
Lifecycle is structural, not conceptual.
|
|
113
77
|
|
|
114
78
|
```ts
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
api,
|
|
118
|
-
events,
|
|
119
|
-
dummy: 42,
|
|
79
|
+
mount((parent) => {
|
|
80
|
+
// runs once, when the root DOM element exists
|
|
120
81
|
});
|
|
121
|
-
```
|
|
122
|
-
````
|
|
123
82
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
p.ctx.api.fetch();
|
|
128
|
-
}
|
|
83
|
+
unmount(() => {
|
|
84
|
+
// guaranteed cleanup before removal
|
|
85
|
+
});
|
|
129
86
|
```
|
|
130
87
|
|
|
131
|
-
-
|
|
132
|
-
-
|
|
133
|
-
-
|
|
134
|
-
- no reactivity
|
|
135
|
-
|
|
136
|
-
Context is for **stable infrastructure** (stores, APIs, event buses),
|
|
137
|
-
not for frequently changing UI state.
|
|
88
|
+
- lifecycle is attached to **actual DOM nodes**
|
|
89
|
+
- children unmount before parents
|
|
90
|
+
- removal means real `destroy`
|
|
138
91
|
|
|
139
|
-
|
|
140
|
-
does not trigger re-renders.
|
|
92
|
+
---
|
|
141
93
|
|
|
142
|
-
##
|
|
94
|
+
## Example
|
|
143
95
|
|
|
144
|
-
|
|
96
|
+
```ts
|
|
97
|
+
import { render, signal } from "@papack/csr";
|
|
98
|
+
import { For } from "@papack/csr/for";
|
|
145
99
|
|
|
146
|
-
|
|
100
|
+
const [items, setItems] = signal([
|
|
101
|
+
{ uuid: "a", name: "A" },
|
|
102
|
+
{ uuid: "b", name: "B" },
|
|
103
|
+
]);
|
|
147
104
|
|
|
148
|
-
|
|
149
|
-
is attached.
|
|
105
|
+
render(<App />, { parent: document.body });
|
|
150
106
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
})
|
|
107
|
+
function App() {
|
|
108
|
+
return (
|
|
109
|
+
<ul>
|
|
110
|
+
<For each={items}>{(item) => <li>{item.name}</li>}</For>
|
|
111
|
+
</ul>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
155
114
|
```
|
|
156
115
|
|
|
157
|
-
|
|
158
|
-
- runs after the DOM node exists
|
|
159
|
-
- used for subscriptions, timers, imperative DOM work
|
|
116
|
+
## Structural Rendering
|
|
160
117
|
|
|
161
|
-
### `
|
|
118
|
+
### `Show`
|
|
162
119
|
|
|
163
|
-
|
|
120
|
+
Controls **existence**, not visibility.
|
|
164
121
|
|
|
165
122
|
```ts
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
123
|
+
<Show when={visible}>
|
|
124
|
+
<User />
|
|
125
|
+
</Show>
|
|
169
126
|
```
|
|
170
127
|
|
|
171
|
-
-
|
|
172
|
-
-
|
|
173
|
-
-
|
|
174
|
-
- guaranteed execution
|
|
128
|
+
- when `false`, the subtree is destroyed
|
|
129
|
+
- when `true`, it is rendered fresh
|
|
130
|
+
- lifecycle runs correctly on both transitions
|
|
175
131
|
|
|
176
|
-
|
|
132
|
+
### `For` (intentionally restricted)
|
|
177
133
|
|
|
178
|
-
`For` is **not
|
|
179
|
-
It is a **keyed structural renderer**.
|
|
134
|
+
`For` is a **keyed structural renderer**, not a generic iterator.
|
|
180
135
|
|
|
181
|
-
|
|
136
|
+
Rules:
|
|
182
137
|
|
|
183
|
-
- `each`
|
|
184
|
-
- each item
|
|
185
|
-
- each
|
|
186
|
-
- no fallbacks, no
|
|
138
|
+
- `each` must be an array
|
|
139
|
+
- each item must be an object
|
|
140
|
+
- each item must have a stable key (`uuid`)
|
|
141
|
+
- no fallbacks, no heuristics
|
|
187
142
|
|
|
188
143
|
```ts
|
|
189
144
|
type Item = {
|
|
190
|
-
uuid: string;
|
|
145
|
+
uuid: string;
|
|
191
146
|
[key: string]: any;
|
|
192
147
|
};
|
|
193
148
|
```
|
|
194
149
|
|
|
195
|
-
|
|
150
|
+
What `For` does:
|
|
196
151
|
|
|
197
152
|
- detects:
|
|
198
153
|
|
|
154
|
+
- additions
|
|
155
|
+
- removals
|
|
199
156
|
- order changes
|
|
200
|
-
- added items
|
|
201
|
-
- removed items
|
|
202
157
|
|
|
203
158
|
- performs:
|
|
204
159
|
|
|
205
160
|
- DOM moves (`insertBefore`)
|
|
206
|
-
- rendering
|
|
161
|
+
- rendering only for new keys
|
|
207
162
|
- `destroy()` for removed keys
|
|
208
163
|
|
|
209
|
-
|
|
164
|
+
What `For` does **not** do:
|
|
210
165
|
|
|
211
166
|
- no content diffing
|
|
212
167
|
- no re-rendering existing items
|
|
213
168
|
- no prop patching
|
|
214
|
-
- no heuristic matching
|
|
215
169
|
|
|
216
170
|
> If an item’s content changes, the item itself must be reactive.
|
|
217
171
|
|
|
218
172
|
---
|
|
219
173
|
|
|
220
|
-
## Mutation
|
|
174
|
+
## Mutation is allowed
|
|
221
175
|
|
|
222
176
|
```ts
|
|
223
177
|
setItems((prev) => {
|
|
@@ -230,16 +184,33 @@ This is **correct**.
|
|
|
230
184
|
|
|
231
185
|
Why:
|
|
232
186
|
|
|
233
|
-
-
|
|
234
|
-
-
|
|
235
|
-
- `For`
|
|
187
|
+
- signals are active
|
|
188
|
+
- updates are not reference-based
|
|
189
|
+
- `For` only cares about keys and order
|
|
236
190
|
|
|
237
|
-
##
|
|
191
|
+
## Routing
|
|
238
192
|
|
|
239
|
-
|
|
193
|
+
Routing is **just application state**.
|
|
240
194
|
|
|
241
195
|
```ts
|
|
242
|
-
|
|
243
|
-
|
|
196
|
+
const [route, setRoute] = signal("home");
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Structure is derived explicitly:
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
effect(route, (r) => {
|
|
203
|
+
setIsHome(() => r === "home");
|
|
204
|
+
setIsSettings(() => r === "settings");
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
<Show when={isHome}>
|
|
210
|
+
<Home />
|
|
211
|
+
</Show>
|
|
212
|
+
|
|
213
|
+
<Show when={isSettings}>
|
|
214
|
+
<Settings />
|
|
244
215
|
</Show>
|
|
245
216
|
```
|