@effindomv2/fui-rs 0.1.3

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 (115) hide show
  1. package/COMMERCIAL.md +7 -0
  2. package/Cargo.toml +34 -0
  3. package/LICENSE.md +9 -0
  4. package/README.md +88 -0
  5. package/package.json +54 -0
  6. package/scripts/build.sh +227 -0
  7. package/scripts/check-runtime-dependency.sh +41 -0
  8. package/scripts/framework-host-services.ts +40 -0
  9. package/scripts/generate-host-events.ts +17 -0
  10. package/scripts/generate-host-services.ts +28 -0
  11. package/scripts/hostgen/common.ts +73 -0
  12. package/scripts/hostgen/registry.ts +159 -0
  13. package/scripts/hostgen/rust-host-events.ts +171 -0
  14. package/scripts/hostgen/rust-host-services.ts +257 -0
  15. package/src/animation.rs +625 -0
  16. package/src/app.rs +324 -0
  17. package/src/assets.rs +572 -0
  18. package/src/bindings/mod.rs +1 -0
  19. package/src/bindings/ui.rs +705 -0
  20. package/src/bitmap.rs +317 -0
  21. package/src/bridge_callbacks.rs +242 -0
  22. package/src/context_menu_manager.rs +332 -0
  23. package/src/controls/anti_selection_area.rs +54 -0
  24. package/src/controls/button.rs +542 -0
  25. package/src/controls/checkbox.rs +372 -0
  26. package/src/controls/combobox.rs +1574 -0
  27. package/src/controls/context_menu.rs +1367 -0
  28. package/src/controls/control_template_set.rs +46 -0
  29. package/src/controls/control_tokens.rs +596 -0
  30. package/src/controls/dialog.rs +590 -0
  31. package/src/controls/dropdown.rs +1010 -0
  32. package/src/controls/form.rs +229 -0
  33. package/src/controls/internal/button_presenter.rs +142 -0
  34. package/src/controls/internal/checkbox_indicator_presenter.rs +210 -0
  35. package/src/controls/internal/dropdown_chevron_presenter.rs +134 -0
  36. package/src/controls/internal/dropdown_field_presenter.rs +269 -0
  37. package/src/controls/internal/dropdown_option_row_presenter.rs +184 -0
  38. package/src/controls/internal/mod.rs +13 -0
  39. package/src/controls/internal/pressable_indicator_presenter.rs +26 -0
  40. package/src/controls/internal/pressable_labeled_control.rs +492 -0
  41. package/src/controls/internal/radio_indicator_presenter.rs +208 -0
  42. package/src/controls/internal/selectable_popup_list.rs +597 -0
  43. package/src/controls/internal/slider_presenter.rs +282 -0
  44. package/src/controls/internal/switch_indicator_presenter.rs +235 -0
  45. package/src/controls/internal/text_input_core.rs +1074 -0
  46. package/src/controls/internal/text_input_presenter.rs +108 -0
  47. package/src/controls/mod.rs +235 -0
  48. package/src/controls/nav_link.rs +395 -0
  49. package/src/controls/popup.rs +191 -0
  50. package/src/controls/progress_bar.rs +287 -0
  51. package/src/controls/radio_button.rs +348 -0
  52. package/src/controls/radio_group.rs +283 -0
  53. package/src/controls/selection_area.rs +82 -0
  54. package/src/controls/shared.rs +68 -0
  55. package/src/controls/slider.rs +701 -0
  56. package/src/controls/switch.rs +293 -0
  57. package/src/controls/templating.rs +49 -0
  58. package/src/controls/tests.rs +3905 -0
  59. package/src/controls/text_area.rs +207 -0
  60. package/src/controls/text_input.rs +192 -0
  61. package/src/debug.rs +146 -0
  62. package/src/drag_drop.rs +424 -0
  63. package/src/drag_gesture.rs +258 -0
  64. package/src/drawing.rs +385 -0
  65. package/src/event.rs +1603 -0
  66. package/src/external_drop.rs +467 -0
  67. package/src/fetch.rs +500 -0
  68. package/src/ffi.rs +3542 -0
  69. package/src/file.rs +1677 -0
  70. package/src/focus_adorner.rs +307 -0
  71. package/src/focus_visibility.rs +121 -0
  72. package/src/frame_scheduler.rs +159 -0
  73. package/src/frame_signal.rs +64 -0
  74. package/src/generated/ffi.rs +798 -0
  75. package/src/generated/framework_host_services.rs +74 -0
  76. package/src/generated/mod.rs +2 -0
  77. package/src/host_services.rs +162 -0
  78. package/src/image_sampling.rs +78 -0
  79. package/src/keyboard_scroll.rs +137 -0
  80. package/src/keyboard_scroll_tracker.rs +385 -0
  81. package/src/lib.rs +547 -0
  82. package/src/logger.rs +56 -0
  83. package/src/mobile_text_selection_toolbar.rs +1034 -0
  84. package/src/navigation.rs +48 -0
  85. package/src/node/core.rs +2210 -0
  86. package/src/node/custom_drawable.rs +149 -0
  87. package/src/node/flex_box.rs +874 -0
  88. package/src/node/grid.rs +304 -0
  89. package/src/node/helpers.rs +442 -0
  90. package/src/node/image.rs +506 -0
  91. package/src/node/mod.rs +315 -0
  92. package/src/node/scroll_bar.rs +845 -0
  93. package/src/node/scroll_box.rs +514 -0
  94. package/src/node/scroll_state.rs +121 -0
  95. package/src/node/scroll_view.rs +557 -0
  96. package/src/node/svg_node.rs +474 -0
  97. package/src/node/text_node.rs +633 -0
  98. package/src/node/virtual_list.rs +678 -0
  99. package/src/panic_hook.rs +36 -0
  100. package/src/persisted.rs +274 -0
  101. package/src/platform.rs +556 -0
  102. package/src/popup_presenter.rs +344 -0
  103. package/src/selection_handle_adorner.rs +838 -0
  104. package/src/signal.rs +134 -0
  105. package/src/text.rs +1065 -0
  106. package/src/theme.rs +571 -0
  107. package/src/timers.rs +106 -0
  108. package/src/tool_tip.rs +167 -0
  109. package/src/tool_tip_manager.rs +691 -0
  110. package/src/transitions.rs +41 -0
  111. package/src/typography.rs +520 -0
  112. package/src/viewport.rs +108 -0
  113. package/src/worker.rs +308 -0
  114. package/src/worker_job.rs +82 -0
  115. package/src/worker_runtime.rs +172 -0
