jsg 0.1.2 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e678932e88c5f4332664d8f94c855c6cf5c37eccb4c0ff55779061d20ee7a8f
4
- data.tar.gz: eded22db06e19449e076d82332e9aa00db620fff95fd6c69d1b3a6710815eb2c
3
+ metadata.gz: 656528ab1a482268d018a12df7b3cd5fda4d74f08368926f7fb665a4f5f3621f
4
+ data.tar.gz: a63fec4f4c89edc7e116aa878781aa9235bcdf17967f12508ccea62ab5bcef19
5
5
  SHA512:
6
- metadata.gz: e7f34af3bdec9dd82ed50497bbe141bfd95c2bfc8dd0417fa9312a2893ce1547ddeb438d806070f151693aef6695bfd8fd79571fc010edcdf66dd72b84a8b742
7
- data.tar.gz: 1cb61dbdeb8b53b6697e3521b86ef0db32e3050d66ac69fc658a7a22601cc3a6c62fe08c402e205a7378546f9f8761cfdd6914063b93881f3c45204badab815c
6
+ metadata.gz: e231cea6d1794b20a9bffc034ac38020968288177af7c49f973b7901deb4a02cf4c86fa7ec8655e5ac7c078293c9b986d9cb8fdfd712d199ed349b80ca74ef80
7
+ data.tar.gz: cfc908766616ad9b18026b20369caf5d01e79e7d2bde8463d8af9f619a3c1b86c5d686476b007759c15fa9dfc4dfa74e0ee375dac929bff53e9fd123e0c472ed
data/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.1] - 2026-09-29
4
+
5
+ - First release published from GitHub Actions with RubyGems trusted publishing (`.github/workflows/release.yml`, triggered by `v*` tags).
6
+ - CI runs the ruby.wasm tests on Node.js 24. Node 22's WASI crashes once Ruby loads files from a mounted directory.
7
+
8
+ ## [0.2.0] - 2026-09-29
9
+
10
+ - Fix `require "jsg"`: the library moved from the gem root to `lib/jsg.rb`, so it is on the load path.
11
+ - Fix the syntax additions never running: `JSGPatch` is now prepended to `JS::Object` instead of included. The js gem defines `method_missing`, `respond_to_missing?`, `to_a` and `nil?` on `JS::Object` itself, which shadowed the included module. With js 2.10 (where `JS::Object` is a `BasicObject`) 0.1.x also failed to load with `uninitialized constant JS::Object::JSGPatch`.
12
+ - Rewrite the helpers so they no longer call themselves recursively once active (`to_a`, `isJSArray`, `__props`).
13
+ - `?` methods use JavaScript truthiness. `undefined?` is only true for `undefined`. `each` also iterates array-like objects such as a `NodeList`.
14
+ - A capitalized name without arguments returns the constructor (`JS.global.URLSearchParams.new(...)`). Other JavaScript errors are no longer swallowed.
15
+ - Add `tap` to `JS::Object` again (lost when js 2.10 made it a `BasicObject`), so `createElement("h2").tap { ... }` works.
16
+ - Remove the `==` override. The js gem already compares with `true`, `false` and `nil`.
17
+ - The template's index.html loads `src/main.rb` with `JS::RequireRemote` instead of overriding `require_relative` globally. `rbwasm build` never packed `src/` into the wasm file.
18
+ - Update the template to ruby.wasm 2.10.1 (browser script, `js` and `ruby_wasm` gems). `jsg build` builds Ruby 4.0 with `bundle exec rbwasm`.
19
+ - `jsg new` reports errors instead of silently ignoring them, and gets a `--skip-build` option. `jsg server` accepts a port and explains how to install WEBrick when it is missing. New `jsg version`.
20
+ - The gem depends on `js` and `ruby_wasm` `~> 2.10` on every platform, and no longer on `erb` (a default gem).
21
+ - Add a README and tests (rspec for the command line tool, ruby.wasm on Node.js for `lib/jsg.rb`).
22
+
23
+ ## [0.1.2] - 2024-05-18
24
+
25
+ - Don't require
26
+
3
27
  ## [0.1.0] - 2024-05-09
4
28
 
5
29
  - Initial release
