DEV Community

RAHUL DHOLE
RAHUL DHOLE

Posted on

Using Ruby WASM with Vue.js for Client-Side Computation

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:

  1. Ruby Functions: The Calculator class contains methods like add, subtract, multiply, and divide. These methods take two numbers and an operation string as input and return the result.
  2. Exposing Ruby Functions: We use JS.global[:calculate] to expose the Ruby calculate 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:

  1. 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.
  2. Triggering Calculation: When the "Calculate" button is clicked, Vue calls the calculate() method, which interacts with Ruby WASM.
  3. 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.
  4. 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:

Try the Ruby WASM Calculator

Source Code

You can view the full source code for this project on GitHub:

Source Code on GitHub

Neon image

Serverless Postgres in 300ms (!)

10 free databases with autoscaling, scale-to-zero, and read replicas. Start building without infrastructure headaches. No credit card needed.

Try for Free →

Top comments (0)

Jetbrains image

Build Secure, Ship Fast

Discover best practices to secure CI/CD without slowing down your pipeline.

Read more

👋 Kindness is contagious

Engage with a wealth of insights in this thoughtful article, valued within the supportive DEV Community. Coders of every background are welcome to join in and add to our collective wisdom.

A sincere "thank you" often brightens someone’s day. Share your gratitude in the comments below!

On DEV, the act of sharing knowledge eases our journey and fortifies our community ties. Found value in this? A quick thank you to the author can make a significant impact.

Okay