In this article, we explore how to run Ruby code directly in the browser using WebAssembly (WASM) and integrate it with Vue.js for creating interactive web applications. The main focus is on how Ruby functions can be executed in the browser and how they interact with Vue for dynamic behavior.
What is Ruby WASM?
Ruby WASM is a way to compile Ruby code into WebAssembly, allowing it to run directly in the browser with near-native performance. This allows you to execute Ruby logic in the browser without needing a backend server, making your web applications faster and more responsive.
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Ruby WASM Calculator</title> | |
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"> | |
</head> | |
<body> | |
<div id="app" class="flex items-center justify-center min-h-screen bg-gray-100"> | |
<div class="bg-white p-6 rounded shadow-md w-100 rounded-3xl"> | |
<h1 class="text-2xl font-bold">Ruby WASM</h1> | |
<p class="text-sm text-gray-600 mb-4"> | |
This is a simple calculator using Ruby WASM. | |
<a href="https://gist.github.com/rahuldhole/f08f3f9ff829df3e245046c33f23a392" class="text-sm text-blue-500 underline hover:text-red-600">Source Code</a> | |
</p> | |
<div class="space-y-4"> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 opacity-50">Number 1</label> | |
<input | |
type="number" | |
v-model.number="num1" | |
class="mt-1 p-2 block w-full border-gray-300 rounded-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500" | |
> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 opacity-50">Number 2</label> | |
<input | |
type="number" | |
v-model.number="num2" | |
class="mt-1 p-2 block w-full border-gray-300 rounded-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500" | |
> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 opacity-50">Operation</label> | |
<select | |
v-model="operation" | |
class="mt-1 p-2 block w-full border-gray-300 rounded-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500" | |
> | |
<option value="add">Add</option> | |
<option value="subtract">Subtract</option> | |
<option value="multiply">Multiply</option> | |
<option value="divide">Divide</option> | |
</select> | |
</div> | |
<button | |
@click="calculate" | |
class="w-full bg-blue-500 text-white py-2 px-4 rounded-full hover:bg-blue-600 transition" | |
> | |
Calculate | |
</button> | |
<p v-if="result !== null" class="text-lg font-semibold">Result: {{ result }}</p> | |
<p v-if="error" class="text-red-500">{{ error }}</p> | |
</div> | |
</div> | |
</div> | |
<!-- Vue.js CDN --> | |
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script> | |
<!-- Ruby WASM CDN --> | |
<script src="https://cdn.jsdelivr.net/npm/@ruby/3.4-wasm-wasi@2.7.1/dist/browser.script.iife.js"></script> | |
<!-- Ruby Code --> | |
<script type="text/ruby"> | |
require "js" | |
class Calculator | |
def self.calculate(num1, num2, operation) | |
num1 = num1.to_f | |
num2 = num2.to_f | |
operation = operation.to_s | |
case operation | |
when "add" | |
num1 + num2 | |
when "subtract" | |
num1 - num2 | |
when "multiply" | |
num1 * num2 | |
when "divide" | |
if num2.zero? | |
raise "Division by zero" | |
else | |
num1 / num2 | |
end | |
else | |
raise "Invalid operation: #{operation}" | |
end | |
end | |
end | |
# Expose the calculate method to JavaScript | |
JS.global[:calculate] = proc { |num1, num2, operation| Calculator.calculate(num1, num2, operation) } | |
</script> | |
<!-- Vue Application --> | |
<script> | |
new Vue({ | |
el: '#app', | |
data: { | |
num1: 1, | |
num2: 1, | |
operation: "add", | |
result: null, | |
error: null | |
}, | |
mounted() { | |
if (typeof window.calculate !== 'function') { | |
console.warn('Ruby WASM not fully initialized yet'); | |
} | |
}, | |
methods: { | |
waitForRubyWasm() { | |
return new Promise((resolve, reject) => { | |
if (typeof window.calculate !== 'function') { | |
setTimeout(() => { | |
this.error = "Ruby WASM not initialized. Waiting for it..."; | |
waitForRubyWasm(resolve, reject); | |
}, 100); | |
} else { | |
this.error = null; | |
resolve(); | |
} | |
}); | |
}, | |
calculate() { | |
this.waitForRubyWasm().then(() => { | |
try { | |
if (typeof window.calculate !== 'function') { | |
throw new Error("Ruby WASM not initialized"); | |
} | |
const result = window.calculate(this.num1, this.num2, this.operation); | |
this.result = result; | |
this.error = null; | |
} catch (e) { | |
this.error = e.message; | |
this.result = null; | |
} | |
}); | |
} | |
} | |
}); | |
</script> | |
</body> | |
</html> |
Integrating Ruby Functions in Vue.js
In our project, we build a simple calculator using Ruby WASM and Vue.js. While Vue.js handles the UI and dynamic interaction, Ruby WASM is responsible for performing the calculations.
Setting up Ruby WASM
Ruby code can be compiled into WebAssembly using tools like ruby-wasm
, which enables Ruby functions to be run in the browser. We define our Ruby functions as follows:
- We create a
Calculator
class with methods for basic arithmetic operations (add, subtract, multiply, divide). - These Ruby functions are exposed to the JavaScript global scope so that they can be invoked by the Vue.js application.
Here’s a basic overview of the process:
-
Ruby Functions: The
Calculator
class contains methods likeadd
,subtract
,multiply
, anddivide
. These methods take two numbers and an operation string as input and return the result. -
Exposing Ruby Functions: We use
JS.global[:calculate]
to expose the Rubycalculate
method to JavaScript. This makes it accessible from the Vue.js instance running in the browser.
Vue.js Handling User Interaction
Vue.js is used to create an interactive interface for the user to input numbers, select an operation, and trigger the calculation. Here’s how Vue.js interacts with Ruby WASM:
-
User Input: The user enters two numbers and selects an operation (add, subtract, multiply, or divide) using Vue's two-way binding with
v-model
. -
Triggering Calculation: When the "Calculate" button is clicked, Vue calls the
calculate()
method, which interacts with Ruby WASM. -
Waiting for WASM: Vue ensures that Ruby WASM is fully loaded before calling the
calculate()
method. If Ruby WASM isn’t initialized yet, the app waits and displays a message to the user. -
Fetching Result: Once Ruby WASM is ready, the
calculate()
function is called with the user inputs, and the result is returned back to Vue.js. If there's an error (e.g., division by zero), it’s caught and displayed in the UI.
Handling Errors
Ruby WASM allows us to raise errors, such as division by zero, and Vue.js handles these errors gracefully. The result or error message is displayed dynamically in the UI, giving the user immediate feedback.
Example Flow
- The user inputs numbers, selects an operation, and clicks the "Calculate" button.
- Vue checks if Ruby WASM is initialized.
- The Ruby
calculate
function is invoked, performing the requested operation. - The result is displayed on the page, or an error message appears if something went wrong.
This integration allows us to offload computational logic to Ruby while maintaining an interactive and responsive UI with Vue.js.
Try It Yourself
You can try the Ruby WASM calculator in your browser by visiting the following link:
Source Code
You can view the full source code for this project on GitHub:
Top comments (0)