Release Notes
Crate release v0.7.0; ships on npm as sasso@0.10.0 (wasm + native — same
core). The dart-sass byte-parity campaign: every project in the new
real-world corpus (bench/real-world/real_world.md)
now compiles, and most match dart-sass 1.101 byte-for-byte.
Added (npm package — sasso/native; released as sasso@0.9.0 on npm)
sasso/nativesubpath: the native addon as a first-class npm entry.
Prebuilt binaries publish as exact-version-pinnedoptionalDependencies
(sasso-native-{darwin-arm64, darwin-x64, linux-x64-gnu, linux-arm64-gnu}) —
npm installs only the matching platform, the release workflow builds and
byte-parity-tests every binary against the wasm reference before publishing,
and unsupported platforms get a clear error pointing back at the wasm
entries. Resolution order:SASSO_NATIVE_BINARYoverride → platform
package → repo-local build.
Added (repo — native Node addon)
napi/: a native Node addon binding the core crate directly (F4 of
docs/ASYNC_PERF_ARCHITECTURE.md) — no wasm, no asyncify. Same dart-sass
modern API as thesassonpm package, verified byte-identical to the
wasm engine's output across the in-repo corpora (which is itself dart-sass
byte-exact). Async compiles each run on their own OS thread: ~3× engine
speed over the wasm modules, and a concurrent 8-entry cold build finishes
in ~1.14× a single compile's time (true multi-core parallelism; the wasm
engine's single JS thread serializes CPU-bound fan-out). User importers, custom functions,
and loggers bridge to JS;loadPaths/relative resolution run natively.
Repo-buildable (bash napi/build.sh,node napi/test.mjs); publishing
waits on a per-platform prebuild matrix.
Changed (npm package — async path performance; released as sasso@0.8.0 on npm)
- Concurrent
compileStringAsync/compileAsynccalls no longer serialize.
The single asyncify-instance lock is replaced by a lazily-grown pool of
asyncify engines (default cap:min(4, cpu cores), tunable via the new
configure({ asyncInstances })). While one compile awaits an asynchronous
importer, other compiles run on other engines — a bundler fanning out N
sass entries no longer queues them end-to-end (measured: N=8 fan-out with
2 ms importer latency, makespan −75.6%). A process that never overlaps
async compiles still pays for exactly one instance; each additional engine
reserves its own wasm memory (incl. the arena) plus a 1 MiB asyncify stack. - Synchronously-resolving importers and custom functions no longer pay the
asyncify suspension on the async APIs. Results that settle synchronously
(plain return values — including the built-inloadPathsfilesystem chain
and sass-loader's cache-hit resolutions) are delivered without an
unwind/rewind cycle. AloadPaths-onlycompileStringAsyncnow suspends
zero times. Genuinely-async importers behave exactly as before. sasso/speed's async APIs now run a speed-optimized (-O3) asyncify
module (sasso.speed.async.wasm, ~3.2 MB / 1.0 MB gzip) instead of
sharing the size-optimized one — ~2× engine throughput at v8 steady state
(long-lived processes; one-shot CLI-style runs are dominated by v8 tiering
and see little change). The defaultsassoentry is unchanged.- Degraded async modules (built without
wasm-opt) previously crashed on the
first importer callback; they now work for synchronously-resolving chains
and reject genuinely-async importers with a clear error.
Fixed
- Real-world corpora now compile — four previously failed. Callable
closures capture the defining file's@usenamespace tables (dart
Environment.closure()), fixing "There is no module with the namespace
X" for functions/mixins reached via@importor multi-hop@forward
(uswds'sunits(), quasar'sstr-fe()). CSS escapes are literal
identifier text in selector scans (.govuk-\!-font-size-19,
govuk-frontend). In the indented syntax,as *terminates a
@use/@forwardprelude instead of reading as a pending multiplication
(vuetify), and a trailing-comma selector line with a pseudo-glued colon
(&:active,/i[type="s"]::-webkit-x,) continues the list instead of
being silently dropped (quasar — a correctness bug, not formatting). - Byte-parity with dart-sass across the serialization surface. Loud
comments dedent at serialize time (interpolated banners included) and an
indented/**opener stays glued; comments registered before a module's
first load re-emit at every dependency edge (bulma's/* Bulma Form */);
@imported files carry their own file identity (no cross-file trailing
-comment gluing); invisible@extend-only rules leave no blank-line group
end; selector lists keep their authored line structure inside nested
at-rule wraps and plain-CSS imports (which also unquote identifier
attribute values like dart's parser); multi-&parent expansion
interleaves column-major (mastodon's adjacent-state selectors); a&
nested in pseudo parens substitutes inside multi-&parts
(:not(&--mini-animate)). - Chained
@extendproducts keep dart's registration order. Each
@extend's extender list is pre-extended by the store accumulated so far
(dart'saddSelector), so.navbar > .container, … .container-xxlcomes
out in forward order instead of reversed. - Errors inside loaded files are attributed to that file. The snippet
renders from the erring file and the trace stacks one frame per loader
(_mod.scss 1:13 @use/main.scss 1:1 root stylesheet), matching
dart for@use,@forward, and@importchains — parse errors
included. Previously the root file's name and snippet were shown with the
inner file's line numbers. @medianested inside an unknown at-rule now compiles. dart's
_inUnknownAtRulecontext legalizes bare declarations without an enclosing
style rule, so the canonical Tailwind v4 idiom
@utility container { @media (width >= 96rem) { max-width: 87.5rem; } }
parses and emits verbatim (byte-matched to dart-sass 1.101, including the
classicmin-widthsyntax, interpolated queries, and mixed
declaration-plus-@mediabodies). A bare declaration in a top-level
@mediastill errors like dart. Previously:Error: expected "{".- Keyframe selector lists now join on one line, matching dart-sass. A
multi-line authored frame selector (0%,\n60%,\n100% {) was emitted with
the author's line breaks preserved, as style-rule selector lists are; dart
re-serializes keyframe stops joined with", "and drops the breaks
(0%, 60%, 100% {). Found compiling a real-world Rails corpus (a Bootstrap
→ Tailwind compat layer) where this was the only byte difference across
~132 KB of output.
Install sasso 0.7.0
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/momiji-rs/sasso/releases/download/v0.7.0/sasso-installer.sh | shInstall prebuilt binaries via powershell script
powershell -ExecutionPolicy Bypass -c "irm https://github.com/momiji-rs/sasso/releases/download/v0.7.0/sasso-installer.ps1 | iex"Download sasso 0.7.0
| File | Platform | Checksum |
|---|---|---|
| sasso-aarch64-apple-darwin.tar.xz | Apple Silicon macOS | checksum |
| sasso-x86_64-apple-darwin.tar.xz | Intel macOS | checksum |
| sasso-x86_64-pc-windows-msvc.zip | x64 Windows | checksum |
| sasso-aarch64-unknown-linux-gnu.tar.xz | ARM64 Linux | checksum |
| sasso-x86_64-unknown-linux-gnu.tar.xz | x64 Linux | checksum |
| sasso-aarch64-unknown-linux-musl.tar.xz | ARM64 MUSL Linux | checksum |
| sasso-x86_64-unknown-linux-musl.tar.xz | x64 MUSL Linux | checksum |