funicular 0.2.0 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +54 -0
- data/README.md +5 -5
- data/Rakefile +23 -15
- data/demo/test_chartjs.html +17 -17
- data/demo/test_component.html +15 -15
- data/demo/test_error_boundary.html +41 -41
- data/demo/test_router.html +57 -57
- data/demo/tic-tac-toe.html +29 -29
- data/docs/architecture.md +43 -30
- data/lib/funicular/compiler.rb +15 -16
- data/lib/funicular/helpers/picoruby_helper.rb +24 -1
- data/lib/funicular/ssr/runtime.rb +2 -0
- data/lib/funicular/ssr.rb +2 -1
- data/lib/funicular/testing/node_runner.rb +1 -1
- data/lib/funicular/vendor/mrbc/VERSION +1 -0
- data/lib/funicular/vendor/{picorbc/picorbc.js → mrbc/mrbc.js} +14 -1
- data/lib/funicular/vendor/mrbc/mrbc.wasm +0 -0
- data/lib/funicular/vendor/picoruby/VERSION +1 -1
- data/lib/funicular/vendor/picoruby/debug/init.iife.js +19 -4
- data/lib/funicular/vendor/picoruby/debug/picoruby.js +38 -23
- data/lib/funicular/vendor/picoruby/debug/picoruby.wasm +0 -0
- data/lib/funicular/vendor/picoruby/dist/init.iife.js +19 -4
- data/lib/funicular/vendor/picoruby/dist/picoruby.js +1 -1
- data/lib/funicular/vendor/picoruby/dist/picoruby.wasm +0 -0
- data/lib/funicular/vendor/picoruby-test-node/VERSION +1 -1
- data/lib/funicular/vendor/picoruby-test-node/picoruby.js +47 -23
- data/lib/funicular/vendor/picoruby-test-node/picoruby.wasm +0 -0
- data/lib/funicular/vendor/picoruby-test-node/picoruby.wasm.map +1 -1
- data/lib/funicular/version.rb +1 -1
- data/lib/generators/funicular/chat/templates/funicular_chat_component.rb.tt +47 -46
- data/lib/tasks/funicular.rake +1 -1
- data/minitest/commands_routes_test.rb +97 -0
- data/minitest/compiler_test.rb +195 -0
- data/minitest/configuration_test.rb +64 -0
- data/minitest/fixtures/funicular_app/components/greeting_component.rb +6 -6
- data/minitest/form_for_test.rb +106 -0
- data/minitest/funicular_test.rb +28 -2
- data/minitest/hydration_test.rb +2 -2
- data/minitest/middleware_test.rb +154 -0
- data/minitest/picoruby_helper_test.rb +139 -0
- data/minitest/route_parser_test.rb +139 -0
- data/minitest/schema_test.rb +23 -0
- data/minitest/ssr_test.rb +57 -0
- data/minitest/support/rails_stub.rb +59 -0
- data/minitest/test_helper.rb +20 -0
- data/minitest/testing_test.rb +267 -0
- data/minitest/view_context_test.rb +101 -0
- data/mrblib/component.rb +210 -229
- data/mrblib/debug.rb +7 -6
- data/mrblib/differ.rb +3 -1
- data/mrblib/error_boundary.rb +19 -27
- data/mrblib/form_builder.rb +34 -24
- data/mrblib/funicular.rb +2 -1
- data/mrblib/html_serializer.rb +14 -13
- data/mrblib/http.rb +12 -1
- data/mrblib/patcher.rb +16 -9
- data/mrblib/router.rb +9 -5
- data/mrblib/runtime.rb +28 -0
- data/mrblib/styles.rb +13 -17
- data/mrblib/vdom.rb +92 -9
- data/mrblib/view_context.rb +135 -0
- data/sig/component.rbs +34 -85
- data/sig/error_boundary.rbs +4 -4
- data/sig/form_builder.rbs +2 -1
- data/sig/html_serializer.rbs +2 -1
- data/sig/http.rbs +1 -0
- data/sig/patcher.rbs +1 -1
- data/sig/router.rbs +2 -13
- data/sig/runtime.rbs +13 -0
- data/sig/styles.rbs +3 -4
- data/sig/vdom.rbs +11 -2
- data/sig/view_context.rbs +47 -0
- metadata +19 -5
- data/lib/funicular/vendor/picorbc/VERSION +0 -1
- data/lib/funicular/vendor/picorbc/picorbc.wasm +0 -0
data/demo/tic-tac-toe.html
CHANGED
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
on_click.call if on_click
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
def render
|
|
42
|
-
button(class: 'square', onclick: :handle_click) do
|
|
41
|
+
def render(h)
|
|
42
|
+
h.button(class: 'square', onclick: :handle_click) do
|
|
43
43
|
props[:value] || ''
|
|
44
44
|
end
|
|
45
45
|
end
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
}
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
-
def render
|
|
58
|
+
def render(h)
|
|
59
59
|
squares = props[:squares]
|
|
60
60
|
x_is_next = props[:x_is_next]
|
|
61
61
|
|
|
@@ -66,22 +66,22 @@
|
|
|
66
66
|
"Next player: #{x_is_next ? 'X' : 'O'}"
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
-
div do
|
|
70
|
-
div(class: 'status') { status }
|
|
71
|
-
div(class: 'board-row') do
|
|
72
|
-
component(Square, value: squares[0], on_click: handle_click(0))
|
|
73
|
-
component(Square, value: squares[1], on_click: handle_click(1))
|
|
74
|
-
component(Square, value: squares[2], on_click: handle_click(2))
|
|
69
|
+
h.div do |hh|
|
|
70
|
+
hh.div(class: 'status') { status }
|
|
71
|
+
hh.div(class: 'board-row') do |hhh|
|
|
72
|
+
hhh.component(Square, value: squares[0], on_click: handle_click(0))
|
|
73
|
+
hhh.component(Square, value: squares[1], on_click: handle_click(1))
|
|
74
|
+
hhh.component(Square, value: squares[2], on_click: handle_click(2))
|
|
75
75
|
end
|
|
76
|
-
div(class: 'board-row') do
|
|
77
|
-
component(Square, value: squares[3], on_click: handle_click(3))
|
|
78
|
-
component(Square, value: squares[4], on_click: handle_click(4))
|
|
79
|
-
component(Square, value: squares[5], on_click: handle_click(5))
|
|
76
|
+
hh.div(class: 'board-row') do |hhh|
|
|
77
|
+
hhh.component(Square, value: squares[3], on_click: handle_click(3))
|
|
78
|
+
hhh.component(Square, value: squares[4], on_click: handle_click(4))
|
|
79
|
+
hhh.component(Square, value: squares[5], on_click: handle_click(5))
|
|
80
80
|
end
|
|
81
|
-
div(class: 'board-row') do
|
|
82
|
-
component(Square, value: squares[6], on_click: handle_click(6))
|
|
83
|
-
component(Square, value: squares[7], on_click: handle_click(7))
|
|
84
|
-
component(Square, value: squares[8], on_click: handle_click(8))
|
|
81
|
+
hh.div(class: 'board-row') do |hhh|
|
|
82
|
+
hhh.component(Square, value: squares[6], on_click: handle_click(6))
|
|
83
|
+
hhh.component(Square, value: squares[7], on_click: handle_click(7))
|
|
84
|
+
hhh.component(Square, value: squares[8], on_click: handle_click(8))
|
|
85
85
|
end
|
|
86
86
|
end
|
|
87
87
|
end
|
|
@@ -96,8 +96,8 @@
|
|
|
96
96
|
end
|
|
97
97
|
|
|
98
98
|
def handle_play(i)
|
|
99
|
-
history = state
|
|
100
|
-
current_move = state
|
|
99
|
+
history = state[:history]
|
|
100
|
+
current_move = state[:current_move]
|
|
101
101
|
current_squares = history[current_move].dup
|
|
102
102
|
|
|
103
103
|
current_squares[i] = (current_move % 2 == 0) ? 'X' : 'O'
|
|
@@ -115,30 +115,30 @@
|
|
|
115
115
|
}
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
-
def render
|
|
119
|
-
history = state
|
|
120
|
-
current_move = state
|
|
118
|
+
def render(h)
|
|
119
|
+
history = state[:history]
|
|
120
|
+
current_move = state[:current_move]
|
|
121
121
|
x_is_next = (current_move % 2 == 0)
|
|
122
122
|
current_squares = history[current_move]
|
|
123
123
|
|
|
124
|
-
div(class: 'game') do
|
|
125
|
-
div(class: 'game-board') do
|
|
126
|
-
component(Board,
|
|
124
|
+
h.div(class: 'game') do |hh|
|
|
125
|
+
hh.div(class: 'game-board') do |hhh|
|
|
126
|
+
hhh.component(Board,
|
|
127
127
|
x_is_next: x_is_next,
|
|
128
128
|
squares: current_squares,
|
|
129
129
|
on_play: ->(i) { handle_play(i) }
|
|
130
130
|
)
|
|
131
131
|
end
|
|
132
|
-
div(class: 'game-info') do
|
|
133
|
-
ol do
|
|
132
|
+
hh.div(class: 'game-info') do |hhh|
|
|
133
|
+
hhh.ol do |hhhh|
|
|
134
134
|
history.each_with_index do |_squares, move|
|
|
135
135
|
description = if move > 0
|
|
136
136
|
"Go to move ##{move}"
|
|
137
137
|
else
|
|
138
138
|
'Go to game start'
|
|
139
139
|
end
|
|
140
|
-
li(key: move) do
|
|
141
|
-
button(onclick: jump_to(move)) { description }
|
|
140
|
+
hhhh.li(key: move) do |item|
|
|
141
|
+
item.button(onclick: jump_to(move)) { description }
|
|
142
142
|
end
|
|
143
143
|
end
|
|
144
144
|
end
|
data/docs/architecture.md
CHANGED
|
@@ -26,34 +26,47 @@ stay free of browser-only calls on any server code path
|
|
|
26
26
|
|
|
27
27
|
## `mrblib/` runtime: responsibilities
|
|
28
28
|
|
|
29
|
-
| File(s)
|
|
30
|
-
|
|
31
|
-
| `funicular.rb`
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
34
|
-
| `
|
|
35
|
-
| `
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
42
|
-
| `
|
|
43
|
-
| `
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
29
|
+
| File(s) | Responsibility |
|
|
30
|
+
|---------------------------------------------------------|-------------------------------------------------------------------------------------------------|
|
|
31
|
+
| `funicular.rb` | Top-level module: `start`, `router`, `server?`, `debug_color` export |
|
|
32
|
+
| `runtime.rb` | Per-app runtime context propagated through render/SSR/hydration |
|
|
33
|
+
| `view_context.rb` | Thin render facade passed as `render(h)`: elements, components, forms, routes, styles, suspense |
|
|
34
|
+
| `component.rb` | `Funicular::Component` base: state, props, lifecycle, suspense loading, refs |
|
|
35
|
+
| `vdom.rb` | Virtual DOM nodes, including component vnodes with ordinary `children` |
|
|
36
|
+
| `differ.rb` | `Differ.diff(old, new)` -- minimal patch set, key-based list reconciliation |
|
|
37
|
+
| `patcher.rb` | `Patcher.apply(dom, patches)` -- apply patches to the real DOM |
|
|
38
|
+
| `html_serializer.rb` | `VDOM::HTMLSerializer` -- VDOM to HTML string (used by SSR) |
|
|
39
|
+
| `router.rb` | Client-side router, route DSL, per-runtime route helper object, History API |
|
|
40
|
+
| `model.rb` | Object-REST Mapper (`all`/`find`/`create`/`update`/`destroy`) |
|
|
41
|
+
| `http.rb` | Low-level fetch wrapper, CSRF, IndexedDB response cache |
|
|
42
|
+
| `cable.rb` | ActionCable-compatible consumer/subscription client |
|
|
43
|
+
| `store.rb`, `store_singleton.rb`, `store_collection.rb` | IndexedDB-backed stores, scope API, `subscribes_to`, event dispatch |
|
|
44
|
+
| `form_builder.rb` | `h.form_for` field helpers with inline error rendering |
|
|
45
|
+
| `0_validations.rb`, `1_validators.rb` | ActiveModel-style validators and `errors` |
|
|
46
|
+
| `styles.rb` | CSS-in-Ruby `styles { |css| css.define ... }` and `h.styles[...]` access |
|
|
47
|
+
| `error_boundary.rb` | `ErrorBoundary` component |
|
|
48
|
+
| `file_upload.rb` | File / FormData upload helper |
|
|
49
|
+
| `debug.rb` | Development-only component/error registry for the DevTools extension |
|
|
50
|
+
| `environment_inquirer.rb` | Environment detection (`server?`, `development?`) |
|
|
49
51
|
|
|
50
52
|
The render cycle: a state change calls `patch()`, which rebuilds the component's
|
|
51
|
-
VDOM
|
|
52
|
-
with `Patcher`. Event handlers are native DOM listeners,
|
|
53
|
+
VDOM by calling `render(h)`, diffs it against the previous VDOM with `Differ`,
|
|
54
|
+
and applies the result with `Patcher`. Event handlers are native DOM listeners,
|
|
55
|
+
re-bound on each render.
|
|
56
|
+
|
|
57
|
+
Application code receives all render helpers through `h`. HTML is authored as
|
|
58
|
+
`h.div`, custom elements as `h.tag(:custom_element)`, child components as
|
|
59
|
+
`h.component`, forms as `h.form_for`, styles as `h.styles[:name]`, resources as
|
|
60
|
+
`h.resources[:name]`, and routes as `h.routes.user_path(id)`. Component state is
|
|
61
|
+
explicitly read with `state[:name]` or `state.fetch(:name)`.
|
|
62
|
+
|
|
63
|
+
Component children are ordinary VDOM children stored on
|
|
64
|
+
`VDOM::Component#children`; there is no delayed `children_block` prop. This keeps
|
|
65
|
+
SSR, diffing, ErrorBoundary rendering, and hydration on the same data model.
|
|
53
66
|
|
|
54
67
|
## `lib/` Rails integration
|
|
55
68
|
|
|
56
|
-
- `compiler.rb` -- runs the vendored `
|
|
69
|
+
- `compiler.rb` -- runs the vendored `mrbc` (WebAssembly, via Node.js) to
|
|
57
70
|
compile `app/funicular/**/*.rb` (models, then stores, then components, then
|
|
58
71
|
initializers) into a single `app/assets/builds/app.mrb`. `-g` is added in
|
|
59
72
|
development for debug symbols.
|
|
@@ -73,12 +86,12 @@ with `Patcher`. Event handlers are native DOM listeners, re-bound on each render
|
|
|
73
86
|
## Vendored artifacts
|
|
74
87
|
|
|
75
88
|
`rake copy_wasm` (run by `rake build`) copies the PicoRuby.wasm runtime and the
|
|
76
|
-
`
|
|
89
|
+
`mrbc` compiler from the sibling `mrbgems/picoruby-wasm/npm/` directory into
|
|
77
90
|
`lib/funicular/vendor/`:
|
|
78
91
|
|
|
79
92
|
- `vendor/picoruby/dist/` -- production runtime build
|
|
80
93
|
- `vendor/picoruby/debug/` -- development runtime build (debug symbols)
|
|
81
|
-
- `vendor/
|
|
94
|
+
- `vendor/mrbc/` -- the mruby compiler (run through Node.js)
|
|
82
95
|
|
|
83
96
|
Because `copy_wasm` reads sibling directories inside the picoruby repository, it
|
|
84
97
|
only works from within that checkout -- see Development below.
|
|
@@ -87,11 +100,11 @@ only works from within that checkout -- see Development below.
|
|
|
87
100
|
|
|
88
101
|
For SSR the `mrblib/` framework is loaded into the Rails process under CRuby.
|
|
89
102
|
`Funicular::SSR.render(path:, state:)` resolves the path against the routes in
|
|
90
|
-
`app/funicular/initializer.rb`, builds
|
|
91
|
-
with `HTMLSerializer`. The state is also
|
|
92
|
-
so the browser can hydrate the markup
|
|
93
|
-
deterministic and free of browser-only
|
|
94
|
-
sides.
|
|
103
|
+
`app/funicular/initializer.rb`, builds a `Runtime` around that router, builds the
|
|
104
|
+
component's VDOM, and serializes it with `HTMLSerializer`. The state is also
|
|
105
|
+
embedded as `window.__FUNICULAR_STATE__` so the browser can hydrate the markup
|
|
106
|
+
rather than rebuild it. Keep `render(h)` deterministic and free of browser-only
|
|
107
|
+
calls so the same code is safe on both sides.
|
|
95
108
|
|
|
96
109
|
## Development
|
|
97
110
|
|
data/lib/funicular/compiler.rb
CHANGED
|
@@ -5,11 +5,11 @@ require "shellwords"
|
|
|
5
5
|
module Funicular
|
|
6
6
|
class Compiler
|
|
7
7
|
class NodeNotFoundError < StandardError; end
|
|
8
|
-
class
|
|
8
|
+
class MrbcMissingError < StandardError; end
|
|
9
9
|
|
|
10
|
-
#
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
# mrbc.js + mrbc.wasm bundled into the gem at build time by `rake copy_wasm`.
|
|
11
|
+
MRBC_DIR = File.expand_path("vendor/mrbc", __dir__)
|
|
12
|
+
MRBC_JS = File.join(MRBC_DIR, "mrbc.js")
|
|
13
13
|
|
|
14
14
|
# Ordered list of application source files under app/funicular/.
|
|
15
15
|
# Order matters: models -> stores -> components -> initializer, so that
|
|
@@ -36,19 +36,19 @@ module Funicular
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def compile
|
|
39
|
-
|
|
39
|
+
check_mrbc_availability!
|
|
40
40
|
gather_source_files
|
|
41
41
|
compile_to_mrb
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
private
|
|
45
45
|
|
|
46
|
-
def
|
|
47
|
-
unless File.exist?(
|
|
48
|
-
raise
|
|
49
|
-
Vendored
|
|
46
|
+
def check_mrbc_availability!
|
|
47
|
+
unless File.exist?(MRBC_JS)
|
|
48
|
+
raise MrbcMissingError, <<~ERROR
|
|
49
|
+
Vendored mrbc not found at #{MRBC_JS}.
|
|
50
50
|
|
|
51
|
-
The funicular gem ships
|
|
51
|
+
The funicular gem ships mrbc.js + mrbc.wasm inside the gem
|
|
52
52
|
package. This file is missing, which likely means the gem was not
|
|
53
53
|
installed correctly. Try reinstalling:
|
|
54
54
|
|
|
@@ -61,7 +61,7 @@ module Funicular
|
|
|
61
61
|
raise NodeNotFoundError, <<~ERROR
|
|
62
62
|
Node.js executable not found.
|
|
63
63
|
|
|
64
|
-
Funicular compiles Ruby to .mrb using a WebAssembly build of
|
|
64
|
+
Funicular compiles Ruby to .mrb using a WebAssembly build of mrbc
|
|
65
65
|
which is run via Node.js. Please install Node.js and ensure `node`
|
|
66
66
|
is on your PATH (or set the NODE environment variable).
|
|
67
67
|
ERROR
|
|
@@ -88,6 +88,8 @@ module Funicular
|
|
|
88
88
|
end
|
|
89
89
|
|
|
90
90
|
# Create a small temp file for ENV setting
|
|
91
|
+
output_dir = File.dirname(output_file)
|
|
92
|
+
FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir)
|
|
91
93
|
env_file = "#{output_file}.env.rb"
|
|
92
94
|
File.open(env_file, "w") do |f|
|
|
93
95
|
f.puts "ENV['FUNICULAR_ENV'] = '#{Rails.env}'"
|
|
@@ -106,12 +108,9 @@ module Funicular
|
|
|
106
108
|
end
|
|
107
109
|
|
|
108
110
|
def compile_to_mrb
|
|
109
|
-
output_dir = File.dirname(output_file)
|
|
110
|
-
FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir)
|
|
111
|
-
|
|
112
111
|
all_files = @source_files.dup
|
|
113
112
|
all_files << @env_file if @env_file
|
|
114
|
-
argv = [node_command,
|
|
113
|
+
argv = [node_command, MRBC_JS]
|
|
115
114
|
argv << "-g" if debug_mode
|
|
116
115
|
argv += ["-o", output_file.to_s]
|
|
117
116
|
argv += all_files.map(&:to_s)
|
|
@@ -129,7 +128,7 @@ module Funicular
|
|
|
129
128
|
result = system(*argv)
|
|
130
129
|
|
|
131
130
|
unless result
|
|
132
|
-
raise "Failed to compile with
|
|
131
|
+
raise "Failed to compile with mrbc. Command: #{Shellwords.join(argv)}"
|
|
133
132
|
end
|
|
134
133
|
|
|
135
134
|
log "Successfully compiled to #{output_file}"
|
|
@@ -11,6 +11,11 @@ module Funicular
|
|
|
11
11
|
local_dist: "/picoruby/dist/init.iife.js"
|
|
12
12
|
}.freeze
|
|
13
13
|
|
|
14
|
+
LOCAL_VARIANTS = {
|
|
15
|
+
local_debug: "debug",
|
|
16
|
+
local_dist: "dist"
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
14
19
|
# Minimal CSS the gem ships for class names it emits itself (e.g.
|
|
15
20
|
# FormBuilder error states). Read once; see assets/funicular.css.
|
|
16
21
|
BASE_CSS_PATH = File.expand_path("../assets/funicular.css", __dir__)
|
|
@@ -101,12 +106,30 @@ module Funicular
|
|
|
101
106
|
end
|
|
102
107
|
format(CDN_URL_TEMPLATE, version: version)
|
|
103
108
|
elsif (path = LOCAL_PATHS[source])
|
|
104
|
-
path
|
|
109
|
+
local_picoruby_src(path, source)
|
|
105
110
|
else
|
|
106
111
|
raise ArgumentError,
|
|
107
112
|
"Unknown picoruby source: #{source.inspect}. Expected one of #{Funicular::Configuration::SOURCES.inspect}"
|
|
108
113
|
end
|
|
109
114
|
end
|
|
115
|
+
|
|
116
|
+
def local_picoruby_src(path, source)
|
|
117
|
+
cache_key = local_picoruby_cache_key(source)
|
|
118
|
+
return path if cache_key.nil?
|
|
119
|
+
|
|
120
|
+
"#{path}?v=#{cache_key}"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def local_picoruby_cache_key(source)
|
|
124
|
+
variant = LOCAL_VARIANTS.fetch(source)
|
|
125
|
+
version = Funicular.vendored_wasm_version
|
|
126
|
+
wasm = File.join(Funicular::VENDOR_PICORUBY_DIR, variant, "picoruby.wasm")
|
|
127
|
+
mtime = File.mtime(wasm).to_i
|
|
128
|
+
|
|
129
|
+
[version || Funicular::VERSION, mtime].join("-")
|
|
130
|
+
rescue Errno::ENOENT
|
|
131
|
+
Funicular.vendored_wasm_version || Funicular::VERSION
|
|
132
|
+
end
|
|
110
133
|
end
|
|
111
134
|
end
|
|
112
135
|
end
|
data/lib/funicular/ssr.rb
CHANGED
|
@@ -30,8 +30,9 @@ module Funicular
|
|
|
30
30
|
return { html: "", state: {}, component: nil } unless component_class
|
|
31
31
|
|
|
32
32
|
instance = component_class.new(symbolize_keys(params).merge(props))
|
|
33
|
+
instance.runtime = Funicular::Runtime.new(router)
|
|
33
34
|
instance.seed_state(state)
|
|
34
|
-
html = Funicular::VDOM::HTMLSerializer.serialize(instance.build_vdom)
|
|
35
|
+
html = Funicular::VDOM::HTMLSerializer.serialize(instance.build_vdom, instance.runtime)
|
|
35
36
|
|
|
36
37
|
{ html: html, state: state, component: component_class }
|
|
37
38
|
end
|
|
@@ -117,9 +117,9 @@ module Funicular
|
|
|
117
117
|
|
|
118
118
|
def default_runtime_dir
|
|
119
119
|
ENV["FUNICULAR_TEST_PICORUBY_DIR"] ||
|
|
120
|
-
existing_path(File.join(gem_root, "lib", "funicular", "vendor", "picoruby-test-node")) ||
|
|
121
120
|
existing_path(File.expand_path("../picoruby/build/picoruby-wasm-test/bin", gem_root)) ||
|
|
122
121
|
existing_path(File.expand_path("../../build/picoruby-wasm-test/bin", gem_root)) ||
|
|
122
|
+
existing_path(File.join(gem_root, "lib", "funicular", "vendor", "picoruby-test-node")) ||
|
|
123
123
|
File.join(gem_root, "lib", "funicular", "vendor", "picoruby-test-node")
|
|
124
124
|
end
|
|
125
125
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
4.0.1
|
|
@@ -619,7 +619,7 @@ function createExportWrapper(name, nargs) {
|
|
|
619
619
|
var wasmBinaryFile;
|
|
620
620
|
|
|
621
621
|
function findWasmBinary() {
|
|
622
|
-
return locateFile('
|
|
622
|
+
return locateFile('mrbc.wasm');
|
|
623
623
|
}
|
|
624
624
|
|
|
625
625
|
function getBinarySync(file) {
|
|
@@ -5128,6 +5128,8 @@ var wasmImports = {
|
|
|
5128
5128
|
/** @export */
|
|
5129
5129
|
invoke_vi,
|
|
5130
5130
|
/** @export */
|
|
5131
|
+
invoke_vii,
|
|
5132
|
+
/** @export */
|
|
5131
5133
|
invoke_viii
|
|
5132
5134
|
};
|
|
5133
5135
|
|
|
@@ -5164,6 +5166,17 @@ function invoke_viii(index,a1,a2,a3) {
|
|
|
5164
5166
|
}
|
|
5165
5167
|
}
|
|
5166
5168
|
|
|
5169
|
+
function invoke_vii(index,a1,a2) {
|
|
5170
|
+
var sp = stackSave();
|
|
5171
|
+
try {
|
|
5172
|
+
getWasmTableEntry(index)(a1,a2);
|
|
5173
|
+
} catch(e) {
|
|
5174
|
+
stackRestore(sp);
|
|
5175
|
+
if (e !== e+0) throw e;
|
|
5176
|
+
_setThrew(1, 0);
|
|
5177
|
+
}
|
|
5178
|
+
}
|
|
5179
|
+
|
|
5167
5180
|
function invoke_vi(index,a1) {
|
|
5168
5181
|
var sp = stackSave();
|
|
5169
5182
|
try {
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
4.0.
|
|
1
|
+
4.0.1
|
|
@@ -47,7 +47,15 @@
|
|
|
47
47
|
Module.picorubyRun = function() {
|
|
48
48
|
const MRB_TICK_UNIT = 4; // Must match the value in build_config/picoruby-wasm.rb
|
|
49
49
|
const BATCH_DURATION = 16; // ~1 frame (16.67ms)
|
|
50
|
+
const IDLE_DELAY = 4;
|
|
50
51
|
const MAX_CATCHUP_TICKS = 10; // Cap to avoid freeze when tab returns from background
|
|
52
|
+
const runStepStatus = Module._mrb_run_step_status || function() {
|
|
53
|
+
const result = Module._mrb_run_step();
|
|
54
|
+
return result < 0 ? -1 : 1;
|
|
55
|
+
};
|
|
56
|
+
const gcSchedulerPending = Module._mrb_gc_scheduler_pending_wasm || function() {
|
|
57
|
+
return 0;
|
|
58
|
+
};
|
|
51
59
|
let lastTick = performance.now();
|
|
52
60
|
function run() {
|
|
53
61
|
const now = performance.now();
|
|
@@ -61,13 +69,20 @@
|
|
|
61
69
|
lastTick = now;
|
|
62
70
|
}
|
|
63
71
|
const sliceStart = performance.now();
|
|
72
|
+
let progressed = false;
|
|
64
73
|
while (performance.now() - sliceStart < BATCH_DURATION) {
|
|
65
|
-
const
|
|
66
|
-
if (
|
|
67
|
-
console.error('
|
|
74
|
+
const status = runStepStatus();
|
|
75
|
+
if (status < 0) {
|
|
76
|
+
console.error('mrb_run_step_status returned', status, '- scheduler continues');
|
|
77
|
+
break;
|
|
68
78
|
}
|
|
79
|
+
if (status === 0) {
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
progressed = true;
|
|
69
83
|
}
|
|
70
|
-
|
|
84
|
+
const delay = progressed || gcSchedulerPending() === 1 ? 0 : IDLE_DELAY;
|
|
85
|
+
setTimeout(run, delay);
|
|
71
86
|
}
|
|
72
87
|
run();
|
|
73
88
|
};
|