sasso (Ruby)
In-process SCSS / Sass → CSS compilation for Ruby, backed by sasso — a pure-Rust, dependency-free dart-sass alternative that targets byte-for-byte parity with current dart-sass. Shipped as a native extension (Rust via magnus + rb-sys); no Node, no subprocess, no Dart VM.
This gem is the Ruby binding. The compiler core lives in the separate
momiji-rs/sassorepo (crate on crates.io); this gem pins it exactly and carries its version number.
Install
# Gemfile
gem "sasso"$ bundle installRuby 3.2, 3.3, 3.4 and 4.0 are supported, and CI tests each of them on Linux and macOS. Ruby 4.0 needs sasso 0.14.1 or later.
Precompiled native gems for all four Ruby versions ship for these platforms, so they need no Rust toolchain:
| OS | platforms |
|---|---|
| Linux (glibc) |
x86_64-linux, aarch64-linux
|
| Linux (musl) |
x86_64-linux-musl, aarch64-linux-musl
|
| macOS |
x86_64-darwin, arm64-darwin
|
| Windows | x64-mingw-ucrt |
On any other platform the gem compiles from source with cargo, which needs a
Rust toolchain.
Usage
require "sasso"
Sasso.compile_string("$c: #336699; a { color: $c; &:hover { color: red } }")
# => "a {\n color: #336699;\n}\na:hover {\n color: red;\n}\n"
# Minified:
Sasso.compile_string("a { b: 1px }", style: :compressed) # => "a{b:1px}"
# Indented .sass syntax:
Sasso.compile_string("a\n b: 1px\n", indented: true)
# A file (syntax inferred from the extension; diagnostics get the file name):
Sasso.compile("app/assets/stylesheets/application.scss",
style: :compressed, load_paths: ["vendor/stylesheets"])Options (compile_string / compile)
| Option | Default | Meaning |
|---|---|---|
style: |
:expanded |
:expanded or :compressed
|
syntax: |
:scss |
:scss, :sass, or :css
|
indented: |
false |
shorthand for syntax: :sass
|
load_paths: |
[] |
directories searched for @use/@forward/@import
|
url: |
nil |
filename shown in diagnostics (enables the rich dart-style error block) |
alert_ascii: |
false |
ASCII-only diagnostics |
charset: |
true |
prefix non-ASCII output with @charset "UTF-8"; (a BOM when compressed) |
source_map: |
false |
return a Sasso::CompileResult instead of a String |
source_map_include_sources: |
false |
embed each source's text in the map's sourcesContent
|
quiet: |
false |
print no @warn/@debug/deprecation diagnostics |
quiet_deps: |
false |
drop deprecation warnings raised inside dependencies |
silence_deprecations: |
[] |
deprecation ids (Strings or Symbols) whose warnings are dropped |
on_warn: |
nil |
a callable receiving each diagnostic; replaces the stderr printing |
Source maps
With source_map: true the return value is a Sasso::CompileResult — #css is
the same String you would get otherwise, and #source_map is a parsed Source Map
v3 Hash. Pass url: so the map can name the entry stylesheet.
r = Sasso.compile_string(scss, source_map: true, url: "application.scss")
r.css # => "a {\n color: red;\n}"
r.source_map["version"] # => 3
r.source_map["sources"] # => ["application.scss"]Diagnostics
@warn, @debug and deprecation warnings print to $stderr by default, the
same dart-style block the sasso CLI and dart-sass print. To change that:
# Silence them entirely:
Sasso.compile_string(scss, quiet: true)
# Silence only what dependencies deprecate (dart-sass `quietDeps`) — files
# resolved through a load path. The entry stylesheet's own still print.
Sasso.compile_string(scss, load_paths: ["vendor/stylesheets"], quiet_deps: true)
# Silence particular deprecations by id (dart-sass `silenceDeprecations`),
# wherever they are raised. Every other diagnostic still prints.
Sasso.compile_string(scss, silence_deprecations: [:import, "color-functions"])
# Or take delivery yourself, which suppresses the printing:
Sasso.compile_string(scss, url: "in.scss", on_warn: ->(d) {
Rails.logger.warn(d[:formatted]) # the block the compiler would have printed
})Each diagnostic is a Hash of Sasso::WARNING_KEYS: :kind (:warn/:debug),
:deprecation, :deprecation_id, :message, :formatted, :url, :line and
:path. :url is dart's display form of the file; :path identifies it (the
importer's canonical path), which is what distinguishes a dependency from the
entry stylesheet. quiet: and on_warn: are mutually exclusive.
Two things to know about what on_warn: receives:
- A compile that warns and then fails delivers its warnings first and raises
Sasso::CompileErrorafter, so a diagnostic is never lost to the error — the callable is the only thing reporting them once it has taken over. If the callable itself raises, theCompileErrorstill wins and carries that exception as its#cause. - Like dart-sass, the compiler caps a repeated deprecation at five per id and
then emits one summary diagnostic —
"N repetitive deprecation warnings omitted."— with no:deprecation_id, no:urland:lineof0. Code that keys on the id or formats"#{d[:url]}:#{d[:line]}"should expect it. There is no verbose mode that lifts the cap; the compiler core does not offer one.
Versions
Since 0.14.0 the gem version tracks the core compiler crate it bundles: gem
0.14.0 pins crate 0.14.0. A gem-only fix takes the next patch, so Sasso::VERSION
may sit ahead of the crate within a minor — Sasso::CORE_VERSION reads the
version out of the linked binary and is the authority on what is loaded.
Sasso::VERSION # => "0.14.0" the gem
Sasso::CORE_VERSION # => "0.14.0" the compiler actually linked inErrors
A compile failure raises Sasso::CompileError (a Sasso::Error < StandardError)
whose #message is the compiler's full diagnostic — the same text the sasso
CLI prints.
begin
Sasso.compile_string("a { b: 1px + 1em }")
rescue Sasso::CompileError => e
warn e.message
endFramework integrations
Using a Ruby web framework? These drop-in gems build on this one and compile
your Sass/SCSS in-process (no Node, no Dart, no subprocess), byte-for-byte
identical to dart-sass — typically ~6–7× faster per compile than the Node sass
default:
-
Rails (Propshaft + Sprockets) —
sasso-rails -
Bridgetown —
bridgetown-sasso -
Hanami (2.1+) —
hanami-sasso
Conformance
The core passes 100% of the attempted official sass-spec suite
byte-for-byte against dart-sass; see the
core repo. For the inputs
below, sasso's output is byte-identical to sass-embedded (dart-sass).
Performance
Because sasso compiles in-process (a direct Rust call — no subprocess, no
IPC, no Dart VM), it avoids the per-call protocol roundtrip of sass-embedded
and the process-spawn cost paid by any out-of-process compiler.
Compiling the same SCSS (variables, nesting, @for, math) on an Apple M2 Max,
Ruby 3.4.1, against sass-embedded 1.104.1 and sassc 2.4.0:
sasso (this gem) |
sass-embedded (dart-sass) |
sassc (libsass) |
|
|---|---|---|---|
| Warm — small component (256 B) | 13.3 µs | 125 µs (9.4×) | 1148 µs (87×) |
| Warm — ~180 rules (5.5 KB) | 215 µs | 906 µs (4.2×) | 10234 µs (48×) |
Cold start (require + first compile) |
3.2 ms | 40.6 ms (12.7×) | 37.0 ms (12×) |
Parenthesised values are how much slower the other gem is than sasso.
-
Per-request compiling (e.g. a Sinatra route): in-process latency is ~13 µs
vs ~125 µs for
sass-embedded's pipe roundtrip to its Dart subprocess. -
One-shot builds (e.g.
rails assets:precompile): the dominant cost is the ~41 ms Dart subprocess spawn, whichsassodoes not pay (~3 ms cold).
The engine is also heavily perf-tuned (a scoped bump arena, reference-counted values). Numbers are representative of one machine; run your own with your stylesheets.
License
MIT, matching the Sass ecosystem (the core sasso compiler crate remains
dual-licensed MIT OR Apache-2.0).