@rip-lang/ui 0.1.3 → 0.3.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/package.json CHANGED
@@ -1,15 +1,11 @@
1
1
  {
2
2
  "name": "@rip-lang/ui",
3
- "version": "0.1.3",
4
- "description": "Zero-build reactive web framework — VFS, file-based routing, reactive stash",
3
+ "version": "0.3.0",
4
+ "description": "Zero-build reactive web framework — rip.js + ui.rip + launch(url)",
5
5
  "type": "module",
6
- "main": "ui.js",
6
+ "main": "ui.rip",
7
7
  "exports": {
8
- ".": "./ui.js",
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,14 +32,10 @@
39
32
  "author": "Steve Shreeve <steve.shreeve@gmail.com>",
40
33
  "license": "MIT",
41
34
  "dependencies": {
42
- "rip-lang": "^3.4.4"
35
+ "rip-lang": "^3.6.0"
43
36
  },
44
37
  "files": [
45
- "ui.js",
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
  ],
package/serve.rip CHANGED
@@ -1,24 +1,22 @@
1
1
  # ==============================================================================
2
- # @rip-lang/ui/serve — Rip UI Middleware for rip-api
2
+ # @rip-lang/ui/serve — Rip UI Server Middleware
3
3
  # ==============================================================================
4
4
  #
5
- # Serves the Rip UI framework files, auto-generated page manifests, and
6
- # provides an SSE hot-reload channel for live development.
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 app: '/demo', dir: dir, title: 'My App'
12
11
  #
13
12
  # Options:
14
- # base: string — URL prefix for framework files (default: '/rip-ui')
15
- # pages: string — directory containing .rip page files (default: 'pages')
16
- # watch: booleanenable SSE hot-reload endpoint (default: false)
17
- # debounce: number ms to batch filesystem events (default: 250)
18
- #
19
- # Registered routes:
20
- # GET {base}/* framework JS files, manifest, SSE watch
21
- # GET /pages/* — individual .rip page files (for hot-reload refetch)
13
+ # app: string — URL mount point (default: '')
14
+ # dir: string — app directory on disk (default: '.')
15
+ # components: string components subdirectory name (default: 'components')
16
+ # watch: booleanenable SSE hot-reload endpoint (default: false)
17
+ # debounce: number — ms to batch filesystem events (default: 250)
18
+ # state: object — initial app state passed via bundle
19
+ # title: string document title
22
20
  #
23
21
  # ==============================================================================
24
22
 
@@ -26,61 +24,70 @@ import { get } from '@rip-lang/api'
26
24
  import { watch as fsWatch } from 'node:fs'
27
25
 
28
26
  export ripUI = (opts = {}) ->
29
- base = opts.base or '/rip-ui'
30
- pagesDir = opts.pages or 'pages'
31
- enableWatch = opts.watch or false
32
- debounceMs = opts.debounce or 250
33
- uiDir = import.meta.dir
34
-
35
- # Resolve compiler (rip.browser.js) from the rip-lang package
27
+ prefix = opts.app or ''
28
+ appDir = opts.dir or '.'
29
+ componentsDir = opts.components or "#{appDir}/components"
30
+ enableWatch = opts.watch or false
31
+ debounceMs = opts.debounce or 250
32
+ appState = opts.state or null
33
+ appTitle = opts.title or null
34
+ uiDir = import.meta.dir
35
+
36
+ # Resolve compiler (rip.browser.js)
36
37
  compilerPath = null
37
38
  try
38
39
  compilerPath = Bun.fileURLToPath(import.meta.resolve('rip-lang/docs/dist/rip.browser.js'))
39
40
  catch
40
41
  compilerPath = "#{uiDir}/../../docs/dist/rip.browser.js"
41
42
 
42
- # Framework file map: logical name → filesystem path
43
- files =
44
- 'ui.js': "#{uiDir}/ui.js"
45
- 'stash.js': "#{uiDir}/stash.js"
46
- 'vfs.js': "#{uiDir}/vfs.js"
47
- 'router.js': "#{uiDir}/router.js"
48
- 'renderer.js': "#{uiDir}/renderer.js"
49
- 'compiler.js': compilerPath
50
-
51
- # ---------------------------------------------------------------------------
52
- # Route: /pages/* — individual .rip page files (for hot-reload refetch)
53
- # ---------------------------------------------------------------------------
54
-
55
- get '/pages/*', (c) ->
56
- name = c.req.path.slice('/pages/'.length)
57
- c.send "#{pagesDir}/#{name}", 'text/plain; charset=UTF-8'
58
-
59
- # ---------------------------------------------------------------------------
60
- # Route: {base}/* — framework files, manifest, SSE watch
61
- # ---------------------------------------------------------------------------
62
-
63
- get "#{base}/*", (c) ->
64
- name = c.req.path.slice(base.length + 1)
65
-
66
- # Framework JS files
67
- if files[name]
68
- return c.send files[name], 'application/javascript'
69
-
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
- if name is 'manifest.json'
43
+ # ----------------------------------------------------------------------------
44
+ # Route: /rip/* — framework files (compiler + ui.rip), registered once
45
+ # ----------------------------------------------------------------------------
46
+
47
+ unless ripUI._registered
48
+ get "/rip/browser.js", (c) -> c.send compilerPath, 'application/javascript'
49
+ get "/rip/ui.rip", (c) -> c.send "#{uiDir}/ui.rip", 'text/plain; charset=UTF-8'
50
+ ripUI._registered = true
51
+
52
+ # ----------------------------------------------------------------------------
53
+ # Route: {prefix}/components/* — individual .rip component files (for hot-reload)
54
+ # Route: {prefix}/bundle — app bundle (components + data as JSON)
55
+ # Route: {prefix}/watch — SSE hot-reload stream
56
+ # ----------------------------------------------------------------------------
57
+
58
+ get "#{prefix}/components/*", (c) ->
59
+ name = c.req.path.slice("#{prefix}/components/".length)
60
+ c.send "#{componentsDir}/#{name}", 'text/plain; charset=UTF-8'
61
+
62
+ bundleCache = null
63
+ bundleDirty = true
64
+
65
+ # Invalidate bundle cache when components change
66
+ if enableWatch
67
+ fsWatch componentsDir, { recursive: true }, (event, filename) ->
68
+ bundleDirty = true if filename?.endsWith('.rip')
69
+
70
+ get "#{prefix}/bundle", (c) ->
71
+ if bundleDirty or not bundleCache
73
72
  glob = new Bun.Glob("**/*.rip")
74
- bundle = {}
75
- paths = Array.from(glob.scanSync(pagesDir))
73
+ components = {}
74
+ paths = Array.from(glob.scanSync(componentsDir))
76
75
  for path in paths
77
- bundle["pages/#{path}"] = Bun.file("#{pagesDir}/#{path}").text!
78
- manifestPath = "#{pagesDir}/.manifest.json"
79
- Bun.write manifestPath, JSON.stringify(bundle)
80
- return c.send manifestPath, 'application/json'
76
+ components["components/#{path}"] = Bun.file("#{componentsDir}/#{path}").text!
77
+
78
+ data = {}
79
+ data.title = appTitle if appTitle
80
+ data.watch = enableWatch
81
+ if appState
82
+ data[k] = v for k, v of appState
81
83
 
82
- # SSE watch endpoint — debounced, notify-only, with heartbeat
83
- if name is 'watch' and enableWatch
84
+ bundleCache = JSON.stringify({ components, data })
85
+ bundleDirty = false
86
+
87
+ new Response bundleCache, headers: { 'Content-Type': 'application/json' }
88
+
89
+ if enableWatch
90
+ get "#{prefix}/watch", (c) ->
84
91
  encoder = new TextEncoder()
85
92
  pending = new Set()
86
93
  timer = null
@@ -93,7 +100,7 @@ export ripUI = (opts = {}) ->
93
100
  clearInterval(heartbeat) if heartbeat
94
101
  watcher = heartbeat = timer = null
95
102
 
96
- return new Response new ReadableStream(
103
+ new Response new ReadableStream(
97
104
  start: (controller) ->
98
105
  send = (event, data) ->
99
106
  try
@@ -101,10 +108,8 @@ export ripUI = (opts = {}) ->
101
108
  catch
102
109
  cleanup()
103
110
 
104
- # Send initial connection confirmation
105
111
  send 'connected', { time: Date.now() }
106
112
 
107
- # Heartbeat every 5s to prevent Bun's idle timeout from closing the SSE
108
113
  heartbeat = setInterval ->
109
114
  try
110
115
  controller.enqueue encoder.encode(": heartbeat\n\n")
@@ -112,17 +117,15 @@ export ripUI = (opts = {}) ->
112
117
  cleanup()
113
118
  , 5000
114
119
 
115
- # Flush pending changes as one batched notification
116
120
  flush = ->
117
121
  paths = Array.from(pending)
118
122
  pending.clear()
119
123
  timer = null
120
124
  send('changed', { paths }) if paths.length > 0
121
125
 
122
- # Watch the pages directory for .rip file changes
123
- watcher = fsWatch pagesDir, { recursive: true }, (event, filename) ->
126
+ watcher = fsWatch componentsDir, { recursive: true }, (event, filename) ->
124
127
  return unless filename?.endsWith('.rip')
125
- pending.add "pages/#{filename}"
128
+ pending.add "components/#{filename}"
126
129
  clearTimeout(timer) if timer
127
130
  timer = setTimeout(flush, debounceMs)
128
131
 
@@ -133,8 +136,5 @@ export ripUI = (opts = {}) ->
133
136
  'Cache-Control': 'no-cache'
134
137
  'Connection': 'keep-alive'
135
138
 
136
- # Unknown path under /rip-ui/
137
- new Response 'Not Found', status: 404
138
-
139
- # Return pass-through middleware for use()
139
+ # Return pass-through middleware
140
140
  (c, next) -> next!()