intertwingly

It’s just data

Rails to the Edge and Beyond


Rails remains the fastest way to go from idea to working application. Convention over configuration, omakase defaults, a decade of refinements—nothing else matches its productivity for CRUD applications.

Serverless is the future of deployment. No servers to manage, automatic scaling, pay-per-request pricing, global distribution. But serverless usually means rewriting for proprietary platforms—and once you're in, switching is costly.

Rails requires Ruby. Serverless platforms run JavaScript. Until now, you had to choose.

What if you didn't have to choose?


Ruby2JS and the Transpilation Spectrum

Ruby runs in more places than ever. Each implementation makes different tradeoffs between semantic fidelity and ecosystem integration:

Implementation Semantics Ecosystem
CRuby Reference Ruby C extensions
JRuby Minor threading differences JVM libraries
Ruby WASM Full Ruby semantics Awkward JS interop
Opal Minor behavior differences Ruby-ish JS abstractions
Ruby2JS No metaprogramming Native JavaScript

Ruby2JS produces JavaScript that is JavaScript. A Ruby array becomes a JS array. A Ruby class becomes a JS class. You lose runtime metaprogramming—no method_missing—but you gain seamless access to the JavaScript ecosystem without impedance mismatch.


Introducing Juntos

Juntos is a set of Ruby2JS filters and a small runtime that implement a Rails-compatible framework for JavaScript platforms.

Rails is built on metaprogramming. Write has_many :comments and Rails generates methods at runtime. Juntos inverts this: instead of runtime generation, filters pre-compute what Rails would generate at transpile time. The Rails DSL is finite and declarative—filters recognize every pattern and expand it statically.

The result: idiomatic JavaScript that runs anywhere.

# app/models/article.rb
class Article < ApplicationRecord
  has_many :comments, dependent: :destroy
  validates :title, presence: true
end

becomes:

// dist/app/models/article.js
class Article extends ApplicationRecord {
  static associations = { comments: { type: "hasMany", dependent: "destroy" } };
  static validations = { title: { presence: true } };
}

Same models. Same controllers. Same ERB templates. Different programming language.


The Demo

Create the classic Rails blog—articles with nested comments, validations, Tailwind CSS:

curl -sL https://raw.githubusercontent.com/ruby2js/ruby2js/master/test/blog/create-blog | bash
cd blog

Run it with Rails:

bin/rails db:prepare
bin/rails server

Run it in the browser with IndexedDB:

bin/juntos dev -d dexie

Run it on Node.js with SQLite:

bin/juntos migrate -d sqlite
bin/juntos up -d sqlite

Deploy to Vercel Edge with Neon:

bin/juntos migrate -d neon
bin/juntos deploy -d neon

Deploy to Cloudflare Workers with D1:

bin/juntos migrate -d d1
bin/juntos deploy -d d1

Five runtimes. Same code. The database determines the deployment target—Neon implies Vercel, D1 implies Cloudflare. In practice, you configure config/database.yml per environment and just use -e:

development:
  adapter: dexie
production:
  adapter: neon
bin/juntos deploy -e production

The Juntos documentation has the full tutorial with prerequisites and deployment guides.


Different Platforms, Different Capabilities

Juntos currently targets six JavaScript platforms. The same code runs on all of them, but they aren't equivalent:

Platform Strengths Constraints
Browser Full sourcemaps, offline-capable, no server needed No SMTP, no server-side APIs
Node/Bun/Deno Full capabilities + ecosystem (Puppeteer, LangChain, TensorFlow) Requires hosting
Vercel/Cloudflare Global edge, auto-scaling, pay-per-request No filesystem, no jobs, no websockets

For a CRUD blog, any platform works—pick based on deployment preferences. For applications that need browser automation, AI/ML pipelines, or background processing, these differences guide your choice.


Why This Matters

No lock-in. The generated dist/ directory is a complete JavaScript application. You could fork it and continue development in pure JS—no Ruby required. The output isn't compiled bytecode; it's readable code you can understand and modify. Or stay in Ruby and switch from Browser to Node to Vercel without changing any code.

Rails fidelity. Other frameworks are Rails-inspired. Juntos aims for Rails patterns exactly. Rails developers feel at home. Rails documentation mostly applies.

Multi-target. No other approach offers the same code running in browsers with IndexedDB, on Node.js with SQLite, and on edge functions with serverless databases.

The filters handle the translation—different runtime APIs, different database bindings, different deployment configs. Same Rails code.


What's Possible

This is early. Rough edges remain. But the core works—not as a proof of concept, but as deployable applications. The architecture is proven; what remains is polish and expanding coverage.

The architecture extends beyond what's implemented today. An Action Mailer filter could transform UserMailer.welcome(@user).deliver_later into Resend or SendGrid API calls. An Active Storage filter could transform @article.image.attach(params[:image]) into Cloudflare R2 or Vercel Blob uploads. A Vite plugin could make Ruby a first-class frontend language—hot module replacement, tree shaking, and integration with Vue, Svelte, or Astro components.

The Rails DSL becomes the abstraction; the JavaScript ecosystem provides the implementation. See the roadmap for what's possible.


Learn More

Juntos—Spanish for "together"—is what emerges when Ruby2JS transpiles Rails patterns. Ruby and JavaScript. Rails conventions and serverless platforms. Your productivity and the modern deployment story. Together—you don't have to choose.


Ruby2JS is open source: github.com/ruby2js/ruby2js