Fresco

An application framework for Ruby that compiles to a static binary via Spinel. Two modes from the same source tree:

  • Dev: CRuby with auto-reload — bin/dev.
  • Release: a single static binary, no Ruby on the host — bin/release produces build/app.

A build-time DSL (routes, schema, models, migrations) is captured under CRuby and codegen'd into generated/. The generated tree is what Spinel AOT-compiles for release; CRuby loads the same tree for dev.

For a runnable tour of the framework, see afomera/fresco-example.

Install

gem install fresco

Or in a Gemfile:

gem "fresco"

Requires Ruby >= 3.0 for the dev loop. The release binary has no Ruby runtime dependency on the host machine.

Installing Spinel

fresco dev runs under CRuby and does not need Spinel. fresco release shells out to a spinel binary to AOT-compile your app, so you need Spinel installed before you ship a build.

Spinel doesn't ship a prebuilt binary today — clone and build it from source:

# Pick a checkout location. We'll symlink the resulting binary into your
# Fresco app, so anywhere on disk is fine.
git clone https://github.com/matz/spinel.git
cd spinel

make deps    # fetch and build Spinel's vendored dependencies
make all     # build the spinel binary

That produces a spinel executable in the repo root. fresco release looks for it in this order:

  1. ./spinel in the Fresco app directory (symlink or copy)
  2. ./vendor/spinel/bin/spinel
  3. spinel on $PATH

The simplest setup is a symlink at the app root:

cd path/to/your-fresco-app
ln -s /absolute/path/to/spinel/spinel ./spinel

.gitignore the symlink — the binary is per-machine.

macOS Postgres note

If your app uses the Postgres adapter, fresco release auto-detects Homebrew's keg-only libpq (/opt/homebrew/opt/libpq, /usr/local/opt/libpq) and Postgres.app, and prepends their include/ and lib/ to CPATH / LIBRARY_PATH so Spinel's compile of the libpq shim resolves libpq-fe.h and -lpq without manual export. SQLite builds need no extra setup.

Quick start

fresco new bookshelf
cd bookshelf
bundle install
bin/dev

Open http://localhost:3030/.

To build a release binary:

ln -s /path/to/spinel/spinel ./spinel
bin/release
./build/app

Commands

fresco new <name>    Scaffold a new app in ./<name>/
fresco build         Regenerate generated/* from config + app/
fresco dev           Run the CRuby dev loop (build + watch + serve)
fresco release       Build a Spinel-compiled binary at ./build/app

The scaffolded app's bin/dev, bin/build, and bin/release are thin wrappers around bundle exec fresco <subcommand>.

fresco new flags

--fresco-path <dir>   Pin the generated Gemfile to a path-installed
                      fresco (useful when co-developing fresco itself).

App layout

A Fresco app generated by fresco new looks like:

app/
  action.rb            # base class
  actions/             # request handlers
  models/              # build-time model declarations
  views/               # ERB templates (compiled at build time)
config/
  app.rb               # framework config
  database.rb          # adapter + DSN (omit for no DB)
  routes.rb            # route table
db/
  schema.rb            # table declarations
  migrations/          # versioned SQL migrations
bin/
  dev, build, release  # wrappers for `bundle exec fresco …`
app.rb                 # Spinel entry point

generated/ and build/ are produced by the toolchain and should be gitignored.

How dev and release stay in sync

Both modes load the same generated/ tree. fresco dev re-runs the build step on file changes; fresco release runs it once and hands the result to Spinel. There is no separate "production codepath" to drift — if a route works in dev, it works in the compiled binary (modulo Spinel's Ruby subset, which rubocop_spinel lints for at build time when present in your Gemfile).

Development of the gem itself

bundle install
bundle exec rake build     # → pkg/fresco-<version>.gem
bundle exec rake install   # build + install locally
bundle exec rake release   # tag + push to rubygems.org

When iterating on fresco alongside an app, scaffold the app against your working copy:

fresco new myapp --fresco-path ../fresco

License

MIT.