funicular 0.0.1 → 0.1.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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +56 -1
  3. data/README.md +58 -20
  4. data/Rakefile +74 -2
  5. data/demo/keymap_editor.html +582 -0
  6. data/demo/test_cable.html +179 -0
  7. data/demo/test_chartjs.html +235 -0
  8. data/demo/test_component.html +201 -0
  9. data/demo/test_diff_patch.html +146 -0
  10. data/demo/test_error_boundary.html +284 -0
  11. data/demo/test_router.html +257 -0
  12. data/demo/test_vdom.html +100 -0
  13. data/demo/tic-tac-toe.html +201 -0
  14. data/docs/README.md +419 -0
  15. data/docs/advanced-features.md +632 -0
  16. data/docs/architecture.md +409 -0
  17. data/docs/components-and-state.md +539 -0
  18. data/docs/data-fetching.md +528 -0
  19. data/docs/forms.md +446 -0
  20. data/docs/rails-integration.md +426 -0
  21. data/docs/realtime.md +543 -0
  22. data/docs/routing-and-navigation.md +427 -0
  23. data/docs/styling.md +285 -0
  24. data/exe/funicular +32 -0
  25. data/lib/funicular/assets/funicular.rb +21 -0
  26. data/lib/funicular/assets/funicular_debug.css +73 -0
  27. data/lib/funicular/assets/funicular_debug.js +183 -0
  28. data/lib/funicular/commands/routes.rb +69 -0
  29. data/lib/funicular/compiler.rb +135 -0
  30. data/lib/funicular/configuration.rb +76 -0
  31. data/lib/funicular/helpers/picoruby_helper.rb +50 -0
  32. data/lib/funicular/middleware.rb +98 -0
  33. data/lib/funicular/railtie.rb +26 -0
  34. data/lib/funicular/route_parser.rb +137 -0
  35. data/lib/funicular/vendor/picorbc/VERSION +1 -0
  36. data/lib/funicular/vendor/picorbc/picorbc.js +5283 -0
  37. data/lib/funicular/vendor/picorbc/picorbc.wasm +0 -0
  38. data/lib/funicular/vendor/picoruby/VERSION +1 -0
  39. data/lib/funicular/vendor/picoruby/debug/init.iife.js +130 -0
  40. data/lib/funicular/vendor/picoruby/debug/picoruby.js +6404 -0
  41. data/lib/funicular/vendor/picoruby/debug/picoruby.wasm +0 -0
  42. data/lib/funicular/vendor/picoruby/dist/init.iife.js +130 -0
  43. data/lib/funicular/vendor/picoruby/dist/picoruby.js +2 -0
  44. data/lib/funicular/vendor/picoruby/dist/picoruby.wasm +0 -0
  45. data/lib/funicular/version.rb +1 -1
  46. data/lib/funicular.rb +29 -1
  47. data/lib/tasks/funicular.rake +135 -0
  48. data/minitest/funicular_test.rb +13 -0
  49. data/minitest/test_helper.rb +7 -0
  50. data/mrbgem.rake +15 -0
  51. data/mrblib/cable.rb +417 -0
  52. data/mrblib/component.rb +911 -0
  53. data/mrblib/debug.rb +205 -0
  54. data/mrblib/differ.rb +244 -0
  55. data/mrblib/environment_inquirer.rb +34 -0
  56. data/mrblib/error_boundary.rb +125 -0
  57. data/mrblib/file_upload.rb +184 -0
  58. data/mrblib/form_builder.rb +284 -0
  59. data/mrblib/funicular.rb +156 -0
  60. data/mrblib/http.rb +89 -0
  61. data/mrblib/model.rb +146 -0
  62. data/mrblib/patcher.rb +203 -0
  63. data/mrblib/router.rb +229 -0
  64. data/mrblib/styles.rb +83 -0
  65. data/mrblib/vdom.rb +273 -0
  66. data/sig/cable.rbs +65 -0
  67. data/sig/component.rbs +141 -0
  68. data/sig/debug.rbs +28 -0
  69. data/sig/differ.rbs +18 -0
  70. data/sig/environment_iquirer.rbs +10 -0
  71. data/sig/error_boundary.rbs +14 -0
  72. data/sig/file_upload.rbs +18 -0
  73. data/sig/form_builder.rbs +29 -0
  74. data/sig/funicular.rbs +11 -1
  75. data/sig/http.rbs +22 -0
  76. data/sig/model.rbs +23 -0
  77. data/sig/patcher.rbs +15 -0
  78. data/sig/router.rbs +43 -0
  79. data/sig/styles.rbs +25 -0
  80. data/sig/vdom.rbs +59 -0
  81. metadata +119 -8
