ruby_wasm_ui 0.8.3 → 0.9.1
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/.github/workflows/rspec.yml +0 -2
- data/Makefile +56 -0
- data/README.md +26 -6
- data/examples/.gitignore +4 -0
- data/examples/Gemfile.lock +15 -17
- data/examples/src/index.html +21 -0
- data/examples/src/index.rb +26 -0
- data/exe/ruby-wasm-ui +6 -0
- data/lib/ruby_wasm_ui/cli/command/base.rb +192 -0
- data/lib/ruby_wasm_ui/cli/command/dev.rb +207 -0
- data/lib/ruby_wasm_ui/cli/command/pack.rb +36 -0
- data/lib/ruby_wasm_ui/cli/command/rebuild.rb +38 -0
- data/lib/ruby_wasm_ui/cli/command/setup.rb +159 -0
- data/lib/ruby_wasm_ui/cli/command.rb +48 -0
- data/lib/ruby_wasm_ui/version.rb +1 -1
- data/lib/ruby_wasm_ui.rb +8 -8
- data/package-lock.json +2 -2
- data/package.json +1 -1
- data/packages/npm-packages/runtime/package-lock.json +2 -2
- data/packages/npm-packages/runtime/package.json +1 -1
- data/packages/npm-packages/runtime/rollup.config.mjs +1 -1
- data/spec/ruby_wasm_ui/cli/command/base_spec.rb +503 -0
- data/spec/ruby_wasm_ui/cli/command/dev_spec.rb +442 -0
- data/spec/ruby_wasm_ui/cli/command/pack_spec.rb +131 -0
- data/spec/ruby_wasm_ui/cli/command/rebuild_spec.rb +95 -0
- data/spec/ruby_wasm_ui/cli/command/setup_spec.rb +251 -0
- data/spec/ruby_wasm_ui/cli/command_spec.rb +118 -0
- data/{packages/npm-packages/runtime/spec → spec}/spec_helper.rb +1 -1
- metadata +96 -38
- data/packages/npm-packages/runtime/Gemfile +0 -3
- data/packages/npm-packages/runtime/Gemfile.lock +0 -26
- /data/lib/ruby_wasm_ui/{app.rb → runtime/app.rb} +0 -0
- /data/lib/ruby_wasm_ui/{component.rb → runtime/component.rb} +0 -0
- /data/lib/ruby_wasm_ui/{dispatcher.rb → runtime/dispatcher.rb} +0 -0
- /data/lib/ruby_wasm_ui/{dom → runtime/dom}/attributes.rb +0 -0
- /data/lib/ruby_wasm_ui/{dom → runtime/dom}/destroy_dom.rb +0 -0
- /data/lib/ruby_wasm_ui/{dom → runtime/dom}/events.rb +0 -0
- /data/lib/ruby_wasm_ui/{dom → runtime/dom}/mount_dom.rb +0 -0
- /data/lib/ruby_wasm_ui/{dom → runtime/dom}/patch_dom.rb +0 -0
- /data/lib/ruby_wasm_ui/{dom → runtime/dom}/scheduler.rb +0 -0
- /data/lib/ruby_wasm_ui/{dom.rb → runtime/dom.rb} +0 -0
- /data/lib/ruby_wasm_ui/{nodes_equal.rb → runtime/nodes_equal.rb} +0 -0
- /data/lib/ruby_wasm_ui/{template → runtime/template}/build_conditional_group.rb +0 -0
- /data/lib/ruby_wasm_ui/{template → runtime/template}/build_for_group.rb +0 -0
- /data/lib/ruby_wasm_ui/{template → runtime/template}/build_vdom.rb +0 -0
- /data/lib/ruby_wasm_ui/{template → runtime/template}/parser.rb +0 -0
- /data/lib/ruby_wasm_ui/{template.rb → runtime/template.rb} +0 -0
- /data/lib/ruby_wasm_ui/{utils → runtime/utils}/arrays.rb +0 -0
- /data/lib/ruby_wasm_ui/{utils → runtime/utils}/objects.rb +0 -0
- /data/lib/ruby_wasm_ui/{utils → runtime/utils}/props.rb +0 -0
- /data/lib/ruby_wasm_ui/{utils → runtime/utils}/strings.rb +0 -0
- /data/lib/ruby_wasm_ui/{utils.rb → runtime/utils.rb} +0 -0
- /data/lib/ruby_wasm_ui/{vdom.rb → runtime/vdom.rb} +0 -0
- /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/component_spec.rb +0 -0
- /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/dom/scheduler_spec.rb +0 -0
- /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/nodes_equal_spec.rb +0 -0
- /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/template/build_conditional_group_spec.rb +0 -0
- /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/template/build_for_group_spec.rb +0 -0
- /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/template/build_vdom_spec.rb +0 -0
- /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/template/parser_spec.rb +0 -0
- /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/utils/arrays_spec.rb +0 -0
- /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/utils/objects_spec.rb +0 -0
- /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/utils/props_spec.rb +0 -0
- /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/utils/strings_spec.rb +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bd9d0a33dc10a59959527fb70eb2080fb612f5c1d07cea603eb23ca5c957f342
|
|
4
|
+
data.tar.gz: c5dad3fabee2912ea43fe45d0fdbc899357323e127edd572b5572d4cc474a3cd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7cd4c88f0f24adb3521448e94eae17a69836b517bf916e752e8dcb69793d8ec22930eda80a5492f4f5118fabeb546112e6bf142854beee415574ba55474f7b9c
|
|
7
|
+
data.tar.gz: 316e8854f16c214e0f0ce488584d5c3c0ace723fe7b5cd47f45e3961367c29cb72073772519944c71e6d1e8d50db3020e25844c3f6f2224c2dda4412fe414ccf
|
data/.github/workflows/rspec.yml
CHANGED
data/Makefile
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
.PHONY: bump help
|
|
2
|
+
|
|
3
|
+
help:
|
|
4
|
+
@echo "Usage: make bump <version>"
|
|
5
|
+
@echo " make bump VERSION=<version>"
|
|
6
|
+
@echo "Example: make bump 0.9.0"
|
|
7
|
+
@echo ""
|
|
8
|
+
@echo "This command updates the version number in the following files:"
|
|
9
|
+
@echo " - lib/ruby_wasm_ui/version.rb"
|
|
10
|
+
@echo " - package.json"
|
|
11
|
+
@echo " - packages/npm-packages/runtime/package.json"
|
|
12
|
+
@echo " - README.md"
|
|
13
|
+
@echo " - package-lock.json (via npm install)"
|
|
14
|
+
@echo " - packages/npm-packages/runtime/package-lock.json (via npm install)"
|
|
15
|
+
|
|
16
|
+
# Get version from position argument (if VERSION is not already set)
|
|
17
|
+
VERSION_ARG := $(word 2,$(MAKECMDGOALS))
|
|
18
|
+
ifndef VERSION
|
|
19
|
+
ifneq ($(VERSION_ARG),)
|
|
20
|
+
VERSION := $(VERSION_ARG)
|
|
21
|
+
endif
|
|
22
|
+
endif
|
|
23
|
+
|
|
24
|
+
bump:
|
|
25
|
+
@if [ -z "$(VERSION)" ]; then \
|
|
26
|
+
echo "Error: VERSION is required. Usage: make bump 0.9.0"; \
|
|
27
|
+
exit 1; \
|
|
28
|
+
fi
|
|
29
|
+
@echo "Bumping version to $(VERSION)..."
|
|
30
|
+
@# Update lib/ruby_wasm_ui/version.rb
|
|
31
|
+
@sed -i '' 's/VERSION = ".*"/VERSION = "$(VERSION)"/' lib/ruby_wasm_ui/version.rb
|
|
32
|
+
@echo "✓ Updated lib/ruby_wasm_ui/version.rb"
|
|
33
|
+
@# Update Gemfile.lock
|
|
34
|
+
@bundle install
|
|
35
|
+
@echo "✓ Updated Gemfile.lock"
|
|
36
|
+
@# Update root package.json
|
|
37
|
+
@sed -i '' 's/"version": ".*"/"version": "$(VERSION)"/' package.json
|
|
38
|
+
@echo "✓ Updated package.json"
|
|
39
|
+
@# Update packages/npm-packages/runtime/package.json
|
|
40
|
+
@sed -i '' 's/"version": ".*"/"version": "$(VERSION)"/' packages/npm-packages/runtime/package.json
|
|
41
|
+
@echo "✓ Updated packages/npm-packages/runtime/package.json"
|
|
42
|
+
@# Update README.md (unpkg.com URL)
|
|
43
|
+
@sed -i '' 's|unpkg.com/ruby-wasm-ui@[0-9.]*|unpkg.com/ruby-wasm-ui@$(VERSION)|' README.md
|
|
44
|
+
@echo "✓ Updated README.md"
|
|
45
|
+
@echo ""
|
|
46
|
+
@echo "Updating package-lock.json files..."
|
|
47
|
+
@npm install --package-lock-only
|
|
48
|
+
@echo "✓ Updated root package-lock.json"
|
|
49
|
+
@cd packages/npm-packages/runtime && npm install --package-lock-only && cd ../../..
|
|
50
|
+
@echo "✓ Updated packages/npm-packages/runtime/package-lock.json"
|
|
51
|
+
@echo ""
|
|
52
|
+
@echo "Version bumped to $(VERSION) successfully!"
|
|
53
|
+
|
|
54
|
+
# Prevent make from treating the version argument as a target
|
|
55
|
+
%:
|
|
56
|
+
@:
|
data/README.md
CHANGED
|
@@ -21,7 +21,7 @@ Create an HTML file:
|
|
|
21
21
|
<!DOCTYPE html>
|
|
22
22
|
<html>
|
|
23
23
|
<head>
|
|
24
|
-
<script src="https://unpkg.com/ruby-wasm-ui@0.
|
|
24
|
+
<script src="https://unpkg.com/ruby-wasm-ui@0.9.1"></script>
|
|
25
25
|
<script defer type="text/ruby" src="app.rb"></script>
|
|
26
26
|
</head>
|
|
27
27
|
<body>
|
|
@@ -112,19 +112,39 @@ bundle install
|
|
|
112
112
|
|
|
113
113
|
### Building Your Application
|
|
114
114
|
|
|
115
|
-
1.
|
|
115
|
+
1. Set up your project (first time only):
|
|
116
116
|
|
|
117
117
|
```bash
|
|
118
|
-
bundle exec
|
|
118
|
+
bundle exec ruby-wasm-ui setup
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
This command will:
|
|
122
|
+
- Configure excluded gems for WASM build
|
|
123
|
+
- Build Ruby WASM file
|
|
124
|
+
- Update `.gitignore`
|
|
125
|
+
- Create initial `src/index.html` and `src/index.rb` files
|
|
126
|
+
|
|
127
|
+
**Additional Commands:**
|
|
128
|
+
|
|
129
|
+
- **Development server**: Start a development server with file watching and auto-build:
|
|
130
|
+
```bash
|
|
131
|
+
bundle exec ruby-wasm-ui dev
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
- **Rebuild Ruby WASM**: Rebuild the Ruby WASM file when you add new gems:
|
|
135
|
+
```bash
|
|
136
|
+
bundle exec ruby-wasm-ui rebuild
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Deployment
|
|
140
|
+
|
|
141
|
+
Pack your application files for deployment:
|
|
122
142
|
|
|
123
143
|
```bash
|
|
124
|
-
bundle exec
|
|
144
|
+
bundle exec ruby-wasm-ui pack
|
|
125
145
|
```
|
|
126
146
|
|
|
127
|
-
This command packs your Ruby files from the `./src` directory into the WASM file.
|
|
147
|
+
This command packs your Ruby files from the `./src` directory into the WASM file and outputs to the `dist` directory for deployment.
|
|
128
148
|
|
|
129
149
|
### Creating Your HTML File
|
|
130
150
|
|
data/examples/.gitignore
ADDED
data/examples/Gemfile.lock
CHANGED
|
@@ -1,38 +1,36 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ../..
|
|
3
3
|
specs:
|
|
4
|
-
ruby_wasm_ui (0.
|
|
4
|
+
ruby_wasm_ui (0.9.0)
|
|
5
5
|
js (~> 2.7)
|
|
6
|
+
listen (~> 3.8)
|
|
7
|
+
puma (~> 6.0)
|
|
8
|
+
rack (~> 3.0)
|
|
6
9
|
ruby_wasm (~> 2.7)
|
|
7
10
|
|
|
8
11
|
GEM
|
|
9
12
|
remote: https://rubygems.org/
|
|
10
13
|
specs:
|
|
14
|
+
ffi (1.17.2-arm64-darwin)
|
|
11
15
|
js (2.7.2)
|
|
16
|
+
listen (3.9.0)
|
|
17
|
+
rb-fsevent (~> 0.10, >= 0.10.3)
|
|
18
|
+
rb-inotify (~> 0.9, >= 0.9.10)
|
|
12
19
|
logger (1.7.0)
|
|
20
|
+
nio4r (2.7.5)
|
|
21
|
+
puma (6.6.1)
|
|
22
|
+
nio4r (~> 2.0)
|
|
23
|
+
rack (3.2.4)
|
|
24
|
+
rb-fsevent (0.11.2)
|
|
25
|
+
rb-inotify (0.11.1)
|
|
26
|
+
ffi (~> 1.0)
|
|
13
27
|
ruby_wasm (2.7.2)
|
|
14
28
|
logger
|
|
15
|
-
ruby_wasm (2.7.2-aarch64-linux)
|
|
16
|
-
logger
|
|
17
|
-
ruby_wasm (2.7.2-aarch64-linux-musl)
|
|
18
|
-
logger
|
|
19
29
|
ruby_wasm (2.7.2-arm64-darwin)
|
|
20
30
|
logger
|
|
21
|
-
ruby_wasm (2.7.2-x86_64-darwin)
|
|
22
|
-
logger
|
|
23
|
-
ruby_wasm (2.7.2-x86_64-linux)
|
|
24
|
-
logger
|
|
25
|
-
ruby_wasm (2.7.2-x86_64-linux-musl)
|
|
26
|
-
logger
|
|
27
31
|
|
|
28
32
|
PLATFORMS
|
|
29
|
-
aarch64-linux
|
|
30
|
-
aarch64-linux-musl
|
|
31
33
|
arm64-darwin
|
|
32
|
-
ruby
|
|
33
|
-
x86_64-darwin
|
|
34
|
-
x86_64-linux
|
|
35
|
-
x86_64-linux-musl
|
|
36
34
|
|
|
37
35
|
DEPENDENCIES
|
|
38
36
|
ruby_wasm_ui!
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<title>My App</title>
|
|
6
|
+
<script type="module">
|
|
7
|
+
import { DefaultRubyVM } from "https://cdn.jsdelivr.net/npm/@ruby/wasm-wasi@2.7.2/dist/browser/+esm";
|
|
8
|
+
const response = await fetch("./src.wasm");
|
|
9
|
+
const module = await WebAssembly.compileStreaming(response);
|
|
10
|
+
const { vm } = await DefaultRubyVM(module);
|
|
11
|
+
vm.evalAsync(`
|
|
12
|
+
require "ruby_wasm_ui"
|
|
13
|
+
require_relative './src/index.rb'
|
|
14
|
+
`);
|
|
15
|
+
</script>
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<h1>My App</h1>
|
|
19
|
+
<div id="app"></div>
|
|
20
|
+
</body>
|
|
21
|
+
</html>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Simple Hello World component
|
|
2
|
+
HelloComponent = RubyWasmUi.define_component(
|
|
3
|
+
state: ->(props) {
|
|
4
|
+
{ message: props[:message] || "Hello, Ruby WASM UI!" }
|
|
5
|
+
},
|
|
6
|
+
template: ->() {
|
|
7
|
+
RubyWasmUi::Template::Parser.parse_and_eval(<<~HTML, binding)
|
|
8
|
+
<div>
|
|
9
|
+
<h2>{state[:message]}</h2>
|
|
10
|
+
<button on="{ click: -> { update_message } }">
|
|
11
|
+
Click me!
|
|
12
|
+
</button>
|
|
13
|
+
</div>
|
|
14
|
+
HTML
|
|
15
|
+
},
|
|
16
|
+
methods: {
|
|
17
|
+
update_message: ->() {
|
|
18
|
+
update_state(message: "You clicked the button!")
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Create and mount the app
|
|
24
|
+
app = RubyWasmUi::App.create(HelloComponent, message: "Hello, Ruby WASM UI!")
|
|
25
|
+
app_element = JS.global[:document].getElementById("app")
|
|
26
|
+
app.mount(app_element)
|
data/exe/ruby-wasm-ui
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "open3"
|
|
5
|
+
require "bundler/setup"
|
|
6
|
+
require "ruby_wasm"
|
|
7
|
+
require "ruby_wasm/cli"
|
|
8
|
+
|
|
9
|
+
module RubyWasmUi
|
|
10
|
+
module Cli
|
|
11
|
+
class Command
|
|
12
|
+
class Base
|
|
13
|
+
def self.description
|
|
14
|
+
nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def run(_argv)
|
|
18
|
+
raise NotImplementedError, "Subclasses must implement #run"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
protected
|
|
22
|
+
|
|
23
|
+
def run_command(command, exit_on_error: true)
|
|
24
|
+
log_debug("Executing command: #{command}")
|
|
25
|
+
|
|
26
|
+
Open3.popen3(command) do |stdin, stdout, stderr, wait_thr|
|
|
27
|
+
stdin.close
|
|
28
|
+
|
|
29
|
+
# Read stdout and stderr in separate threads for real-time output
|
|
30
|
+
stdout_thread = Thread.new do
|
|
31
|
+
stdout.each_line do |line|
|
|
32
|
+
print line
|
|
33
|
+
$stdout.flush
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
stderr_thread = Thread.new do
|
|
38
|
+
stderr.each_line do |line|
|
|
39
|
+
$stderr.print line
|
|
40
|
+
$stderr.flush
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Wait for both threads to finish reading
|
|
45
|
+
stdout_thread.join
|
|
46
|
+
stderr_thread.join
|
|
47
|
+
|
|
48
|
+
# Wait for the process to finish
|
|
49
|
+
exit_status = wait_thr.value
|
|
50
|
+
|
|
51
|
+
unless exit_status.success?
|
|
52
|
+
log_error("Command failed: #{command}")
|
|
53
|
+
if exit_on_error
|
|
54
|
+
raise SystemExit.new(exit_status.exitstatus)
|
|
55
|
+
else
|
|
56
|
+
return false
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
true
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def ensure_src_directory
|
|
65
|
+
unless Dir.exist?("src")
|
|
66
|
+
log_error("src directory not found. Please run 'ruby-wasm-ui setup' first.")
|
|
67
|
+
raise SystemExit.new(1)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def ensure_ruby_wasm
|
|
72
|
+
unless File.exist?("ruby.wasm")
|
|
73
|
+
log_error("ruby.wasm not found. Please run 'ruby-wasm-ui setup' first.")
|
|
74
|
+
raise SystemExit.new(1)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
protected
|
|
79
|
+
|
|
80
|
+
def log_info(message)
|
|
81
|
+
puts "[INFO] #{message}"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def log_success(message)
|
|
85
|
+
puts "[SUCCESS] #{message}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def log_error(message)
|
|
89
|
+
puts "[ERROR] #{message}"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def log_debug(message)
|
|
93
|
+
puts "[DEBUG] #{message}"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def check_ruby_version
|
|
97
|
+
ruby_version = RUBY_VERSION.split('.').map(&:to_i)
|
|
98
|
+
major, minor = ruby_version[0], ruby_version[1]
|
|
99
|
+
|
|
100
|
+
if major < 3 || (major == 3 && minor < 2)
|
|
101
|
+
log_error("Ruby WASM requires Ruby 3.2 or higher. Current version: #{RUBY_VERSION}")
|
|
102
|
+
raise SystemExit.new(1)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
"#{major}.#{minor}"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def configure_excluded_gems
|
|
109
|
+
# Get all gems from Bundler definition including all dependencies
|
|
110
|
+
definition = Bundler.definition
|
|
111
|
+
resolved = definition.resolve
|
|
112
|
+
|
|
113
|
+
# Get all resolved specs (including transitive dependencies)
|
|
114
|
+
all_specs = resolved.materialize(definition.requested_dependencies)
|
|
115
|
+
gem_names = all_specs.map(&:name).uniq
|
|
116
|
+
|
|
117
|
+
# Always exclude gems that cause WASM build errors
|
|
118
|
+
# These gems have native extensions that don't work in WASM environment
|
|
119
|
+
always_excluded = %w[nio4r puma rack listen ffi]
|
|
120
|
+
|
|
121
|
+
# Exclude gems that cause build errors or are unnecessary for WASM
|
|
122
|
+
# Keep essential gems like 'js' for WASM
|
|
123
|
+
excluded_gems = gem_names.select do |gem_name|
|
|
124
|
+
# Exclude gems that cause WASM build errors
|
|
125
|
+
always_excluded.include?(gem_name) ||
|
|
126
|
+
# Exclude development/test gems
|
|
127
|
+
gem_name.start_with?("rspec", "rubocop", "rake")
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Always add always_excluded gems to ensure they're excluded even if not in dependencies
|
|
131
|
+
excluded_gems.concat(always_excluded)
|
|
132
|
+
excluded_gems.uniq!
|
|
133
|
+
|
|
134
|
+
# Add to EXCLUDED_GEMS
|
|
135
|
+
RubyWasm::Packager::EXCLUDED_GEMS.concat(excluded_gems)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def build_ruby_wasm(ruby_version_str)
|
|
139
|
+
command = ["build", "--ruby-version", ruby_version_str, "-o", "ruby.wasm"]
|
|
140
|
+
cli = RubyWasm::CLI.new(stdout: $stdout, stderr: $stderr)
|
|
141
|
+
cli.run(command)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def ensure_dist_directory
|
|
145
|
+
unless Dir.exist?("dist")
|
|
146
|
+
Dir.mkdir("dist")
|
|
147
|
+
log_info("Created dist directory")
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def copy_non_ruby_files
|
|
152
|
+
log_info("Copying non-Ruby files from src to dist...")
|
|
153
|
+
|
|
154
|
+
copied_files = []
|
|
155
|
+
Dir.glob("src/**/*").each do |src_path|
|
|
156
|
+
next if File.directory?(src_path)
|
|
157
|
+
next if src_path.end_with?(".rb")
|
|
158
|
+
|
|
159
|
+
# Get relative path from src directory
|
|
160
|
+
relative_path = src_path.sub(/^src\//, "")
|
|
161
|
+
dest_path = File.join("dist", relative_path)
|
|
162
|
+
|
|
163
|
+
# Create destination directory if needed
|
|
164
|
+
dest_dir = File.dirname(dest_path)
|
|
165
|
+
FileUtils.mkdir_p(dest_dir) unless Dir.exist?(dest_dir)
|
|
166
|
+
|
|
167
|
+
# Copy file
|
|
168
|
+
FileUtils.cp(src_path, dest_path)
|
|
169
|
+
copied_files << relative_path
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
if copied_files.any?
|
|
173
|
+
log_success("✓ Copied #{copied_files.size} file(s): #{copied_files.join(', ')}")
|
|
174
|
+
else
|
|
175
|
+
log_info("No non-Ruby files to copy")
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def pack_wasm(exit_on_error: true, log_prefix: "Packing")
|
|
180
|
+
command = "bundle exec rbwasm pack ruby.wasm --dir ./src::./src -o dist/src.wasm"
|
|
181
|
+
log_info("#{log_prefix}: #{command}")
|
|
182
|
+
|
|
183
|
+
success = run_command(command, exit_on_error: exit_on_error)
|
|
184
|
+
if success
|
|
185
|
+
log_success("✓ Pack completed")
|
|
186
|
+
end
|
|
187
|
+
success
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
require "listen"
|
|
5
|
+
require "rack"
|
|
6
|
+
require "thread"
|
|
7
|
+
require "rbconfig"
|
|
8
|
+
|
|
9
|
+
module RubyWasmUi
|
|
10
|
+
module Cli
|
|
11
|
+
class Command
|
|
12
|
+
class Dev < Base
|
|
13
|
+
def self.description
|
|
14
|
+
"Start development server with file watching and auto-build"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def run(_argv)
|
|
18
|
+
log_info("Starting development server...")
|
|
19
|
+
puts ""
|
|
20
|
+
|
|
21
|
+
ensure_src_directory
|
|
22
|
+
ensure_ruby_wasm
|
|
23
|
+
|
|
24
|
+
# Initial build
|
|
25
|
+
log_info("Performing initial build...")
|
|
26
|
+
build
|
|
27
|
+
log_success("✓ Initial build completed")
|
|
28
|
+
puts ""
|
|
29
|
+
|
|
30
|
+
# Start file watcher in a separate thread
|
|
31
|
+
@build_lock = Mutex.new
|
|
32
|
+
@build_queue = Queue.new
|
|
33
|
+
@listener = nil
|
|
34
|
+
@watcher_thread = nil
|
|
35
|
+
|
|
36
|
+
@watcher_thread = Thread.new do
|
|
37
|
+
start_file_watcher
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Register cleanup hook to ensure cleanup runs on exit
|
|
41
|
+
@cleanup_done = false
|
|
42
|
+
at_exit do
|
|
43
|
+
cleanup unless @cleanup_done
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Start development server
|
|
47
|
+
begin
|
|
48
|
+
start_server
|
|
49
|
+
ensure
|
|
50
|
+
cleanup
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def open_browser(url)
|
|
57
|
+
case RbConfig::CONFIG["host_os"]
|
|
58
|
+
when /darwin/
|
|
59
|
+
system("open", url)
|
|
60
|
+
when /linux/
|
|
61
|
+
system("xdg-open", url)
|
|
62
|
+
when /mswin|mingw|cygwin/
|
|
63
|
+
system("start", url)
|
|
64
|
+
else
|
|
65
|
+
log_info("Please open #{url} in your browser")
|
|
66
|
+
end
|
|
67
|
+
rescue => e
|
|
68
|
+
log_info("Could not open browser automatically: #{e.message}")
|
|
69
|
+
log_info("Please open #{url} in your browser")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def cleanup
|
|
73
|
+
return if @cleanup_done
|
|
74
|
+
@cleanup_done = true
|
|
75
|
+
@listener&.stop
|
|
76
|
+
@watcher_thread&.kill
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def build
|
|
80
|
+
ensure_dist_directory
|
|
81
|
+
|
|
82
|
+
success = pack_wasm(exit_on_error: false, log_prefix: "Building")
|
|
83
|
+
if success
|
|
84
|
+
copy_non_ruby_files
|
|
85
|
+
log_success("✓ Build completed")
|
|
86
|
+
else
|
|
87
|
+
log_error("Build failed")
|
|
88
|
+
end
|
|
89
|
+
success
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def start_file_watcher
|
|
93
|
+
@listener = Listen.to("src") do |modified, added, removed|
|
|
94
|
+
files_changed = (modified + added + removed).reject do |file|
|
|
95
|
+
# Ignore temporary files and common build artifacts
|
|
96
|
+
file.end_with?(".swp", ".tmp", "~", ".wasm")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
if files_changed.any?
|
|
100
|
+
log_info("Files changed: #{files_changed.join(', ')}")
|
|
101
|
+
|
|
102
|
+
# Debounce: add to queue
|
|
103
|
+
@build_queue << :build
|
|
104
|
+
|
|
105
|
+
# Process queue with debouncing
|
|
106
|
+
Thread.new do
|
|
107
|
+
sleep 0.5 # Debounce delay
|
|
108
|
+
begin
|
|
109
|
+
if @build_queue.pop(true)
|
|
110
|
+
@build_queue.clear
|
|
111
|
+
@build_lock.synchronize do
|
|
112
|
+
build
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
rescue ThreadError
|
|
116
|
+
# Queue is empty, ignore
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
@listener.start
|
|
123
|
+
rescue => e
|
|
124
|
+
log_error("File watcher error: #{e.message}")
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def start_server
|
|
128
|
+
port = 8080
|
|
129
|
+
log_info("Starting development server on http://localhost:#{port}")
|
|
130
|
+
log_info("Press Ctrl+C to stop")
|
|
131
|
+
puts ""
|
|
132
|
+
|
|
133
|
+
# Static file server application
|
|
134
|
+
static_app = lambda do |env|
|
|
135
|
+
path = env["PATH_INFO"]
|
|
136
|
+
# Remove leading slash and handle root path
|
|
137
|
+
relative_path = path == "/" ? "index.html" : path.sub(/^\//, "")
|
|
138
|
+
|
|
139
|
+
# Serve files from dist directory
|
|
140
|
+
file_path = File.join(Dir.pwd, "dist", relative_path)
|
|
141
|
+
|
|
142
|
+
if File.exist?(file_path) && File.file?(file_path)
|
|
143
|
+
content_type = Rack::Mime.mime_type(File.extname(file_path), "text/html")
|
|
144
|
+
[200, { "Content-Type" => content_type }, [File.read(file_path)]]
|
|
145
|
+
else
|
|
146
|
+
[404, { "Content-Type" => "text/plain" }, ["File not found: #{path}"]]
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# CORS middleware
|
|
151
|
+
cors_middleware = lambda do |env|
|
|
152
|
+
if env["REQUEST_METHOD"] == "OPTIONS"
|
|
153
|
+
[
|
|
154
|
+
200,
|
|
155
|
+
{
|
|
156
|
+
"Access-Control-Allow-Origin" => "*",
|
|
157
|
+
"Access-Control-Allow-Methods" => "GET, POST, OPTIONS",
|
|
158
|
+
"Access-Control-Allow-Headers" => "Content-Type"
|
|
159
|
+
},
|
|
160
|
+
[]
|
|
161
|
+
]
|
|
162
|
+
else
|
|
163
|
+
status, headers, body = static_app.call(env)
|
|
164
|
+
headers["Access-Control-Allow-Origin"] = "*"
|
|
165
|
+
headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"
|
|
166
|
+
headers["Access-Control-Allow-Headers"] = "Content-Type"
|
|
167
|
+
[status, headers, body]
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Create Rack application
|
|
172
|
+
app = Rack::Builder.new do
|
|
173
|
+
run cors_middleware
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Handle shutdown
|
|
177
|
+
trap("INT") do
|
|
178
|
+
log_info("\nShutting down server...")
|
|
179
|
+
cleanup
|
|
180
|
+
raise SystemExit.new(0)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Use Puma handler (Rack 3.0 compatible)
|
|
184
|
+
begin
|
|
185
|
+
require "rack/handler/puma"
|
|
186
|
+
log_info("Using handler: puma")
|
|
187
|
+
|
|
188
|
+
# Open browser after a short delay to ensure server is ready
|
|
189
|
+
Thread.new do
|
|
190
|
+
sleep 1
|
|
191
|
+
open_browser("http://localhost:#{port}/index.html")
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
Rack::Handler::Puma.run(app, Port: port, Host: "0.0.0.0")
|
|
195
|
+
rescue LoadError => e
|
|
196
|
+
log_error("Puma handler not available: #{e.message}")
|
|
197
|
+
log_error("Please ensure puma gem is installed: gem install puma")
|
|
198
|
+
raise SystemExit.new(1)
|
|
199
|
+
end
|
|
200
|
+
rescue => e
|
|
201
|
+
log_error("Server error: #{e.message}")
|
|
202
|
+
raise SystemExit.new(1)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module RubyWasmUi
|
|
6
|
+
module Cli
|
|
7
|
+
class Command
|
|
8
|
+
class Pack < Base
|
|
9
|
+
def self.description
|
|
10
|
+
"Pack WASM file by packing Ruby source files"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def run(_argv)
|
|
14
|
+
log_info("Packing WASM file...")
|
|
15
|
+
puts ""
|
|
16
|
+
|
|
17
|
+
ensure_src_directory
|
|
18
|
+
ensure_ruby_wasm
|
|
19
|
+
ensure_dist_directory
|
|
20
|
+
|
|
21
|
+
# Pack WASM file
|
|
22
|
+
pack
|
|
23
|
+
|
|
24
|
+
# Copy non-Ruby files from src to dist
|
|
25
|
+
copy_non_ruby_files
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def pack
|
|
31
|
+
pack_wasm(exit_on_error: true, log_prefix: 'Packing')
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|