ruby_wasm_ui 0.8.2 → 0.9.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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec.yml +0 -2
  3. data/Makefile +53 -0
  4. data/README.md +22 -4
  5. data/examples/.gitignore +3 -0
  6. data/examples/Gemfile.lock +15 -17
  7. data/examples/src/index.html +21 -0
  8. data/examples/src/index.rb +26 -0
  9. data/exe/ruby-wasm-ui +6 -0
  10. data/lib/ruby_wasm_ui/cli/command/base.rb +174 -0
  11. data/lib/ruby_wasm_ui/cli/command/dev.rb +206 -0
  12. data/lib/ruby_wasm_ui/cli/command/pack.rb +36 -0
  13. data/lib/ruby_wasm_ui/cli/command/rebuild.rb +38 -0
  14. data/lib/ruby_wasm_ui/cli/command/setup.rb +130 -0
  15. data/lib/ruby_wasm_ui/cli/command.rb +48 -0
  16. data/lib/ruby_wasm_ui/version.rb +1 -1
  17. data/lib/ruby_wasm_ui.rb +8 -8
  18. data/package-lock.json +2 -2
  19. data/package.json +1 -1
  20. data/packages/npm-packages/runtime/package-lock.json +2 -2
  21. data/packages/npm-packages/runtime/package.json +3 -3
  22. data/packages/npm-packages/runtime/rollup.config.mjs +68 -10
  23. data/spec/ruby_wasm_ui/cli/command/base_spec.rb +358 -0
  24. data/spec/ruby_wasm_ui/cli/command/dev_spec.rb +412 -0
  25. data/spec/ruby_wasm_ui/cli/command/pack_spec.rb +127 -0
  26. data/spec/ruby_wasm_ui/cli/command/rebuild_spec.rb +95 -0
  27. data/spec/ruby_wasm_ui/cli/command/setup_spec.rb +186 -0
  28. data/spec/ruby_wasm_ui/cli/command_spec.rb +118 -0
  29. data/{packages/npm-packages/runtime/spec → spec}/spec_helper.rb +1 -1
  30. metadata +96 -38
  31. data/packages/npm-packages/runtime/Gemfile +0 -3
  32. data/packages/npm-packages/runtime/Gemfile.lock +0 -26
  33. /data/lib/ruby_wasm_ui/{app.rb → runtime/app.rb} +0 -0
  34. /data/lib/ruby_wasm_ui/{component.rb → runtime/component.rb} +0 -0
  35. /data/lib/ruby_wasm_ui/{dispatcher.rb → runtime/dispatcher.rb} +0 -0
  36. /data/lib/ruby_wasm_ui/{dom → runtime/dom}/attributes.rb +0 -0
  37. /data/lib/ruby_wasm_ui/{dom → runtime/dom}/destroy_dom.rb +0 -0
  38. /data/lib/ruby_wasm_ui/{dom → runtime/dom}/events.rb +0 -0
  39. /data/lib/ruby_wasm_ui/{dom → runtime/dom}/mount_dom.rb +0 -0
  40. /data/lib/ruby_wasm_ui/{dom → runtime/dom}/patch_dom.rb +0 -0
  41. /data/lib/ruby_wasm_ui/{dom → runtime/dom}/scheduler.rb +0 -0
  42. /data/lib/ruby_wasm_ui/{dom.rb → runtime/dom.rb} +0 -0
  43. /data/lib/ruby_wasm_ui/{nodes_equal.rb → runtime/nodes_equal.rb} +0 -0
  44. /data/lib/ruby_wasm_ui/{template → runtime/template}/build_conditional_group.rb +0 -0
  45. /data/lib/ruby_wasm_ui/{template → runtime/template}/build_for_group.rb +0 -0
  46. /data/lib/ruby_wasm_ui/{template → runtime/template}/build_vdom.rb +0 -0
  47. /data/lib/ruby_wasm_ui/{template → runtime/template}/parser.rb +0 -0
  48. /data/lib/ruby_wasm_ui/{template.rb → runtime/template.rb} +0 -0
  49. /data/lib/ruby_wasm_ui/{utils → runtime/utils}/arrays.rb +0 -0
  50. /data/lib/ruby_wasm_ui/{utils → runtime/utils}/objects.rb +0 -0
  51. /data/lib/ruby_wasm_ui/{utils → runtime/utils}/props.rb +0 -0
  52. /data/lib/ruby_wasm_ui/{utils → runtime/utils}/strings.rb +0 -0
  53. /data/lib/ruby_wasm_ui/{utils.rb → runtime/utils.rb} +0 -0
  54. /data/lib/ruby_wasm_ui/{vdom.rb → runtime/vdom.rb} +0 -0
  55. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/component_spec.rb +0 -0
  56. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/dom/scheduler_spec.rb +0 -0
  57. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/nodes_equal_spec.rb +0 -0
  58. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/template/build_conditional_group_spec.rb +0 -0
  59. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/template/build_for_group_spec.rb +0 -0
  60. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/template/build_vdom_spec.rb +0 -0
  61. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/template/parser_spec.rb +0 -0
  62. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/utils/arrays_spec.rb +0 -0
  63. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/utils/objects_spec.rb +0 -0
  64. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/utils/props_spec.rb +0 -0
  65. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/utils/strings_spec.rb +0 -0
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module RubyWasmUi
6
+ module Cli
7
+ class Command
8
+ class Rebuild < Base
9
+ def self.description
10
+ "Rebuild Ruby WASM file (useful when gems are added)"
11
+ end
12
+
13
+ def run(_argv)
14
+ log_info("Rebuilding Ruby WASM...")
15
+ puts ""
16
+
17
+ # Check Ruby version
18
+ ruby_version_str = check_ruby_version
19
+
20
+ # Configure excluded gems for WASM build
21
+ log_info("Step 1/2: Configuring excluded gems...")
22
+ configure_excluded_gems
23
+ log_success("✓ Excluded gems configured")
24
+
25
+ # Build Ruby WASM
26
+ puts ""
27
+ log_info("Step 2/2: Building Ruby WASM...")
28
+ log_info("Running: rbwasm build --ruby-version #{ruby_version_str} -o ruby.wasm")
29
+ build_ruby_wasm(ruby_version_str)
30
+ log_success("✓ Ruby WASM build completed")
31
+
32
+ puts ""
33
+ log_success("Rebuild completed successfully!")
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module RubyWasmUi
6
+ module Cli
7
+ class Command
8
+ class Setup < Base
9
+ def self.description
10
+ "Set up the project for ruby-wasm-ui"
11
+ end
12
+
13
+ def run(_argv)
14
+ log_info("Setting up ruby-wasm-ui project...")
15
+ puts ""
16
+
17
+ # Check Ruby version
18
+ ruby_version_str = check_ruby_version
19
+
20
+ # Configure excluded gems for WASM build
21
+ log_info("Step 1/3: Configuring excluded gems...")
22
+ configure_excluded_gems
23
+ log_success("✓ Excluded gems configured")
24
+
25
+ # Build Ruby WASM
26
+ puts ""
27
+ log_info("Step 2/3: Building Ruby WASM...")
28
+ log_info("Running: rbwasm build --ruby-version #{ruby_version_str} -o ruby.wasm")
29
+ build_ruby_wasm(ruby_version_str)
30
+ log_success("✓ Ruby WASM build completed")
31
+
32
+ # Update .gitignore
33
+ puts ""
34
+ log_info("Step 3/4: Updating .gitignore...")
35
+ update_gitignore(["*.wasm", "/rubies", "/build"])
36
+ log_success("✓ .gitignore updated")
37
+
38
+ # Create initial files
39
+ puts ""
40
+ log_info("Step 4/4: Creating initial files...")
41
+ create_initial_files
42
+
43
+ puts ""
44
+ log_success("Setup completed successfully!")
45
+ end
46
+
47
+ private
48
+
49
+ def create_initial_files
50
+ # Skip if src directory exists
51
+ if Dir.exist?("src")
52
+ log_info("src directory already exists, skipping initial file creation")
53
+ return
54
+ end
55
+
56
+ # Skip if files already exist
57
+ if File.exist?("src/index.html")
58
+ log_info("src/index.html already exists, skipping initial file creation")
59
+ return
60
+ end
61
+
62
+ if File.exist?("src/index.rb")
63
+ log_info("src/index.rb already exists, skipping initial file creation")
64
+ return
65
+ end
66
+
67
+ # Create src directory
68
+ Dir.mkdir("src")
69
+
70
+ # Create index.html
71
+ File.write("src/index.html", <<~HTML)
72
+ <!DOCTYPE html>
73
+ <html lang="en">
74
+ <head>
75
+ <meta charset="UTF-8" />
76
+ <title>My App</title>
77
+ <script type="module">
78
+ import { DefaultRubyVM } from "https://cdn.jsdelivr.net/npm/@ruby/wasm-wasi@2.7.2/dist/browser/+esm";
79
+ const response = await fetch("../src.wasm");
80
+ const module = await WebAssembly.compileStreaming(response);
81
+ const { vm } = await DefaultRubyVM(module);
82
+ vm.evalAsync(`
83
+ require "ruby_wasm_ui"
84
+ require_relative './src/index.rb'
85
+ `);
86
+ </script>
87
+ </head>
88
+ <body>
89
+ <h1>My App</h1>
90
+ <div id="app"></div>
91
+ </body>
92
+ </html>
93
+ HTML
94
+
95
+ # Create index.rb
96
+ File.write("src/index.rb", <<~RUBY)
97
+ # Simple Hello World component
98
+ HelloComponent = RubyWasmUi.define_component(
99
+ state: ->(props) {
100
+ { message: props[:message] || "Hello, Ruby WASM UI!" }
101
+ },
102
+ template: ->() {
103
+ RubyWasmUi::Template::Parser.parse_and_eval(<<~HTML, binding)
104
+ <div>
105
+ <h2>{state[:message]}</h2>
106
+ <button on="{ click: -> { update_message } }">
107
+ Click me!
108
+ </button>
109
+ </div>
110
+ HTML
111
+ },
112
+ methods: {
113
+ update_message: ->() {
114
+ update_state(message: "You clicked the button!")
115
+ }
116
+ }
117
+ )
118
+
119
+ # Create and mount the app
120
+ app = RubyWasmUi::App.create(HelloComponent, message: "Hello, Ruby WASM UI!")
121
+ app_element = JS.global[:document].getElementById("app")
122
+ app.mount(app_element)
123
+ RUBY
124
+
125
+ log_success("✓ Initial files created: src/index.html, src/index.rb")
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "command/setup"
4
+ require_relative "command/dev"
5
+ require_relative "command/pack"
6
+ require_relative "command/rebuild"
7
+
8
+ module RubyWasmUi
9
+ module Cli
10
+ class Command
11
+ COMMANDS = {
12
+ "setup" => Command::Setup,
13
+ "dev" => Command::Dev,
14
+ "pack" => Command::Pack,
15
+ "rebuild" => Command::Rebuild
16
+ }.freeze
17
+
18
+ def self.run(argv)
19
+ command_name = argv[0]
20
+
21
+ if command_name.nil?
22
+ show_usage
23
+ raise SystemExit.new(1)
24
+ end
25
+
26
+ command_class = COMMANDS[command_name]
27
+ if command_class.nil?
28
+ puts "Unknown command: #{command_name}"
29
+ puts ""
30
+ show_usage
31
+ raise SystemExit.new(1)
32
+ end
33
+
34
+ command_class.new.run(argv[1..-1])
35
+ end
36
+
37
+ def self.show_usage
38
+ puts "Usage: ruby-wasm-ui <command>"
39
+ puts ""
40
+ puts "Commands:"
41
+ COMMANDS.each do |name, klass|
42
+ description = klass.description || ""
43
+ puts " #{name.ljust(12)}#{description}"
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyWasmUi
4
- VERSION = "0.8.2"
4
+ VERSION = "0.9.0"
5
5
  end
