Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ PATH
specs:
ruby_wasm_ui (0.8.3)
js (~> 2.7)
listen (~> 3.8)
puma (~> 6.0)
rack (~> 3.0)
ruby_wasm (~> 2.7)

GEM
Expand All @@ -11,20 +14,37 @@ GEM
date (3.5.0)
diff-lcs (1.6.2)
erb (6.0.0)
ffi (1.17.2)
ffi (1.17.2-aarch64-linux-gnu)
ffi (1.17.2-aarch64-linux-musl)
ffi (1.17.2-arm64-darwin)
ffi (1.17.2-x86_64-darwin)
ffi (1.17.2-x86_64-linux-gnu)
ffi (1.17.2-x86_64-linux-musl)
io-console (0.8.1)
irb (1.15.3)
pp (>= 0.6.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
js (2.7.2)
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
logger (1.7.0)
nio4r (2.7.5)
pp (0.6.3)
prettyprint
prettyprint (0.2.0)
psych (5.2.6)
date
stringio
puma (6.6.1)
nio4r (~> 2.0)
rack (3.2.4)
rake (13.3.1)
rb-fsevent (0.11.2)
rb-inotify (0.11.1)
ffi (~> 1.0)
rdoc (6.15.1)
erb
psych (>= 4.0.0)
Expand Down
3 changes: 3 additions & 0 deletions examples/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.wasm
/rubies
/build
30 changes: 14 additions & 16 deletions examples/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,34 @@ PATH
specs:
ruby_wasm_ui (0.8.3)
js (~> 2.7)
listen (~> 3.8)
puma (~> 6.0)
rack (~> 3.0)
ruby_wasm (~> 2.7)

GEM
remote: https://rubygems.org/
specs:
ffi (1.17.2-arm64-darwin)
js (2.7.2)
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
logger (1.7.0)
nio4r (2.7.5)
puma (6.6.1)
nio4r (~> 2.0)
rack (3.2.4)
rb-fsevent (0.11.2)
rb-inotify (0.11.1)
ffi (~> 1.0)
ruby_wasm (2.7.2)
logger
ruby_wasm (2.7.2-aarch64-linux)
logger
ruby_wasm (2.7.2-aarch64-linux-musl)
logger
ruby_wasm (2.7.2-arm64-darwin)
logger
ruby_wasm (2.7.2-x86_64-darwin)
logger
ruby_wasm (2.7.2-x86_64-linux)
logger
ruby_wasm (2.7.2-x86_64-linux-musl)
logger

PLATFORMS
aarch64-linux
aarch64-linux-musl
arm64-darwin
ruby
x86_64-darwin
x86_64-linux
x86_64-linux-musl

DEPENDENCIES
ruby_wasm_ui!
Expand Down
21 changes: 21 additions & 0 deletions examples/src/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>My App</title>
<script type="module">
import { DefaultRubyVM } from "https://cdn.jsdelivr.net/npm/@ruby/wasm-wasi@2.7.2/dist/browser/+esm";
const response = await fetch("../src.wasm");
const module = await WebAssembly.compileStreaming(response);
const { vm } = await DefaultRubyVM(module);
vm.evalAsync(`
require "ruby_wasm_ui"
require_relative './src/index.rb'
`);
</script>
</head>
<body>
<h1>My App</h1>
<div id="app"></div>
</body>
</html>
26 changes: 26 additions & 0 deletions examples/src/index.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Simple Hello World component
HelloComponent = RubyWasmUi.define_component(
state: ->(props) {
{ message: props[:message] || "Hello, Ruby WASM UI!" }
},
template: ->() {
RubyWasmUi::Template::Parser.parse_and_eval(<<~HTML, binding)
<div>
<h2>{state[:message]}</h2>
<button on="{ click: -> { update_message } }">
Click me!
</button>
</div>
HTML
},
methods: {
update_message: ->() {
update_state(message: "You clicked the button!")
}
}
)

# Create and mount the app
app = RubyWasmUi::App.create(HelloComponent, message: "Hello, Ruby WASM UI!")
app_element = JS.global[:document].getElementById("app")
app.mount(app_element)
6 changes: 6 additions & 0 deletions exe/ruby-wasm-ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require_relative "../lib/ruby_wasm_ui/cli/command"

RubyWasmUi::Cli::Command.run(ARGV)
48 changes: 48 additions & 0 deletions lib/ruby_wasm_ui/cli/command.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# frozen_string_literal: true

require_relative "command/setup"
require_relative "command/dev"
require_relative "command/pack"
require_relative "command/rebuild"

module RubyWasmUi
module Cli
class Command
COMMANDS = {
"setup" => Command::Setup,
"dev" => Command::Dev,
"pack" => Command::Pack,
"rebuild" => Command::Rebuild
}.freeze

def self.run(argv)
command_name = argv[0]

if command_name.nil?
show_usage
raise SystemExit.new(1)
end

command_class = COMMANDS[command_name]
if command_class.nil?
puts "Unknown command: #{command_name}"
puts ""
show_usage
raise SystemExit.new(1)
end

command_class.new.run(argv[1..-1])
end

def self.show_usage
puts "Usage: ruby-wasm-ui <command>"
puts ""
puts "Commands:"
COMMANDS.each do |name, klass|
description = klass.description || ""
puts " #{name.ljust(12)}#{description}"
end
end
end
end
end
174 changes: 174 additions & 0 deletions lib/ruby_wasm_ui/cli/command/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# frozen_string_literal: true

require "open3"
require "bundler/setup"
require "ruby_wasm"
require "ruby_wasm/cli"

module RubyWasmUi
module Cli
class Command
class Base
def self.description
nil
end

def run(_argv)
raise NotImplementedError, "Subclasses must implement #run"
end

protected

def run_command(command, exit_on_error: true)
log_debug("Executing command: #{command}")

Open3.popen3(command) do |stdin, stdout, stderr, wait_thr|
stdin.close

# Read stdout and stderr in separate threads for real-time output
stdout_thread = Thread.new do
stdout.each_line do |line|
print line
$stdout.flush
end
end

stderr_thread = Thread.new do
stderr.each_line do |line|
$stderr.print line
$stderr.flush
end
end

# Wait for both threads to finish reading
stdout_thread.join
stderr_thread.join

# Wait for the process to finish
exit_status = wait_thr.value

unless exit_status.success?
log_error("Command failed: #{command}")
if exit_on_error
raise SystemExit.new(exit_status.exitstatus)
else
return false
end
end

true
end
end

def ensure_src_directory
unless Dir.exist?("src")
log_error("src directory not found. Please run 'ruby-wasm-ui setup' first.")
raise SystemExit.new(1)
end
end

def ensure_ruby_wasm
unless File.exist?("ruby.wasm")
log_error("ruby.wasm not found. Please run 'ruby-wasm-ui setup' first.")
raise SystemExit.new(1)
end
end

def update_gitignore(entries_to_add)
gitignore_path = ".gitignore"

# Read existing .gitignore or create new content
if File.exist?(gitignore_path)
content = File.read(gitignore_path)
lines = content.lines.map(&:chomp)
else
lines = []
end

# Add entries that don't already exist
added_entries = []
entries_to_add.each do |entry|
unless lines.include?(entry)
lines << entry
added_entries << entry
end
end

# Write back to .gitignore
File.write(gitignore_path, lines.join("\n") + "\n")
if added_entries.any?
log_info("Added to .gitignore: #{added_entries.join(', ')}")
else
log_info("No new entries added to .gitignore (all entries already exist)")
end
end

protected

def log_info(message)
puts "[INFO] #{message}"
end

def log_success(message)
puts "[SUCCESS] #{message}"
end

def log_error(message)
puts "[ERROR] #{message}"
end

def log_debug(message)
puts "[DEBUG] #{message}"
end

def check_ruby_version
ruby_version = RUBY_VERSION.split('.').map(&:to_i)
major, minor = ruby_version[0], ruby_version[1]

if major < 3 || (major == 3 && minor < 2)
log_error("Ruby WASM requires Ruby 3.2 or higher. Current version: #{RUBY_VERSION}")
raise SystemExit.new(1)
end

"#{major}.#{minor}"
end

def configure_excluded_gems
# Get all gems from Bundler definition including all dependencies
definition = Bundler.definition
resolved = definition.resolve

# Get all resolved specs (including transitive dependencies)
all_specs = resolved.materialize(definition.requested_dependencies)
gem_names = all_specs.map(&:name).uniq

# Always exclude gems that cause WASM build errors
# These gems have native extensions that don't work in WASM environment
always_excluded = %w[nio4r puma rack listen ffi]

# Exclude gems that cause build errors or are unnecessary for WASM
# Keep essential gems like 'js' for WASM
excluded_gems = gem_names.select do |gem_name|
# Exclude gems that cause WASM build errors
always_excluded.include?(gem_name) ||
# Exclude development/test gems
gem_name.start_with?("rspec", "rubocop", "rake")
end

# Always add always_excluded gems to ensure they're excluded even if not in dependencies
excluded_gems.concat(always_excluded)
excluded_gems.uniq!

# Add to EXCLUDED_GEMS
RubyWasm::Packager::EXCLUDED_GEMS.concat(excluded_gems)
end

def build_ruby_wasm(ruby_version_str)
command = ["build", "--ruby-version", ruby_version_str, "-o", "ruby.wasm"]
cli = RubyWasm::CLI.new(stdout: $stdout, stderr: $stderr)
cli.run(command)
end
end
end
end
end
Loading
Loading