data/README.md CHANGED
@@ -1,34 +1,119 @@
1
- # Jsg
1
+ # jsg
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
3
+ jsg helps you set up [ruby.wasm](https://github.com/ruby/ruby.wasm) browser projects, and it adds a friendlier syntax on top of the [js gem](https://rubygems.org/gems/js).
4
4
 
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/jsg`. To experiment with that code, run `bin/console` for an interactive prompt.
5
+ It has two parts:
6
+
7
+ - the `jsg` command, which creates, builds and serves a project
8
+ - `require "jsg"` inside the browser, which lets you write `document.title` instead of `JS.global[:document][:title]`
9
+
10
+ jsg 0.2 targets ruby.wasm 2.10.1 and builds Ruby 4.0 for the browser.
6
11
 
7
12
  ## Installation
8
13
 
9
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
14
+ ```bash
15
+ gem install jsg
16
+ ```
17
+
18
+ This also installs `ruby_wasm`, which provides the `rbwasm` build tool. `jsg server` needs WEBrick, which is not a default gem anymore:
19
+
20
+ ```bash
21
+ gem install webrick
22
+ ```
23
+
24
+ ## Creating a project
25
+
26
+ ```bash
27
+ jsg new myapp # copy the template into ./myapp, then run jsg build
28
+ cd myapp
29
+ jsg server # serve the folder on http://localhost:8080 (or the next free port)
30
+ ```
31
+
32
+ `jsg new myapp --skip-build` only copies the template. The project contains:
33
+
34
+ - `index.html`, with a `<script type="text/ruby">` block that requires jsg and loads `src/main.rb`
35
+ - `src/main.rb`, the place for your code
36
+ - `assets/browser.script.iife.js`, ruby.wasm's browser script, set up to load `assets/ruby-app.wasm`
37
+ - `Gemfile` with `ruby_wasm`, `js` and `jsg`
38
+ - a `.gitignore` for the `build/` and `rubies/` folders
10
39
 
11
- Install the gem and add to the application's Gemfile by executing:
40
+ `jsg build` runs `bundle install` and then `bundle exec rbwasm build --ruby-version 4.0 -o assets/ruby-app.wasm`. The wasm file contains Ruby and the gems from the Gemfile, not your own files: `index.html` fetches `src/main.rb` from the web server with `JS::RequireRemote`, so a page reload picks up your changes. Run `jsg build` again only when you change the Gemfile. The first build compiles CRuby and the gems for WebAssembly and takes a while. Later builds reuse the `build/` and `rubies/` folders.
12
41
 
13
- $ bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
42
+ ## Syntax
14
43
 
15
- If bundler is not being used to manage dependencies, install the gem by executing:
44
+ ```ruby
45
+ require "jsg"
16
46
 
17
- $ gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
47
+ d = JSG.document
48
+ d.getElementById("spinner").style.display = "none"
18
49
 
19
- ## Usage
50
+ d.createElement("h2").tap do |h2|
51
+ h2.innerText = "Examples"
52
+ d.body.appendChild(h2)
53
+ end
54
+ ```
20
55
 
21
- TODO: Write usage instructions here
56
+ What `require "jsg"` adds to `JS::Object`:
57
+
58
+ | You write | Result |
59
+ | --- | --- |
60
+ | `element.innerText` | property value, converted with `to_rb` |
61
+ | `element.innerText = "Hi"` | sets the property (the value is converted with `to_js`) |
62
+ | `document.getElementById("x")` | calls the method, result converted with `to_rb` |
63
+ | `element.hidden?`, `list.includes?(3)` | `true`/`false`, using JavaScript truthiness |
64
+ | `JS.global.URLSearchParams.new("a=1")` | a capitalized name without arguments returns the constructor |
65
+ | `obj.missing` | a property that is `undefined` raises `NoMethodError` |
66
+
67
+ `to_rb` converts JavaScript values to Ruby:
68
+
69
+ | JavaScript | Ruby |
70
+ | --- | --- |
71
+ | number | `Float` (`3` becomes `3.0`) |
72
+ | string | `String` |
73
+ | boolean | `true` / `false` |
74
+ | bigint | `Integer` |
75
+ | symbol | `Symbol` |
76
+ | `null` | `nil` |
77
+ | Array | `Array`, elements converted too |
78
+ | anything else, including `undefined` | stays a `JS::Object` |
79
+
80
+ More helpers:
81
+
82
+ ```ruby
83
+ JSG.window # JS.global, also JSG.w
84
+ JSG.document # JS.global[:document], also JSG.d
85
+ JSG.q("p") # document.querySelectorAll("p")
86
+ JSG.global # anything else is forwarded to the JS module
87
+
88
+ list = JSG.q("li")
89
+ list.to_a # array-like objects (NodeList, ...) to a Ruby Array
90
+ list.each { |li| li.classList.add("done") }
91
+ obj.to_a(convertTypes: false) # keep the elements as JS::Object
92
+
93
+ value.nil? # true for null and undefined
94
+ value.undefined? # true only for undefined
95
+ value.typeof?(:string)
96
+ el.tap { |e| ... } # like Kernel#tap (JS::Object is a BasicObject since js 2.10)
97
+ value == true # comparing with true, false and nil works (JavaScript's == rules)
98
+ ```
22
99
 
23
100
  ## Development
24
101
 
25
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
102
+ ```bash
103
+ bin/setup
104
+ bundle exec rake # rspec, the ruby.wasm tests and rubocop
105
+ ```
106
+
107
+ To release a new version, update `lib/jsg/version.rb` and `CHANGELOG.md`, commit, and push a matching tag:
26
108
 
27
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
109
+ ```bash
110
+ git tag v0.2.1
111
+ git push origin v0.2.1
112
+ ```
28
113
 
29
- ## Contributing
114
+ The `Release` workflow runs the tests and publishes the gem to RubyGems.org with trusted publishing, so no API key is stored in GitHub.
30
115
 
31
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/jsg.
116
+ The specs in `spec/` test the command line tool on your normal Ruby. `lib/jsg.rb` needs the js gem, which only works inside ruby.wasm, so its tests in `test/wasm` run the prebuilt ruby.wasm on Node.js (`bundle exec rake test:wasm`, needs Node.js 24 or newer and npm).
32
117
 
33
118
  ## License
34
119
 
data/exe/jsg CHANGED
@@ -1,98 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- #require "jsg"
4
+ require_relative "../lib/jsg/cli"
4
5
 
5
- require "erb"
6
- require "fileutils"
7
-
8
- module JSG
9
- # create a new project at project_name
10
- def self.create_project(project_name)
11
- base_path = File.expand_path("../template", __dir__)
12
- destination = File.join(Dir.pwd, project_name)
13
-
14
- if Dir.exist?(destination)
15
- raise "Sorry #{destination} already exists, please choose other name or delete the folder"
16
- end
17
- FileUtils.mkdir_p(destination)
18
-
19
- Dir.glob("#{base_path}/**/*").each do |file_path|
20
- next if File.directory?(file_path)
21
-
22
-
23
- content = File.read(file_path)
24
- result = if file_path.end_with?(".erb")
25
- template = ERB.new(content)
26
- template.result(binding)
27
- else
28
- content
29
- end
30
-
31
- relative_path = file_path.sub("#{base_path}/", "").gsub(/\.erb$/, "")
32
- new_file_path = File.join(destination, relative_path)
33
-
34
- p new_file_path
35
-
36
- new_file_path.gsub!("gitignore", ".gitignore")
37
- FileUtils.mkdir_p(File.dirname(new_file_path))
38
- File.write(new_file_path, result)
39
- end
40
- end
41
-
42
- def self.build
43
- system("bundle install")
44
- system("rbwasm build -o assets/ruby-app.wasm")
45
- end
46
-
47
- def self.server
48
- require 'webrick'
49
-
50
- port = 8080
51
-
52
- loop do
53
- begin
54
- server = WEBrick::HTTPServer.new(BindAddres: '0.0.0.0', :Port => port, :DocumentRoot => Dir.pwd)
55
- trap('INT') { server.shutdown }
56
- puts "Serving at port http://0.0.0.0.:#{port}"
57
- server.start
58
- break
59
- rescue Errno::EADDRINUSE
60
- port += 1
61
- end
62
- end
63
-
64
- end
65
- end
66
-
67
- command = ARGV.shift # First argument is the command
68
-
69
- case command
70
- when "new"
71
- #options = parse_options
72
- project_name = ARGV.shift # Next argument should be the project name
73
-
74
- if project_name
75
- begin
76
- JSG.create_project(project_name)
77
- # check if we have an internet connection or think about caching this somewhere.
78
- Dir.chdir(project_name) do
79
- JSG.build
80
- end
81
- rescue StandardError => e
82
- end
83
- else
84
- puts "Error: Project name is required."
85
- #puts "For help, run: jsg new --help"
86
- exit 1
87
- end
88
- when "build"
89
- JSG.build
90
- when "serve", "server"
91
- JSG.server
92
- else
93
- puts "Unknown command: #{command}"
94
- puts "Use 'jsg new projectname' for creating new projects."
95
- puts "Use 'jsg build to build the .wasm file of the project."
96
- puts "Use 'jsg server to start a server."
97
- exit 1
98
- end
6
+ exit JSG::CLI.run(ARGV.dup)
data/lib/jsg/cli.rb ADDED
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+ require "fileutils"
5
+ require_relative "version"
6
+
7
+ module JSG
8
+ class Error < StandardError; end
9
+
10
+ # The `jsg` command line tool. Runs on the host, not inside ruby.wasm,
11
+ # so it must not require "js".
12
+ module CLI
13
+ TEMPLATE_PATH = File.expand_path("../../template", __dir__)
14
+ # Ruby version that `jsg build` compiles to WebAssembly. Matches the
15
+ # @ruby/4.0-wasm-wasi browser script in template/assets.
16
+ WASM_RUBY_VERSION = "4.0"
17
+
18
+ USAGE = <<~TEXT
19
+ Usage:
20
+ jsg new PROJECT [--skip-build] create a new project (and build it)
21
+ jsg build bundle install, then build assets/ruby-app.wasm (Ruby 4.0)
22
+ jsg server [PORT] serve the current directory (default port 8080)
23
+ jsg version print the jsg version
24
+ TEXT
25
+
26
+ module_function
27
+
28
+ def run(argv)
29
+ command = argv.shift
30
+ case command
31
+ when "new"
32
+ skip_build = !argv.delete("--skip-build").nil?
33
+ project_name = argv.shift
34
+ return usage_error("Error: Project name is required.") unless project_name
35
+
36
+ destination = create_project(project_name)
37
+ Dir.chdir(destination) { build } unless skip_build
38
+ when "build"
39
+ build
40
+ when "serve", "server"
41
+ server(port: Integer(argv.shift || 8080))
42
+ when "version", "-v", "--version"
43
+ puts VERSION
44
+ else
45
+ return usage_error("Unknown command: #{command}") if command
46
+
47
+ puts USAGE
48
+ end
49
+ 0
50
+ rescue Error => e
51
+ warn e.message
52
+ 1
53
+ end
54
+
55
+ def usage_error(message)
56
+ warn message
57
+ warn USAGE
58
+ 1
59
+ end
60
+
61
+ # Copies the template into dir/project_name. Files ending in .erb are
62
+ # rendered with project_name available. Returns the destination path.
63
+ def create_project(project_name, dir: Dir.pwd)
64
+ destination = File.join(dir, project_name)
65
+ if File.exist?(destination)
66
+ raise Error, "Sorry, #{destination} already exists. Please choose another name or delete the folder."
67
+ end
68
+
69
+ Dir.glob("**/*", base: TEMPLATE_PATH).sort.each do |relative_path|
70
+ source = File.join(TEMPLATE_PATH, relative_path)
71
+ next if File.directory?(source)
72
+
73
+ content = File.read(source)
74
+ content = ERB.new(content).result_with_hash(project_name: project_name) if relative_path.end_with?(".erb")
75
+
76
+ target = relative_path.delete_suffix(".erb")
77
+ target = target.sub(%r{(\A|/)gitignore\z}, "\\1.gitignore")
78
+ target_path = File.join(destination, target)
79
+ FileUtils.mkdir_p(File.dirname(target_path))
80
+ File.write(target_path, content)
81
+ puts "create #{File.join(project_name, target)}"
82
+ end
83
+ destination
84
+ end
85
+
86
+ def build
87
+ system("bundle", "install") or raise Error, "bundle install failed"
88
+ system("bundle", "exec", "rbwasm", "build", "--ruby-version", WASM_RUBY_VERSION, "-o", "assets/ruby-app.wasm") or
89
+ raise Error, "rbwasm build failed"
90
+ end
91
+
92
+ def server(port: 8080)
93
+ begin
94
+ require "webrick"
95
+ rescue LoadError
96
+ raise Error, "jsg server needs the webrick gem. Install it with: gem install webrick"
97
+ end
98
+
99
+ loop do
100
+ server = WEBrick::HTTPServer.new(BindAddress: "0.0.0.0", Port: port, DocumentRoot: Dir.pwd)
101
+ trap("INT") { server.shutdown }
102
+ puts "Serving #{Dir.pwd} at http://localhost:#{port}"
103
+ server.start
104
+ break
105
+ rescue Errno::EADDRINUSE
106
+ port += 1
107
+ end
108
+ end
109
+ end
110
+ end
data/lib/jsg/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSG
4
- VERSION = "0.1.2"
4
+ VERSION = "0.2.1"
5
5
  end
data/lib/jsg.rb ADDED
@@ -0,0 +1,211 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "jsg/version"
4
+ require "js"
5
+
6
+ # Syntax additions for JS::Object.
7
+ #
8
+ # The module is prepended to JS::Object (not included), because the js gem
9
+ # defines method_missing, respond_to_missing?, to_a and (since js 2.10) nil?
10
+ # directly on JS::Object. An included module sits behind the class in the
11
+ # ancestor chain, so those methods would never be reached.
12
+ #
13
+ # The methods in here only use JS::Object#[], #call, #typeof and
14
+ # #strictly_eql? to talk to JavaScript, never the method_missing shortcuts,
15
+ # so they cannot recurse into themselves.
16
+ module JSGPatch
17
+ SETTER = /\A[A-Za-z_]\w*=\z/
18
+ PREDICATE = /\A[A-Za-z_]\w*\?\z/
19
+
20
+ # Methods added to the JS module.
21
+ module ClassMethods
22
+ # JS.try_convert_to_rb(obj) -> Ruby Object or JS::Object
23
+ #
24
+ # Converts the given object to a Ruby object using to_rb. Returns the
25
+ # argument unchanged if it cannot be converted.
26
+ def try_convert_to_rb(obj)
27
+ obj.respond_to?(:to_rb) ? obj.to_rb : obj
28
+ end
29
+ end
30
+
31
+ # Converts an array-like JavaScript object (Array, NodeList, arguments, ...)
32
+ # to a Ruby Array. The elements are converted with to_rb unless
33
+ # convertTypes is false.
34
+ # rubocop:disable-next Naming/MethodParameterName, Naming/VariableName
35
+ def to_a(convertTypes: true)
36
+ as_array = JS.global[:Array].call(:from, self)
37
+ Array.new(as_array[:length].to_i) do |index|
38
+ item = as_array[index]
39
+ convertTypes ? item.to_rb : item
40
+ end
41
+ end
42
+
43
+ # Converts JavaScript primitives and arrays to Ruby objects:
44
+ # number -> Float, string -> String, boolean -> true/false,
45
+ # bigint -> Integer, symbol -> Symbol, null -> nil, Array -> Array.
46
+ # Everything else (objects, functions, undefined) is returned as JS::Object.
47
+ def to_rb
48
+ case typeof
49
+ when "number" then to_f
50
+ when "string" then to_s
51
+ when "boolean" then strictly_eql?(JS::True)
52
+ when "bigint" then to_i
53
+ when "symbol" then self[:description].to_s.to_sym
54
+ when "object"
55
+ return nil if strictly_eql?(JS::Null)
56
+
57
+ isJSArray ? to_a : self
58
+ else
59
+ self
60
+ end
61
+ end
62
+
63
+ def isJSArray # rubocop:disable Naming/MethodName, Naming/PredicateMethod
64
+ JS.global[:Array].call(:isArray, self).strictly_eql?(JS::True)
65
+ end
66
+
67
+ def typeof?(type)
68
+ typeof == type.to_s
69
+ end
70
+
71
+ # Iterates over the elements of an array-like object (anything with a
72
+ # numeric length), otherwise over the property names of the object and its
73
+ # prototype chain.
74
+ def each(&block)
75
+ return Enumerator.new { |yielder| each { |item| yielder << item } } unless block
76
+
77
+ if array_like?
78
+ to_a.each(&block)
79
+ else
80
+ __props.each(&block)
81
+ end
82
+ self
83
+ end
84
+
85
+ # Kernel#tap, which JS::Object lost when the js gem 2.10 made it a
86
+ # BasicObject. (#then is left out on purpose: Promises have their own.)
87
+ def tap
88
+ yield self
89
+ self
90
+ end
91
+
92
+ # true for JavaScript null and undefined
93
+ def nil?
94
+ strictly_eql?(JS::Null) || strictly_eql?(JS::Undefined)
95
+ end
96
+
97
+ # true only for JavaScript undefined
98
+ def undefined?
99
+ strictly_eql?(JS::Undefined)
100
+ end
101
+
102
+ # Property names of the object and its prototype chain, as Symbols.
103
+ def __props
104
+ object = JS.global[:Object]
105
+ props = []
106
+ current = self
107
+ until current.nil?
108
+ names = object.call(:getOwnPropertyNames, current)
109
+ props.concat(names.to_a)
110
+ current = object.call(:getPrototypeOf, current)
111
+ end
112
+ props.uniq.map(&:to_sym)
113
+ end
114
+
115
+ # element.innerText = "Hello" # sets a property (value converted with to_js)
116
+ # element.hidden? # property or method result as true/false (JS truthiness)
117
+ # document.title # reads a property, converted with to_rb
118
+ # document.getElementById("x") # calls a method, result converted with to_rb
119
+ # JS.global.URLSearchParams # capitalized name without arguments: returns the constructor
120
+ #
121
+ # Anything else, e.g. a property that is undefined, goes to the js gem's
122
+ # method_missing.
123
+ def method_missing(sym, *args, &block)
124
+ name = sym.to_s
125
+
126
+ if SETTER.match?(name) && args.length == 1 && block.nil?
127
+ self[name[0..-2]] = args[0]
128
+ return args[0]
129
+ end
130
+
131
+ if PREDICATE.match?(name)
132
+ value = self[name[0..-2]]
133
+ value = call(name[0..-2], *args, &block) if value.typeof == "function"
134
+ return JS.global.call(:Boolean, value).strictly_eql?(JS::True)
135
+ end
136
+
137
+ value = self[sym]
138
+ case value.typeof
139
+ when "undefined"
140
+ super
141
+ when "function"
142
+ # Calling a class like URLSearchParams without `new` throws, so return
143
+ # the constructor itself and let the caller use `.new(...)`.
144
+ return value if args.empty? && block.nil? && name.match?(/\A[A-Z]/)
145
+
146
+ call(sym, *args, &block).to_rb
147
+ else
148
+ value.to_rb
149
+ end
150
+ end
151
+
152
+ def respond_to_missing?(sym, _include_private = false)
153
+ name = sym.to_s
154
+ return true if SETTER.match?(name) || PREDICATE.match?(name)
155
+ return false unless typeof?(:object) || typeof?(:function)
156
+
157
+ !self[sym].undefined?
158
+ end
159
+
160
+ private
161
+
162
+ def array_like?
163
+ return false unless typeof?(:object) && !nil?
164
+
165
+ isJSArray || self[:length].typeof?(:number)
166
+ end
167
+ end
168
+
169
+ # Applying the patch to JS::Object. See the comment on JSGPatch for why this is
170
+ # a prepend and not an include.
171
+ class JS::Object # rubocop:disable Style/ClassAndModuleChildren
172
+ prepend ::JSGPatch
173
+ end
174
+
175
+ module JS
176
+ extend JSGPatch::ClassMethods
177
+ end
178
+
179
+ # Shortcuts for the global object and the document.
180
+ module JSG
181
+ class Error < StandardError; end
182
+
183
+ def self.window
184
+ JS.global
185
+ end
186
+
187
+ def self.document
188
+ JS.global[:document]
189
+ end
190
+
191
+ def self.querySelectorAll(*args) # rubocop:disable Naming/MethodName
192
+ document.querySelectorAll(*args)
193
+ end
194
+
195
+ singleton_class.alias_method :w, :window
196
+ singleton_class.alias_method :d, :document
197
+ singleton_class.alias_method :q, :querySelectorAll
198
+
199
+ # Forwards everything else to the JS module, e.g. JSG.global or JSG.eval.
200
+ def self.method_missing(method, ...)
201
+ if JS.respond_to?(method)
202
+ JS.send(method, ...)
203
+ else
204
+ super
205
+ end
206
+ end
207
+
208
+ def self.respond_to_missing?(method, include_private = false)
209
+ JS.respond_to?(method, include_private) || super
210
+ end
211
+ end
data/sig/jsg.rbs CHANGED
@@ -1,4 +1,4 @@
1
- module Jsg
1
+ module JSG
2
2
  VERSION: String
3
3
  # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
4
  end
data/template/Gemfile CHANGED
@@ -2,8 +2,9 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- # gem "rails"
6
-
7
- gem "ruby_wasm", "~> 2.6"
8
- gem "js", "~> 2.6"
9
- gem 'jsg'
5
+ # ruby_wasm provides the rbwasm build tool, js is the JavaScript bridge used
6
+ # inside the browser. Keep both on the same version as
7
+ # assets/browser.script.iife.js (ruby.wasm 2.10.1).
8
+ gem "ruby_wasm", "~> 2.10.1"
9
+ gem "js", "~> 2.10.1"
10
+ gem "jsg", "~> 0.2"