PHPackages                             shyim/sasso-ffi - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. shyim/sasso-ffi

ActiveComposer-plugin[Utility &amp; Helpers](/categories/utility)

shyim/sasso-ffi
===============

Pure-PHP FFI polyfill for ext-sasso (shyim/sasso), the pure-Rust SCSS to CSS compiler, with a Composer plugin that downloads the matching native library

0.1.0(today)09↑2900%1MITPHPPHP &gt;=8.2CI passing

Since Jul 30Pushed today1 watchersCompare

[ Source](https://github.com/shyim/php-sasso-ffi)[ Packagist](https://packagist.org/packages/shyim/sasso-ffi)[ Docs](https://github.com/shyim/sasso-ffi)[ RSS](/packages/shyim-sasso-ffi/feed)WikiDiscussions main Synced today

READMEChangelog (1)Dependencies (3)Versions (2)Used By (1)

shyim/sasso-ffi
===============

[](#shyimsasso-ffi)

A pure-PHP **polyfill for [ext-sasso](https://github.com/shyim/php-sasso)**, the PHP extension that compiles SCSS/Sass → CSS with the pure-Rust [sasso](https://github.com/shyim/sasso) compiler.

Same classes, same constants, same method signatures — implemented over PHP FFI against sasso's C ABI instead of a compiled extension. Write against `Sasso\Compiler` once and it runs whether or not the extension is installed.

```
composer require shyim/sasso-ffi
```

```
use Sasso\Compiler;

$css = (new Compiler())
    ->setStyle(Compiler::STYLE_COMPRESSED)
    ->addImportPath(__DIR__ . '/scss')
    ->compile('@use "base"; .x { color: base.$brand; }');
```

Requires PHP &gt;= 8.2 and `ext-ffi`. No `node`, no `sass` binary, no build step.

Polyfill, not an alternative
----------------------------

[](#polyfill-not-an-alternative)

This package **conflicts** with both `ext-sasso` and `shyim/sasso`:

```
"conflict": {
    "ext-sasso": "*",
    "shyim/sasso": "*"
}
```

If the native extension is present, Composer refuses to install the polyfill — the extension already provides these classes, and two definitions of `Sasso\Compiler` cannot coexist:

```
Problem 1
  - ext-sasso is present at version 0.2.0 and cannot be modified by Composer
  - shyim/sasso-ffi dev-main conflicts with ext-sasso *.

```

The intended pattern is to depend on the polyfill and let anyone who has the extension satisfy the same API natively — so prefer the extension where you can install it, and fall back to this everywhere else (shared hosting, a base image you don't control, an environment without a compiler).

API
---

[](#api)

Identical to ext-sasso's stubs.

```
namespace Sasso;

class Compiler {
    const STYLE_EXPANDED = 0;
    const STYLE_COMPRESSED = 1;
    const SYNTAX_SCSS = 0;
    const SYNTAX_SASS = 1;
    const SYNTAX_CSS = 2;

    public function setStyle(int $style): static;
    public function setSyntax(int $syntax): static;
    public function setUnicode(bool $unicode): static;
    public function setUrl(?string $url = null): static;
    public function addImportPath(string $path): static;
    public function setImportPaths(array $paths): static;
    public function setImporter(mixed $importer): static;   // ?Sasso\Importer
    public function compile(string $source): string;
}

interface Importer {
    public function canonicalize(string $url, bool $fromImport, ?string $containingUrl = null): ?string;
    public function load(string $canonicalUrl): ?ImporterResult;
}

class ImporterResult {
    public string $contents;
    public int $syntax;             // SYNTAX_* constant
    public ?string $sourceMapUrl;
    public function __construct(string $contents, ?int $syntax = null, ?string $sourceMapUrl = null);
}

class CompileException extends \Exception {}
```

Setters are fluent and mutate the compiler; an out-of-range `STYLE_*`/`SYNTAX_*`value throws `\ValueError` at `compile()` time, matching the extension. The compiler is reusable across compiles.

### Errors

[](#errors)

`Sasso\CompileException` carries sasso's diagnostic — a byte-exact snippet when a url is set, otherwise the `Error:  (line:col)` one-liner. The position is part of the message; like the extension, there are no extra accessors.

```
try {
    (new Compiler())->setUrl('app.scss')->compile('.a { color: ; }');
} catch (Sasso\CompileException $e) {
    echo $e->getMessage();
}
```

```
Error: unexpected character ';' in value
  ╷
1 │ .a { color: ; }
  │             ^
  ╵
  app.scss 1:13  root stylesheet

```

### Custom importers

[](#custom-importers)

```
use Sasso\{Compiler, Importer, ImporterResult};

final class ArrayImporter implements Importer
{
    public function __construct(private array $files) {}

    public function canonicalize(string $url, bool $fromImport, ?string $containingUrl = null): ?string
    {
        return isset($this->files[$url]) ? "array:$url" : null;
    }

    public function load(string $canonicalUrl): ?ImporterResult
    {
        return new ImporterResult($this->files[substr($canonicalUrl, 6)]);
    }
}

$css = (new Compiler())
    ->setImporter(new ArrayImporter(['theme' => '$accent: hotpink;']))
    ->addImportPath(__DIR__ . '/scss')   // fallback when canonicalize() returns null
    ->compile('@use "theme" as t; .btn { color: t.$accent; }');
```

The importer is consulted first; configured import paths act as a **fallback**when `canonicalize()` returns `null`. That mirrors ext-sasso — note the raw C ABI does not do this (an importer there replaces load paths outright), so the polyfill emulates the fallback itself, following Sass's partial conventions (`foo.scss`, `_foo.scss`, `foo/_index.scss`).

Throwing from either method aborts the compile and your exception propagates unchanged rather than being flattened into a `CompileException`.

How the native library gets there
---------------------------------

[](#how-the-native-library-gets-there)

This package is also a Composer plugin. On `composer install`/`update` it detects the target triple for your PHP process, downloads the matching `-c-api`archive from the sasso release, verifies its SHA-256 against a pinned checksum, and extracts the shared library to `vendor/shyim/sasso-ffi/bin//`.

Allow the plugin once in the consuming project (Composer 2.2+):

```
{
    "config": {
        "allow-plugins": {
            "shyim/sasso-ffi": true
        }
    }
}
```

Detection follows the **PHP binary**, not the host CPU — an x86\_64 PHP under Rosetta gets the x86\_64 library, which is the one it can actually load.

TargetLibrary`x86_64-apple-darwin``libsasso.dylib``aarch64-apple-darwin``libsasso.dylib``x86_64-unknown-linux-gnu``libsasso.so``aarch64-unknown-linux-gnu``libsasso.so``x86_64-unknown-linux-musl``libsasso.so``aarch64-unknown-linux-musl``libsasso.so``x86_64-pc-windows-msvc``sasso.dll`If the plugin never runs — `--no-plugins`, a `vendor/` built on another platform, a PHAR — the library is fetched on first use instead, so a failed download at install time is a warning rather than a hard error.

```
# When installed as a dependency (plugin allowed):
composer sasso:install
composer sasso:install --target=x86_64-unknown-linux-gnu
composer sasso:install --target=all --force

# Always works (root checkout, --no-plugins, CI):
php bin/sasso-install
php bin/sasso-install --target=x86_64-unknown-linux-gnu
php bin/sasso-install --target=all --force
# or, after composer install in a consumer project:
vendor/bin/sasso-install
```

`composer sasso:install` is registered by the plugin. Composer never loads the **root** package as a plugin, so inside this repository use `php bin/sasso-install`(or `composer run sasso-install`) instead.

Cross-target prefetch is the useful one for Docker: bake the Linux library into an image from an arm64 laptop without waiting for the container to fetch it.

Environment variables
---------------------

[](#environment-variables)

VariableEffect`SASSO_LIBRARY`Load this library file directly; skips detection and download`SASSO_TARGET`Force a target triple`SASSO_DOWNLOAD_BASE_URL`Fetch archives from a mirror (disables the pinned checksum)`SASSO_SKIP_DOWNLOAD=1`Composer plugin does nothing at install time`SASSO_NO_DOWNLOAD=1`Never fetch at runtime; error insteadAir-gapped builds want `SASSO_LIBRARY` (or a vendored `bin//`) plus `SASSO_NO_DOWNLOAD=1`, which turns a missing library into an explicit error rather than a network call.

Notes
-----

[](#notes)

- Bundled sasso release: **0.8.2** (`Sasso\Platform::VERSION`). The library's own `sasso_version()` reports a separate internal compiler version that tracks the release tag loosely — use `Platform::VERSION` for the release these bindings target.
- The FFI handle is loaded once per process and shared across compilers.
- Only `-c-api` archives are published for the seven targets above. There is no 32-bit build, so those platforms need `SASSO_LIBRARY` pointing at your own.
- Beyond the extension's surface this package also exposes `Sasso\Platform`, `Sasso\Downloader`, and the plugin classes. They handle binary provisioning, which the extension has no equivalent of; the compiler API itself adds nothing.

Development
-----------

[](#development)

```
composer install
vendor/bin/phpunit
```

The test suite is written strictly against the ext-sasso surface, so it can be run against the native extension unchanged to verify parity.

License
-------

[](#license)

MIT. sasso and ext-sasso are separate projects under their own licenses.

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance100

Actively maintained with recent releases

Popularity7

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity35

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

0d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/7c45ef9077b73fce78afbfab2fa27e611a453dd77de003e2785ac84105d02bef?d=identicon)[shyim](/maintainers/shyim)

---

Top Contributors

[![shyim](https://avatars.githubusercontent.com/u/6224096?v=4)](https://github.com/shyim "shyim (4 commits)")

---

Tags

polyfillcsssassscsscompilerffisasso

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/shyim-sasso-ffi/health.svg)

```
[![Health](https://phpackages.com/badges/shyim-sasso-ffi/health.svg)](https://phpackages.com/packages/shyim-sasso-ffi)
```

###  Alternatives

[scssphp/scssphp

scssphp is a compiler for SCSS written in PHP.

62930.2M323](/packages/scssphp-scssphp)[wapplersystems/ws-scss

Compiles SCSS to CSS at runtime with caching, TypoScript variables and EXT: import support

11149.9k8](/packages/wapplersystems-ws-scss)[panique/laravel-sass

Compiles your Sass .scss files to .css every time you run your app (in development)

70173.7k1](/packages/panique-laravel-sass)[efficiently/larasset

Larasset is a library for Laravel 5 which manage assets in an easy way.

674.8k](/packages/efficiently-larasset)[trentrichardson/cakephp-shrink

Compiles, combines, and minifies javascript, coffee, less, scss, and css

1619.4k](/packages/trentrichardson-cakephp-shrink)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
