rip-lang 3.7.4 → 3.8.8
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/CHANGELOG.md +97 -1
- package/README.md +42 -34
- package/docs/RIP-INTERNALS.md +2 -4
- package/docs/RIP-LANG.md +150 -3
- package/docs/RIP-TYPES.md +1 -2
- package/docs/demo.html +342 -0
- package/docs/dist/rip-ui.min.js +516 -0
- package/docs/dist/rip-ui.min.js.br +0 -0
- package/docs/dist/rip.browser.js +285 -406
- package/docs/dist/rip.browser.min.js +166 -204
- package/docs/dist/rip.browser.min.js.br +0 -0
- package/docs/dist/ui.js +956 -0
- package/docs/dist/ui.min.js +2 -0
- package/docs/dist/ui.min.js.br +0 -0
- package/docs/dist/ui.rip +957 -0
- package/docs/dist/ui.rip.br +0 -0
- package/docs/examples.rip +180 -0
- package/docs/index.html +3 -1599
- package/docs/playground-app.html +1022 -0
- package/docs/playground-js.html +1645 -0
- package/docs/playground-rip-ui.html +1419 -0
- package/docs/playground-rip.html +1450 -0
- package/docs/rip-fav.svg +5 -0
- package/package.json +3 -3
- package/scripts/serve.js +3 -2
- package/src/browser.js +21 -5
- package/src/compiler.js +148 -221
- package/src/components.js +100 -95
- package/src/grammar/README.md +234 -0
- package/src/grammar/lunar.rip +2412 -0
- package/src/grammar/solar.rip +18 -4
- package/src/lexer.js +53 -24
- package/src/parser-rd.js +3242 -0
- package/src/parser.js +6 -5
- package/src/repl.js +24 -5
- package/docs/NOTES.md +0 -93
- package/docs/RIP-GUIDE.md +0 -698
- package/docs/RIP-REACTIVITY.md +0 -311
package/docs/RIP-REACTIVITY.md
DELETED
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
<img src="https://raw.githubusercontent.com/shreeve/rip-lang/main/docs/rip.png" style="width:50px" /> <br>
|
|
2
|
-
|
|
3
|
-
# Rip Reactivity
|
|
4
|
-
|
|
5
|
-
Rip implements a **fine-grained reactive system** that rivals and often exceeds the capabilities of major frameworks like Vue, Svelte, Solid, and React — in just ~200 lines of runtime code.
|
|
6
|
-
|
|
7
|
-
## The Reactive Triad
|
|
8
|
-
|
|
9
|
-
Rip's entire reactivity model is built on three foundational concepts:
|
|
10
|
-
|
|
11
|
-
| Primitive | Description | Read As | Role | Purpose |
|
|
12
|
-
|-----------|-------------|---------|------|---------|
|
|
13
|
-
| `:=` | state | "has state" | **Source** | Where reactive data originates |
|
|
14
|
-
| `~=` | computed | "always equals" | **Derivation** | Computed values (lazy, cached) |
|
|
15
|
-
| `~>` | effect | "reacts to" | **Reaction** | Side effects when data changes |
|
|
16
|
-
|
|
17
|
-
These three primitives are the **minimal complete set** for reactive programming — everything React, Vue, Svelte, and Solid can do with state management, Rip can do too.
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## Why These Three Are Complete
|
|
22
|
-
|
|
23
|
-
Every reactive system reduces to these three concepts:
|
|
24
|
-
|
|
25
|
-
| Framework | Source | Derivation | Reaction |
|
|
26
|
-
|-----------|--------|------------|----------|
|
|
27
|
-
| **Rip** | `:=` | `~=` | `~>` |
|
|
28
|
-
| Angular | `signal()` | `computed()` | `effect()` |
|
|
29
|
-
| Imba | `@property` | implicit | implicit |
|
|
30
|
-
| MobX | `observable` | `computed` | `autorun` |
|
|
31
|
-
| Next.js | `useState` | `useMemo` | `useEffect` |
|
|
32
|
-
| React | `useState` | `useMemo` | `useEffect` |
|
|
33
|
-
| Solid | `createSignal` | `createMemo` | `createEffect` |
|
|
34
|
-
| Svelte 4 | `let x` | `$: x` | `$: {}` |
|
|
35
|
-
| Svelte 5 | `$state` | `$derived` | `$effect` |
|
|
36
|
-
| Vue | `ref()` | `computed()` | `watch()` |
|
|
37
|
-
|
|
38
|
-
### What You Can Build From These Three
|
|
39
|
-
|
|
40
|
-
- **Stores** → objects with state properties
|
|
41
|
-
- **Two-way binding** → state + effect that syncs
|
|
42
|
-
- **Async resources** → state + effect that fetches
|
|
43
|
-
- **Event handling** → update state → triggers reactions
|
|
44
|
-
- **Derived stores** → computed from other state
|
|
45
|
-
|
|
46
|
-
### What's Missing Without Any One
|
|
47
|
-
|
|
48
|
-
- **Without `:=`** → No source of truth
|
|
49
|
-
- **Without `~=`** → Manual tracking or inefficient effects
|
|
50
|
-
- **Without `~>`** → Can't react to changes (no side effects)
|
|
51
|
-
|
|
52
|
-
---
|
|
53
|
-
|
|
54
|
-
## Quick Example
|
|
55
|
-
|
|
56
|
-
```coffee
|
|
57
|
-
count := 0 # count has state 0
|
|
58
|
-
doubled ~= count * 2 # doubled always equals count * 2
|
|
59
|
-
logger ~> console.log count # reacts to count changes
|
|
60
|
-
~> console.log count # same thing, but the "fire and forget" version
|
|
61
|
-
|
|
62
|
-
increment: -> count += 1
|
|
63
|
-
|
|
64
|
-
increment() # Logs: 1
|
|
65
|
-
increment() # Logs: 2
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
---
|
|
69
|
-
|
|
70
|
-
## How It Works
|
|
71
|
-
|
|
72
|
-
### State (`:=`) — "has state"
|
|
73
|
-
|
|
74
|
-
State creates a **reactive container** that tracks its readers and notifies them on change.
|
|
75
|
-
|
|
76
|
-
```coffee
|
|
77
|
-
count := 0 # count has state 0
|
|
78
|
-
count += 1 # Update triggers dependents
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
**Compiles to:**
|
|
82
|
-
```javascript
|
|
83
|
-
const count = __state(0);
|
|
84
|
-
count.value += 1;
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
**What happens internally:**
|
|
88
|
-
1. Reading `count.value` inside an effect/computed **tracks** the dependency
|
|
89
|
-
2. Writing to `count.value` **notifies** all subscribers
|
|
90
|
-
3. Effects re-run, computeds mark dirty
|
|
91
|
-
|
|
92
|
-
### Computed (`~=`) — "always equals"
|
|
93
|
-
|
|
94
|
-
Computed creates a **computed value** that automatically updates when dependencies change.
|
|
95
|
-
|
|
96
|
-
```coffee
|
|
97
|
-
count := 0
|
|
98
|
-
doubled ~= count * 2 # doubled always equals count * 2
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
**Key features:**
|
|
102
|
-
- **Lazy** — only computes when read
|
|
103
|
-
- **Cached** — won't recompute unless dependencies change
|
|
104
|
-
- **Chainable** — computeds can depend on other computeds
|
|
105
|
-
|
|
106
|
-
### Effect (`~>`) — "reacts to"
|
|
107
|
-
|
|
108
|
-
The effect operator runs **side effects** when dependencies change. Dependencies are auto-tracked from reactive values read in the body.
|
|
109
|
-
|
|
110
|
-
```coffee
|
|
111
|
-
~> document.title = "Count: #{count}"
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
**Key features:**
|
|
115
|
-
- **Auto-tracking** — dependencies detected automatically from body
|
|
116
|
-
- **Immediate** — runs once immediately to establish dependencies
|
|
117
|
-
- **Controllable** — optionally assign to a variable to control the effect
|
|
118
|
-
|
|
119
|
-
**Syntax:**
|
|
120
|
-
```coffee
|
|
121
|
-
# Fire and forget (no assignment)
|
|
122
|
-
~> console.log count
|
|
123
|
-
|
|
124
|
-
# Controllable (assign to variable)
|
|
125
|
-
logger ~> console.log count
|
|
126
|
-
logger.stop! # Pause reactions
|
|
127
|
-
logger.run! # Resume reactions
|
|
128
|
-
logger.cancel! # Permanent disposal
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
---
|
|
132
|
-
|
|
133
|
-
## Comparison with Major Frameworks
|
|
134
|
-
|
|
135
|
-
### State
|
|
136
|
-
|
|
137
|
-
| Feature | Rip | Vue | Solid | React |
|
|
138
|
-
|---------|:---:|:---:|:-----:|:-----:|
|
|
139
|
-
| Auto-tracking on read | ✅ | ✅ | ✅ | ❌ |
|
|
140
|
-
| Same-value skip | ✅ | ✅ | ✅ | ✅ |
|
|
141
|
-
| Re-entry protection | ✅ | ❌ | ❌ | ❌ |
|
|
142
|
-
| Lock for SSR | ✅ | ❌ | ❌ | ❌ |
|
|
143
|
-
| Cleanup/disposal | ✅ | ✅ | ✅ | ❌ |
|
|
144
|
-
| Raw read (untracked) | ✅ | ✅ | ✅ | ❌ |
|
|
145
|
-
| Primitive coercion | ✅ | ❌ | ❌ | N/A |
|
|
146
|
-
|
|
147
|
-
**Rip advantage:** `.lock()`, `.kill()`, `.read()` utilities that others lack.
|
|
148
|
-
|
|
149
|
-
### Computed
|
|
150
|
-
|
|
151
|
-
| Feature | Rip | Vue | Solid | MobX |
|
|
152
|
-
|---------|:---:|:---:|:-----:|:----:|
|
|
153
|
-
| Lazy evaluation | ✅ | ✅ | ✅ | ✅ |
|
|
154
|
-
| Cached until deps change | ✅ | ✅ | ✅ | ✅ |
|
|
155
|
-
| Dirty propagation | ✅ | ✅ | ✅ | ✅ |
|
|
156
|
-
| Auto dependency cleanup | ✅ | ✅ | ✅ | ✅ |
|
|
157
|
-
| Chainable | ✅ | ✅ | ✅ | ✅ |
|
|
158
|
-
| Read without tracking | ✅ | ❌ | ❌ | ❌ |
|
|
159
|
-
| Lock (freeze value) | ✅ | ❌ | ❌ | ❌ |
|
|
160
|
-
|
|
161
|
-
**Rip advantage:** `.read()` for untracked access, `.lock()` to freeze.
|
|
162
|
-
|
|
163
|
-
### Effect (`~>`)
|
|
164
|
-
|
|
165
|
-
| Feature | Rip | Vue | Svelte | React |
|
|
166
|
-
|---------|:---:|:---:|:------:|:-----:|
|
|
167
|
-
| Auto-tracking | ✅ | ✅ | ✅ | ❌ |
|
|
168
|
-
| No manual deps array | ✅ | ✅ | ✅ | ❌ |
|
|
169
|
-
| Runs immediately | ✅ | ✅ | ✅ | ✅ |
|
|
170
|
-
| Controllable (stop/run) | ✅ | ✅ | ✅ | ❌ |
|
|
171
|
-
| Re-runs on change | ✅ | ✅ | ✅ | ✅ |
|
|
172
|
-
|
|
173
|
-
**Rip advantage over React:** No manual dependency arrays!
|
|
174
|
-
|
|
175
|
-
```javascript
|
|
176
|
-
// React - manual, error-prone
|
|
177
|
-
useEffect(() => {
|
|
178
|
-
document.title = `Count: ${count}`;
|
|
179
|
-
}, [count]); // 😩 Must list deps manually
|
|
180
|
-
|
|
181
|
-
// Rip - automatic
|
|
182
|
-
~> document.title = "Count: #{count}" // 🎉 Deps tracked automatically
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
### Bundle Size
|
|
186
|
-
|
|
187
|
-
| Framework | Runtime Size |
|
|
188
|
-
|-----------|-------------|
|
|
189
|
-
| React | ~40 KB (minified) |
|
|
190
|
-
| Vue | ~34 KB |
|
|
191
|
-
| Svelte | ~2 KB (but grows with app size) |
|
|
192
|
-
| **Rip** | **~4 KB** (full runtime) |
|
|
193
|
-
|
|
194
|
-
---
|
|
195
|
-
|
|
196
|
-
## Advanced Features
|
|
197
|
-
|
|
198
|
-
### Batching
|
|
199
|
-
|
|
200
|
-
Group multiple updates into a single flush:
|
|
201
|
-
|
|
202
|
-
```javascript
|
|
203
|
-
__batch(() => {
|
|
204
|
-
count.value = 1;
|
|
205
|
-
name.value = "Alice";
|
|
206
|
-
// Effects run once at the end, not twice
|
|
207
|
-
});
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
### Untracked Reads
|
|
211
|
-
|
|
212
|
-
Read a value without creating a dependency:
|
|
213
|
-
|
|
214
|
-
```javascript
|
|
215
|
-
const currentValue = count.read(); // No tracking
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
### Locking (SSR/Hydration)
|
|
219
|
-
|
|
220
|
-
Prevent writes during server-side rendering or freeze values:
|
|
221
|
-
|
|
222
|
-
```javascript
|
|
223
|
-
count.lock(); // Now immutable
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
### Cleanup
|
|
227
|
-
|
|
228
|
-
Dispose of reactive values:
|
|
229
|
-
|
|
230
|
-
```javascript
|
|
231
|
-
const finalValue = count.kill(); // Returns value, clears subscribers
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
---
|
|
235
|
-
|
|
236
|
-
## The Architecture
|
|
237
|
-
|
|
238
|
-
```
|
|
239
|
-
┌─────────────┐ reads ┌─────────────┐
|
|
240
|
-
│ STATE │◄──────────────│ EFFECT │
|
|
241
|
-
│ count │ │ (side fx) │
|
|
242
|
-
└──────┬──────┘ └─────────────┘
|
|
243
|
-
│ ▲
|
|
244
|
-
│ notifies │ triggers
|
|
245
|
-
▼ │
|
|
246
|
-
┌─────────────┐ reads ┌──────┴──────┐
|
|
247
|
-
│ COMPUTED │◄──────────────│ EFFECT │
|
|
248
|
-
│ doubled │ │ (logger) │
|
|
249
|
-
└─────────────┘ └─────────────┘
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
1. **State** is the source of truth
|
|
253
|
-
2. **Computed** derives from state (lazy, cached)
|
|
254
|
-
3. **Effects** react to state/computed changes
|
|
255
|
-
4. **Changes propagate** through the dependency graph
|
|
256
|
-
|
|
257
|
-
---
|
|
258
|
-
|
|
259
|
-
## Why Fine-Grained Reactivity?
|
|
260
|
-
|
|
261
|
-
Rip uses **fine-grained reactivity** (like Vue/Solid), not Virtual DOM diffing (like React).
|
|
262
|
-
|
|
263
|
-
| Approach | How it works | Pros | Cons |
|
|
264
|
-
|----------|--------------|------|------|
|
|
265
|
-
| **VDOM** (React) | Re-render tree, diff, patch | Simple mental model | Overhead, requires optimization |
|
|
266
|
-
| **Fine-grained** (Rip) | Track dependencies, update directly | Surgical updates, fast | More complex internally |
|
|
267
|
-
|
|
268
|
-
### Result
|
|
269
|
-
|
|
270
|
-
- **No VDOM overhead** — changes propagate directly to subscribers
|
|
271
|
-
- **Surgical updates** — only the exact things that changed update
|
|
272
|
-
- **No `useMemo`/`useCallback` dance** — caching is automatic
|
|
273
|
-
- **Smaller bundles** — no diffing algorithm needed
|
|
274
|
-
|
|
275
|
-
---
|
|
276
|
-
|
|
277
|
-
## Summary
|
|
278
|
-
|
|
279
|
-
Rip's reactivity system:
|
|
280
|
-
|
|
281
|
-
✅ **The Reactive Triad** — state (`:=`), computed (`~=`), effect (`~>`) <br>
|
|
282
|
-
✅ **Natural reading** — "has state", "always equals", "reacts to" <br>
|
|
283
|
-
✅ **Minimal complete set** — same model as Vue, Solid, MobX <br>
|
|
284
|
-
✅ **Lazy computed** — only calculates when needed <br>
|
|
285
|
-
✅ **Fine-grained** — surgical updates to subscribers <br>
|
|
286
|
-
✅ **Controllable effects** — `.stop!`, `.run!`, `.cancel!` when needed <br>
|
|
287
|
-
✅ **Tiny runtime** — ~200 lines, ~4 KB <br>
|
|
288
|
-
✅ **Extra utilities** — `.lock()`, `.read()`, `.kill()` that others lack <br>
|
|
289
|
-
|
|
290
|
-
**On par with Vue/Solid. Better than React. A fraction of the size.**
|
|
291
|
-
|
|
292
|
-
---
|
|
293
|
-
|
|
294
|
-
## Types and Reactivity
|
|
295
|
-
|
|
296
|
-
Reactive operators work with Rip's optional type system:
|
|
297
|
-
|
|
298
|
-
```coffee
|
|
299
|
-
count:: number := 0 # Typed state
|
|
300
|
-
doubled:: number ~= count * 2 # Typed computed
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
Type annotations are erased from `.js` output. In `.d.ts` output, reactive
|
|
304
|
-
state emits `Signal<T>` and computed values emit `Computed<T>`:
|
|
305
|
-
|
|
306
|
-
```ts
|
|
307
|
-
declare const count: Signal<number>;
|
|
308
|
-
declare const doubled: Computed<number>;
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
See [RIP-TYPES.md](RIP-TYPES.md) for the complete type system specification.
|