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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +56 -1
- data/README.md +58 -20
- data/Rakefile +74 -2
- data/demo/keymap_editor.html +582 -0
- data/demo/test_cable.html +179 -0
- data/demo/test_chartjs.html +235 -0
- data/demo/test_component.html +201 -0
- data/demo/test_diff_patch.html +146 -0
- data/demo/test_error_boundary.html +284 -0
- data/demo/test_router.html +257 -0
- data/demo/test_vdom.html +100 -0
- data/demo/tic-tac-toe.html +201 -0
- data/docs/README.md +419 -0
- data/docs/advanced-features.md +632 -0
- data/docs/architecture.md +409 -0
- data/docs/components-and-state.md +539 -0
- data/docs/data-fetching.md +528 -0
- data/docs/forms.md +446 -0
- data/docs/rails-integration.md +426 -0
- data/docs/realtime.md +543 -0
- data/docs/routing-and-navigation.md +427 -0
- data/docs/styling.md +285 -0
- data/exe/funicular +32 -0
- data/lib/funicular/assets/funicular.rb +21 -0
- data/lib/funicular/assets/funicular_debug.css +73 -0
- data/lib/funicular/assets/funicular_debug.js +183 -0
- data/lib/funicular/commands/routes.rb +69 -0
- data/lib/funicular/compiler.rb +135 -0
- data/lib/funicular/configuration.rb +76 -0
- data/lib/funicular/helpers/picoruby_helper.rb +50 -0
- data/lib/funicular/middleware.rb +98 -0
- data/lib/funicular/railtie.rb +26 -0
- data/lib/funicular/route_parser.rb +137 -0
- data/lib/funicular/vendor/picorbc/VERSION +1 -0
- data/lib/funicular/vendor/picorbc/picorbc.js +5283 -0
- data/lib/funicular/vendor/picorbc/picorbc.wasm +0 -0
- data/lib/funicular/vendor/picoruby/VERSION +1 -0
- data/lib/funicular/vendor/picoruby/debug/init.iife.js +130 -0
- data/lib/funicular/vendor/picoruby/debug/picoruby.js +6404 -0
- data/lib/funicular/vendor/picoruby/debug/picoruby.wasm +0 -0
- data/lib/funicular/vendor/picoruby/dist/init.iife.js +130 -0
- data/lib/funicular/vendor/picoruby/dist/picoruby.js +2 -0
- data/lib/funicular/vendor/picoruby/dist/picoruby.wasm +0 -0
- data/lib/funicular/version.rb +1 -1
- data/lib/funicular.rb +29 -1
- data/lib/tasks/funicular.rake +135 -0
- data/minitest/funicular_test.rb +13 -0
- data/minitest/test_helper.rb +7 -0
- data/mrbgem.rake +15 -0
- data/mrblib/cable.rb +417 -0
- data/mrblib/component.rb +911 -0
- data/mrblib/debug.rb +205 -0
- data/mrblib/differ.rb +244 -0
- data/mrblib/environment_inquirer.rb +34 -0
- data/mrblib/error_boundary.rb +125 -0
- data/mrblib/file_upload.rb +184 -0
- data/mrblib/form_builder.rb +284 -0
- data/mrblib/funicular.rb +156 -0
- data/mrblib/http.rb +89 -0
- data/mrblib/model.rb +146 -0
- data/mrblib/patcher.rb +203 -0
- data/mrblib/router.rb +229 -0
- data/mrblib/styles.rb +83 -0
- data/mrblib/vdom.rb +273 -0
- data/sig/cable.rbs +65 -0
- data/sig/component.rbs +141 -0
- data/sig/debug.rbs +28 -0
- data/sig/differ.rbs +18 -0
- data/sig/environment_iquirer.rbs +10 -0
- data/sig/error_boundary.rbs +14 -0
- data/sig/file_upload.rbs +18 -0
- data/sig/form_builder.rbs +29 -0
- data/sig/funicular.rbs +11 -1
- data/sig/http.rbs +22 -0
- data/sig/model.rbs +23 -0
- data/sig/patcher.rbs +15 -0
- data/sig/router.rbs +43 -0
- data/sig/styles.rbs +25 -0
- data/sig/vdom.rbs +59 -0
- metadata +119 -8
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
# Funicular Architecture
|
|
2
|
+
|
|
3
|
+
This document provides a comprehensive analysis of Funicular's architectural design decisions and how they compare to other modern SPA frameworks.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Overview](#overview)
|
|
8
|
+
- [Architecture Design Decisions](#architecture-design-decisions)
|
|
9
|
+
- [1. Data Flow](#1-data-flow)
|
|
10
|
+
- [2. Rendering Strategy](#2-rendering-strategy)
|
|
11
|
+
- [3. Reactivity Model](#3-reactivity-model)
|
|
12
|
+
- [4. Component Model](#4-component-model)
|
|
13
|
+
- [5. State Management](#5-state-management)
|
|
14
|
+
- [6. Routing](#6-routing)
|
|
15
|
+
- [7. Rendering Mode](#7-rendering-mode)
|
|
16
|
+
- [8. Template System](#8-template-system)
|
|
17
|
+
- [9. Build Strategy](#9-build-strategy)
|
|
18
|
+
- [10. Concurrency](#10-concurrency)
|
|
19
|
+
- [11. Event System](#11-event-system)
|
|
20
|
+
- [12. Error Handling](#12-error-handling)
|
|
21
|
+
- [13. Data Fetching](#13-data-fetching)
|
|
22
|
+
- [Comparison with Other Frameworks](#comparison-with-other-frameworks)
|
|
23
|
+
- [Design Philosophy](#design-philosophy)
|
|
24
|
+
- [Trade-offs](#trade-offs)
|
|
25
|
+
- [Best Use Cases](#best-use-cases)
|
|
26
|
+
|
|
27
|
+
## Overview
|
|
28
|
+
|
|
29
|
+
Funicular is a **unidirectional, Virtual DOM-based SPA framework** for PicoRuby.wasm. It adopts design patterns similar to React while embracing Ruby's expressiveness and integrating seamlessly with Rails.
|
|
30
|
+
|
|
31
|
+
**Core Principle**: Pure Ruby browser applications with explicit, predictable data flow.
|
|
32
|
+
|
|
33
|
+
## Architecture Design Decisions
|
|
34
|
+
|
|
35
|
+
### 1. Data Flow
|
|
36
|
+
|
|
37
|
+
**Choice**: Unidirectional (One-way) Data Binding
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
# State flows DOWN to UI
|
|
41
|
+
state -> VDOM -> DOM rendering
|
|
42
|
+
|
|
43
|
+
# Events flow UP to trigger state updates
|
|
44
|
+
DOM events -> event handlers -> patch() -> state update -> re-render
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Evidence**:
|
|
48
|
+
- State is immutable, updatable only via `patch()`
|
|
49
|
+
- Direct state mutation is blocked by `StateAccessor`
|
|
50
|
+
- Form inputs require explicit event handlers + `patch()` calls
|
|
51
|
+
|
|
52
|
+
**Comparison**:
|
|
53
|
+
|
|
54
|
+
| Framework | Binding Type | Update Mechanism |
|
|
55
|
+
|-----------|--------------|------------------|
|
|
56
|
+
| **Funicular** | Unidirectional | `patch()` + VDOM diffing |
|
|
57
|
+
| React | Unidirectional | `setState()` + VDOM diffing |
|
|
58
|
+
| Vue 2 | Bidirectional (v-model) | Reactivity proxy + VDOM |
|
|
59
|
+
| Angular | Bidirectional | Two-way data binding |
|
|
60
|
+
| Svelte | Unidirectional | Compile-time reactivity |
|
|
61
|
+
|
|
62
|
+
### 2. Rendering Strategy
|
|
63
|
+
|
|
64
|
+
**Choice**: Virtual DOM with Diffing Algorithm
|
|
65
|
+
|
|
66
|
+
**Implementation**:
|
|
67
|
+
- Custom VDOM implementation (`vdom.rb`)
|
|
68
|
+
- `Differ.diff()` calculates minimal changes
|
|
69
|
+
- `Patcher.apply()` applies patches to real DOM
|
|
70
|
+
- **Key-based reconciliation** for efficient list updates
|
|
71
|
+
|
|
72
|
+
**Code Reference**:
|
|
73
|
+
```ruby
|
|
74
|
+
# Component re-renders when state changes
|
|
75
|
+
def re_render
|
|
76
|
+
new_vdom = build_vdom
|
|
77
|
+
patches = VDOM::Differ.diff(@vdom, new_vdom)
|
|
78
|
+
@dom_element = VDOM::Patcher.apply(@dom_element, patches)
|
|
79
|
+
@vdom = new_vdom
|
|
80
|
+
end
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Benefits**:
|
|
84
|
+
- Efficient DOM updates (minimal mutations)
|
|
85
|
+
- Declarative UI (describe what, not how)
|
|
86
|
+
- Cross-platform potential (VDOM is abstraction layer)
|
|
87
|
+
|
|
88
|
+
### 3. Reactivity Model
|
|
89
|
+
|
|
90
|
+
**Choice**: Explicit Updates (Manual Trigger)
|
|
91
|
+
|
|
92
|
+
| Approach | Mechanism | Examples |
|
|
93
|
+
|----------|-----------|----------|
|
|
94
|
+
| **Explicit Updates** | Manual `setState()`/`patch()` | React, **Funicular** |
|
|
95
|
+
| Auto-tracking | Proxy/Getter dependency tracking | Vue 3, Solid.js |
|
|
96
|
+
| Compile-time | Compiler analyzes dependencies | Svelte |
|
|
97
|
+
|
|
98
|
+
**Example**:
|
|
99
|
+
```ruby
|
|
100
|
+
def handle_input(event)
|
|
101
|
+
patch(username: event.target[:value]) # Explicit call
|
|
102
|
+
end
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Benefits**:
|
|
106
|
+
- Simple, predictable
|
|
107
|
+
- Low runtime overhead
|
|
108
|
+
- Easy to debug (explicit control flow)
|
|
109
|
+
|
|
110
|
+
### 4. Component Model
|
|
111
|
+
|
|
112
|
+
**Choice**: Class-based Components
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
class ChatComponent < Funicular::Component
|
|
116
|
+
def initialize_state
|
|
117
|
+
{ messages: [] }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def component_mounted
|
|
121
|
+
# Lifecycle hook
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def render
|
|
125
|
+
div { "Chat UI" }
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Features**:
|
|
131
|
+
- Lifecycle hooks: `component_mounted`, `component_unmounted`
|
|
132
|
+
- Instance variables for component-local data
|
|
133
|
+
- Suspense support for async data loading
|
|
134
|
+
- Similar to React Class Components
|
|
135
|
+
|
|
136
|
+
### 5. State Management
|
|
137
|
+
|
|
138
|
+
**Choice**: Local State + Props Drilling + Model Layer
|
|
139
|
+
|
|
140
|
+
| Approach | Description | Example |
|
|
141
|
+
|----------|-------------|---------|
|
|
142
|
+
| **Local State** | Component-scoped `@state` | **Funicular** |
|
|
143
|
+
| Global Store | Centralized state tree | Redux, Vuex |
|
|
144
|
+
| Context/DI | Share state within tree | React Context |
|
|
145
|
+
|
|
146
|
+
**Architecture**:
|
|
147
|
+
- Components manage their own state (`@state`)
|
|
148
|
+
- Parent-to-child communication via `props`
|
|
149
|
+
- Server state managed by `Model` layer (ActiveRecord-style API)
|
|
150
|
+
|
|
151
|
+
**No Global Store**: Funicular intentionally omits Redux-style global state to keep things simple. For shared state, use:
|
|
152
|
+
- Props drilling
|
|
153
|
+
- Model layer for server data
|
|
154
|
+
- Component composition
|
|
155
|
+
|
|
156
|
+
### 6. Routing
|
|
157
|
+
|
|
158
|
+
**Choice**: Client-Side Routing (History API)
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
Funicular.start(container: 'app') do |router|
|
|
162
|
+
router.get('/chat/:channel_id', to: ChatComponent, as: 'chat_channel')
|
|
163
|
+
end
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Features**:
|
|
167
|
+
- Rails-style DSL
|
|
168
|
+
- URL parameter extraction (`/chat/:id` -> `{ id: "123" }`)
|
|
169
|
+
- Auto-generated URL helpers: `chat_channel_path(id)`
|
|
170
|
+
- History API for SPA navigation
|
|
171
|
+
|
|
172
|
+
### 7. Rendering Mode
|
|
173
|
+
|
|
174
|
+
**Choice**: Pure Client-Side Rendering (CSR)
|
|
175
|
+
|
|
176
|
+
| Mode | Description | Funicular Support |
|
|
177
|
+
|------|-------------|-------------------|
|
|
178
|
+
| **CSR** | Render in browser | ✅ Yes |
|
|
179
|
+
| SSR | Server-side rendering | ❌ No (for now...) |
|
|
180
|
+
| SSG | Static site generation | ❌ No |
|
|
181
|
+
| ISR | Incremental static regen | ❌ No |
|
|
182
|
+
|
|
183
|
+
**Reason**: PicoRuby.wasm runs only in browsers. Rails serves JSON APIs + assets.
|
|
184
|
+
|
|
185
|
+
### 8. Template System
|
|
186
|
+
|
|
187
|
+
**Choice**: Ruby DSL (Not JSX or Template Strings)
|
|
188
|
+
|
|
189
|
+
```ruby
|
|
190
|
+
def render
|
|
191
|
+
div(class: 'container') do
|
|
192
|
+
h1 { 'Welcome' }
|
|
193
|
+
input(value: state.username, oninput: :handle_input)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Features**:
|
|
199
|
+
- HTML tag names are Ruby methods
|
|
200
|
+
- Blocks for child elements
|
|
201
|
+
- Event handlers as symbols or Procs
|
|
202
|
+
- Full Ruby expressiveness (loops, conditionals, etc.)
|
|
203
|
+
|
|
204
|
+
**Comparison**:
|
|
205
|
+
|
|
206
|
+
| Framework | Template Syntax |
|
|
207
|
+
|-----------|-----------------|
|
|
208
|
+
| React | JSX (XML-like) |
|
|
209
|
+
| Vue | Template strings with directives |
|
|
210
|
+
| Svelte | HTML-like template syntax |
|
|
211
|
+
| **Funicular** | **Ruby DSL** |
|
|
212
|
+
|
|
213
|
+
### 9. Build Strategy
|
|
214
|
+
|
|
215
|
+
**Choice**: Runtime Execution (No Build Step)
|
|
216
|
+
|
|
217
|
+
| Approach | Description | Examples |
|
|
218
|
+
|----------|-------------|----------|
|
|
219
|
+
| **Runtime** | Code runs directly in browser | Vue (CDN), **Funicular** |
|
|
220
|
+
| Compile-time | Pre-build required | Svelte, Angular |
|
|
221
|
+
| Hybrid | JSX compiled, runtime VDOM | React |
|
|
222
|
+
|
|
223
|
+
**Funicular's Approach**:
|
|
224
|
+
- `app/funicular/**/*.rb` files are compiled to mruby bytecode (`.mrb`) via `picorbc`
|
|
225
|
+
- Compilation output: `app/assets/builds/app.mrb`
|
|
226
|
+
- Rails autoloading is explicitly disabled for `app/funicular/` directory
|
|
227
|
+
- Asset pipeline automatically handles compilation (hooks into `assets:precompile`)
|
|
228
|
+
- Developers don't need to manually compile (transparent automation)
|
|
229
|
+
|
|
230
|
+
**Build Process**:
|
|
231
|
+
```bash
|
|
232
|
+
# Automatic in production
|
|
233
|
+
rake assets:precompile # -> calls funicular:compile
|
|
234
|
+
|
|
235
|
+
# Manual compilation (if needed)
|
|
236
|
+
rake funicular:compile
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Benefits**:
|
|
240
|
+
- Automated compilation via asset pipeline
|
|
241
|
+
- Developers work with plain Ruby files
|
|
242
|
+
- Production serves optimized bytecode
|
|
243
|
+
- No separate build tool required (uses existing Rails toolchain)
|
|
244
|
+
|
|
245
|
+
### 10. Concurrency
|
|
246
|
+
|
|
247
|
+
**Choice**: Synchronous Rendering + Async Data Fetching
|
|
248
|
+
|
|
249
|
+
- `patch()` triggers immediate `re_render()`
|
|
250
|
+
- No batching or scheduling
|
|
251
|
+
- Suspense feature for async data with loading states
|
|
252
|
+
- `min_delay` option prevents spinner flickering
|
|
253
|
+
|
|
254
|
+
```ruby
|
|
255
|
+
use_suspense :user,
|
|
256
|
+
->(resolve, reject) { User.find(id, &resolve) },
|
|
257
|
+
min_delay: 300 # Minimum loading spinner duration
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### 11. Event System
|
|
261
|
+
|
|
262
|
+
**Choice**: Native DOM Events (Not Synthetic Events)
|
|
263
|
+
|
|
264
|
+
- Direct `addEventListener` usage
|
|
265
|
+
- Event listeners re-bound on each re-render
|
|
266
|
+
- `cleanup_events()` prevents memory leaks
|
|
267
|
+
- No event pooling or synthetic event objects
|
|
268
|
+
|
|
269
|
+
**Unified Callback Handling**:
|
|
270
|
+
|
|
271
|
+
All event handlers accept `Symbol | Method | Proc`:
|
|
272
|
+
|
|
273
|
+
```ruby
|
|
274
|
+
# All valid:
|
|
275
|
+
button(onclick: :handle_click) # Symbol (recommended)
|
|
276
|
+
button(onclick: method(:handle_click)) # Method (explicit)
|
|
277
|
+
button(onclick: -> { handle_click }) # Proc (inline)
|
|
278
|
+
button(onclick: -> { patch(count: count + 1) }) # Proc (inline logic)
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Recommendation**:
|
|
282
|
+
- Use `:symbol` for method references (concise)
|
|
283
|
+
- Use `-> { }` for inline logic
|
|
284
|
+
- Use `method(:name)` when passing callbacks to child components
|
|
285
|
+
|
|
286
|
+
### 12. Error Handling
|
|
287
|
+
|
|
288
|
+
**Choice**: Error Boundary Pattern (React-style)
|
|
289
|
+
|
|
290
|
+
```ruby
|
|
291
|
+
component(Funicular::ErrorBoundary,
|
|
292
|
+
fallback: ->(error) { div { "Error: #{error.message}" } }
|
|
293
|
+
) do
|
|
294
|
+
component(RiskyComponent)
|
|
295
|
+
end
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
- Catches errors from child components
|
|
299
|
+
- Displays fallback UI
|
|
300
|
+
- Prevents entire app crash
|
|
301
|
+
- Stack-based boundary resolution
|
|
302
|
+
|
|
303
|
+
### 13. Data Fetching
|
|
304
|
+
|
|
305
|
+
**Choice**: Manual Fetch + Model Abstraction Layer
|
|
306
|
+
|
|
307
|
+
**Low-level**: `HTTP.get`, `HTTP.post`
|
|
308
|
+
**High-level**: `Model.all`, `Model.find`, `Model.create`
|
|
309
|
+
|
|
310
|
+
```ruby
|
|
311
|
+
# ActiveRecord-style API
|
|
312
|
+
User.all do |users, error|
|
|
313
|
+
patch(users: users)
|
|
314
|
+
end
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**Features**:
|
|
318
|
+
- Callback-based (not Promise-based)
|
|
319
|
+
- Automatic CSRF token handling
|
|
320
|
+
- Rails API integration
|
|
321
|
+
- No built-in caching (like SWR/React Query)
|
|
322
|
+
|
|
323
|
+
## Comparison with Other Frameworks
|
|
324
|
+
|
|
325
|
+
### Funicular vs React
|
|
326
|
+
|
|
327
|
+
| Aspect | Funicular | React |
|
|
328
|
+
|--------|-----------|-------|
|
|
329
|
+
| Language | Ruby | JavaScript/JSX |
|
|
330
|
+
| Components | Class-based | Function (Hooks) or Class |
|
|
331
|
+
| State Update | `patch()` | `setState()` / `useState()` |
|
|
332
|
+
| VDOM | Custom implementation | Custom implementation |
|
|
333
|
+
| Data Fetching | Model layer | Manual / libraries |
|
|
334
|
+
| Routing | Built-in | Separate library |
|
|
335
|
+
| Build Step | Asset Pipeline integration | Required (Babel/JSX) |
|
|
336
|
+
|
|
337
|
+
### Funicular vs Vue
|
|
338
|
+
|
|
339
|
+
| Aspect | Funicular | Vue |
|
|
340
|
+
|--------|-----------|-----|
|
|
341
|
+
| Data Binding | Unidirectional | Bidirectional (v-model) |
|
|
342
|
+
| Reactivity | Explicit `patch()` | Auto-tracking (Proxy) |
|
|
343
|
+
| Templates | Ruby DSL | HTML-like templates |
|
|
344
|
+
| SSR | No | Yes |
|
|
345
|
+
|
|
346
|
+
### Funicular vs Svelte
|
|
347
|
+
|
|
348
|
+
| Aspect | Funicular | Svelte |
|
|
349
|
+
|--------|-----------|--------|
|
|
350
|
+
| VDOM | Yes | No (compiles to imperative code) |
|
|
351
|
+
| Reactivity | Explicit | Compile-time analysis |
|
|
352
|
+
| Build Step | Asset Pipeline integration | Required (compiler) |
|
|
353
|
+
|
|
354
|
+
## Design Philosophy
|
|
355
|
+
|
|
356
|
+
Funicular embodies **"PicoRuby.wasm-based React"** with these principles:
|
|
357
|
+
|
|
358
|
+
1. **Ruby First**: Leverage Ruby's expressiveness for frontend development
|
|
359
|
+
2. **Explicit Over Magic**: Predictable, explicit state updates
|
|
360
|
+
3. **Rails Integration**: Seamless Rails API + ActionCable + Asset Pipeline integration
|
|
361
|
+
4. **Simple by Default**: No global state, no complex build tools
|
|
362
|
+
5. **Progressive Enhancement**: Start simple, add complexity when needed
|
|
363
|
+
|
|
364
|
+
## Trade-offs
|
|
365
|
+
|
|
366
|
+
### Strengths
|
|
367
|
+
|
|
368
|
+
✅ **Ruby Expressiveness**: Full Ruby syntax in templates
|
|
369
|
+
```ruby
|
|
370
|
+
state.messages.map { |msg| div(key: msg.id) { msg.content } }
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
✅ **No Build Step**: Instant development feedback
|
|
374
|
+
|
|
375
|
+
✅ **Rails Integration**: CSRF tokens, ActiveRecord-style APIs
|
|
376
|
+
|
|
377
|
+
✅ **Lightweight Dependencies**: Minimal gem dependencies
|
|
378
|
+
|
|
379
|
+
### Limitations
|
|
380
|
+
|
|
381
|
+
❌ **No SSR**: PicoRuby.wasm is browser-only (for now...)
|
|
382
|
+
|
|
383
|
+
❌ **No npm Ecosystem**: Limited to Ruby gems
|
|
384
|
+
|
|
385
|
+
❌ **State Management**: No global store (can be complex at scale)
|
|
386
|
+
|
|
387
|
+
❌ **Performance Overhead**: WebAssembly Ruby execution slower than native JS
|
|
388
|
+
|
|
389
|
+
## Best Use Cases
|
|
390
|
+
|
|
391
|
+
### ✅ Well-Suited For:
|
|
392
|
+
|
|
393
|
+
- **Rails SPA Features**: Adding interactive UI to Rails apps
|
|
394
|
+
- **Small to Medium SPAs**: Dashboard, admin panels, chat apps
|
|
395
|
+
- **Ruby Teams**: Frontend work by Ruby developers
|
|
396
|
+
- **Rapid Prototyping**: Quick interactive UI development
|
|
397
|
+
|
|
398
|
+
### ❌ Not Ideal For:
|
|
399
|
+
|
|
400
|
+
- **SEO-Critical Apps**: Needs SSR (use Rails views or Next.js)
|
|
401
|
+
- **Large-Scale SPAs**: Complex state management requirements
|
|
402
|
+
- **Mobile Apps**: Use React Native or native solutions
|
|
403
|
+
- **Performance-Critical**: Real-time games, high-frequency updates
|
|
404
|
+
|
|
405
|
+
## Conclusion
|
|
406
|
+
|
|
407
|
+
Funicular adopts proven patterns from React (unidirectional flow, VDOM) while providing unique value through **Ruby ecosystem integration**. It's not trying to replace JavaScript frameworks for all use cases, but rather offers a compelling alternative for Ruby teams building interactive web applications.
|
|
408
|
+
|
|
409
|
+
The architecture prioritizes **simplicity, predictability, and Rails integration** over features like maximum performance. This makes it an excellent choice for its target use cases: Ruby teams building SPAs as part of Rails applications.
|