DEV Community

loading...

A StimulusReflex Sandbox on Glitch

julianrubisch
・4 min read

Earlier this month, @hopsoft published a ~75LOC single-file Rails app that demonstrates a working StimulusReflex-powered mini-app. It's an impressive feat of software engineering... but without a real-world use case, it's just a curiosity.

I was able to adapt sr_mini to work on Glitch, with the express goal of providing a way to embed a live StimulusReflex or CableReady app into a blog post. This gadget invites people to interact with the SR/CR stack and see the code behind working examples. Best of all, they won't have to worry about cloning a repo or security concerns.

This post first introduces stimulus-reflex-sandbox, then goes into specific detail on how it came together.

Why make a StimulusReflex sandbox?

We've had a StimulusReflex Expo site for a few years, but it requires that people go to a different site. Often, it's not clear if people fully understand that they are using a fully server-rendered Rails app that is delivering updates via WebSockets. Many people who visit are not yet able to take the code snippets presented and immediately understand how it works or how they could use it themselves. Sandbox examples are great because they are minimalist and direct. It's easy to look at two examples and see what's different.

However, the main reason we're excited is that Glitch apps are "remixable". You can clone your own copy and immediately start experimenting with a feature or technique, live in the browser, without needing to even have Rails configured on your device. Moreover, you can do that without any chance of us exposing ourselves to security risks associated with the execution of arbitrary Ruby code. Everyone wins.

What does the default sandbox show?

Out of the box, stimulus-reflex-sandbox implements a simple counter using the declared Reflex syntax (eg. setting a data attribute on the button) and a Page Morph, which is the default operation mode in StimulusReflex. Every time you click, it runs the Reflex action method, re-runs the page's controller action, re-renders the page, broadcasts it to the client using CableReady and then the client updates only the parts of the DOM that have changed - in this case, the number you are incrementing.

You can learn more about all three Morph Modes in the documentation, or check out this handy flowchart.

How do I develop in the sandbox?

The app is configured in such a way that every change to a Ruby or Javascript file will trigger a restart, and every time you added a Ruby gem or Javascript package will trigger a re-install (see below).

What limitations are there?

Glitch is a JavaScript-first environment, so working with Ruby is not as smooth as it could be. In fact, we install Ruby 3 so that we're not stuck with Ruby 2.3!

There is no database enabled, but you do have Redis available.

How stimulus-reflex-sandbox was made

Fitting the SR/CR + Rails + Redis stack into Glitch's 200MB space constraint was a major challenge. I had to do quite an elaborate limbo dance of installing, zipping, downloading, compiling, unzipping to stay below that limit.

Install Ruby 3.0.1 with Rbenv

Check out Rbenv from Github and install ruby-build

$ git clone https://github.com/rbenv/rbenv.git ~/.rbenv
$ mkdir -p "$(rbenv root)"/plugins
$ git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build
Enter fullscreen mode Exit fullscreen mode

Install Ruby 3.0.1 and symlink it to /app/bin

$ rbenv install 3.0.1
$ ln -s /app/.rbenv/shims/* /app/bin
Enter fullscreen mode Exit fullscreen mode

Remove ri completely since it takes up space and we don't need it. Then, create a tarball from .rbenv because we will need the disk space for the following steps.

$ rm -rf .rbenv/versions/3.0.1/share/ri/*
$ tar cvfz rbenv.tar.gz .rbenv
Enter fullscreen mode Exit fullscreen mode

Configure rubygems not to load documentation to save space:

$ echo "gem: --no-document" > .gemrc
Enter fullscreen mode Exit fullscreen mode

Install Redis from Source

Follow this guide to download and compile redis:

$ wget https://download.redis.io/releases/redis-6.2.2.tar.gz
$ tar xvfz redis-6.2.2.tar.gz
$ rm redis-6.2.2.tar.gz
$ cd redis-6.2.2 && make
Enter fullscreen mode Exit fullscreen mode

Now move the redis server binary to the app's bin folder, then remove the source folder:

$ mv src/redis-server /app/bin
$ cd .. && rm -rf redis-6.2.2
Enter fullscreen mode Exit fullscreen mode

Restore Ruby:

$ tar xvfz rbenv.tar.gz
Enter fullscreen mode Exit fullscreen mode

That's about it. I did have to remove the .cache, .bundle and .local folders a few times when space ran out, though.

Redis Configuration

For this very limited setup, we don't want Redis to dump its data when it's shut down. Also, we need to enfore an eviction policy, otherwise it's going to fill up our RAM quickly:

# .redis.conf
maxmemory 2mb
maxmemory-policy volatile-lru
save ""
appendonly no
Enter fullscreen mode Exit fullscreen mode

Stitching It Together

Being Javascript-first, we need to use a package.json to run our app. This, however, entails that npm install is run automatically and node_modules don't count towards the disk size limit. We use the concurrently package to start both redis and the Rails app:

{
  ...,
  "scripts": {
    "build": "snowpack build",
    "redis": "redis-server .redis.conf",
    "install": "rm -rf tmp .local .bundle && bundle install --no-cache && rm -rf .bundle",
    "start": "concurrently npm:redis \"npm:build && ruby application.rb $PORT\""
  },
  "engines": {
    "node": "14.x"
  },
  ...
}

Enter fullscreen mode Exit fullscreen mode

The watch.json declares what when to trigger a re-install or restart:

{
  "install": {
    "include": ["^Gemfile", "^package.json", "^\\.env$"],
    "exclude": []
  },
  "restart": {
    "include": ["\\.rb$", "\\.js$"],
    "exclude": ["^public/"]
  },
  "throttle": 10000
}
Enter fullscreen mode Exit fullscreen mode

Discussion (0)