@rip-lang/ui 0.1.3 → 0.3.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 +209 -770
- package/package.json +7 -18
- package/serve.rip +98 -79
- package/ui.rip +964 -0
- package/renderer.js +0 -397
- package/router.js +0 -325
- package/stash.js +0 -413
- package/ui.js +0 -208
- package/vfs.js +0 -215
package/package.json
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rip-lang/ui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Zero-build reactive web framework —
|
|
3
|
+
"version": "0.3.2",
|
|
4
|
+
"description": "Zero-build reactive web framework — rip.js + ui.rip + launch(url)",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "ui.
|
|
6
|
+
"main": "ui.rip",
|
|
7
7
|
"exports": {
|
|
8
|
-
".": "./ui.
|
|
9
|
-
"./stash": "./stash.js",
|
|
10
|
-
"./vfs": "./vfs.js",
|
|
11
|
-
"./router": "./router.js",
|
|
12
|
-
"./renderer": "./renderer.js",
|
|
8
|
+
".": "./ui.rip",
|
|
13
9
|
"./serve": "./serve.rip"
|
|
14
10
|
},
|
|
15
11
|
"scripts": {
|
|
@@ -19,9 +15,6 @@
|
|
|
19
15
|
"ui",
|
|
20
16
|
"framework",
|
|
21
17
|
"reactive",
|
|
22
|
-
"vfs",
|
|
23
|
-
"router",
|
|
24
|
-
"stash",
|
|
25
18
|
"signals",
|
|
26
19
|
"no-build",
|
|
27
20
|
"rip",
|
|
@@ -39,19 +32,15 @@
|
|
|
39
32
|
"author": "Steve Shreeve <steve.shreeve@gmail.com>",
|
|
40
33
|
"license": "MIT",
|
|
41
34
|
"dependencies": {
|
|
42
|
-
"rip-lang": "^3.
|
|
35
|
+
"rip-lang": "^3.9.1"
|
|
43
36
|
},
|
|
44
37
|
"files": [
|
|
45
|
-
"ui.
|
|
46
|
-
"stash.js",
|
|
47
|
-
"vfs.js",
|
|
48
|
-
"router.js",
|
|
49
|
-
"renderer.js",
|
|
38
|
+
"ui.rip",
|
|
50
39
|
"serve.rip",
|
|
51
40
|
"README.md"
|
|
52
41
|
],
|
|
53
42
|
"peerDependencies": {
|
|
54
|
-
"@rip-lang/api": ">=1.1.
|
|
43
|
+
"@rip-lang/api": ">=1.1.6"
|
|
55
44
|
},
|
|
56
45
|
"peerDependenciesMeta": {
|
|
57
46
|
"@rip-lang/api": {
|
package/serve.rip
CHANGED
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
# ==============================================================================
|
|
2
|
-
# @rip-lang/ui/serve — Rip UI Middleware
|
|
2
|
+
# @rip-lang/ui/serve — Rip UI Server Middleware
|
|
3
3
|
# ==============================================================================
|
|
4
4
|
#
|
|
5
|
-
# Serves the Rip UI
|
|
6
|
-
#
|
|
5
|
+
# Serves the Rip UI runtime, auto-generated app bundles, and optional
|
|
6
|
+
# SSE hot-reload.
|
|
7
7
|
#
|
|
8
8
|
# Usage:
|
|
9
9
|
# import { ripUI } from '@rip-lang/ui/serve'
|
|
10
|
-
#
|
|
11
|
-
# use ripUI pages: 'pages', watch: true
|
|
10
|
+
# use ripUI dir: dir, components: 'pages', includes: ['ui'], watch: true, title: 'My App'
|
|
12
11
|
#
|
|
13
12
|
# Options:
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
13
|
+
# app: string — URL mount point (default: '')
|
|
14
|
+
# dir: string — app directory on disk (default: '.')
|
|
15
|
+
# components: string — directory for page components, relative to dir (default: 'components')
|
|
16
|
+
# includes: array — directories for shared components, relative to dir (default: [])
|
|
17
|
+
# watch: boolean — enable SSE hot-reload endpoint (default: false)
|
|
18
|
+
# debounce: number — ms to batch filesystem events (default: 250)
|
|
19
|
+
# state: object — initial app state passed via bundle
|
|
20
|
+
# title: string — document title
|
|
22
21
|
#
|
|
23
22
|
# ==============================================================================
|
|
24
23
|
|
|
@@ -26,74 +25,96 @@ import { get } from '@rip-lang/api'
|
|
|
26
25
|
import { watch as fsWatch } from 'node:fs'
|
|
27
26
|
|
|
28
27
|
export ripUI = (opts = {}) ->
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
28
|
+
prefix = opts.app or ''
|
|
29
|
+
appDir = opts.dir or '.'
|
|
30
|
+
componentsDir = "#{appDir}/#{opts.components or 'components'}"
|
|
31
|
+
includeDirs = (opts.includes or []).map (d) -> "#{appDir}/#{d}"
|
|
32
|
+
enableWatch = opts.watch or false
|
|
33
|
+
debounceMs = opts.debounce or 250
|
|
34
|
+
appState = opts.state or null
|
|
35
|
+
appTitle = opts.title or null
|
|
36
|
+
uiDir = import.meta.dir
|
|
37
|
+
|
|
38
|
+
# Resolve rip-ui.min.js (compiler + UI framework bundled)
|
|
39
|
+
bundlePath = null
|
|
37
40
|
try
|
|
38
|
-
|
|
41
|
+
bundlePath = Bun.fileURLToPath(import.meta.resolve('rip-lang/docs/dist/rip-ui.min.js'))
|
|
39
42
|
catch
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
#
|
|
43
|
-
files
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
#
|
|
52
|
-
# Route: /
|
|
53
|
-
#
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
c.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if
|
|
43
|
+
bundlePath = "#{uiDir}/../../docs/dist/rip-ui.min.js"
|
|
44
|
+
|
|
45
|
+
# ----------------------------------------------------------------------------
|
|
46
|
+
# Route: /rip/* — framework files, registered once
|
|
47
|
+
# ----------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
unless ripUI._registered
|
|
50
|
+
get "/rip/rip-ui.min.js", (c) -> c.send bundlePath, 'application/javascript'
|
|
51
|
+
ripUI._registered = true
|
|
52
|
+
|
|
53
|
+
# ----------------------------------------------------------------------------
|
|
54
|
+
# Route: {prefix}/components/* — individual .rip component files (for hot-reload)
|
|
55
|
+
# Route: {prefix}/bundle — app bundle (components + data as JSON)
|
|
56
|
+
# Route: {prefix}/watch — SSE hot-reload stream
|
|
57
|
+
# ----------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
get "#{prefix}/components/*", (c) ->
|
|
60
|
+
name = c.req.path.slice("#{prefix}/components/".length)
|
|
61
|
+
c.send "#{componentsDir}/#{name}", 'text/plain; charset=UTF-8'
|
|
62
|
+
|
|
63
|
+
bundleCache = null
|
|
64
|
+
bundleDirty = true
|
|
65
|
+
|
|
66
|
+
# Invalidate bundle cache when components change
|
|
67
|
+
if enableWatch
|
|
68
|
+
fsWatch componentsDir, { recursive: true }, (event, filename) ->
|
|
69
|
+
bundleDirty = true if filename?.endsWith('.rip')
|
|
70
|
+
for incDir in includeDirs
|
|
71
|
+
fsWatch incDir, { recursive: true }, (event, filename) ->
|
|
72
|
+
bundleDirty = true if filename?.endsWith('.rip')
|
|
73
|
+
|
|
74
|
+
get "#{prefix}/bundle", (c) ->
|
|
75
|
+
if bundleDirty or not bundleCache
|
|
73
76
|
glob = new Bun.Glob("**/*.rip")
|
|
74
|
-
|
|
75
|
-
paths = Array.from(glob.scanSync(
|
|
77
|
+
components = {}
|
|
78
|
+
paths = Array.from(glob.scanSync(componentsDir))
|
|
76
79
|
for path in paths
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
components["components/#{path}"] = Bun.file("#{componentsDir}/#{path}").text!
|
|
81
|
+
|
|
82
|
+
# Merge external include directories into components/_lib/
|
|
83
|
+
for dir in includeDirs
|
|
84
|
+
incPaths = Array.from(glob.scanSync(dir))
|
|
85
|
+
for path in incPaths
|
|
86
|
+
key = "components/_lib/#{path}"
|
|
87
|
+
components[key] = Bun.file("#{dir}/#{path}").text! unless components[key]
|
|
88
|
+
|
|
89
|
+
data = {}
|
|
90
|
+
data.title = appTitle if appTitle
|
|
91
|
+
data.watch = enableWatch
|
|
92
|
+
if appState
|
|
93
|
+
data[k] = v for k, v of appState
|
|
94
|
+
|
|
95
|
+
bundleCache = JSON.stringify({ components, data })
|
|
96
|
+
bundleDirty = false
|
|
81
97
|
|
|
82
|
-
|
|
83
|
-
|
|
98
|
+
new Response bundleCache, headers: { 'Content-Type': 'application/json' }
|
|
99
|
+
|
|
100
|
+
if enableWatch
|
|
101
|
+
get "#{prefix}/watch", (c) ->
|
|
84
102
|
encoder = new TextEncoder()
|
|
85
103
|
pending = new Set()
|
|
86
104
|
timer = null
|
|
87
105
|
watcher = null
|
|
88
106
|
heartbeat = null
|
|
89
107
|
|
|
108
|
+
watchers = []
|
|
109
|
+
|
|
90
110
|
cleanup = ->
|
|
91
|
-
|
|
111
|
+
w.close() for w in watchers
|
|
92
112
|
clearTimeout(timer) if timer
|
|
93
113
|
clearInterval(heartbeat) if heartbeat
|
|
94
|
-
|
|
114
|
+
watchers = []
|
|
115
|
+
heartbeat = timer = null
|
|
95
116
|
|
|
96
|
-
|
|
117
|
+
new Response new ReadableStream(
|
|
97
118
|
start: (controller) ->
|
|
98
119
|
send = (event, data) ->
|
|
99
120
|
try
|
|
@@ -101,10 +122,8 @@ export ripUI = (opts = {}) ->
|
|
|
101
122
|
catch
|
|
102
123
|
cleanup()
|
|
103
124
|
|
|
104
|
-
# Send initial connection confirmation
|
|
105
125
|
send 'connected', { time: Date.now() }
|
|
106
126
|
|
|
107
|
-
# Heartbeat every 5s to prevent Bun's idle timeout from closing the SSE
|
|
108
127
|
heartbeat = setInterval ->
|
|
109
128
|
try
|
|
110
129
|
controller.enqueue encoder.encode(": heartbeat\n\n")
|
|
@@ -112,19 +131,22 @@ export ripUI = (opts = {}) ->
|
|
|
112
131
|
cleanup()
|
|
113
132
|
, 5000
|
|
114
133
|
|
|
115
|
-
# Flush pending changes as one batched notification
|
|
116
134
|
flush = ->
|
|
117
135
|
paths = Array.from(pending)
|
|
118
136
|
pending.clear()
|
|
119
137
|
timer = null
|
|
120
138
|
send('changed', { paths }) if paths.length > 0
|
|
121
139
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
140
|
+
addWatcher = (dir, keyPrefix) ->
|
|
141
|
+
watchers.push fsWatch dir, { recursive: true }, (event, filename) ->
|
|
142
|
+
return unless filename?.endsWith('.rip')
|
|
143
|
+
pending.add "#{keyPrefix}#{filename}"
|
|
144
|
+
clearTimeout(timer) if timer
|
|
145
|
+
timer = setTimeout(flush, debounceMs)
|
|
146
|
+
|
|
147
|
+
addWatcher componentsDir, 'components/'
|
|
148
|
+
for incDir in includeDirs
|
|
149
|
+
addWatcher incDir, 'components/_lib/'
|
|
128
150
|
|
|
129
151
|
cancel: -> cleanup()
|
|
130
152
|
),
|
|
@@ -133,8 +155,5 @@ export ripUI = (opts = {}) ->
|
|
|
133
155
|
'Cache-Control': 'no-cache'
|
|
134
156
|
'Connection': 'keep-alive'
|
|
135
157
|
|
|
136
|
-
|
|
137
|
-
new Response 'Not Found', status: 404
|
|
138
|
-
|
|
139
|
-
# Return pass-through middleware for use()
|
|
158
|
+
# Return pass-through middleware
|
|
140
159
|
(c, next) -> next!()
|