├── .gitignore ├── .gitmodules ├── Gemfile ├── LICENSE.md ├── README.md ├── Rakefile ├── assets ├── mruby │ ├── include │ │ ├── mrbconf.h │ │ ├── mruby.h │ │ └── mruby │ │ │ ├── array.h │ │ │ ├── boxing_nan.h │ │ │ ├── boxing_no.h │ │ │ ├── boxing_word.h │ │ │ ├── class.h │ │ │ ├── common.h │ │ │ ├── compile.h │ │ │ ├── data.h │ │ │ ├── debug.h │ │ │ ├── dump.h │ │ │ ├── error.h │ │ │ ├── gc.h │ │ │ ├── hash.h │ │ │ ├── irep.h │ │ │ ├── istruct.h │ │ │ ├── khash.h │ │ │ ├── numeric.h │ │ │ ├── object.h │ │ │ ├── opcode.h │ │ │ ├── proc.h │ │ │ ├── range.h │ │ │ ├── re.h │ │ │ ├── string.h │ │ │ ├── throw.h │ │ │ ├── value.h │ │ │ ├── variable.h │ │ │ └── version.h │ └── libmruby.a ├── mruby_init.c └── template.html ├── bin └── ruby-wasm ├── build_config.rb ├── hello.rb ├── lib ├── wasm.rb └── wasm │ ├── colorize.rb │ └── version.rb └── wasm.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.gem 3 | Gemfile.lock 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "mruby"] 2 | path = mruby 3 | url = https://github.com/mruby/mruby.git 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Tom Black ([blacktm.com](http://www.blacktm.com)) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ruby on WebAssembly 2 | 3 | Welcome! 👋 4 | 5 | This is the repo for the `wasm` gem and also a place to start a conversation about Ruby and WebAssembly, and how our community might leverage this new capability. For some background, [check out my blog post](http://www.blacktm.com/blog/ruby-on-webassembly) on the subject. This is largely an experiment at the moment — there are plenty of things this gem doesn't do, but it's a starting point and will hopefully get you up and running quickly so we can learn and explore together. 6 | 7 | Feel free to open an issue about anything at all, even just to chat. Enjoy! 8 | 9 | ## Building the gem and dependencies 10 | 11 | Run `rake` to build this gem and install locally. 12 | 13 | Use `rake mruby_latest` to set the MRuby submodule to the latest release and `rake mruby_master` to switch to the master branch. 14 | 15 | Run `rake build_mruby` to build an MRuby static library for WebAssembly, which will be placed in the `assets/` directory (a prebuilt one is already there). 16 | 17 | When a new version of MRuby is released, update the version number in the `Rakefile`, run `rake mruby_latest`, and `rake build_mruby`. 18 | 19 | ## Using the gem 20 | 21 | Get it with `gem install wasm` 22 | 23 | Once installed, you'll have access to the `ruby-wasm` command-line utility. Use this utility to build and serve Ruby apps for WebAssembly. Running it without any options will print its usage. Troubleshoot your WebAssembly toolchain with `ruby-wasm doctor` 24 | 25 | What this gem currently does: 26 | - Compile a single Ruby script to a binary `.wasm` file 27 | 28 | What it doesn't do yet (maybe something you'd like to work on 🤔): 29 | - Build several scripts at a time, or pull in others via `require` 30 | - Allow you to call JavaScript from Ruby, or vice versa 31 | - Use any arbitrary Ruby gem; we're using MRuby here, so you _can_ use [mrbgems](https://github.com/mruby/mruby/wiki/Mrbgems-FAQ) ([see list](https://github.com/mruby/mgem-list)) 32 | - Fetch and interpret a Ruby script on page load 33 | 34 | ### A quick example 35 | 36 | First, make sure you have the WebAssembly toolchain installed and activated — see the ["Getting Started" guide](http://webassembly.org/getting-started/developers-guide) for details. 37 | 38 | Start by cloning this repo (the `--recursive` option also grabs the MRuby submodule and initializes it): 39 | 40 | ``` 41 | git clone --recursive https://github.com/blacktm/ruby-wasm.git 42 | ``` 43 | 44 | `cd` into the directory. Notice there's a file called `hello.rb` — we're going to build it for WebAssembly! 45 | 46 | Make sure you have this `wasm` gem installed. Remember, you can check for issues using `ruby-wasm doctor` 47 | 48 | Now, we'll build (or compile) the "Hello Ruby!" app using: 49 | 50 | ``` 51 | ruby-wasm build hello.rb 52 | ``` 53 | 54 | This will create a `build/` directory and generate the following files: 55 | - `app.wasm` — Our compiled Ruby app in binary WebAssembly format 56 | - `app.js` — JavaScript needed to fetch our `.wasm` binary and initialize the WebAssembly environment ([learn more](http://webassembly.org/getting-started/js-api)) 57 | - `app.html` — A simple HTML template with a ` 17 | 18 | -------------------------------------------------------------------------------- /bin/ruby-wasm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | require 'wasm/colorize' 4 | require 'wasm/version' 5 | 6 | # The installed gem directory 7 | @gem_dir = "#{Gem::Specification.find_by_name('wasm').gem_dir}" 8 | 9 | # Compilation optimizations flag 10 | @optimize = false 11 | 12 | # Build a Ruby file 13 | def build(rb_file) 14 | 15 | # Check if source file provided is good 16 | if !rb_file 17 | puts "Provide a Ruby file to build" 18 | exit 19 | elsif !File.exists? rb_file 20 | puts "Can't find file: #{rb_file}" 21 | exit 22 | end 23 | 24 | # Check for compiler toolchain issues 25 | if doctor(:building) 26 | puts "Fix errors before building.\n\n" 27 | return false 28 | end 29 | 30 | # Clean up the build directory 31 | FileUtils.rm_f 'build/app.c' 32 | FileUtils.rm_f 'build/app.js' 33 | FileUtils.rm_f 'build/app.wasm' 34 | FileUtils.rm_f 'build/app.html' 35 | 36 | # Create the build directory 37 | FileUtils.mkdir_p 'build' 38 | 39 | print "\nCompiling #{rb_file}..." 40 | 41 | # Create MRuby bytecode from Ruby source file 42 | `mrbc -Bruby_app -obuild/app.c #{rb_file}` 43 | 44 | # Add MRuby init code to app bytecode 45 | open('build/app.c', 'a') do |f| 46 | f << "\n\n" << File.read("#{@gem_dir}/assets/mruby_init.c") 47 | end 48 | 49 | # Compile using Emscripten 50 | `emcc -s WASM=1 #{ if @optimize then '-Os' end } -I #{@gem_dir + '/assets/mruby/include'} build/app.c #{@gem_dir + '/assets/mruby/libmruby.a'} -o build/app.js #{ if @optimize then '--closure 1' end }` 51 | 52 | # Copy HTML template from gem assets to build directory 53 | FileUtils.cp "#{@gem_dir}/assets/template.html", 'build/app.html' 54 | 55 | # Clean up 56 | FileUtils.rm_f 'build/app.c' 57 | 58 | puts "done".success, " 59 | Files created: 60 | build/ 61 | ├── app.html 62 | ├── app.js 63 | └── app.wasm\n\n" 64 | 65 | end 66 | 67 | # Serve a build WebAssembly binary 68 | def serve(open_browser) 69 | 70 | if !File.exists? 'build/app.html' 71 | puts 'No WebAssembly app built!' 72 | exit 73 | end 74 | 75 | if open_browser 76 | open_cmd = 'open' 77 | 78 | case RUBY_PLATFORM 79 | when /linux/ 80 | open_cmd = "xdg-#{open_cmd}" 81 | when /mingw/ 82 | open_cmd = 'start' 83 | end 84 | 85 | Thread.new do 86 | sleep 2 87 | `#{open_cmd} http://localhost:8000/app.html` 88 | end 89 | end 90 | 91 | `ruby -run -ehttpd ./build -p8000` 92 | end 93 | 94 | # Check for problems 95 | def doctor(mode = nil) 96 | 97 | errors = false 98 | mruby_errors = false 99 | emscripten_errors = false 100 | 101 | puts "\nChecking for MRuby" 102 | 103 | # Check for `mrbc` 104 | print ' mrbc...' 105 | if `which mrbc`.empty? 106 | puts 'not found'.error 107 | mruby_errors = true 108 | else 109 | puts 'found'.success 110 | end 111 | 112 | puts "\nChecking for Emscripten tools" 113 | 114 | # Check for `emcc` 115 | print ' emcc...' 116 | if `which emcc`.empty? 117 | puts 'not found'.error 118 | emscripten_errors = true 119 | else 120 | puts 'found'.success 121 | end 122 | 123 | # Check for `emar` 124 | print ' emar...' 125 | if `which emar`.empty? 126 | puts 'not found'.error 127 | emscripten_errors = true 128 | else 129 | puts 'found'.success 130 | end 131 | 132 | if mruby_errors || emscripten_errors then errors = true end 133 | 134 | if errors 135 | puts "\nErrors were found!\n\n" 136 | if mruby_errors 137 | puts "* Did you install MRuby?" 138 | end 139 | if emscripten_errors 140 | puts "* Did you run \`./emsdk_env.sh\` ?", " For help, check out the \"Getting Started\" guide on webassembly.org" 141 | end 142 | puts "\n" 143 | else 144 | puts "\n👍 Everything looks good!\n\n" unless mode == :building 145 | end 146 | 147 | errors 148 | end 149 | 150 | # Check Command-line Arguments ################################################# 151 | 152 | usage = 'Ruby on WebAssembly'.bold + "\n 153 | Usage: ruby-wasm 154 | [-v|--version] 155 | 156 | Summary of commands and options: 157 | build Build a Ruby source file 158 | --optimize Compile with all optimizations 159 | serve Serve the build WebAssembly binary 160 | -o|--open Open the default web browser after serving 161 | doctor Check for problems with your WebAssembly toolchain 162 | -v|--version Prints the installed version\n\n" 163 | 164 | case ARGV[0] 165 | when 'build' 166 | if ARGV.delete '--optimize' then @optimize = true end 167 | build ARGV[1] 168 | when 'serve' 169 | case ARGV[1] 170 | when '-o', '--open' 171 | serve(true) 172 | else 173 | serve(false) 174 | end 175 | when 'doctor' 176 | doctor 177 | when '-v', '--version' 178 | puts WASM::VERSION 179 | else 180 | puts usage 181 | end 182 | -------------------------------------------------------------------------------- /build_config.rb: -------------------------------------------------------------------------------- 1 | MRuby::Build.new do |conf| 2 | toolchain :gcc 3 | conf.gembox 'default' 4 | end 5 | 6 | MRuby::CrossBuild.new('emscripten') do |conf| 7 | toolchain :clang 8 | conf.gembox 'default' 9 | conf.cc.command = 'emcc' 10 | conf.cc.flags = %W(-Os) 11 | conf.linker.command = 'emcc' 12 | conf.archiver.command = 'emar' 13 | end 14 | -------------------------------------------------------------------------------- /hello.rb: -------------------------------------------------------------------------------- 1 | puts "Hello Ruby!" 2 | -------------------------------------------------------------------------------- /lib/wasm.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blacktm/ruby-wasm/408f6984f7d1beaa3266704f560406d2d9cf7c38/lib/wasm.rb -------------------------------------------------------------------------------- /lib/wasm/colorize.rb: -------------------------------------------------------------------------------- 1 | # String#colorize 2 | 3 | # Extend `String` to include some fancy colors 4 | class String 5 | def colorize(c); "\e[#{c}m#{self}\e[0m" end 6 | def bold; colorize('1') end 7 | def blue; colorize('1;34') end 8 | def success; colorize('1;32') end 9 | def error; colorize('1;31') end 10 | end 11 | -------------------------------------------------------------------------------- /lib/wasm/version.rb: -------------------------------------------------------------------------------- 1 | # version.rb 2 | 3 | module WASM 4 | VERSION = '0.0.2' 5 | end 6 | -------------------------------------------------------------------------------- /wasm.gemspec: -------------------------------------------------------------------------------- 1 | require_relative 'lib/wasm/version' 2 | 3 | Gem::Specification.new do |s| 4 | s.name = 'wasm' 5 | s.version = WASM::VERSION 6 | s.summary = 'Ruby on WebAssembly' 7 | s.description = 'Helping you get started with Ruby and WebAssembly' 8 | s.homepage = 'https://github.com/blacktm/ruby-wasm' 9 | s.license = 'MIT' 10 | s.author = 'Tom Black' 11 | s.email = 'tom@blacktm.com' 12 | 13 | s.files = Dir.glob('lib/**/*') + 14 | Dir.glob('assets/**/*') + 15 | ['build_config.rb'] 16 | s.executables << 'ruby-wasm' 17 | end 18 | --------------------------------------------------------------------------------