@@ -0,0 +1,426 @@
1
+ # Rails Integration
2
+
3
+ This document describes CRubyGem funicular as a Rails plugin.
4
+
5
+ ## Seamless Rails integration
6
+
7
+ - Automatic compilation of Ruby files to mruby bytecode (.mrb)
8
+ - Development mode with debug symbols (-g option)
9
+ - Production mode with optimized bytecode (no debug symbols)
10
+ - Auto-recompilation in development when source files change
11
+ - Rails-style routing with `link_to` helper and URL path helpers
12
+ - RESTful HTTP method support (GET, POST, PUT, PATCH, DELETE)
13
+ - Built-in CSRF protection for non-GET requests
14
+
15
+ ## Prerequisites
16
+
17
+ Funicular bundles a WebAssembly build of the `picorbc` mruby compiler and
18
+ runs it through Node.js. Make sure Node.js is installed on any machine that
19
+ performs Funicular compilation (your development workstation, CI, and any
20
+ host that runs `assets:precompile`).
21
+
22
+ ```bash
23
+ node --version
24
+ ```
25
+
26
+ You do **not** need to install `@picoruby/picorbc` from npm; the gem ships
27
+ with the compiler already vendored.
28
+
29
+ ## Installation
30
+
31
+ ### 1. Add the gem
32
+
33
+ ```ruby
34
+ # Gemfile
35
+ gem "funicular"
36
+ ```
37
+
38
+ ```bash
39
+ bundle install
40
+ ```
41
+
42
+ ### 2. Run the install task
43
+
44
+ ```bash
45
+ bundle exec rake funicular:install
46
+ ```
47
+
48
+ This runs two sub-tasks:
49
+
50
+ #### `funicular:install:wasm`
51
+
52
+ Copies the PicoRuby.wasm runtime (dist and debug builds) into your Rails app:
53
+
54
+ ```
55
+ public/
56
+ picoruby/
57
+ dist/ # production build (smaller, no debug symbols)
58
+ init.iife.js
59
+ picoruby.js
60
+ picoruby.wasm
61
+ debug/ # development build (larger, with debug symbols)
62
+ init.iife.js
63
+ picoruby.js
64
+ picoruby.wasm
65
+ ```
66
+
67
+ The files in `public/picoruby/` should be added to `.gitignore` and
68
+ re-installed after gem updates.
69
+
70
+ #### `funicular:install:debug_assets`
71
+
72
+ Copies the component highlighter stylesheet and script for development use:
73
+
74
+ ```
75
+ app/assets/
76
+ javascripts/funicular_debug.js
77
+ stylesheets/funicular_debug.css
78
+ config/initializers/funicular.rb
79
+ ```
80
+
81
+ The generated `config/initializers/funicular.rb` is the place to configure
82
+ which PicoRuby.wasm source `picoruby_include_tag` uses (see below).
83
+
84
+ ### 3. Add the script tag to your layout
85
+
86
+ Replace any hardcoded PicoRuby `<script>` tag with the view helper:
87
+
88
+ ```erb
89
+ <%# app/views/layouts/application.html.erb %>
90
+ <head>
91
+ ...
92
+ <%= picoruby_include_tag %>
93
+ </head>
94
+ ```
95
+
96
+ `picoruby_include_tag` chooses the right build automatically:
97
+
98
+ | Environment | Default source | Path served |
99
+ |---|---|---|
100
+ | development | `:local_debug` | `/picoruby/debug/init.iife.js` |
101
+ | test | `:local_debug` | `/picoruby/debug/init.iife.js` |
102
+ | production | `:local_dist` | `/picoruby/dist/init.iife.js` |
103
+
104
+ You can override the source per environment in `config/initializers/funicular.rb`:
105
+
106
+ ```ruby
107
+ Funicular.configure do |config|
108
+ # Use jsDelivr CDN in production instead of self-hosting:
109
+ config.production_source = :cdn
110
+
111
+ # The CDN version defaults to the @picoruby/wasm-wasi version vendored in
112
+ # the gem. Override only if you need a specific version:
113
+ # config.cdn_version = "4.0.0"
114
+ end
115
+ ```
116
+
117
+ Available sources:
118
+
119
+ | Value | Description |
120
+ |---|---|
121
+ | `:local_debug` | `public/picoruby/debug/init.iife.js` |
122
+ | `:local_dist` | `public/picoruby/dist/init.iife.js` |
123
+ | `:cdn` | `https://cdn.jsdelivr.net/npm/@picoruby/wasm-wasi@<version>/dist/init.iife.js` |
124
+
125
+ You can also override the source for a single tag:
126
+
127
+ ```erb
128
+ <%= picoruby_include_tag source: :cdn %>
129
+ ```
130
+
131
+ ### 4. (Optional) Add debug assets to your layout
132
+
133
+ ```erb
134
+ <% if Rails.env.development? %>
135
+ <%= javascript_include_tag "funicular_debug", "data-turbo-track": "reload" %>
136
+ <%= stylesheet_link_tag "funicular_debug", "data-turbo-track": "reload" %>
137
+ <% end %>
138
+ ```
139
+
140
+ See [Component Debug Highlighter](#component-debug-highlighter) for details.
141
+
142
+ ## Usage
143
+
144
+ ### Directory Structure
145
+
146
+ Place your Funicular application files in the following structure:
147
+
148
+ ```
149
+ app/funicular/
150
+ models/ # UI models
151
+ user.rb
152
+ session.rb
153
+ components/ # UI components
154
+ login_component.rb
155
+ chat_component.rb
156
+ initializer.rb # Application initialization (optional)
157
+ ```
158
+
159
+ The `initializer.rb` file (or any file ending with `_initializer.rb`) is loaded last, after all models and components. Use it for application setup code like routing configuration.
160
+
161
+ ### Compilation
162
+
163
+ #### Development Mode
164
+
165
+ In development mode, Funicular automatically recompiles your Ruby files when they change. The compiled bytecode includes debug symbols (-g option).
166
+
167
+ To manually compile:
168
+
169
+ ```bash
170
+ bundle exec rake funicular:compile
171
+ ```
172
+
173
+ Output:
174
+ - File: `app/assets/builds/app.mrb`
175
+ - Debug mode: enabled
176
+ - Size: ~19KB (with debug symbols)
177
+
178
+ The compiled file is placed in `app/assets/builds/` so that Rails asset pipeline (Propshaft) can process it and serve it from `public/assets/` with proper cache busting.
179
+
180
+ #### Production Mode
181
+
182
+ In production mode, compile without debug symbols for smaller file size:
183
+
184
+ ```bash
185
+ RAILS_ENV=production bundle exec rake funicular:compile
186
+ ```
187
+
188
+ Output:
189
+ - File: `app/assets/builds/app.mrb`
190
+ - Debug mode: disabled
191
+ - Size: ~16KB (optimized)
192
+
193
+ The compilation task is automatically run before `assets:precompile` in production deployments.
194
+
195
+ ### Loading in Views
196
+
197
+ Include the compiled bytecode in your view using the `asset_path` helper. If you have an `initializer.rb` file, it will execute automatically when the mrb file loads:
198
+
199
+ ```erb
200
+ <div id="app"></div>
201
+
202
+ <script type="application/x-mrb" src="<%= asset_path('app.mrb') %>"></script>
203
+ ```
204
+
205
+ The `asset_path` helper ensures that:
206
+ - In development: The file is served from `app/assets/builds/` via Propshaft
207
+ - In production: The file is served from `public/assets/` with a digest hash for cache busting (e.g., `application-abc123.mrb`)
208
+
209
+ Example `app/funicular/initializer.rb`:
210
+
211
+ ```ruby
212
+ puts "Funicular Chat App initializing..."
213
+
214
+ # Load all model schemas before starting the app
215
+ Funicular.load_schemas({ User => "user", Session => "session", Channel => "channel" }) do
216
+ # Start the application after all schemas are loaded
217
+ Funicular.start(container: 'app') do |router|
218
+ router.get('/login', to: LoginComponent, as: 'login')
219
+ router.get('/chat/:channel_id', to: ChatComponent, as: 'chat_channel')
220
+ router.get('/settings', to: SettingsComponent, as: 'settings')
221
+ router.delete('/logout', to: LogoutComponent, as: 'logout')
222
+ router.set_default('/login')
223
+ end
224
+ end
225
+ ```
226
+
227
+ ### File Concatenation Order
228
+
229
+ Funicular concatenates files in the following order:
230
+
231
+ 1. `app/funicular/models/**/*.rb` (alphabetically)
232
+ 2. `app/funicular/components/**/*.rb` (alphabetically)
233
+ 3. `app/funicular/initializer.rb` and `app/funicular/*_initializer.rb`
234
+
235
+ This ensures that:
236
+ - Model classes are defined before components that depend on them
237
+ - Components are defined before initialization code that uses them
238
+
239
+ ### Routing
240
+
241
+ Funicular provides Rails-style routing with automatic URL helper generation and RESTful HTTP method support.
242
+
243
+ #### Defining Routes
244
+
245
+ Use Rails-style DSL in your `initializer.rb`:
246
+
247
+ ```ruby
248
+ Funicular.start(container: 'app') do |router|
249
+ # GET routes with URL helpers
250
+ router.get('/login', to: LoginComponent, as: 'login')
251
+ router.get('/users/:id', to: UserComponent, as: 'user')
252
+ router.get('/users/:id/edit', to: EditUserComponent, as: 'edit_user')
253
+
254
+ # RESTful routes
255
+ router.post('/users', to: CreateUserComponent, as: 'users')
256
+ router.patch('/users/:id', to: UpdateUserComponent, as: 'update_user')
257
+ router.delete('/users/:id', to: DeleteUserComponent, as: 'delete_user')
258
+
259
+ # Set default route
260
+ router.set_default('/login')
261
+ end
262
+ ```
263
+
264
+ The `as` option automatically generates URL helper methods (e.g., `login_path`, `user_path`).
265
+
266
+ #### Using URL Helpers
267
+
268
+ URL helpers are automatically available in all components:
269
+
270
+ ```ruby
271
+ class UserListComponent < Funicular::Component
272
+ def render
273
+ div do
274
+ # Static path
275
+ link_to login_path do
276
+ span { "Login" }
277
+ end
278
+
279
+ # Path with parameter from state/props
280
+ state.users.each do |user|
281
+ link_to user_path(user.id) do
282
+ span { user.name }
283
+ end
284
+ end
285
+
286
+ # Or pass model object with id method
287
+ link_to edit_user_path(state.current_user) do
288
+ span { "Edit Profile" }
289
+ end
290
+ end
291
+ end
292
+ end
293
+ ```
294
+
295
+ #### Using link_to Helper
296
+
297
+ The `link_to` helper creates navigation links with automatic routing:
298
+
299
+ ```ruby
300
+ # GET navigation (uses History API)
301
+ link_to settings_path, class: "button" do
302
+ span { "Settings" }
303
+ end
304
+
305
+ # Path with dynamic data
306
+ link_to chat_channel_path(props[:channel]) do
307
+ div(class: "channel-name") { "# #{props[:channel].name}" }
308
+ div(class: "channel-desc") { props[:channel].description }
309
+ end
310
+
311
+ # RESTful actions (uses Fetch API)
312
+ link_to user_path(state.user), method: :delete, class: "danger" do
313
+ span { "Delete Account" }
314
+ end
315
+
316
+ # Supported HTTP methods: :get, :post, :put, :patch, :delete
317
+ ```
318
+
319
+ #### CSRF Protection
320
+
321
+ Non-GET requests automatically include CSRF tokens from Rails meta tags:
322
+
323
+ ```erb
324
+ <!-- In your Rails layout -->
325
+ <head>
326
+ <%= csrf_meta_tags %>
327
+ </head>
328
+ ```
329
+
330
+ Funicular automatically reads the CSRF token and includes it in `X-CSRF-Token` header for POST, PUT, PATCH, and DELETE requests.
331
+
332
+ #### Viewing Routes
333
+
334
+ Display all defined routes with the Rake task:
335
+
336
+ ```bash
337
+ rake funicular:routes
338
+ ```
339
+
340
+ Output example:
341
+
342
+ ```
343
+ Method Path Component Helper
344
+ ----------------------------------------------------------
345
+ GET /login LoginComponent login_path
346
+ GET /chat/:channel_id ChatComponent chat_channel_path
347
+ GET /settings SettingsComponent settings_path
348
+ DELETE /logout LogoutComponent logout_path
349
+
350
+ Total: 4 routes
351
+ ```
352
+
353
+ #### Backward Compatibility
354
+
355
+ The old `add_route` method is still supported:
356
+
357
+ ```ruby
358
+ # Old style (still works)
359
+ router.add_route('/login', LoginComponent)
360
+
361
+ # With URL helper
362
+ router.add_route('/login', LoginComponent, as: 'login')
363
+ ```
364
+
365
+ ## Rails Asset Pipeline Integration
366
+
367
+ Funicular integrates with Rails' asset pipeline (Propshaft) following Rails best practices:
368
+
369
+ ### Directory Structure
370
+
371
+ ```
372
+ app/
373
+ funicular/ # Source files (version controlled)
374
+ models/
375
+ components/
376
+ initializer.rb
377
+ assets/
378
+ builds/ # Compiled output (gitignored)
379
+ app.mrb # Generated by funicular:compile
380
+ .keep # Keep directory in git
381
+ ```
382
+
383
+ ### Development vs Production
384
+
385
+ **Development:**
386
+ - Files in `app/assets/builds/` are served directly by Propshaft
387
+ - Middleware automatically recompiles when source files change
388
+ - Debug symbols included for better error messages
389
+
390
+ **Production:**
391
+ - `rake assets:precompile` runs `funicular:compile` first
392
+ - Propshaft copies files to `public/assets/` with digest hashes
393
+ - Example: `app.mrb` -> `app-abc123def456.mrb`
394
+ - Debug symbols excluded for smaller file size
395
+
396
+ ### Cache Busting
397
+
398
+ Using `asset_path('app.mrb')` in views ensures:
399
+ - Correct path resolution in all environments
400
+ - Automatic cache busting when files change
401
+ - Standard Rails asset handling
402
+
403
+ ## Development Tools
404
+
405
+ ### Component Debug Highlighter
406
+
407
+ Funicular provides a debug tool that visually highlights components with `data-component` attributes in development mode.
408
+
409
+ `funicular:install` (or `funicular:install:debug_assets`) copies the
410
+ required files and adds them to your layout as shown in [Installation](#installation).
411
+
412
+ #### Features
413
+
414
+ In development mode, components automatically get `data-component` attributes with their class name. The debug tool:
415
+
416
+ - Highlights components with a green/yellow/pink/cyan outline
417
+ ```ruby
418
+ # in app/funicular/initializer.rb
419
+ Funicular.debug_color = "pink" # Options: "green", "yellow", "pink", "cyan", or nil to disable
420
+ ```
421
+ - Shows a triangle indicator in the bottom-right corner
422
+ - Displays component name and id value (if exists) on hover
423
+ - Does not distort layout (uses `outline` instead of `border`)
424
+
425
+ This helps developers quickly identify which component class renders each part of the UI.
426
+