data/lib/ruby_wasm_ui.rb CHANGED
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "ruby_wasm_ui/app"
4
- require_relative "ruby_wasm_ui/component"
5
- require_relative "ruby_wasm_ui/dispatcher"
6
- require_relative "ruby_wasm_ui/dom"
7
- require_relative "ruby_wasm_ui/nodes_equal"
8
- require_relative "ruby_wasm_ui/template"
9
- require_relative "ruby_wasm_ui/utils"
10
- require_relative "ruby_wasm_ui/vdom"
3
+ require_relative "ruby_wasm_ui/runtime/app"
4
+ require_relative "ruby_wasm_ui/runtime/component"
5
+ require_relative "ruby_wasm_ui/runtime/dispatcher"
6
+ require_relative "ruby_wasm_ui/runtime/dom"
7
+ require_relative "ruby_wasm_ui/runtime/nodes_equal"
8
+ require_relative "ruby_wasm_ui/runtime/template"
9
+ require_relative "ruby_wasm_ui/runtime/utils"
10
+ require_relative "ruby_wasm_ui/runtime/vdom"
11
11
  require_relative "ruby_wasm_ui/version"
12
12
 
13
13
  module RubyWasmUi
data/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "ruby-wasm-ui-project",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "ruby-wasm-ui-project",
9
- "version": "0.8.2",
9
+ "version": "0.9.0",
10
10
  "license": "MIT",
