{"id":51152571,"url":"https://github.com/momiji-rs/sasso","last_synced_at":"2026-06-26T07:03:31.072Z","repository":{"id":362999145,"uuid":"1261565086","full_name":"momiji-rs/sasso","owner":"momiji-rs","description":"A pure-Rust SCSS to CSS compiler (a dart-sass alternative). Zero dependencies, wasm-friendly, lib + CLI.","archived":false,"fork":false,"pushed_at":"2026-06-23T16:40:15.000Z","size":2461,"stargazers_count":1,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-06-23T17:26:42.877Z","etag":null,"topics":["compiler","css","css-preprocessor","dart-sass","embeddable","parser","rust","rust-lang","sass","scss","transpiler","wasm","webassembly"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/momiji-rs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-06T21:32:14.000Z","updated_at":"2026-06-23T16:40:01.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/momiji-rs/sasso","commit_stats":null,"previous_names":["momiji-rs/sasso"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/momiji-rs/sasso","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/momiji-rs%2Fsasso","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/momiji-rs%2Fsasso/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/momiji-rs%2Fsasso/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/momiji-rs%2Fsasso/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/momiji-rs","download_url":"https://codeload.github.com/momiji-rs/sasso/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/momiji-rs%2Fsasso/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34700915,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-23T02:00:07.161Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["compiler","css","css-preprocessor","dart-sass","embeddable","parser","rust","rust-lang","sass","scss","transpiler","wasm","webassembly"],"created_at":"2026-06-26T07:03:30.347Z","updated_at":"2026-06-26T07:03:31.045Z","avatar_url":"https://github.com/momiji-rs.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# sasso\n\n[![crates.io](https://img.shields.io/crates/v/sasso.svg)](https://crates.io/crates/sasso)\n[![docs.rs](https://img.shields.io/docsrs/sasso)](https://docs.rs/sasso)\n[![CI](https://github.com/momiji-rs/sasso/actions/workflows/ci.yml/badge.svg)](https://github.com/momiji-rs/sasso/actions/workflows/ci.yml)\n[![CodSpeed](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://app.codspeed.io/momiji-rs/sasso?utm_source=badge)\n[![sass-spec](https://img.shields.io/badge/sass--spec-100%25_of_attempted-brightgreen)](#conformance)\n[![dart-sass](https://img.shields.io/badge/dart--sass-1.101_parity-blue)](#conformance)\n[![runtime deps](https://img.shields.io/badge/runtime_deps-0-brightgreen)](Cargo.toml)\n[![license](https://img.shields.io/crates/l/sasso.svg)](#license)\n\nA pure-Rust **SCSS → CSS compiler** — a from-scratch dart-sass alternative.\nZero runtime dependencies, wasm-friendly, usable as a **library** and a\n**CLI**, and designed to match **current** dart-sass byte-for-byte on the\nsubset it implements.\n\n\u003e Status: v0.x, maturing fast. Compiles real-world SCSS and indented `.sass`\n\u003e byte-identically to dart-sass 1.101, and **passes 100% of the *attempted*\n\u003e official [sass-spec](https://github.com/sass/sass-spec) suite\n\u003e (13,896 / 13,896, zero failures)** — tracked as a ratchet (see\n\u003e [Conformance](#conformance) for exactly what that denominator means). What\n\u003e remains is real-world breadth and hardening, not spec coverage.\n\n## Why another Sass compiler?\n\n`grass` is the incumbent Rust implementation and a strong one (it compiles\nBootstrap/Bulma byte-accurately and is ~2× faster than dart-sass). But it is\npinned to dart-sass **1.54.3** (mid-2022) and predates the CSS Color Level 4\noverhaul, so it diverges from current dart-sass on, e.g., fractional color\nchannels (`rgb(63.75, 127.5, 191.25)` vs rounded hex) and emits hex where\ndart-sass now keeps `rgb()`/`hsl()` forms. `sasso` targets **current**\ndart-sass exactly, with a span-first parser, a modern color model, and a\nzero-dependency, sandbox-friendly core. See\n[`docs/GRASS_LANDSCAPE.md`](docs/GRASS_LANDSCAPE.md) for the full analysis.\n\n## Features (this slice)\n\n- `$variables`, lexical scoping, `!default`, `!global`\n- Nesting, the `\u0026` parent selector (with selector-list multiplication), and\n  combinator normalization (`\u003e`, `+`, `~`)\n- `#{}` interpolation in selectors, property names and values\n- `//` (stripped) and `/* */` (preserved) comments\n- Numbers with units and unit arithmetic (`$pad * 2 → 16px`)\n- A full color model with fractional channels + author-spelling preservation\n  (`red`, `#336699`, `rgb()`/`hsl()` round-trip unchanged)\n- Color functions: `rgb`/`rgba`/`hsl`/`hsla`/`mix`/`lighten`/`darken`/\n  `percentage` (+ `red`/`green`/`blue`/`alpha`)\n- `@import` partial inlining through a pluggable [`Importer`] (CSS imports\n  pass through)\n- `expanded` and `compressed` output styles\n- **Source maps (v3)** — byte-exact to dart-sass 1.101, on the library\n  (`compile_with_source_map`), the CLI (`--source-map`), and wasm\n  (`compile(scss, { sourceMap: true })`)\n- Verbatim preservation of CSS functions it doesn't own (`calc`, `var`,\n  `clamp`, `translateX`, …)\n\nSince this slice was written the ratchet has added a great deal more —\n`@mixin`/`@function`, control flow (`@if`/`@each`/`@for`/`@while`), `@extend`\nand `%placeholder`s, a `calc()` engine, the CSS unit system + math functions,\nfull CSS Color 4 color spaces (`oklch`/`lab`/`color()`…), structured\n`@media`/`@supports`, maps, the `@use`/`@forward` module system (built-in\n`sass:*` modules + user files), and the indented `.sass` syntax. **The\ncompiler now passes 100% of the attempted sass-spec suite (13,896 / 13,896,\nzero failures)** byte-for-byte against dart-sass 1.101 — 11,405 byte-exact CSS\noutputs plus 2,491 error specs it correctly rejects (see\n[Conformance](#conformance)).\n\n## Install\n\n**CLI — prebuilt binaries.** Every release ships static binaries for Linux\n(gnu + musl), macOS and Windows (x86_64 / aarch64), built with\n[cargo-dist](https://github.com/axodotdev/cargo-dist). Grab one from the\n[Releases](https://github.com/momiji-rs/sasso/releases) page, or:\n\n```console\n$ curl -fsSL https://github.com/momiji-rs/sasso/releases/latest/download/sasso-installer.sh | sh   # Linux/macOS\n$ cargo binstall sasso        # fetch the prebuilt binary\n$ cargo install sasso         # build from source (needs a Rust toolchain)\n```\n\n**Library — crates.io.**\n\n```console\n$ cargo add sasso\n```\n\n**WebAssembly — npm.** A tiny, dependency-free wasm build that mirrors the\ndart-sass *modern* JS API, so it's a drop-in for the `sass` npm package in build\ntools (no wasm-bindgen, no native add-ons):\n\n```console\n$ npm install sasso\n```\n\n```js\nimport { compileString } from \"sasso\";\ncompileString(\"a { color: #ffffff }\", { style: \"compressed\" }).css; // a{color:#fff}\n```\n\nWorks as the `sass` implementation in **webpack/sass-loader** (`implementation:\nrequire(\"sasso\"), api: \"modern\"`) and **Vite** (alias `\"sass\": \"npm:sasso\"`).\n\n## Library usage\n\n```rust\nuse sasso::{compile, Options, OutputStyle};\n\nlet scss = \"$c: #336699; .a { color: $c; \u0026:hover { color: lighten($c, 10%); } }\";\nlet css = compile(scss, \u0026Options::default()).unwrap();\nassert!(css.contains(\"a:hover\"));\n\n// Minified:\nlet min = compile(scss, \u0026Options::default().with_style(OutputStyle::Compressed)).unwrap();\n```\n\n`@import` resolution is controlled by an [`Importer`] you supply, so file\naccess stays on your side of any sandbox:\n\n```rust\nuse sasso::{compile, Importer, Options};\n\nstruct MyFs;\nimpl Importer for MyFs {\n    fn resolve(\u0026self, path: \u0026str) -\u003e Option\u003cString\u003e {\n        std::fs::read_to_string(format!(\"scss/_{path}.scss\")).ok()\n    }\n}\nlet css = compile(\"@import \\\"base\\\";\", \u0026Options::default().with_importer(\u0026MyFs)).unwrap();\n```\n\nA ready-made [`FsImporter`] is provided for standalone/CLI use.\n\n## CLI usage\n\n```console\n$ cargo install --path .            # installs the `sasso` binary\n$ sasso input.scss              # CSS to stdout (expanded)\n$ sasso --style=compressed input.scss\n$ sasso -I scss/ main.scss      # add @import load paths\n$ echo '.a{color:red}' | sasso --stdin\n```\n\n## Conformance\n\nThe official sass-spec suite is the parity oracle. The harness in\n[`spec/`](spec/) runs the compiler against every spec case and reports a\npass rate; we ratchet it upward over time.\n\n| Metric | Value |\n| --- | --- |\n| sass-spec commit | `1b03109a` (dart-sass 1.101.0) |\n| Total cases | 13,904 |\n| Attempted (excl. 8 dart-sass `:todo`) | 13,896 |\n| **Passing** | **13,896 — 100% of attempted · 0 failures** (99.94% of all 13,904) |\n| ↳ byte-exact CSS output | 11,405 |\n| ↳ error specs correctly rejected | 2,491 |\n\n*Passing* = byte-exact CSS output match **plus** error specs the compiler\ncorrectly rejects — the standard sass-spec conformance metric (the harness\nchecks that an error spec errors; the error *message* is tracked separately as\na non-gating metric). The 8 excluded cases are tagged `:todo` for **dart-sass\nitself** upstream — dart-sass doesn't pass them either; sasso matches\ndart-sass's actual behaviour on all 8 regardless.\n\n**Strict input validation, too.** Matching dart-sass means rejecting what\ndart-sass rejects, not just reproducing its output. sasso errors — rather than\nsilently accepting — on an invalid hex literal (`#00000`), out-of-grammar\n`rgb()`/`hsl()` arguments, a duplicate `@mixin`/`@function` parameter, a\nmisplaced `@content`/`@extend`, a style rule or declaration inside a\n`@function` body, a malformed `:nth-child()` / empty `:not()` selector, a bad\n`@charset` or `@at-root (…)` query, and more — each with dart-sass's exact\nmessage.\n\nRun it yourself:\n\n```console\n$ spec/fetch.sh                                      # clone the suite\n$ cargo build --release\n$ SASS_BIN=target/release/sasso python3 spec/run_spec.py\n```\n\n## Performance\n\n`sasso` is a native, in-process library — no subprocess, no Node, no Dart VM —\nso startup is effectively free, which dominates when a build compiles many\nfiles. On an Apple M2 Max it is the **fastest** of the three engines measured,\nbeating dart-sass by 19–30× end-to-end and leading `grass` (the incumbent Rust\ncompiler) by ~2.3–2.9×:\n\n| Axis | sasso | grass | dart-sass (bin) | npx sass |\n| --- | --- | --- | --- | --- |\n| Startup² | **1.8 ms** | 1.8 ms | 139 ms | 495 ms |\n| Cold single large file | **11.7 ms** | 26.5 ms | 354 ms | 710 ms |\n| Batch (40 files, 1 process) | **49.0 ms** | 136 ms | 916 ms | — |\n| Pure compile (startup removed) | **7.4 ms** | 21.3 ms | ~216 ms¹ | — |\n\n¹ derived (cold − startup) — dart-sass has no in-process loop mode. So sasso is\n~29× faster than dart-sass on **pure compute**, ~30× on a cold single file, and\n~77× on startup; vs `grass` it is ~2.3× cold / ~2.8× batch / ~2.9× pure.\n\n² Startup compiles a 1-rule file, so it sits at the OS process-spawn floor —\nsasso and grass measure **identically (1.8 ms)** here (the *mean* is dominated\nby scheduler jitter at this sub-2 ms scale). The native library and wasm builds\nremove process startup entirely. A\n**scoped bump-arena allocator** (one audited `unsafe` module, Miri- and\nAddressSanitizer-verified; the rest of the library stays `unsafe`-free) gives a\nfurther ~1.5× by turning each compile's allocations into a pointer bump freed\nwholesale at the end. Composite values (strings, lists, maps) are\nreference-counted, so reading a `$variable` is an O(1) refcount bump, not a\ndeep copy — on a large list passed through a call chain without mutation this\ncuts both instructions (~7×) and peak memory (~13×). A round of evaluator\nallocation trimming — skipping the per-rule selector clone when nothing extends\nit, iterating `@each` over the list's shared handle, and dropping redundant\nper-declaration copies — shaves a further ~2.8% off pure compile on\nrepresentative stylesheets (measured by instructions-retired, since the win is\nbelow wall-clock jitter at this ms scale; byte-identical output). Full\nmethodology, per-file numbers and the correctness diff are in\n[`bench/three_way.md`](bench/three_way.md); run it yourself with\n`cd bench \u0026\u0026 RUNS=12 WARMUP=3 LOOP_N=200 bash scripts/run_bench.sh`.\n\n## WebAssembly\n\nBecause the library is zero-dependency and pure `std`, it compiles to\n`wasm32-unknown-unknown` and `wasm32-wasip1` out of the box (built in CI). The\ndeployable `.wasm` cdylib ships in two variants, published to npm as\n[`sasso`](https://www.npmjs.com/package/sasso):\n\n| Variant | Build | Over the wire | Compile (large, in Node)³ |\n| --- | --- | --- | --- |\n| **size** (default) | `opt-level = \"z\"` + LTO + `panic = \"abort\"` + `strip` + `wasm-opt -Oz` | **~854 KB / ~356 KB gzip** | ~27 ms |\n| **speed** (`sasso/speed`) | `opt-level = 3` + `wasm-opt -O3` | **~1.84 MB / ~637 KB gzip** | ~12 ms |\n\n³ in-process compile of the same large file, Node 22 (best-of-N). The wasm tax\nover native `sasso` (7.7 ms) is ~1.5× for the speed build and ~3.5× for the\nsize build; the wasm build runs without the bump arena. Even so the **speed\nbuild (~12 ms) beats native `grass` (21 ms)** and every dart-sass form a Node\ntoolchain can run.\n\nA whole modern Sass compiler — `@use`/`@forward`, `@extend`, the calc engine,\nCSS Color 4 — in a few hundred KB gzipped, far smaller than shipping the\ndart-sass compiler as JavaScript. A browser playground is tracked in the issues.\n\n## Language bindings\n\nsasso ships as a Rust crate and is usable from other languages too. **First-party**\npackages are released by this project and pin a published `sasso` crate version;\n**community** bindings are maintained in their own repos.\n\n| Language | Package | Maintained by | How |\n| --- | --- | --- | --- |\n| **Rust** | [`sasso`](https://crates.io/crates/sasso) (crates.io) | First-party | the core library — see [Library usage](#library-usage) above |\n| **JavaScript / wasm** | [`sasso`](https://www.npmjs.com/package/sasso) (npm) | First-party | the in-repo [`wasm/`](wasm/) cdylib — see [WebAssembly](#webassembly) above |\n| **Ruby** | [`sasso`](https://rubygems.org/gems/sasso) (RubyGems) | First-party | [`momiji-rs/sasso-ruby`](https://github.com/momiji-rs/sasso-ruby) — an in-process native extension (`magnus` + `rb-sys`) around this crate |\n| **Python** | [`sasso`](https://pypi.org/project/sasso/) (PyPI) | First-party | [`momiji-rs/sasso-python`](https://github.com/momiji-rs/sasso-python) — `ctypes` over the C ABI; one prebuilt wheel per platform, no build step |\n| **Go** | [`sasso-go`](https://github.com/momiji-rs/sasso-go) (`go get`) | First-party | [`momiji-rs/sasso-go`](https://github.com/momiji-rs/sasso-go) — **pure Go, no cgo**: embeds the [wasm build](#webassembly) and runs it with [`wazero`](https://github.com/tetratelabs/wazero), so `CGO_ENABLED=0` and cross-compilation just work. String-in/CSS-out (for file-based importers use the C ABI via cgo, [below](#from-go)) |\n| **PHP** | [`shyim/php-sasso`](https://github.com/shyim/php-sasso) (PIE / pecl) | Community ([@shyim](https://github.com/shyim)) | an [ext-php-rs](https://github.com/davidcole1340/ext-php-rs) extension (`Sasso\\Compiler`) wrapping this crate in-process; prebuilt for PHP 8.2–8.5 (Linux glibc/musl, macOS) |\n\n**Ruby framework integrations** build on that gem — drop-in Sass for your stack,\ncompiled **in-process** (no Node, no Dart, no subprocess) and byte-for-byte\nidentical to dart-sass:\n\n| Framework | Gem | Repo |\n| --- | --- | --- |\n| **Rails** (Propshaft + Sprockets) | [`sasso-rails`](https://rubygems.org/gems/sasso-rails) | [`momiji-rs/sasso-rails`](https://github.com/momiji-rs/sasso-rails) |\n| **Bridgetown** | [`bridgetown-sasso`](https://rubygems.org/gems/bridgetown-sasso) | [`momiji-rs/bridgetown-sasso`](https://github.com/momiji-rs/bridgetown-sasso) |\n| **Hanami** (2.1+) | [`hanami-sasso`](https://rubygems.org/gems/hanami-sasso) | [`momiji-rs/hanami-sasso`](https://github.com/momiji-rs/hanami-sasso) |\n\nEach compiles Sass without a Node toolchain — typically ~6–7× faster per compile\nthan the Node `sass` default (and far faster cold, with no process spawn).\n\n## C ABI — use sasso from any language\n\nBeyond the packages above, sasso ships a **C ABI** ([`ffi/`](ffi/)) so any\nlanguage with a C FFI can drive the compiler in-process. Each\n[release](https://github.com/momiji-rs/sasso/releases) attaches a per-target\n**`sasso-\u003cversion\u003e-\u003ctarget\u003e-c-api.tar.xz`** (`.zip` on Windows) containing the\nprebuilt library and the header — the universal substrate every binding sits on:\n\n```\ninclude/sasso.h\nlib/  libsasso.a            # static — link it for a self-contained binary, no runtime dep\n      libsasso.so|.dylib    # dynamic (Windows: sasso.dll + sasso.dll.lib + sasso.lib)\n```\n\nThe ABI is two owned calls plus an optional importer callback — see\n[`ffi/include/sasso.h`](ffi/include/sasso.h), the contract notes there, and\nrunnable bindings for **8 languages** under\n[`ffi/examples/`](ffi/examples/) (C, Go, Ruby, Swift, Deno, Bun, LuaJIT, C#).\n\n### From Go\n\nStatically link `libsasso.a` via cgo for a single self-contained binary\n(no `CGO_ENABLED=0`, but no runtime dependency either):\n\n```go\npackage main\n\n/*\n#cgo CFLAGS: -I./sasso-c-api/include\n#cgo LDFLAGS: ./sasso-c-api/lib/libsasso.a\n#include \u003cstdlib.h\u003e\n#include \"sasso.h\"\n*/\nimport \"C\"\nimport (\n\t\"fmt\"\n\t\"unsafe\"\n)\n\nfunc main() {\n\tsrc := \".a { .b { color: #336699 } }\"\n\tcs := C.CString(src)\n\tdefer C.free(unsafe.Pointer(cs))\n\tr := C.sasso_compile(cs, C.size_t(len(src)), nil)\n\tdefer C.sasso_result_free(r)\n\tif r.ok == 0 {\n\t\tpanic(C.GoString(r.error))\n\t}\n\tfmt.Print(C.GoStringN(r.css, C.int(r.css_len)))\n}\n```\n\nA fuller cgo binding — options, errors, and a custom importer — is in\n[`ffi/examples/go/`](ffi/examples/go/). Prefer no cgo? The first-party\n**[`sasso-go`](https://github.com/momiji-rs/sasso-go)** package is ready-made:\n`go get` it and it embeds the [wasm build](#webassembly) and runs it with\n[`wazero`](https://github.com/tetratelabs/wazero), keeping `CGO_ENABLED=0` and\ntrivial cross-compilation (string-in/CSS-out; use the cgo path above when you\nneed file-based importers). Or `dlopen` the dynamic lib at runtime with\n[`purego`](https://github.com/ebitengine/purego).\n\n## Testing \u0026 coverage\n\n```console\n$ cargo test                                  # unit + integration + doctests (offline)\n$ SASSO_PARITY=1 cargo test --test parity # live diff vs dart-sass (needs `npx sass`)\n$ cargo llvm-cov --workspace                  # coverage report\n$ cargo clippy --all-targets -- -D warnings\n$ cargo fmt --check\n```\n\n## Changelog\n\nNotable changes are recorded in [`CHANGELOG.md`](CHANGELOG.md).\n\n## Code of Conduct\n\nAs a Sass implementation, sasso adopts the\n[Sass Community Guidelines](https://sass-lang.com/community-guidelines/) — see\n[`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md).\n\n## License\n\nLicensed under either of [Apache-2.0](LICENSE-APACHE) or [MIT](LICENSE-MIT)\nat your option.\n\n[`Importer`]: https://docs.rs/sasso\n[`FsImporter`]: https://docs.rs/sasso\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmomiji-rs%2Fsasso","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmomiji-rs%2Fsasso","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmomiji-rs%2Fsasso/lists"}