funicular 0.0.1 → 0.2.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 +79 -0
- data/README.md +66 -20
- data/Rakefile +103 -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/architecture.md +118 -0
- data/exe/funicular +32 -0
- data/lib/funicular/assets/funicular.css +23 -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 +143 -0
- data/lib/funicular/configuration.rb +76 -0
- data/lib/funicular/helpers/picoruby_helper.rb +112 -0
- data/lib/funicular/middleware.rb +123 -0
- data/lib/funicular/plugin.rb +147 -0
- data/lib/funicular/railtie.rb +26 -0
- data/lib/funicular/route_parser.rb +137 -0
- data/lib/funicular/schema.rb +167 -0
- data/lib/funicular/ssr/runtime.rb +101 -0
- data/lib/funicular/ssr.rb +51 -0
- data/lib/funicular/testing/node_runner.mjs +293 -0
- data/lib/funicular/testing/node_runner.rb +190 -0
- data/lib/funicular/testing.rb +22 -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 +6423 -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/vendor/picoruby-test-node/VERSION +1 -0
- data/lib/funicular/vendor/picoruby-test-node/picoruby.js +6885 -0
- data/lib/funicular/vendor/picoruby-test-node/picoruby.wasm +0 -0
- data/lib/funicular/vendor/picoruby-test-node/picoruby.wasm.map +1 -0
- data/lib/funicular/version.rb +1 -1
- data/lib/funicular.rb +32 -1
- data/lib/generators/funicular/chat/chat_generator.rb +104 -0
- data/lib/generators/funicular/chat/templates/application_cable_channel.rb.tt +4 -0
- data/lib/generators/funicular/chat/templates/application_cable_connection.rb.tt +4 -0
- data/lib/generators/funicular/chat/templates/create_funicular_chat_messages.rb.tt +10 -0
- data/lib/generators/funicular/chat/templates/funicular_chat.css.tt +141 -0
- data/lib/generators/funicular/chat/templates/funicular_chat_channel.rb.tt +5 -0
- data/lib/generators/funicular/chat/templates/funicular_chat_component.rb.tt +135 -0
- data/lib/generators/funicular/chat/templates/funicular_chat_component_picotest.rb.tt +64 -0
- data/lib/generators/funicular/chat/templates/funicular_chat_controller.rb.tt +4 -0
- data/lib/generators/funicular/chat/templates/funicular_chat_message.rb.tt +13 -0
- data/lib/generators/funicular/chat/templates/funicular_chat_messages_controller.rb.tt +23 -0
- data/lib/generators/funicular/chat/templates/initializer.rb.tt +4 -0
- data/lib/generators/funicular/chat/templates/show.html.erb.tt +6 -0
- data/lib/tasks/funicular.rake +218 -0
- data/minitest/fixtures/funicular_app/components/greeting_component.rb +16 -0
- data/minitest/fixtures/funicular_app/initializer.rb +5 -0
- data/minitest/funicular_test.rb +13 -0
- data/minitest/hydration_test.rb +87 -0
- data/minitest/plugin_test.rb +51 -0
- data/minitest/schema_test.rb +106 -0
- data/minitest/ssr_test.rb +94 -0
- data/minitest/test_helper.rb +7 -0
- data/minitest/validations_test.rb +183 -0
- data/mrbgem.rake +16 -0
- data/mrblib/0_validations.rb +206 -0
- data/mrblib/1_validators.rb +180 -0
- data/mrblib/cable.rb +432 -0
- data/mrblib/component.rb +1050 -0
- data/mrblib/debug.rb +208 -0
- data/mrblib/differ.rb +254 -0
- data/mrblib/environment_inquirer.rb +34 -0
- data/mrblib/error_boundary.rb +125 -0
- data/mrblib/file_upload.rb +192 -0
- data/mrblib/form_builder.rb +300 -0
- data/mrblib/funicular.rb +245 -0
- data/mrblib/html_serializer.rb +121 -0
- data/mrblib/http.rb +183 -0
- data/mrblib/model.rb +196 -0
- data/mrblib/patcher.rb +269 -0
- data/mrblib/router.rb +266 -0
- data/mrblib/store.rb +304 -0
- data/mrblib/store_collection.rb +171 -0
- data/mrblib/store_singleton.rb +79 -0
- data/mrblib/styles.rb +83 -0
- data/mrblib/vdom.rb +273 -0
- data/sig/cable.rbs +66 -0
- data/sig/component.rbs +149 -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 +24 -1
- data/sig/html_serializer.rbs +20 -0
- data/sig/http.rbs +37 -0
- data/sig/model.rbs +28 -0
- data/sig/patcher.rbs +18 -0
- data/sig/router.rbs +44 -0
- data/sig/store.rbs +89 -0
- data/sig/store_collection.rbs +43 -0
- data/sig/store_singleton.rbs +19 -0
- data/sig/styles.rbs +25 -0
- data/sig/validations.rbs +103 -0
- data/sig/vdom.rbs +59 -0
- metadata +154 -8
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
4.0.0
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
(async function(global) {
|
|
2
|
+
async function initPicoRuby() {
|
|
3
|
+
// Import the factory function
|
|
4
|
+
const baseURL = document.currentScript?.src ? new URL('.', document.currentScript.src).href : '';
|
|
5
|
+
const { default: createModule } = await import(baseURL + 'picoruby.js');
|
|
6
|
+
|
|
7
|
+
async function collectRubyScripts() {
|
|
8
|
+
const rubyScripts = document.querySelectorAll('script[type="text/ruby"]');
|
|
9
|
+
const taskPromises = Array.from(rubyScripts).map(async script => {
|
|
10
|
+
if (script.src) {
|
|
11
|
+
const response = await fetch(script.src);
|
|
12
|
+
if (!response.ok) {
|
|
13
|
+
throw new Error(`Failed to load ${script.src}: ${response.statusText}`);
|
|
14
|
+
}
|
|
15
|
+
const code = await response.text();
|
|
16
|
+
const filename = script.src.split('/').pop() || script.src;
|
|
17
|
+
return { code, filename };
|
|
18
|
+
}
|
|
19
|
+
return { code: script.textContent.trim(), filename: null };
|
|
20
|
+
});
|
|
21
|
+
return Promise.all(taskPromises);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function collectMrbVMCode() {
|
|
25
|
+
const mrbScripts = document.querySelectorAll('script[type="application/x-mrb"]');
|
|
26
|
+
const taskPromises = Array.from(mrbScripts).map(async script => {
|
|
27
|
+
const src = script.src;
|
|
28
|
+
if (src) {
|
|
29
|
+
const response = await fetch(src);
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
throw new Error(`Failed to load ${src}: ${response.statusText}`);
|
|
32
|
+
}
|
|
33
|
+
return await response.arrayBuffer();
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
});
|
|
37
|
+
const results = await Promise.all(taskPromises);
|
|
38
|
+
return results.filter(Boolean);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Create and initialize the module
|
|
42
|
+
const Module = await createModule();
|
|
43
|
+
|
|
44
|
+
// Expose Module for debugging (used by PicoRuby DevTools extension)
|
|
45
|
+
window.picorubyModule = Module;
|
|
46
|
+
|
|
47
|
+
Module.picorubyRun = function() {
|
|
48
|
+
const MRB_TICK_UNIT = 4; // Must match the value in build_config/picoruby-wasm.rb
|
|
49
|
+
const BATCH_DURATION = 16; // ~1 frame (16.67ms)
|
|
50
|
+
const MAX_CATCHUP_TICKS = 10; // Cap to avoid freeze when tab returns from background
|
|
51
|
+
let lastTick = performance.now();
|
|
52
|
+
function run() {
|
|
53
|
+
const now = performance.now();
|
|
54
|
+
let tickCount = 0;
|
|
55
|
+
while (now - lastTick >= MRB_TICK_UNIT && tickCount < MAX_CATCHUP_TICKS) {
|
|
56
|
+
Module._mrb_tick_wasm();
|
|
57
|
+
lastTick += MRB_TICK_UNIT;
|
|
58
|
+
tickCount++;
|
|
59
|
+
}
|
|
60
|
+
if (now - lastTick >= MRB_TICK_UNIT) {
|
|
61
|
+
lastTick = now;
|
|
62
|
+
}
|
|
63
|
+
const sliceStart = performance.now();
|
|
64
|
+
while (performance.now() - sliceStart < BATCH_DURATION) {
|
|
65
|
+
const result = Module._mrb_run_step();
|
|
66
|
+
if (result < 0) {
|
|
67
|
+
console.error('mrb_run_step returned', result, '- scheduler continues');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
setTimeout(run, 0);
|
|
71
|
+
}
|
|
72
|
+
run();
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Initialize WASM and start tasks
|
|
76
|
+
Module.ccall('picorb_init', 'number', [], []);
|
|
77
|
+
|
|
78
|
+
// Collect and create tasks from Ruby script tags
|
|
79
|
+
try {
|
|
80
|
+
const rubyTasks = await collectRubyScripts();
|
|
81
|
+
rubyTasks.forEach(function(task) {
|
|
82
|
+
if (task.filename) {
|
|
83
|
+
Module.ccall('picorb_create_task_with_filename', 'number',
|
|
84
|
+
['string', 'string'], [task.code, task.filename]);
|
|
85
|
+
} else {
|
|
86
|
+
Module.ccall('picorb_create_task', 'number', ['string'], [task.code]);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error('Error loading Ruby tasks:', error);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Collect and create tasks from MRB data tags
|
|
94
|
+
try {
|
|
95
|
+
const mrbTasks = await collectMrbVMCode();
|
|
96
|
+
mrbTasks.forEach(function(buffer) {
|
|
97
|
+
const ptr = Module._malloc(buffer.byteLength);
|
|
98
|
+
if (ptr === 0) {
|
|
99
|
+
throw new Error('Failed to allocate memory in Wasm heap.');
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
Module.HEAPU8.set(new Uint8Array(buffer), ptr);
|
|
103
|
+
const result = Module.ccall('picorb_create_task_from_mrb', 'number', ['number', 'number'], [ptr, buffer.byteLength]);
|
|
104
|
+
if (result !== 0) {
|
|
105
|
+
console.error('Failed to create task from mrb.');
|
|
106
|
+
}
|
|
107
|
+
} finally {
|
|
108
|
+
Module._free(ptr);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error('Error loading MRB tasks:', error);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Also support window.userTasks if present (for backward compatibility)
|
|
116
|
+
if (window.userTasks) {
|
|
117
|
+
window.userTasks.forEach(function(task) {
|
|
118
|
+
Module.ccall('picorb_create_task', 'number', ['string'],
|
|
119
|
+
[typeof task === 'string' ? task : task.code]);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Start PicoRuby execution
|
|
124
|
+
Module.picorubyRun();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
global.initPicoRuby = initPicoRuby;
|
|
128
|
+
|
|
129
|
+
await initPicoRuby();
|
|
130
|
+
})(typeof window !== 'undefined' ? window : this).catch(console.error);
|