@rip-lang/ui 0.1.1 → 0.1.2
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/README.md +223 -40
- package/package.json +1 -1
- package/serve.rip +5 -8
- package/ui.js +1 -1
package/README.md
CHANGED
|
@@ -18,28 +18,43 @@ functions, methods) that Rip already provides.
|
|
|
18
18
|
## Architecture
|
|
19
19
|
|
|
20
20
|
```
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
21
|
+
┌─────────────────────────────────────────┐
|
|
22
|
+
│ Server (Bun + @rip-lang/api) │
|
|
23
|
+
│ │
|
|
24
|
+
│ serve.rip (ripUI middleware) │
|
|
25
|
+
│ ├── /rip-ui/*.js → framework files │
|
|
26
|
+
│ ├── /rip-ui/manifest.json → all pages │
|
|
27
|
+
│ ├── /rip-ui/watch → SSE hot-reload │
|
|
28
|
+
│ └── /pages/*.rip → individual pages │
|
|
29
|
+
└──────────────┬──────────────────────────┘
|
|
30
|
+
│
|
|
31
|
+
┌──────────────────────────┼──────────────────────────┐
|
|
32
|
+
│ │ │
|
|
33
|
+
▼ ▼ ▼
|
|
34
|
+
rip.browser.js (40KB) @rip-lang/ui (~8KB) SSE EventSource
|
|
35
|
+
(Rip compiler) (framework modules) (hot-reload channel)
|
|
36
|
+
│ │ │
|
|
37
|
+
│ ┌────────────────┼────────────────┐ │
|
|
38
|
+
│ │ │ │ │
|
|
39
|
+
│ Reactive Stash Virtual FS Router │
|
|
40
|
+
│ (app state) (file storage) (URL → VFS) │
|
|
41
|
+
│ │ │ │ │
|
|
42
|
+
│ └────────────────┼────────────────┘ │
|
|
43
|
+
│ │ │
|
|
44
|
+
└─────────────────► Renderer ◄────────────────────┘
|
|
45
|
+
(compiles + mounts)
|
|
46
|
+
│
|
|
47
|
+
DOM
|
|
34
48
|
```
|
|
35
49
|
|
|
36
50
|
| Module | Size | Role |
|
|
37
51
|
|--------|------|------|
|
|
38
|
-
| `ui.js` | ~
|
|
52
|
+
| `ui.js` | ~175 lines | `createApp` entry point with `loadBundle`, `watch`, re-exports |
|
|
39
53
|
| `stash.js` | ~400 lines | Deep reactive state tree with path-based navigation |
|
|
40
54
|
| `vfs.js` | ~200 lines | Browser-local Virtual File System with watchers |
|
|
41
55
|
| `router.js` | ~300 lines | File-based router (URL ↔ VFS paths, History API) |
|
|
42
|
-
| `renderer.js` | ~250 lines | Component lifecycle, layouts, transitions |
|
|
56
|
+
| `renderer.js` | ~250 lines | Component lifecycle, layouts, transitions, `remount` |
|
|
57
|
+
| `serve.rip` | ~140 lines | Server middleware: framework files, manifest, SSE hot-reload |
|
|
43
58
|
|
|
44
59
|
## The Idea
|
|
45
60
|
|
|
@@ -70,7 +85,7 @@ with fine-grained DOM manipulation — no virtual DOM diffing.
|
|
|
70
85
|
|---|---|---|
|
|
71
86
|
| **Build step** | Required (Vite, Webpack, etc.) | None — compiler runs in browser |
|
|
72
87
|
| **Bundle size** | 40-100KB+ framework + app bundle | 40KB compiler + ~8KB framework + raw source |
|
|
73
|
-
| **HMR** | Dev server ↔ browser WebSocket |
|
|
88
|
+
| **HMR** | Dev server ↔ browser WebSocket | SSE notify + VFS invalidation + recompile |
|
|
74
89
|
| **Deployment** | Build artifacts (`dist/`) | Source files served as-is |
|
|
75
90
|
| **Component format** | JSX, SFC, templates | Rip source (`.rip` files) |
|
|
76
91
|
| **Reactivity** | Library-specific (hooks, refs, signals) | Language-native (`:=`, `~=`, `~>`) |
|
|
@@ -465,9 +480,158 @@ Compiled components are cached by VFS path. A file is only recompiled when it
|
|
|
465
480
|
changes. The VFS watcher triggers cache invalidation, so updating a file in the
|
|
466
481
|
VFS automatically causes the next render to use the new version.
|
|
467
482
|
|
|
483
|
+
## Server Integration
|
|
484
|
+
|
|
485
|
+
### `ripUI` Middleware
|
|
486
|
+
|
|
487
|
+
The `ripUI` export from `@rip-lang/ui/serve` is a setup function that registers
|
|
488
|
+
routes for serving framework files, auto-generated page manifests, and an SSE
|
|
489
|
+
hot-reload channel. It works with `@rip-lang/api`'s `use()`:
|
|
490
|
+
|
|
491
|
+
```coffee
|
|
492
|
+
import { use } from '@rip-lang/api'
|
|
493
|
+
import { ripUI } from '@rip-lang/ui/serve'
|
|
494
|
+
|
|
495
|
+
use ripUI pages: 'pages', watch: true
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
When called, `ripUI` registers the following routes:
|
|
499
|
+
|
|
500
|
+
| Route | Description |
|
|
501
|
+
|-------|-------------|
|
|
502
|
+
| `GET /rip-ui/ui.js` | Framework entry point |
|
|
503
|
+
| `GET /rip-ui/stash.js` | Reactive state module |
|
|
504
|
+
| `GET /rip-ui/vfs.js` | Virtual File System |
|
|
505
|
+
| `GET /rip-ui/router.js` | File-based router |
|
|
506
|
+
| `GET /rip-ui/renderer.js` | Component renderer |
|
|
507
|
+
| `GET /rip-ui/compiler.js` | Rip compiler (40KB) |
|
|
508
|
+
| `GET /rip-ui/manifest.json` | Auto-generated manifest of all `.rip` pages |
|
|
509
|
+
| `GET /rip-ui/watch` | SSE hot-reload endpoint (when `watch: true`) |
|
|
510
|
+
| `GET /pages/*` | Individual `.rip` page files (for hot-reload refetch) |
|
|
511
|
+
|
|
512
|
+
### Options
|
|
513
|
+
|
|
514
|
+
| Option | Type | Default | Description |
|
|
515
|
+
|--------|------|---------|-------------|
|
|
516
|
+
| `pages` | `string` | `'pages'` | Directory containing `.rip` page files |
|
|
517
|
+
| `base` | `string` | `'/rip-ui'` | URL prefix for framework files |
|
|
518
|
+
| `watch` | `boolean` | `false` | Enable SSE hot-reload endpoint |
|
|
519
|
+
| `debounce` | `number` | `250` | Milliseconds to batch filesystem change events |
|
|
520
|
+
|
|
521
|
+
### Page Manifest
|
|
522
|
+
|
|
523
|
+
The manifest endpoint (`/rip-ui/manifest.json`) auto-discovers every `.rip` file
|
|
524
|
+
in the `pages` directory and bundles them into a single JSON response:
|
|
525
|
+
|
|
526
|
+
```json
|
|
527
|
+
{
|
|
528
|
+
"pages/index.rip": "Home = component\n render\n h1 \"Hello\"",
|
|
529
|
+
"pages/about.rip": "About = component\n render\n h1 \"About\"",
|
|
530
|
+
"pages/counter.rip": "..."
|
|
531
|
+
}
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
The client loads this with `app.loadBundle('/rip-ui/manifest.json')`, which
|
|
535
|
+
populates the VFS in a single request — no need to list pages manually.
|
|
536
|
+
|
|
537
|
+
## Hot Reload
|
|
538
|
+
|
|
539
|
+
The hot-reload system uses a **notify-only** architecture — the server tells the
|
|
540
|
+
browser *which files changed*, then the browser decides what to refetch.
|
|
541
|
+
|
|
542
|
+
### How It Works
|
|
543
|
+
|
|
544
|
+
```
|
|
545
|
+
Developer saves a .rip file
|
|
546
|
+
│
|
|
547
|
+
▼
|
|
548
|
+
Server (fs.watch)
|
|
549
|
+
├── Debounce (250ms, batches rapid saves)
|
|
550
|
+
└── SSE "changed" event: { paths: ["pages/counter.rip"] }
|
|
551
|
+
│
|
|
552
|
+
▼
|
|
553
|
+
Browser (EventSource)
|
|
554
|
+
├── Invalidate VFS entries (fs.delete)
|
|
555
|
+
├── Rebuild router (router.rebuild)
|
|
556
|
+
├── Smart refetch:
|
|
557
|
+
│ ├── Current page file? → fetch immediately
|
|
558
|
+
│ ├── Active layout? → fetch immediately
|
|
559
|
+
│ ├── Eager file? → fetch immediately
|
|
560
|
+
│ └── Other pages? → fetch lazily on next navigation
|
|
561
|
+
└── Re-render (renderer.remount)
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### Server Side
|
|
565
|
+
|
|
566
|
+
The `watch` option enables filesystem monitoring with debounced SSE
|
|
567
|
+
notifications. A heartbeat every 5 seconds keeps the connection alive:
|
|
568
|
+
|
|
569
|
+
```coffee
|
|
570
|
+
use ripUI pages: "#{dir}/pages", watch: true, debounce: 250
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### Client Side
|
|
574
|
+
|
|
575
|
+
Connect to the SSE endpoint after starting the app:
|
|
576
|
+
|
|
577
|
+
```javascript
|
|
578
|
+
app.watch('/rip-ui/watch');
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
The `watch()` method accepts an optional `eager` array — files that should always
|
|
582
|
+
be refetched immediately, even if they're not the current route:
|
|
583
|
+
|
|
584
|
+
```javascript
|
|
585
|
+
app.watch('/rip-ui/watch', {
|
|
586
|
+
eager: ['pages/_layout.rip']
|
|
587
|
+
});
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### `loadBundle(url)`
|
|
591
|
+
|
|
592
|
+
Fetches a JSON manifest containing all page sources and loads them into the VFS
|
|
593
|
+
in a single request. Returns the app instance (chainable).
|
|
594
|
+
|
|
595
|
+
```javascript
|
|
596
|
+
await app.loadBundle('/rip-ui/manifest.json');
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
### `remount()`
|
|
600
|
+
|
|
601
|
+
The renderer's `remount()` method re-renders the current route. This is called
|
|
602
|
+
automatically by `watch()` after refetching changed files, but it can also be
|
|
603
|
+
called manually:
|
|
604
|
+
|
|
605
|
+
```javascript
|
|
606
|
+
app.renderer.remount();
|
|
607
|
+
```
|
|
608
|
+
|
|
468
609
|
## Quick Start
|
|
469
610
|
|
|
470
|
-
###
|
|
611
|
+
### With Server Integration (Recommended)
|
|
612
|
+
|
|
613
|
+
The fastest way to get started is with `@rip-lang/api` and the `ripUI` middleware.
|
|
614
|
+
The middleware serves all framework files, auto-generates a page manifest, and
|
|
615
|
+
provides hot-reload — your server is just a few lines:
|
|
616
|
+
|
|
617
|
+
**`index.rip`** — The complete server:
|
|
618
|
+
|
|
619
|
+
```coffee
|
|
620
|
+
import { get, use, start, notFound } from '@rip-lang/api'
|
|
621
|
+
import { ripUI } from '@rip-lang/ui/serve'
|
|
622
|
+
|
|
623
|
+
dir = import.meta.dir
|
|
624
|
+
|
|
625
|
+
use ripUI pages: "#{dir}/pages", watch: true
|
|
626
|
+
|
|
627
|
+
get '/css/*', -> @send "#{dir}/css/#{@req.path.slice(5)}"
|
|
628
|
+
|
|
629
|
+
notFound -> @send "#{dir}/index.html", 'text/html; charset=UTF-8'
|
|
630
|
+
|
|
631
|
+
start port: 3000
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
**`index.html`** — The HTML shell:
|
|
471
635
|
|
|
472
636
|
```html
|
|
473
637
|
<!DOCTYPE html>
|
|
@@ -476,30 +640,30 @@ VFS automatically causes the next render to use the new version.
|
|
|
476
640
|
<body>
|
|
477
641
|
<div id="app"></div>
|
|
478
642
|
<script type="module">
|
|
479
|
-
import { compileToJS } from '
|
|
480
|
-
import { createApp } from '
|
|
643
|
+
import { compileToJS } from '/rip-ui/compiler.js';
|
|
644
|
+
import { createApp } from '/rip-ui/ui.js';
|
|
481
645
|
|
|
482
646
|
const app = createApp({
|
|
483
647
|
target: '#app',
|
|
484
648
|
compile: compileToJS,
|
|
485
649
|
state: { theme: 'light' }
|
|
486
|
-
})
|
|
650
|
+
});
|
|
487
651
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
'pages/index.rip',
|
|
492
|
-
'pages/about.rip'
|
|
493
|
-
])
|
|
494
|
-
|
|
495
|
-
// Start routing and rendering
|
|
496
|
-
app.start()
|
|
652
|
+
await app.loadBundle('/rip-ui/manifest.json');
|
|
653
|
+
app.start();
|
|
654
|
+
app.watch('/rip-ui/watch');
|
|
497
655
|
</script>
|
|
498
656
|
</body>
|
|
499
657
|
</html>
|
|
500
658
|
```
|
|
501
659
|
|
|
502
|
-
|
|
660
|
+
That's it. Run `bun index.rip` and open `http://localhost:3000`. Every `.rip`
|
|
661
|
+
file in the `pages/` directory is auto-discovered, served as a manifest, and
|
|
662
|
+
hot-reloaded on save.
|
|
663
|
+
|
|
664
|
+
### Standalone (No Server)
|
|
665
|
+
|
|
666
|
+
For static deployments or quick prototyping, you can inline components directly:
|
|
503
667
|
|
|
504
668
|
```html
|
|
505
669
|
<script type="module">
|
|
@@ -525,13 +689,8 @@ VFS automatically causes the next render to use the new version.
|
|
|
525
689
|
|
|
526
690
|
```
|
|
527
691
|
my-app/
|
|
528
|
-
├── index.
|
|
529
|
-
├──
|
|
530
|
-
├── ui.js # Framework entry point
|
|
531
|
-
├── stash.js # Reactive state
|
|
532
|
-
├── vfs.js # Virtual File System
|
|
533
|
-
├── router.js # File-based router
|
|
534
|
-
├── renderer.js # Component renderer
|
|
692
|
+
├── index.rip # Server (uses @rip-lang/api + ripUI middleware)
|
|
693
|
+
├── index.html # HTML shell
|
|
535
694
|
├── pages/
|
|
536
695
|
│ ├── _layout.rip # Root layout (nav, footer)
|
|
537
696
|
│ ├── index.rip # Home page → /
|
|
@@ -544,6 +703,10 @@ my-app/
|
|
|
544
703
|
└── styles.css # Tailwind or plain CSS
|
|
545
704
|
```
|
|
546
705
|
|
|
706
|
+
> **Note:** Framework files (`ui.js`, `stash.js`, `router.js`, etc.) are served
|
|
707
|
+
> automatically by the `ripUI` middleware from the installed `@rip-lang/ui`
|
|
708
|
+
> package — you don't need to copy them into your project.
|
|
709
|
+
|
|
547
710
|
## Render Template Syntax
|
|
548
711
|
|
|
549
712
|
The `render` block uses a concise, indentation-based template syntax:
|
|
@@ -641,7 +804,21 @@ render
|
|
|
641
804
|
| `onError` | `function` | — | Error handler |
|
|
642
805
|
| `onNavigate` | `function` | — | Navigation callback |
|
|
643
806
|
|
|
644
|
-
Returns
|
|
807
|
+
Returns an instance with these methods:
|
|
808
|
+
|
|
809
|
+
| Method | Description |
|
|
810
|
+
|--------|-------------|
|
|
811
|
+
| `start()` | Start routing and rendering |
|
|
812
|
+
| `stop()` | Unmount components, close SSE connection, clean up |
|
|
813
|
+
| `load(paths)` | Fetch `.rip` files from server into VFS |
|
|
814
|
+
| `loadBundle(url)` | Fetch a JSON manifest and bulk-load all pages into VFS |
|
|
815
|
+
| `watch(url, opts?)` | Connect to SSE hot-reload endpoint |
|
|
816
|
+
| `go(path)` | Navigate to a route |
|
|
817
|
+
| `addPage(path, source)` | Add a page to the VFS |
|
|
818
|
+
| `get(key)` | Get app state value |
|
|
819
|
+
| `set(key, value)` | Set app state value |
|
|
820
|
+
|
|
821
|
+
Also exposes: `app` (stash), `fs` (VFS), `router`, `renderer`
|
|
645
822
|
|
|
646
823
|
### `stash(data)`
|
|
647
824
|
|
|
@@ -686,9 +863,15 @@ API.
|
|
|
686
863
|
|
|
687
864
|
MIT
|
|
688
865
|
|
|
866
|
+
## Requirements
|
|
867
|
+
|
|
868
|
+
- [Bun](https://bun.sh) runtime
|
|
869
|
+
- `@rip-lang/api` ≥ 1.1.4 (for `@send` file serving used by `ripUI` middleware)
|
|
870
|
+
- `rip-lang` ≥ 3.1.1 (Rip compiler with browser build)
|
|
871
|
+
|
|
689
872
|
## Links
|
|
690
873
|
|
|
691
874
|
- [Rip Language](https://github.com/shreeve/rip-lang)
|
|
692
|
-
- [@rip-lang/api](../api/README.md)
|
|
693
|
-
- [@rip-lang/server](../server/README.md)
|
|
875
|
+
- [@rip-lang/api](../api/README.md) — API framework (routing, middleware, `@send`)
|
|
876
|
+
- [@rip-lang/server](../server/README.md) — Production server (HTTPS, mDNS, multi-worker)
|
|
694
877
|
- [Report Issues](https://github.com/shreeve/rip-lang/issues)
|
package/package.json
CHANGED
package/serve.rip
CHANGED
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
# ==============================================================================
|
|
24
24
|
|
|
25
25
|
import { get } from '@rip-lang/api'
|
|
26
|
-
import { brotliCompressSync } from 'node:zlib'
|
|
27
26
|
import { watch as fsWatch } from 'node:fs'
|
|
28
27
|
|
|
29
28
|
export ripUI = (opts = {}) ->
|
|
@@ -68,19 +67,17 @@ export ripUI = (opts = {}) ->
|
|
|
68
67
|
if files[name]
|
|
69
68
|
return c.send files[name], 'application/javascript'
|
|
70
69
|
|
|
71
|
-
# Auto-generated manifest — bundles all .rip sources as JSON
|
|
70
|
+
# Auto-generated manifest — bundles all .rip page sources as JSON
|
|
71
|
+
# Written to file so Bun.file() responses proxy cleanly through rip-server
|
|
72
72
|
if name is 'manifest.json'
|
|
73
73
|
glob = new Bun.Glob("**/*.rip")
|
|
74
74
|
bundle = {}
|
|
75
75
|
paths = Array.from(glob.scanSync(pagesDir))
|
|
76
76
|
for path in paths
|
|
77
77
|
bundle["pages/#{path}"] = Bun.file("#{pagesDir}/#{path}").text!
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return
|
|
81
|
-
headers:
|
|
82
|
-
'Content-Type': 'application/json'
|
|
83
|
-
'Content-Encoding': 'br'
|
|
78
|
+
manifestPath = "#{pagesDir}/.manifest.json"
|
|
79
|
+
Bun.write manifestPath, JSON.stringify(bundle)
|
|
80
|
+
return c.send manifestPath, 'application/json'
|
|
84
81
|
|
|
85
82
|
# SSE watch endpoint — debounced, notify-only, with heartbeat
|
|
86
83
|
if name is 'watch' and enableWatch
|
package/ui.js
CHANGED
|
@@ -203,6 +203,6 @@ function defaultErrorHandler({ status, message, path, error }) {
|
|
|
203
203
|
// Version
|
|
204
204
|
// ---------------------------------------------------------------------------
|
|
205
205
|
|
|
206
|
-
export const VERSION = '0.1.
|
|
206
|
+
export const VERSION = '0.1.2';
|
|
207
207
|
|
|
208
208
|
export default createApp;
|