Skip to content

Commit 897e192

Browse files
add ruby-3_2-wasm-wasi package
1 parent c25d17f commit 897e192

File tree

7 files changed

+591
-0
lines changed

7 files changed

+591
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.tgz
Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
# ruby-3_2-wasm-wasi
2+
3+
WebAssembly port of CRuby with WASI.
4+
5+
This package distributes the latest `master` branch of CRuby.
6+
7+
## Installation
8+
9+
For installing ruby-3_2-wasm-wasi family, just run this command in your shell:
10+
11+
```console
12+
$ npm install --save ruby-3_2-wasm-wasi@latest
13+
# or if you want the nightly snapshot
14+
$ npm install --save ruby-3_2-wasm-wasi@next
15+
# or you can specify the exact snapshot version
16+
$ npm install --save [email protected]
17+
```
18+
19+
## Quick Start (for Node.js)
20+
21+
See [the example project](https://github.com/ruby/ruby.wasm/tree/main/packages/npm-packages/ruby-wasm-wasi/example) for more details.
22+
23+
```javascript
24+
import fs from "fs/promises";
25+
import { DefaultRubyVM } from "ruby-3_2-wasm-wasi/dist/node.cjs.js";
26+
27+
const main = async () => {
28+
const binary = await fs.readFile(
29+
// Tips: Replace the binary with debug info if you want symbolicated stack trace.
30+
// (only nightly release for now)
31+
// "./node_modules/ruby-3_2-wasm-wasi/dist/ruby.debug+stdlib.wasm"
32+
"./node_modules/ruby-3_2-wasm-wasi/dist/ruby.wasm"
33+
);
34+
const module = await WebAssembly.compile(binary);
35+
const { vm } = await DefaultRubyVM(module);
36+
37+
vm.eval(`
38+
luckiness = ["Lucky", "Unlucky"].sample
39+
puts "You are #{luckiness}"
40+
`);
41+
};
42+
43+
main();
44+
```
45+
46+
Then you can run the example project in your terminal:
47+
48+
```console
49+
$ node --experimental-wasi-unstable-preview1 index.node.js
50+
```
51+
52+
## Quick Start (for Browser)
53+
54+
In browser, you need a WASI polyfill. See [the example project](https://github.com/ruby/ruby.wasm/tree/main/packages/npm-packages/ruby-wasm-wasi/example) for more details.
55+
56+
```html
57+
<html>
58+
<script src="https://cdn.jsdelivr.net/npm/ruby-3_2-wasm-wasi@latest/dist/browser.umd.js"></script>
59+
<script>
60+
const { DefaultRubyVM } = window["ruby-wasm-wasi"];
61+
const main = async () => {
62+
// Fetch and instantiate WebAssembly binary
63+
const response = await fetch(
64+
// Tips: Replace the binary with debug info if you want symbolicated stack trace.
65+
// (only nightly release for now)
66+
// "https://cdn.jsdelivr.net/npm/ruby-3_2-wasm-wasi@next/dist/ruby.debug+stdlib.wasm"
67+
"https://cdn.jsdelivr.net/npm/ruby-3_2-wasm-wasi@latest/dist/ruby.wasm"
68+
);
69+
const buffer = await response.arrayBuffer();
70+
const module = await WebAssembly.compile(buffer);
71+
const { vm } = await DefaultRubyVM(module);
72+
73+
vm.printVersion();
74+
vm.eval(`
75+
require "js"
76+
luckiness = ["Lucky", "Unlucky"].sample
77+
JS::eval("document.body.innerText = '#{luckiness}'")
78+
`);
79+
};
80+
81+
main();
82+
</script>
83+
<body></body>
84+
</html>
85+
```
86+
87+
## GC limitation with JavaScript interoperability
88+
89+
Since JavaScript's GC system and Ruby's GC system are separated and not cooperative, they cannot collect cyclic references between JavaScript and Ruby objects.
90+
91+
The following code will cause a memory leak:
92+
93+
```javascript
94+
class JNode {
95+
setRNode(rnode) {
96+
this.rnode = rnode;
97+
}
98+
}
99+
jnode = new JNode();
100+
101+
rnode = vm.eval(`
102+
class RNode
103+
def set_jnode(jnode)
104+
@jnode = jnode
105+
end
106+
end
107+
RNode.new
108+
`);
109+
110+
rnode.call("set_jnode", vm.wrap(jnode));
111+
jnode.setRNode(rnode);
112+
```
113+
114+
<!-- The APIs section was generated by `npx documentation readme ../ruby-wasm-wasi/dist/index.esm.js --section=APIs` -->
115+
116+
## APIs
117+
118+
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
119+
120+
#### Table of Contents
121+
122+
- [RubyVM](#rubyvm)
123+
- [Examples](#examples)
124+
- [initialize](#initialize)
125+
- [Parameters](#parameters)
126+
- [setInstance](#setinstance)
127+
- [Parameters](#parameters-1)
128+
- [addToImports](#addtoimports)
129+
- [Parameters](#parameters-2)
130+
- [printVersion](#printversion)
131+
- [eval](#eval)
132+
- [Parameters](#parameters-3)
133+
- [Examples](#examples-1)
134+
- [evalAsync](#evalasync)
135+
- [Parameters](#parameters-4)
136+
- [Examples](#examples-2)
137+
- [wrap](#wrap)
138+
- [Parameters](#parameters-5)
139+
- [Examples](#examples-3)
140+
- [RbValue](#rbvalue)
141+
- [call](#call)
142+
- [Parameters](#parameters-6)
143+
- [Examples](#examples-4)
144+
- [toPrimitive](#toprimitive)
145+
- [Parameters](#parameters-7)
146+
- [toString](#tostring)
147+
- [toJS](#tojs)
148+
- [RbError](#rberror)
149+
150+
### RubyVM
151+
152+
A Ruby VM instance
153+
154+
#### Examples
155+
156+
```javascript
157+
const wasi = new WASI();
158+
const vm = new RubyVM();
159+
const imports = {
160+
wasi_snapshot_preview1: wasi.wasiImport,
161+
};
162+
163+
vm.addToImports(imports);
164+
165+
const instance = await WebAssembly.instantiate(rubyModule, imports);
166+
await vm.setInstance(instance);
167+
wasi.initialize(instance);
168+
vm.initialize();
169+
```
170+
171+
#### initialize
172+
173+
Initialize the Ruby VM with the given command line arguments
174+
175+
##### Parameters
176+
177+
- `args` The command line arguments to pass to Ruby. Must be
178+
an array of strings starting with the Ruby program name. (optional, default `["ruby.wasm","--disable-gems","-e_=0"]`)
179+
180+
#### setInstance
181+
182+
Set a given instance to interact JavaScript and Ruby's
183+
WebAssembly instance. This method must be called before calling
184+
Ruby API.
185+
186+
##### Parameters
187+
188+
- `instance` The WebAssembly instance to interact with. Must
189+
be instantiated from a Ruby built with JS extension, and built
190+
with Reactor ABI instead of command line.
191+
192+
#### addToImports
193+
194+
Add intrinsic import entries, which is necessary to interact JavaScript
195+
and Ruby's WebAssembly instance.
196+
197+
##### Parameters
198+
199+
- `imports` The import object to add to the WebAssembly instance
200+
201+
#### printVersion
202+
203+
Print the Ruby version to stdout
204+
205+
#### eval
206+
207+
Runs a string of Ruby code from JavaScript
208+
209+
##### Parameters
210+
211+
- `code` The Ruby code to run
212+
213+
##### Examples
214+
215+
```javascript
216+
vm.eval("puts 'hello world'");
217+
const result = vm.eval("1 + 2");
218+
console.log(result.toString()); // 3
219+
```
220+
221+
Returns **any** the result of the last expression
222+
223+
#### evalAsync
224+
225+
Runs a string of Ruby code with top-level `JS::Object#await`
226+
Returns a promise that resolves when execution completes.
227+
228+
##### Parameters
229+
230+
- `code` The Ruby code to run
231+
232+
##### Examples
233+
234+
```javascript
235+
const text = await vm.evalAsync(`
236+
require 'js'
237+
response = JS.global.fetch('https://example.com').await
238+
response.text.await
239+
`);
240+
console.log(text.toString()); // <html>...</html>
241+
```
242+
243+
Returns **any** a promise that resolves to the result of the last expression
244+
245+
#### wrap
246+
247+
Wrap a JavaScript value into a Ruby JS::Object
248+
249+
##### Parameters
250+
251+
- `value` The value to convert to RbValue
252+
253+
##### Examples
254+
255+
```javascript
256+
const hash = vm.eval(`Hash.new`);
257+
hash.call("store", vm.eval(`"key1"`), vm.wrap(new Object()));
258+
```
259+
260+
Returns **any** the RbValue object representing the given JS value
261+
262+
### RbValue
263+
264+
A RbValue is an object that represents a value in Ruby
265+
266+
#### call
267+
268+
Call a given method with given arguments
269+
270+
##### Parameters
271+
272+
- `callee` name of the Ruby method to call
273+
- `args` **...any** arguments to pass to the method. Must be an array of RbValue
274+
275+
##### Examples
276+
277+
```javascript
278+
const ary = vm.eval("[1, 2, 3]");
279+
ary.call("push", 4);
280+
console.log(ary.call("sample").toString());
281+
```
282+
283+
#### toPrimitive
284+
285+
- **See**: <https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toPrimitive>
286+
287+
##### Parameters
288+
289+
- `hint` Preferred type of the result primitive value. `"number"`, `"string"`, or `"default"`.
290+
291+
#### toString
292+
293+
Returns a string representation of the value by calling `to_s`
294+
295+
#### toJS
296+
297+
Returns a JavaScript object representation of the value
298+
by calling `to_js`.
299+
300+
Returns null if the value is not convertible to a JavaScript object.
301+
302+
### RbError
303+
304+
**Extends Error**
305+
306+
Error class thrown by Ruby execution
307+
308+
## Building the package from source
309+
310+
The instructions for building a Ruby targeting WebAssembly are available [here](https://github.com/ruby/ruby.wasm#building-from-source).
311+
312+
Then, you can run the following command in your shell:
313+
314+
```console
315+
# Check the directory structure of your Ruby build
316+
$ tree -L 3 path/to/wasm32-unknown-wasi-full-js/
317+
path/to/wasm32-unknown-wasi-full-js/
318+
├── usr
319+
│ └── local
320+
│ ├── bin
321+
│ ├── include
322+
│ ├── lib
323+
│ └── share
324+
└── var
325+
└── lib
326+
└── gems
327+
$ ./build-package.sh path/to/wasm32-unknown-wasi-full-js/
328+
Generating "/Users/katei/.ghq/github.com/ruby/ruby.wasm/packages/ruby-3_2-wasm-wasi/src/bindgen/intrinsics.js"
329+
Generating "/Users/katei/.ghq/github.com/ruby/ruby.wasm/packages/ruby-3_2-wasm-wasi/src/bindgen/rb-abi-guest.d.ts"
330+
Generating "/Users/katei/.ghq/github.com/ruby/ruby.wasm/packages/ruby-3_2-wasm-wasi/src/bindgen/rb-abi-guest.js"
331+
Generating "/Users/katei/.ghq/github.com/ruby/ruby.wasm/packages/ruby-3_2-wasm-wasi/src/bindgen/rb-js-abi-host.d.ts"
332+
Generating "/Users/katei/.ghq/github.com/ruby/ruby.wasm/packages/ruby-3_2-wasm-wasi/src/bindgen/rb-js-abi-host.js"
333+
334+
src/index.ts → dist/index.umd.js, dist/index.esm.js, dist/index.cjs.js...
335+
created dist/index.umd.js, dist/index.esm.js, dist/index.cjs.js in 682ms
336+
```
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/bin/bash
2+
set -eu
3+
4+
usage() {
5+
echo "Usage: $(basename $0) ruby_root"
6+
exit 1
7+
}
8+
9+
if [ $# -lt 1 ]; then
10+
usage
11+
fi
12+
13+
ruby_root="$1"
14+
package_dir="$(cd "$(dirname "$0")" && pwd)"
15+
base_package_dir="$package_dir/../ruby-wasm-wasi"
16+
dist_dir="$package_dir/dist"
17+
repo_dir="$package_dir/../../../"
18+
19+
rm -rf "$dist_dir"
20+
(
21+
cd "$base_package_dir" && \
22+
npm ci && \
23+
./build-package.sh "$ruby_root"
24+
)
25+
set -ex
26+
(cd "$package_dir" && npm run build)
27+
cp -R "$base_package_dir/dist/." "$dist_dir"
28+
$base_package_dir/tools/pack-ruby-wasm.sh "$ruby_root" "$dist_dir"

0 commit comments

Comments
 (0)