package/COMMERCIAL.md ADDED
@@ -0,0 +1,7 @@
1
+ # FUI-RS Commercial License
2
+
3
+ `@effindomv2/fui-rs` is available under a separate commercial license if you
4
+ want to use it without the obligations of AGPL-3.0-only.
5
+
6
+ To discuss commercial licensing, contact the repository maintainer through the
7
+ project's public contact channel.
package/Cargo.toml ADDED
@@ -0,0 +1,34 @@
1
+ [package]
2
+ name = "fui-rs"
3
+ version = "0.1.3"
4
+ edition = "2021"
5
+ license = "AGPL-3.0-only OR LicenseRef-EffinDom-Commercial"
6
+
7
+ [workspace]
8
+ members = [
9
+ ".",
10
+ "crates/demo-home",
11
+ "crates/demo-shared",
12
+ "crates/demo-stage4",
13
+ "crates/demo-stage5",
14
+ "crates/demo-worker",
15
+ "crates/demo-workbench",
16
+ ]
17
+ resolver = "2"
18
+
19
+ [features]
20
+ default = []
21
+ worker-runtime = []
22
+ native-runtime = []
23
+
24
+ [dependencies]
25
+
26
+ [profile.release]
27
+ opt-level = 3
28
+ lto = true
29
+ codegen-units = 1
30
+ panic = "abort"
31
+
32
+ [lib]
33
+ name = "fui"
34
+ crate-type = ["rlib"]
package/LICENSE.md ADDED
@@ -0,0 +1,9 @@
1
+ `@effindomv2/fui-rs` is licensed under the GNU Affero General Public License
2
+ v3.0 (AGPL-3.0-only) or, at your option, the commercial license terms in
3
+ `COMMERCIAL.md`.
4
+
5
+ See:
6
+
7
+ - `../../LICENSE.md`
8
+ - `../../LICENSES/AGPL-3.0-only.md`
9
+ - `COMMERCIAL.md`
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # FUI-RS - Rust SDK for EffinDom v2
2
+
3
+ FUI-RS is the Rust retained-mode SDK for building EffinDom v2 WebAssembly UI
4
+ apps. It provides retained controls, layout nodes, text input, overlays,
5
+ custom drawing, host services, workers, routing support, and app lifecycle
6
+ macros for browser-hosted Rust WASM apps.
7
+
8
+ ## Quickstart
9
+
10
+ ```bash
11
+ # Prerequisites: Rust + wasm32-unknown-unknown target + Binaryen
12
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
13
+ rustup target add wasm32-unknown-unknown
14
+ brew install binaryen
15
+
16
+ # Clone and build
17
+ git clone https://github.com/zion-sati/fui-rs.git
18
+ cd fui-rs
19
+ npm ci
20
+ npm run build
21
+ npm run serve
22
+ ```
23
+
24
+ Open:
25
+
26
+ ```text
27
+ http://127.0.0.1:8080/v2/fui-rs/demo/index.html
28
+ ```
29
+
30
+ Full quickstart: [docs/v2/fui-rs/QUICKSTART.md](../../docs/v2/fui-rs/QUICKSTART.md)
31
+
32
+ ## Minimal app
33
+
34
+ ```rust
35
+ use fui::prelude::*;
36
+
37
+ fn build_page() -> FlexBox {
38
+ ui! {
39
+ column().fill_size().padding(24.0, 24.0, 24.0, 24.0) {
40
+ text("Hello from Rust"),
41
+ button("Click me").on_click(|_| logger::info("App", "clicked")),
42
+ }
43
+ }
44
+ }
45
+
46
+ fui_app!(FlexBox, build_page);
47
+ ```
48
+
49
+ ## SDK docs
50
+
51
+ - [SDK docs index](../../docs/v2/fui-rs/SDK_INDEX.md)
52
+ - [API reference](../../docs/v2/fui-rs/API_REFERENCE.md)
53
+ - [Controls and nodes](../../docs/v2/fui-rs/CONTROLS_AND_NODES.md)
54
+ - [Events and callbacks](../../docs/v2/fui-rs/EVENTS_AND_CALLBACKS.md)
55
+ - [Text input reference](../../docs/v2/fui-rs/TEXT_INPUT_REFERENCE.md)
56
+ - [Forms and autofill](../../docs/v2/fui-rs/FORMS_AND_AUTOFILL.md)
57
+ - [Theming and style matrix](../../docs/v2/fui-rs/THEMING_STYLE_MATRIX.md)
58
+
59
+ ## What is included
60
+
61
+ | Area | Status |
62
+ |---|---|
63
+ | Retained app lifecycle macros | Available |
64
+ | `ui!` mixed child tree macro | Available |
65
+ | Flex/Grid layout nodes | Available |
66
+ | Text, rich text, image, SVG | Available |
67
+ | Buttons, toggles, slider, dropdown, combobox | Available |
68
+ | TextInput/TextArea | Available |
69
+ | Context menu, popup, dialog, tooltip | Available |
70
+ | Selection, mobile text handles, context toolbar | Available |
71
+ | ScrollView, ScrollBox, VirtualList | Available |
72
+ | Custom drawing and text layouts | Available |
73
+ | Browser file/fetch/worker bridges | Available |
74
+ | Host services/events generator support | Available |
75
+
76
+ ## Architecture
77
+
78
+ FUI-RS builds Rust retained UI objects into the EffinDom v2 runtime through the
79
+ browser bridge. Rust app WASM and the UI runtime WASM are separate modules;
80
+ strings and command data cross the bridge through explicit UTF-8/runtime ABI
81
+ calls.
82
+
83
+ Retained controls are cheap clone handles. Cloning a control gives another Rust
84
+ handle to the same retained UI object.
85
+
86
+ ## License
87
+
88
+ AGPL-3.0-only, or commercial license. See [COMMERCIAL.md](COMMERCIAL.md).
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@effindomv2/fui-rs",
3
+ "version": "0.1.3",
4
+ "private": false,
5
+ "license": "AGPL-3.0-only OR LicenseRef-EffinDom-Commercial",
6
+ "description": "EffinDom v2 Rust frontend framework SDK and host generators",
7
+ "type": "module",
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "engines": {
12
+ "node": ">=18"
13
+ },
14
+ "files": [
15
+ "src",
16
+ "scripts",
17
+ "Cargo.toml",
18
+ "README.md",
19
+ "LICENSE.md",
20
+ "COMMERCIAL.md"
21
+ ],
22
+ "scripts": {
23
+ "generate:abi": "mkdir -p build && esbuild ../abi/generate.ts --bundle --format=esm --platform=node --target=node20 --packages=external --outfile=build/generate-abi.mjs && node build/generate-abi.mjs fui-rs-ffi",
24
+ "check:abi": "mkdir -p build && esbuild ../abi/generate.ts --bundle --format=esm --platform=node --target=node20 --packages=external --outfile=build/generate-abi.mjs && node build/generate-abi.mjs fui-rs-ffi --check",
25
+ "generate:host-services": "mkdir -p build && esbuild scripts/generate-host-services.ts --bundle --format=esm --platform=node --target=node20 --packages=external --outfile=build/generate-host-services.mjs && node build/generate-host-services.mjs demo/src/host-services.ts demoHostServices crates/demo-shared/src/generated/host_services.rs fui::host_services",
26
+ "generate:host-events:home": "mkdir -p build && esbuild scripts/generate-host-events.ts --bundle --format=esm --platform=node --target=node20 --packages=external --outfile=build/generate-host-events.mjs && node build/generate-host-events.mjs demo/src/host-events.ts demoHostEvents crates/demo-home/src/generated/host_events.rs",
27
+ "generate:host-events:workbench": "mkdir -p build && esbuild scripts/generate-host-events.ts --bundle --format=esm --platform=node --target=node20 --packages=external --outfile=build/generate-host-events.mjs && node build/generate-host-events.mjs demo/src/host-events.ts demoHostEvents crates/demo-workbench/src/generated/host_events.rs",
28
+ "generate:host-events:stage4": "mkdir -p build && esbuild scripts/generate-host-events.ts --bundle --format=esm --platform=node --target=node20 --packages=external --outfile=build/generate-host-events.mjs && node build/generate-host-events.mjs demo/src/host-events.ts demoHostEvents crates/demo-stage4/src/generated/host_events.rs",
29
+ "generate:host-events:stage5": "mkdir -p build && esbuild scripts/generate-host-events.ts --bundle --format=esm --platform=node --target=node20 --packages=external --outfile=build/generate-host-events.mjs && node build/generate-host-events.mjs demo/src/host-events.ts demoHostEvents crates/demo-stage5/src/generated/host_events.rs",
30
+ "generate:worker-host-services": "mkdir -p build && esbuild scripts/generate-host-services.ts --bundle --format=esm --platform=node --target=node20 --packages=external --outfile=build/generate-host-services.mjs && node build/generate-host-services.mjs demo/src/worker-host-services.ts demoWorkerHostServices crates/demo-shared/src/generated/worker_host_services.rs fui::host_services fui_host_service",
31
+ "generate:framework-host-services": "mkdir -p build && esbuild scripts/generate-host-services.ts --bundle --format=esm --platform=node --target=node20 --packages=external --outfile=build/generate-host-services.mjs && node build/generate-host-services.mjs scripts/framework-host-services.ts frameworkHostServices src/generated/framework_host_services.rs crate::host_services fui_host",
32
+ "generate:host": "npm run generate:host-services && npm run generate:host-events:home && npm run generate:host-events:workbench && npm run generate:host-events:stage4 && npm run generate:host-events:stage5 && npm run generate:worker-host-services && npm run generate:framework-host-services",
33
+ "guard:runtime-dependency": "bash scripts/check-runtime-dependency.sh",
34
+ "lint": "cd ../.. && npm run lint:v2",
35
+ "build": "npm run guard:runtime-dependency && npm run check:abi && bash scripts/build.sh",
36
+ "test:unit": "npm run guard:runtime-dependency && npm run check:abi && npm run generate:host && bash -lc 'source \"$HOME/.cargo/env\" && cargo test --manifest-path Cargo.toml'",
37
+ "test:integration": "npm --prefix ../.. run build:v2:browser-bridge && npm run build && playwright test -c playwright.config.ts",
38
+ "typecheck": "npm run guard:runtime-dependency && npm run check:abi && tsc -p tsconfig.json --noEmit",
39
+ "prepublishOnly": "npm run check:abi && npm run lint && npm run typecheck"
40
+ },
41
+ "dependencies": {
42
+ "@effindomv2/runtime": "0.1.24"
43
+ },
44
+ "devDependencies": {
45
+ "@playwright/test": "1.59.1",
46
+ "@types/node": "^24.10.1",
47
+ "esbuild": "^0.27.7",
48
+ "typescript": "^5.9.3"
49
+ },
50
+ "allowScripts": {
51
+ "esbuild@0.27.7": true,
52
+ "fsevents@2.3.2": true
53
+ }
54
+ }
@@ -0,0 +1,227 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+
5
+ PACKAGE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
6
+ REPO_ROOT="$(cd "${PACKAGE_DIR}/../.." && pwd)"
7
+ OUT_DIR="${REPO_ROOT}/public/v2/fui-rs"
8
+ DEMO_OUT_DIR="${OUT_DIR}/demo"
9
+ PUBLIC_BRIDGE_DIR="${REPO_ROOT}/public/v2/browser-bridge"
10
+ PACKAGE_BRIDGE_DIR="${REPO_ROOT}/v2/browser-bridge/dist"
11
+ SHARED_FONTS_DIR="${REPO_ROOT}/public/v2/fonts"
12
+ SHARED_DEMO_TEXTURE="${REPO_ROOT}/v2/fui-as/demo/demo-texture.png"
13
+ HOST_SERVICE_GENERATOR_BUILD="${PACKAGE_DIR}/build/generate-host-services.mjs"
14
+ HOST_EVENT_GENERATOR_BUILD="${PACKAGE_DIR}/build/generate-host-events.mjs"
15
+ WORKER_BOOTSTRAP_BUILD="${PACKAGE_DIR}/build/worker-bootstrap.js"
16
+ WORKER_BOOTSTRAP_MAP_BUILD="${PACKAGE_DIR}/build/worker-bootstrap.js.map"
17
+
18
+ mkdir -p "${PACKAGE_DIR}/build" "${OUT_DIR}" "${DEMO_OUT_DIR}" "${DEMO_OUT_DIR}/workbench" "${DEMO_OUT_DIR}/stage4" "${DEMO_OUT_DIR}/stage5"
19
+
20
+ cd "${PACKAGE_DIR}"
21
+ if ! command -v cargo >/dev/null 2>&1 && [ -f "${HOME}/.cargo/env" ]; then
22
+ # shellcheck disable=SC1090
23
+ source "${HOME}/.cargo/env"
24
+ fi
25
+ if ! command -v cargo >/dev/null 2>&1; then
26
+ echo "cargo not found. Install Rust and ensure cargo is on PATH." >&2
27
+ exit 1
28
+ fi
29
+
30
+ resolve_runtime_dist_dir() {
31
+ local candidate=""
32
+ local public_manifest="${PUBLIC_BRIDGE_DIR}/effindom.v2.manifest.json"
33
+ local package_manifest="${PACKAGE_BRIDGE_DIR}/effindom.v2.manifest.json"
34
+ local candidates=()
35
+
36
+ if [ -n "${EFFINDOM_RUNTIME_DIST_DIR:-}" ]; then
37
+ candidates+=("${EFFINDOM_RUNTIME_DIST_DIR}")
38
+ fi
39
+
40
+ candidates+=(
41
+ "${PUBLIC_BRIDGE_DIR}"
42
+ "${PACKAGE_BRIDGE_DIR}"
43
+ "${PACKAGE_DIR}/node_modules/@effindomv2/runtime/dist"
44
+ "${REPO_ROOT}/node_modules/@effindomv2/runtime/dist"
45
+ )
46
+
47
+ if [ -f "${public_manifest}" ] && [ -f "${package_manifest}" ] && ! cmp -s "${public_manifest}" "${package_manifest}"; then
48
+ echo "Note: using ${PUBLIC_BRIDGE_DIR} for local runtime assets." >&2
49
+ echo " ${PACKAGE_BRIDGE_DIR} is a staged package copy and may be stale." >&2
50
+ echo " For ABI changes, run repo-root ./build.sh (or npm run build:v2:abi)." >&2
51
+ fi
52
+
53
+ for candidate in "${candidates[@]}"; do
54
+ if [ -f "${candidate}/bridge.js" ] && [ -f "${candidate}/effindom.v2.manifest.json" ] && [ -d "${candidate}/runtime" ]; then
55
+ printf '%s\n' "${candidate}"
56
+ return 0
57
+ fi
58
+ done
59
+
60
+ echo "Could not locate runtime dist assets." >&2
61
+ echo "Expected one of:" >&2
62
+ echo " - \$EFFINDOM_RUNTIME_DIST_DIR" >&2
63
+ echo " - ${PACKAGE_DIR}/node_modules/@effindomv2/runtime/dist" >&2
64
+ echo " - ${REPO_ROOT}/node_modules/@effindomv2/runtime/dist" >&2
65
+ echo " - ${REPO_ROOT}/public/v2/browser-bridge" >&2
66
+ echo " - ${REPO_ROOT}/v2/browser-bridge/dist" >&2
67
+ echo "Install @effindomv2/runtime or build runtime assets first." >&2
68
+ exit 1
69
+ }
70
+
71
+ RUNTIME_DIST_DIR="$(resolve_runtime_dist_dir)"
72
+ optimize_wasm() {
73
+ local wasm_file="$1"
74
+
75
+ if command -v wasm-opt >/dev/null 2>&1; then
76
+ wasm-opt -O3 --strip-debug --strip-producers "${wasm_file}" -o "${wasm_file}"
77
+ else
78
+ echo "wasm-opt not found; skipping Binaryen speed optimization for ${wasm_file}." >&2
79
+ fi
80
+ }
81
+
82
+ generate_host_services() {
83
+ local definition_file="$1"
84
+ local export_name="$2"
85
+ local output_file="$3"
86
+ local runtime_path="${4:-}"
87
+ local host_import_module="${5:-}"
88
+
89
+ npx esbuild "${PACKAGE_DIR}/scripts/generate-host-services.ts" \
90
+ --bundle \
91
+ --format=esm \
92
+ --platform=node \
93
+ --target=node20 \
94
+ --packages=external \
95
+ --outfile="${HOST_SERVICE_GENERATOR_BUILD}"
96
+
97
+ if [ -n "${runtime_path}" ] && [ -n "${host_import_module}" ]; then
98
+ node "${HOST_SERVICE_GENERATOR_BUILD}" \
99
+ "${definition_file}" "${export_name}" "${output_file}" "${runtime_path}" "${host_import_module}"
100
+ elif [ -n "${runtime_path}" ]; then
101
+ node "${HOST_SERVICE_GENERATOR_BUILD}" "${definition_file}" "${export_name}" "${output_file}" "${runtime_path}"
102
+ elif [ -n "${host_import_module}" ]; then
103
+ node "${HOST_SERVICE_GENERATOR_BUILD}" "${definition_file}" "${export_name}" "${output_file}" "" "${host_import_module}"
104
+ else
105
+ node "${HOST_SERVICE_GENERATOR_BUILD}" "${definition_file}" "${export_name}" "${output_file}"
106
+ fi
107
+ }
108
+
109
+ generate_host_events() {
110
+ local definition_file="$1"
111
+ local export_name="$2"
112
+ local output_file="$3"
113
+
114
+ npx esbuild "${PACKAGE_DIR}/scripts/generate-host-events.ts" \
115
+ --bundle \
116
+ --format=esm \
117
+ --platform=node \
118
+ --target=node20 \
119
+ --packages=external \
120
+ --outfile="${HOST_EVENT_GENERATOR_BUILD}"
121
+
122
+ node "${HOST_EVENT_GENERATOR_BUILD}" "${definition_file}" "${export_name}" "${output_file}"
123
+ }
124
+
125
+ generate_host_services "demo/src/host-services.ts" "demoHostServices" "crates/demo-shared/src/generated/host_services.rs" "fui::host_services"
126
+ generate_host_events "demo/src/host-events.ts" "demoHostEvents" "crates/demo-home/src/generated/host_events.rs"
127
+ generate_host_events "demo/src/host-events.ts" "demoHostEvents" "crates/demo-workbench/src/generated/host_events.rs"
128
+ generate_host_events "demo/src/host-events.ts" "demoHostEvents" "crates/demo-stage4/src/generated/host_events.rs"
129
+ generate_host_events "demo/src/host-events.ts" "demoHostEvents" "crates/demo-stage5/src/generated/host_events.rs"
130
+ generate_host_services "demo/src/worker-host-services.ts" "demoWorkerHostServices" "crates/demo-shared/src/generated/worker_host_services.rs" "fui::host_services" "fui_host_service"
131
+ generate_host_services "demo/src/worker-host-services.ts" "demoWorkerHostServices" "crates/demo-worker/src/generated/worker_host_services.rs" "" "fui_host_service"
132
+ generate_host_services "scripts/framework-host-services.ts" "frameworkHostServices" "src/generated/framework_host_services.rs" "crate::host_services" "fui_host"
133
+
134
+ rm -f "${OUT_DIR}/app.wasm" "${OUT_DIR}/harness.js" "${OUT_DIR}/harness.js.map" "${DEMO_OUT_DIR}/demo.wasm"
135
+
136
+ build_demo_route() {
137
+ local package="$1"
138
+ local wasm_name="$2"
139
+ local wasm_out="$3"
140
+
141
+ cargo build --package "${package}" --target wasm32-unknown-unknown --target-dir "${PACKAGE_DIR}/target" --release
142
+ cp "${PACKAGE_DIR}/target/wasm32-unknown-unknown/release/${wasm_name}.wasm" "${wasm_out}"
143
+ optimize_wasm "${wasm_out}"
144
+ }
145
+
146
+ build_demo_route "fui-rs-demo-home" "fui_rs_demo_home" "${DEMO_OUT_DIR}/home.wasm"
147
+ build_demo_route "fui-rs-demo-workbench" "fui_rs_demo_workbench" "${DEMO_OUT_DIR}/workbench.wasm"
148
+ build_demo_route "fui-rs-demo-stage4" "fui_rs_demo_stage4" "${DEMO_OUT_DIR}/stage4.wasm"
149
+ build_demo_route "fui-rs-demo-stage5" "fui_rs_demo_stage5" "${DEMO_OUT_DIR}/stage5.wasm"
150
+ build_demo_route "fui-rs-demo-worker" "fui_rs_demo_worker" "${DEMO_OUT_DIR}/workers.wasm"
151
+ cp "${DEMO_OUT_DIR}/workers.wasm" "${DEMO_OUT_DIR}/workbench/workers.wasm"
152
+ cp "${DEMO_OUT_DIR}/workers.wasm" "${DEMO_OUT_DIR}/stage4/workers.wasm"
153
+ cp "${DEMO_OUT_DIR}/workers.wasm" "${DEMO_OUT_DIR}/stage5/workers.wasm"
154
+
155
+ npx esbuild "${PACKAGE_DIR}/demo/harness.ts" \
156
+ --bundle \
157
+ --format=esm \
158
+ --platform=browser \
159
+ --target=es2020 \
160
+ --minify \
161
+ --outfile="${DEMO_OUT_DIR}/harness.js" \
162
+ --sourcemap
163
+
164
+ npx esbuild "${PACKAGE_DIR}/demo/worker-host-services.ts" \
165
+ --bundle \
166
+ --format=iife \
167
+ --platform=browser \
168
+ --target=es2020 \
169
+ --minify \
170
+ --outfile="${DEMO_OUT_DIR}/worker-host-services.js" \
171
+ --sourcemap
172
+
173
+ npx esbuild "${REPO_ROOT}/v2/browser-bridge/src/managed-harness/worker-bootstrap.ts" \
174
+ --bundle \
175
+ --format=iife \
176
+ --platform=browser \
177
+ --target=es2020 \
178
+ --minify \
179
+ --outfile="${WORKER_BOOTSTRAP_BUILD}" \
180
+ --sourcemap
181
+
182
+ cp "${WORKER_BOOTSTRAP_BUILD}" "${DEMO_OUT_DIR}/worker-bootstrap.js"
183
+ cp "${WORKER_BOOTSTRAP_MAP_BUILD}" "${DEMO_OUT_DIR}/worker-bootstrap.js.map"
184
+
185
+ cp "${PACKAGE_DIR}/browser/index.html" "${OUT_DIR}/index.html"
186
+ cp "${PACKAGE_DIR}/demo/index.html" "${DEMO_OUT_DIR}/index.html"
187
+ cp "${PACKAGE_DIR}/demo/route-shell.html" "${DEMO_OUT_DIR}/workbench/index.html"
188
+ cp "${PACKAGE_DIR}/demo/route-shell.html" "${DEMO_OUT_DIR}/stage4/index.html"
189
+ cp "${PACKAGE_DIR}/demo/route-shell.html" "${DEMO_OUT_DIR}/stage5/index.html"
190
+ cp "${PACKAGE_DIR}/demo/routes.json" "${DEMO_OUT_DIR}/routes.json"
191
+ rm -f "${DEMO_OUT_DIR}/worker-manifest.json"
192
+ if [ -f "${SHARED_DEMO_TEXTURE}" ]; then
193
+ cp "${SHARED_DEMO_TEXTURE}" "${OUT_DIR}/demo-texture.png"
194
+ cp "${SHARED_DEMO_TEXTURE}" "${DEMO_OUT_DIR}/demo-texture.png"
195
+ cp "${SHARED_DEMO_TEXTURE}" "${DEMO_OUT_DIR}/workbench/demo-texture.png"
196
+ cp "${SHARED_DEMO_TEXTURE}" "${DEMO_OUT_DIR}/stage4/demo-texture.png"
197
+ cp "${SHARED_DEMO_TEXTURE}" "${DEMO_OUT_DIR}/stage5/demo-texture.png"
198
+ fi
199
+ cat > "${OUT_DIR}/effindom-runtime-config.js" << 'RUNTIMECFG'
200
+ window.__effindomRuntime = Object.assign({}, window.__effindomRuntime, {
201
+ manifestUrl: './effindom.v2.manifest.json',
202
+ });
203
+ RUNTIMECFG
204
+ cp "${OUT_DIR}/effindom-runtime-config.js" "${DEMO_OUT_DIR}/effindom-runtime-config.js"
205
+ cp "${RUNTIME_DIST_DIR}/bridge.js" "${OUT_DIR}/bridge.js"
206
+ cp "${RUNTIME_DIST_DIR}/bridge.js.map" "${OUT_DIR}/bridge.js.map"
207
+ cp "${RUNTIME_DIST_DIR}/effindom.v2.manifest.json" "${OUT_DIR}/effindom.v2.manifest.json"
208
+ cp "${OUT_DIR}/bridge.js" "${DEMO_OUT_DIR}/bridge.js"
209
+ cp "${OUT_DIR}/bridge.js.map" "${DEMO_OUT_DIR}/bridge.js.map"
210
+ cp "${OUT_DIR}/effindom.v2.manifest.json" "${DEMO_OUT_DIR}/effindom.v2.manifest.json"
211
+ if [ -f "${RUNTIME_DIST_DIR}/icu-asset.json" ]; then
212
+ cp "${RUNTIME_DIST_DIR}/icu-asset.json" "${OUT_DIR}/icu-asset.json"
213
+ cp "${OUT_DIR}/icu-asset.json" "${DEMO_OUT_DIR}/icu-asset.json"
214
+ fi
215
+ rm -rf "${OUT_DIR}/runtime"
216
+ cp -R "${RUNTIME_DIST_DIR}/runtime" "${OUT_DIR}/runtime"
217
+ rm -rf "${OUT_DIR}/fonts"
218
+ rm -rf "${DEMO_OUT_DIR}/fonts"
219
+ if [ -d "${SHARED_FONTS_DIR}" ]; then
220
+ cp -R "${SHARED_FONTS_DIR}" "${OUT_DIR}/fonts"
221
+ cp -R "${SHARED_FONTS_DIR}" "${DEMO_OUT_DIR}/fonts"
222
+ elif [ -d "${RUNTIME_DIST_DIR}/fonts" ]; then
223
+ cp -R "${RUNTIME_DIST_DIR}/fonts" "${OUT_DIR}/fonts"
224
+ cp -R "${RUNTIME_DIST_DIR}/fonts" "${DEMO_OUT_DIR}/fonts"
225
+ fi
226
+ rm -rf "${DEMO_OUT_DIR}/runtime"
227
+ cp -R "${RUNTIME_DIST_DIR}/runtime" "${DEMO_OUT_DIR}/runtime"
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+
5
+ PACKAGE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
6
+ REPO_ROOT="$(cd "${PACKAGE_DIR}/../.." && pwd)"
7
+ PACKAGE_JSON="${PACKAGE_DIR}/package.json"
8
+ RUNTIME_PACKAGE_JSON="${REPO_ROOT}/v2/browser-bridge/package.json"
9
+
10
+ runtime_spec="$(
11
+ node -e '
12
+ const fs = require("node:fs");
13
+ const pkg = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
14
+ process.stdout.write(String(pkg.dependencies?.["@effindomv2/runtime"] ?? ""));
15
+ ' "${PACKAGE_JSON}"
16
+ )"
17
+
18
+ if [ -z "${runtime_spec}" ]; then
19
+ echo "@effindomv2/runtime must be declared in dependencies." >&2
20
+ exit 1
21
+ fi
22
+
23
+ if ! printf '%s' "${runtime_spec}" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.]+)?$'; then
24
+ echo "@effindomv2/runtime must be pinned to an exact version, found: ${runtime_spec}" >&2
25
+ exit 1
26
+ fi
27
+
28
+ if [ -f "${RUNTIME_PACKAGE_JSON}" ]; then
29
+ runtime_version="$(
30
+ node -e '
31
+ const fs = require("node:fs");
32
+ const pkg = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
33
+ process.stdout.write(String(pkg.version ?? ""));
34
+ ' "${RUNTIME_PACKAGE_JSON}"
35
+ )"
36
+
37
+ if [ -n "${runtime_version}" ] && [ "${runtime_spec}" != "${runtime_version}" ]; then
38
+ echo "@effindomv2/fui-rs depends on @effindomv2/runtime@${runtime_spec}, but the local runtime is ${runtime_version}." >&2
39
+ exit 1
40
+ fi
41
+ fi
@@ -0,0 +1,40 @@
1
+ import { defineHostServices, hostService } from "../../browser-bridge/src/managed-harness/host-services.js";
2
+
3
+ function unsupported(): never {
4
+ throw new Error("Framework host service definitions are generator metadata only.");
5
+ }
6
+
7
+ export const frameworkHostServices = defineHostServices({
8
+ frameworkHost: {
9
+ nowMs: hostService({
10
+ importName: "fui_now_ms",
11
+ args: [] as const,
12
+ returns: "f64",
13
+ implementation: () => unsupported(),
14
+ }),
15
+ isDarkMode: hostService({
16
+ importName: "fui_is_dark_mode",
17
+ args: [] as const,
18
+ returns: "bool",
19
+ implementation: () => unsupported(),
20
+ }),
21
+ accentColor: hostService({
22
+ importName: "fui_get_accent_color",
23
+ args: [] as const,
24
+ returns: "u32",
25
+ implementation: () => unsupported(),
26
+ }),
27
+ platformFamily: hostService({
28
+ importName: "fui_get_platform_family",
29
+ args: [] as const,
30
+ returns: "u32",
31
+ implementation: () => unsupported(),
32
+ }),
33
+ isCoarsePointer: hostService({
34
+ importName: "fui_is_coarse_pointer",
35
+ args: [] as const,
36
+ returns: "bool",
37
+ implementation: () => unsupported(),
38
+ }),
39
+ },
40
+ });
@@ -0,0 +1,17 @@
1
+ import path from "node:path";
2
+ import { generateRustHostEventsFile } from "./hostgen/rust-host-events";
3
+
4
+ async function main(): Promise<void> {
5
+ const args = process.argv.slice(2);
6
+ if (args.length < 3) {
7
+ throw new Error("Usage: generate-host-events <module-path> <export-name> <output-path>");
8
+ }
9
+ const [moduleArg, exportName, outputArg] = args as [string, string, string];
10
+ await generateRustHostEventsFile(
11
+ path.resolve(process.cwd(), moduleArg),
12
+ exportName,
13
+ path.resolve(process.cwd(), outputArg),
14
+ );
15
+ }
16
+
17
+ await main();
@@ -0,0 +1,28 @@
1
+ import path from "node:path";
2
+ import { generateRustHostServicesFile } from "./hostgen/rust-host-services";
3
+
4
+ async function main(): Promise<void> {
5
+ const args = process.argv.slice(2);
6
+ if (args.length < 3) {
7
+ throw new Error(
8
+ "Usage: generate-host-services <module-path> <export-name> <output-path> [runtime-path] [host-import-module]",
9
+ );
10
+ }
11
+ const [moduleArg, exportName, outputArg, runtimePathArg, hostModuleArg] = args as [
12
+ string,
13
+ string,
14
+ string,
15
+ string | undefined,
16
+ string | undefined,
17
+ ];
18
+ await generateRustHostServicesFile(
19
+ path.resolve(process.cwd(), moduleArg),
20
+ exportName,
21
+ path.resolve(process.cwd(), outputArg),
22
+ runtimePathArg,
23
+ hostModuleArg ?? "fui_host_service",
24
+ );
25
+ }
26
+
27
+ await main();
28
+
@@ -0,0 +1,73 @@
1
+ import { build } from "esbuild";
2
+ import { promises as fs } from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+
7
+ export function toPosix(value: string): string {
8
+ return value.split(path.sep).join("/");
9
+ }
10
+
11
+ export function relativeImport(fromFile: string, targetFile: string): string {
12
+ let relative = path.relative(path.dirname(fromFile), targetFile);
13
+ relative = relative.replace(/\.[^.]+$/, "");
14
+ if (!relative.startsWith(".")) {
15
+ relative = `./${relative}`;
16
+ }
17
+ return toPosix(relative);
18
+ }
19
+
20
+ export function sourcePathForHeader(sourceModulePath: string): string {
21
+ const relative = path.relative(process.cwd(), sourceModulePath);
22
+ return toPosix(relative.startsWith(".") ? relative : `./${relative}`);
23
+ }
24
+
25
+ export async function loadModuleExport(
26
+ modulePath: string,
27
+ exportName: string,
28
+ tempPrefix: string,
29
+ ): Promise<unknown> {
30
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), tempPrefix));
31
+ const bundledFile = path.join(tempDir, "module.mjs");
32
+ try {
33
+ await build({
34
+ entryPoints: [modulePath],
35
+ outfile: bundledFile,
36
+ bundle: true,
37
+ format: "esm",
38
+ platform: "node",
39
+ target: "node20",
40
+ logLevel: "silent",
41
+ });
42
+ const loaded = await import(pathToFileURL(bundledFile).href) as Record<string, unknown>;
43
+ if (!(exportName in loaded)) {
44
+ throw new Error(`Module does not export "${exportName}".`);
45
+ }
46
+ return loaded[exportName];
47
+ } finally {
48
+ await fs.rm(tempDir, { recursive: true, force: true });
49
+ }
50
+ }
51
+
52
+ export function snakeCaseIdentifier(value: string): string {
53
+ let result = "";
54
+ for (let index = 0; index < value.length; index += 1) {
55
+ const char = value[index] ?? "";
56
+ const previous = index > 0 ? value[index - 1] ?? "" : "";
57
+ const next = index + 1 < value.length ? value[index + 1] ?? "" : "";
58
+ const isUpper = char >= "A" && char <= "Z";
59
+ const prevIsLowerOrDigit =
60
+ (previous >= "a" && previous <= "z") || (previous >= "0" && previous <= "9");
61
+ const nextIsLower = next >= "a" && next <= "z";
62
+ if (
63
+ index > 0 &&
64
+ isUpper &&
65
+ (prevIsLowerOrDigit || ((previous >= "A" && previous <= "Z") && nextIsLower))
66
+ ) {
67
+ result += "_";
68
+ }
69
+ result += char.toLowerCase();
70
+ }
71
+ return result;
72
+ }
73
+