Skip to content

Commit 3981324

Browse files
committed
Ruby Next goes online!
0 parents  commit 3981324

File tree

14 files changed

+468
-0
lines changed

14 files changed

+468
-0
lines changed

.gitignore

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/.bundle/
2+
/.yardoc
3+
/_yardoc/
4+
/coverage/
5+
/doc/
6+
/pkg/
7+
tmp/
8+
dist/
9+
10+
gemfiles/*.lock
11+
Gemfile.lock
12+
13+
mspec/
14+
.rbnext/
15+
rubyspec_temp/
16+
17+
build/
18+
rubies/
19+
20+
ruby.wasm
21+
22+
node_modules/

Gemfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# frozen_string_literal: true
2+
3+
source "https://rubygems.org"
4+
5+
gem "js", "~> 2.4.1.pre.1" unless ENV["JS"] == "false"
6+
gem "ruby_wasm", "~> 2.5.pre.1"
7+
gem "ruby-next", "~> 1.0"

README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Ruby Next in browser
2+
3+
Play with Ruby Next right in the browser (powered by [ruby.wasm](https://github.com/ruby/ruby.wasm)).
4+
5+
## Development
6+
7+
### Prerequisites
8+
9+
- Install [wasi-vfs](https://github.com/kateinoigakukun/wasi-vfs):
10+
11+
```sh
12+
brew tap kateinoigakukun/wasi-vfs https://github.com/kateinoigakukun/wasi-vfs.git
13+
brew install kateinoigakukun/wasi-vfs/wasi-vfs
14+
```
15+
16+
- Install Rust toolchain:
17+
18+
```sh
19+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
20+
```
21+
22+
- Now you're ready to:
23+
24+
```sh
25+
bundle install
26+
```
27+
28+
### Building ruby.wasm
29+
30+
The following command works for me:
31+
32+
```sh
33+
LDFLAGS='-L/opt/homebrew/opt/ruby/lib' bundle exec rbwasm build -o src/ruby.wasm --ruby-version 3.2
34+
```
35+
36+
Looks like compiling Ruby relies on the hardcoded `/usr/local/opt/ruby/lib` path for linking. So, we need to provie the correct way to Ruby (and, I guess, the versions must match).
37+
38+
This would build a JS-compatible WASM module. To build JS-free WASM module, use the `JS=false` env var.
39+
40+
### Testing
41+
42+
Using [wasmtime](https://github.com/bytecodealliance/wasmtime), you can verify the **JS-free** module like this:
43+
44+
```sh
45+
wasmtime run --dir ./::/ src/ruby.wasm ruby-next.rb
46+
```
47+
48+
### Running web version
49+
50+
First, install JS deps (`yarn install`).
51+
52+
Then, run a web server:
53+
54+
```sh
55+
yarn serve
56+
```
57+
58+
Go to [localhost:8000](http://localhost:8000) and see it in action!

bin/build.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import esbuild from "esbuild";
2+
import url from "url";
3+
import wasmPlugin from "./wasmPlugin.js";
4+
import copyPlugin from "./copyPlugin.js";
5+
6+
const entryPoint = url.fileURLToPath(new URL("../src/index", import.meta.url));
7+
const outdir = url.fileURLToPath(new URL("../dist", import.meta.url));
8+
9+
const { metafile } = await esbuild.build({
10+
bundle: true,
11+
entryPoints: [entryPoint],
12+
format: "esm",
13+
metafile: true,
14+
minify: true,
15+
outdir,
16+
plugins: [wasmPlugin, copyPlugin],
17+
sourcemap: true,
18+
splitting: true,
19+
target: "es6",
20+
});
21+
22+
const analysis = await esbuild.analyzeMetafile(metafile);
23+
console.log(analysis);

bin/copyPlugin.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import path from "node:path";
2+
import fs from "node:fs";
3+
import { fileURLToPath } from "node:url";
4+
5+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
6+
7+
export default {
8+
name: "copy",
9+
setup(build) {
10+
build.onEnd(() => {
11+
fs.copyFileSync(
12+
path.resolve(__dirname, "../src/index.html"),
13+
path.resolve(__dirname, "../dist/index.html")
14+
);
15+
});
16+
},
17+
};

bin/serve.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import esbuild from "esbuild";
2+
import url from "url";
3+
import wasmPlugin from "./wasmPlugin.js";
4+
import copyPlugin from "./copyPlugin.js";
5+
6+
const entryPoint = url.fileURLToPath(new URL("../src/index", import.meta.url));
7+
const outdir = url.fileURLToPath(new URL("../dist", import.meta.url));
8+
9+
const ctx = await esbuild.context({
10+
bundle: true,
11+
entryPoints: [entryPoint],
12+
format: "esm",
13+
outdir,
14+
plugins: [wasmPlugin, copyPlugin],
15+
sourcemap: true,
16+
splitting: true,
17+
target: "esnext",
18+
});
19+
20+
const { host, port } = await ctx.serve({ servedir: outdir });
21+
console.log(`Listening at ${host}:${port}`);

bin/wasmPlugin.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// From: https://github.com/ruby-syntax-tree/ruby-syntax-tree.github.io/blob/main/bin/wasmPlugin.js
2+
3+
import path from "node:path";
4+
import fs from "node:fs";
5+
6+
export default {
7+
name: "wasm",
8+
setup(build) {
9+
// Resolve ".wasm" files to a path with a namespace
10+
build.onResolve({ filter: /\.wasm$/ }, (args) => {
11+
// If this is the import inside the stub module, import the
12+
// binary itself. Put the path in the "wasm-binary" namespace
13+
// to tell our binary load callback to load the binary file.
14+
if (args.namespace === "wasm-stub") {
15+
return {
16+
path: args.path,
17+
namespace: "wasm-binary",
18+
};
19+
}
20+
21+
// Otherwise, generate the JavaScript stub module for this
22+
// ".wasm" file. Put it in the "wasm-stub" namespace to tell
23+
// our stub load callback to fill it with JavaScript.
24+
//
25+
// Resolve relative paths to absolute paths here since this
26+
// resolve callback is given "resolveDir", the directory to
27+
// resolve imports against.
28+
if (args.resolveDir === "") {
29+
return; // Ignore unresolvable paths
30+
}
31+
return {
32+
path: path.isAbsolute(args.path)
33+
? args.path
34+
: path.join(args.resolveDir, args.path),
35+
namespace: "wasm-stub",
36+
};
37+
});
38+
39+
// Virtual modules in the "wasm-stub" namespace are filled with
40+
// the JavaScript code for compiling the WebAssembly binary. The
41+
// binary itself is imported from a second virtual module.
42+
build.onLoad({ filter: /.*/, namespace: "wasm-stub" }, async (args) => ({
43+
contents: `import wasm from ${JSON.stringify(args.path)}
44+
export default () => WebAssembly.compile(wasm)`,
45+
}));
46+
47+
// Virtual modules in the "wasm-binary" namespace contain the
48+
// actual bytes of the WebAssembly file. This uses esbuild's
49+
// built-in "binary" loader instead of manually embedding the
50+
// binary data inside JavaScript code ourselves.
51+
build.onLoad({ filter: /.*/, namespace: "wasm-binary" }, async (args) => ({
52+
contents: await fs.promises.readFile(args.path),
53+
loader: "binary",
54+
}));
55+
},
56+
};

package.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"license": "MIT",
3+
"type": "module",
4+
"scripts": {
5+
"build": "node bin/build.js",
6+
"serve": "node bin/serve.js"
7+
},
8+
"dependencies": {
9+
"@bjorn3/browser_wasi_shim": "^0.2.18",
10+
"@ruby/wasm-wasi": "^2.4.1"
11+
},
12+
"devDependencies": {
13+
"esbuild": "^0.17.10"
14+
}
15+
}

ruby-next.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
require "/bundle/setup"
2+
3+
# Make gem no-op
4+
define_singleton_method(:gem) { |*| nil }
5+
6+
require "ruby-next/language"
7+
8+
SOURCE = <<~'RUBY'
9+
greet = proc do
10+
case it
11+
in hello: hello if hello =~ /human/i
12+
'🙂'
13+
in hello: 'martian'
14+
'👽'
15+
end
16+
end
17+
18+
puts greet.call(hello: 'martian')
19+
RUBY
20+
21+
puts RubyNext::Language.transform(SOURCE)

src/index.css

Whitespace-only changes.

0 commit comments

Comments
 (0)