11
11
  "workspaces": [
12
12
  "packages/*"
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruby-wasm-ui-project",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "private": true,
5
5
  "description": "A project to ruby.wasm ui framework",
6
6
  "scripts": {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "ruby-wasm-ui",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "ruby-wasm-ui",
9
- "version": "0.8.2",
9
+ "version": "0.9.0",
10
10
  "license": "MIT",
11
11
  "devDependencies": {
12
12
  "@rollup/plugin-replace": "^6.0.2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruby-wasm-ui",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "description": "",
5
5
  "main": "dist/ruby-wasm-ui.js",
6
6
  "files": [
@@ -10,8 +10,8 @@
10
10
  ],
11
11
  "scripts": {
12
12
  "prepack": "npm run build",
13
- "build": "rm -rf dist && NODE_ENV=production rollup -c",
14
- "build:dev": "rm -rf dist && NODE_ENV=development rollup -c",
13
+ "build": "NODE_ENV=production rollup -c",
14
+ "build:dev": "NODE_ENV=development rollup -c",
15
15
  "lint": "eslint src",
16
16
  "lint:fix": "eslint src --fix",
17
17
  "test": "vitest",
@@ -4,7 +4,13 @@ import filesize from "rollup-plugin-filesize";
4
4
  import replace from "@rollup/plugin-replace";
5
5
  import { glob } from "glob";
6
6
  import process from "process";
7
- import { readFileSync, writeFileSync } from "fs";
7
+ import {
8
+ readFileSync,
9
+ writeFileSync,
10
+ lstatSync,
11
+ unlinkSync,
12
+ realpathSync,
13
+ } from "fs";
8
14
  import { join, dirname } from "path";
9
15
  import { fileURLToPath } from "url";
10
16
 
@@ -12,7 +18,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
12
18
 
13
19
  // Find all Ruby files in ruby_wasm_ui directory
14
20
  const rubyFiles = glob
15
- .sync("src/ruby_wasm_ui/**/*.rb")
21
+ .sync("src/ruby_wasm_ui/runtime/**/*.rb")
16
22
  .map((file) => file.replace("src/", ""))
17
23
  .sort((a, b) => {
18
24
  // Files in root directory should be loaded first
@@ -28,22 +34,68 @@ const rubyFiles = glob
28
34
  // Determine environment based on NODE_ENV
29
35
  const isDevelopment = process.env.NODE_ENV === "development";
30
36
 
37
+ // Plugin to clean up existing symlinks in dist directory before copy
38
+ const cleanupSymlinks = () => {
39
+ return {
40
+ name: "cleanup-symlinks",
41
+ buildStart() {
42
+ const distRubyFilePath = join(__dirname, "dist/ruby_wasm_ui.rb");
43
+ try {
44
+ const stats = lstatSync(distRubyFilePath);
45
+ if (stats.isSymbolicLink() || stats.isFile()) {
46
+ // Remove existing file/symlink before copy plugin runs
47
+ unlinkSync(distRubyFilePath);
48
+ }
49
+ } catch (e) {
50
+ // Ignore errors (file might not exist)
51
+ }
52
+ },
53
+ };
54
+ };
55
+
31
56
  // Plugin to remove require_relative lines from Ruby files
32
57
  const removeRequireRelative = () => {
33
58
  return {
34
59
  name: "remove-require-relative",
35
60
  writeBundle() {
36
- // Process all Ruby files in dist directory recursively
37
- const distRubyFiles = glob.sync("dist/**/*.rb");
61
+ // Process all Ruby files in dist directory (including dist/ruby_wasm_ui.rb)
62
+ // Ensure we only process files in dist/ directory, not lib/ or other source directories
63
+ const distDir = join(__dirname, "dist");
64
+ const distRubyFiles = glob.sync("dist/**/*.rb", {
65
+ cwd: __dirname,
66
+ absolute: false,
67
+ });
38
68
 
39
69
  distRubyFiles.forEach((file) => {
40
70
  const filePath = join(__dirname, file);
41
- let content = readFileSync(filePath, "utf-8");
42
- // Remove lines that start with require_relative (with optional whitespace)
43
- content = content.replace(/^\s*require_relative\s+.*$/gm, "");
44
- // Remove multiple consecutive empty lines
45
- content = content.replace(/\n{3,}/g, "\n\n");
46
- writeFileSync(filePath, content, "utf-8");
71
+ try {
72
+ const stats = lstatSync(filePath);
73
+ let content;
74
+ // If it's a symlink to lib/, read from lib but write to dist
75
+ if (stats.isSymbolicLink()) {
76
+ const resolvedPath = realpathSync(filePath);
77
+ const distDirResolved = realpathSync(distDir);
78
+ if (!resolvedPath.startsWith(distDirResolved)) {
79
+ // Read from lib, but write to dist (replacing the symlink)
80
+ content = readFileSync(resolvedPath, "utf-8");
81
+ unlinkSync(filePath);
82
+ } else {
83
+ // Symlink resolves within dist, read resolved path
84
+ content = readFileSync(resolvedPath, "utf-8");
85
+ }
86
+ } else {
87
+ // Regular file, read directly
88
+ content = readFileSync(filePath, "utf-8");
89
+ }
90
+ // Remove lines that start with require_relative (with optional whitespace)
91
+ content = content.replace(/^\s*require_relative\s+.*$/gm, "");
92
+ // Remove multiple consecutive empty lines
93
+ content = content.replace(/\n{3,}/g, "\n\n");
94
+ writeFileSync(filePath, content, "utf-8");
95
+ } catch (e) {
96
+ // If read/write fails, skip this file
97
+ console.warn(`Could not process file ${filePath}: ${e.message}`);
98
+ }
47
99
  });
48
100
  },
49
101
  };
@@ -66,6 +118,7 @@ export default {
66
118
  "window.RUBY_WASM_UI_FILES": JSON.stringify(rubyFiles),
67
119
  },
68
120
  }),
121
+ cleanupSymlinks(),
69
122
  copy({
70
123
  targets: [
71
124
  {
@@ -78,6 +131,11 @@ export default {
78
131
  dest: "dist",
79
132
  },
80
133
  ],
134
+ // Resolve symbolic links when copying to ensure dist files are regular files
135
+ // This allows us to process dist/ruby_wasm_ui.rb without affecting lib/ruby_wasm_ui.rb
136
+ copySyncOptions: {
137
+ dereference: true,
138
+ },
81
139
  }),
82
140
  cleanup({
83
141
  comments: "none",