scss

package module
v0.0.0-...-42d746e Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 3, 2026 License: BSD-3-Clause Imports: 4 Imported by: 0

README

go-scss/scss

CI Go Reference

A pure-Go (CGO-free) Sass/SCSS compiler whose output tracks Dart Sass, the canonical reference implementation. It compiles both the SCSS and the indented .sass syntaxes to CSS in the expanded and compressed output styles, matching dart-sass byte-for-byte on the common real-world surface.

No cgo, no libsass, no Node — a single static Go dependency for static-site generators, asset pipelines, and language bindings. It also compiles 2–3× faster than dart-sass 1.102 and libsass on the cross-implementation corpus (see BENCHMARKS.md).

import "github.com/go-scss/scss"

res, err := scss.CompileString(`
  $accent: #3498db;
  .btn { color: $accent; &:hover { color: darken($accent, 10%); } }
`, nil)
fmt.Println(res.CSS)

API

Modeled on the modern sass-embedded shape:

func CompileString(source string, opts *Options) (*CompileResult, error)
func Compile(path string, opts *Options) (*CompileResult, error)

type Options struct {
    Syntax    Syntax        // SyntaxSCSS | SyntaxIndented | SyntaxCSS
    Style     OutputStyle   // Expanded | Compressed
    LoadPaths []string
    Importer  Importer      // custom URL resolver (defaults to filesystem)
}
type CompileResult struct { CSS string; LoadedURLs []string; SourceMap string }

Language surface

Feature Status
Variables ($x, !default, !global)
Nesting, parent selector &
Interpolation #{}
@mixin / @include (defaults, keyword & rest args, @content, using)
@function / @return, recursion
@if / @else if / @else, @each, @for, @while
Placeholders %x + @extend (incl. !optional)
@use / @forward (namespacing, as, as *, prefix, config with)
Legacy @import (Sass partials + plain-CSS passthrough)
@media / @supports (nesting, bubbling, media-query merging)
@at-root
Operators: numeric+units, string, comparison, and/or/not
Maps + lists (space/comma/slash/bracketed)
calc() / clamp() / env() preservation
Output styles expanded + compressed (byte-matched)
Comments: // silent, /* */ preserved
Built-in modules sass:math, sass:color, sass:string, sass:list, sass:map, sass:selector, sass:meta (common functions) + global aliases
Built-in functions covered
  • math: div, percentage, round, ceil, floor, abs, min, max, sqrt, sin, cos, tan, pow, hypot, log, clamp, unit, is-unitless, compatible
  • color: rgb(a), hsl(a), red, green, blue, hue, saturation, lightness, alpha, mix, lighten, darken, saturate, desaturate, adjust-hue, grayscale, invert, complement, opacify, transparentize, adjust, scale, change, ie-hex-str
  • string: quote, unquote, length, insert, index, slice, to-upper-case, to-lower-case, split, unique-id
  • list: length, nth, set-nth, join, append, zip, index, separator, is-bracketed, slash
  • map: get, set, merge, remove, has-key, keys, values
  • selector: nest, append, unify
  • meta: type-of, inspect, keywords, *-exists
  • plus the global (un-namespaced) aliases (map-get, str-length, nth, …).

Differential correctness (the compat gate)

Correctness is defined as matching dart-sass byte-for-byte. Two oracles enforce it:

  1. sass-spec — the canonical conformance suite (github.com/sass/sass-spec), whose HRX archives pair each input.scss with the reference output.css. The harness scores exactly the way the official runner scores dart-sass: it honours the options.yml annotation system (:ignore_for:/:todo: for dart-sass are excluded, not failed), the per-impl expected-output overrides (output-dart-sass.css), and the --load-path import resolution — so the denominator is the dart-applicable success set. See sass-spec conformance below for the exact audited figures. A representative, self-contained, all-passing subset of 1575 cases is frozen under testdata/spec as the in-repo conformance gate, so CI needs no sass-spec checkout; the full suite (and the dart-sass ceiling oracle via SASS_SPEC_ORACLE=…) runs skip-gated (SASS_SPEC_PATH=…) and as a non-blocking CI job.
  2. Live dart-sass — a hand-curated corpus of representative .scss/.sass files is compiled with both engines and diffed byte-for-byte (all byte-match dart-sass 1.102 in expanded and compressed). Frozen as golden testdata; a skip-gated live test re-verifies against a real sass binary when present.
go test ./...                                   # golden + frozen sass-spec gate (no dart-sass needed)
sass --version && go test ./...                 # also runs the live differential
SASS_SPEC_PATH=/path/to/sass-spec go test -run TestSassSpecFull -v ./   # full suite pass rate

The CSS Color 4 color-space module is now implemented (see residuals for the named exotic cases that remain).

sass-spec conformance

Audited 2026-08-03 against a full sass/sass-spec checkout, with dart-sass 1.102.0 run through the same annotation-aware harness as the ceiling oracle (GOWORK=off, SASS_SPEC_ORACLE=sass). Over the 11406 dart-applicable success-output cases:

passes of denominator
go-scss 11230 / 11406 98.46%
dart-sass 1.102 (achievable ceiling) 11341 / 11406 99.43%
go-scss as a share of the ceiling 11230 / 11341 ~99.0%

go-scss is byte-exact against dart-sass 1.102 across the entire real-world language. It even matches the vendored fixture where current dart-sass does not on 16 cases — stale fixtures that dart-sass 1.102 itself now fails (its last-ULP behaviour drifted; go-scss still matches the frozen expectation).

The remaining 176 go-scss misses are now fully irreducible — every closeable case has been closed. Oracle-bucketed (dart-sass 1.102 run through the same harness, so every count below is proven, not estimated):

bucket count why it is where it is
libm-ULP (color/math last-bit) 127 Irreducible. Far-out-of-gamut color.to-space conversions and math asymptotes (math.tan, math.pow) that differ from dart in the last 1–2 ULPs. Not closeable without CGO or breaking cross-arch determinism: pure-Go math vs dart's platform libm, where products are rounded separately so results stay identical across all six arches.
stale-vendored 49 Outside the achievable ceiling. The vendored fixture is stale and dart-sass 1.102 itself fails these (oracle-proven); go-scss matches current dart, not the drifted fixture. On 16 neighbouring stale cases go-scss is in fact ahead of dart 1.102 — it still matches the frozen expectation that dart has drifted away from.

127 + 49 = 176, and 11230 + 176 = 11406; the residual is now 100% irreducible — the 127 libm-ULP cases (cross-arch determinism) plus the 49 stale-vendored cases (dart-sass 1.102 fails them too), with no pure-Go-closeable case left. The 65-case gap to the dart ceiling is exactly the 49 both-fail stale cases plus the 16 cases go-scss wins outright.

Now closed: issue_2055 (:not/:has selector self-composition) passes as of the in-loop extender re-extension — the former lone architectural residual is gone; every case with a known pure-Go / CGO=0 / cross-arch-deterministic path is now closed except the two nested-@import cases above. The per-import-clone @extend cluster (use/extend/scope/*, meta.load-css mid-stream clone) is likewise closed — use/extend/scope scores 10/10 via the per-module clone-store rebuild.

Scored via the annotation-aware harness that reproduces the official runner's options.yml/per-impl-override logic; the full per-case bucket assignment is reproducible with TestSassSpecFull + SASS_SPEC_ORACLE.

Honest residuals

Dart Sass output is the source of truth; where this compiler intentionally diverges it says so. Divergences are measured against the sass-spec suite (spec case named in parentheses):

Closed (now match dart-sass 1.102):

  • Legacy color serialization — computed colors now carry floating-point channels and serialize exactly as dart does: integer-channel colors as their CSS keyword or hex (e.g. invert(#fff)black), fractional-channel colors as rgb()/rgba() percentages (e.g. mix(red, blue)rgb(50%, 0%, 50%), color.adjust(plum, $saturation: -200%)rgb(74.7058823529%, …)).
  • Non-finite numbers1/0, math.cos(∞) serialize as calc(infinity) / calc(-infinity) / calc(NaN).
  • Hyphen/underscore-insensitive identifiers — variables, functions, and mixins fold _- on both definition and lookup (function_existsfunction-exists).
  • meta.feature-exists returns the correct booleans for known features.
  • CSS Color Level 4 color-space module — all Color 4 spaces (srgb, srgb-linear, display-p3, a98-rgb, prophoto-rgb, rec2020, xyz/xyz-d65, xyz-d50, lab, lch, oklab, oklch) plus the color(), lab()/lch()/oklab()/oklch()/hwb() constructors and the sass:color module (space, to-space, channel, is-legacy, is-missing, is-in-gamut, is-powerless, same, to-gamut with clip/local-minde, plus change/adjust/scale/mix/invert/complement extended with $space and space-specific channels). Conversion matrices, gamma companding, Bradford D65↔D50 adaptation, OkLab/OkLCH, missing/none-channel carrying and powerless-channel rules match dart-sass 1.102 byte-for-byte (all products are rounded separately to keep results identical to dart across every architecture — dart never fuses multiply-add). This closed ~4400 sass-spec cases.
  • Special-number passthrough in color functionscalc()/var()/env()/attr() and min()/max()/clamp() channel arguments now serialize as dart does (all core_functions/color/**/special_functions/* pass).
  • sass:selector + @extend unificationselector.extend, selector.is-superselector, selector.parse and complex/compound selector unification are implemented; every dart-applicable core_functions/selector/** and @extend case now passes, including the former issue_2055 :not/:has self-composition residual (closed by re-extending in-loop extenders so self-composition matches dart).
  • Per-import-clone @extend scope@extend interacting with @use/@import module boundaries (use/extend/scope/*, meta.load-css mid-stream clone) now matches dart; use/extend/scope scores 10/10 via the per-module clone-store rebuild.
  • Modern media-query syntax — range (width < 100px) and and/or/not logic merging/pruning match dart for every dart-applicable case; the remaining css/media/** fixtures are stale-vendored (dart-sass 1.102 fails them too).

Still divergent (named, not hidden — see the sass-spec conformance table for exact per-bucket counts):

  • Extreme out-of-gamut colors and math asymptotes (libm-ULP bucket, 127, irreducible) — far-out-of-gamut color.to-space conversions (core_functions/color/to_space/*/out_of_range/far) and math asymptotes (math.tan, math.pow) differ from dart in the last 1–2 ULPs: the magnitudes amplify unavoidable floating-point rounding-order differences between pure-Go math and dart's platform libm. Not closeable without CGO or breaking cross-arch determinism.
  • Stale-vendored fixtures (stale-vendored bucket, 49, outside the ceiling) — the vendored output.css is out of date and dart-sass 1.102 itself fails these, so they lie outside the achievable ceiling. On 16 neighbouring cases go-scss is ahead of dart 1.102. With the nested plain-CSS @import placement now matching dart (a plain-CSS @import url(...) reached through a mixin/@if inside a style rule stays nested under the rule, per _visitStaticImport's _parent != _root rule), no pure-Go-closeable residual remains.
  • Source maps — not emitted (CompileResult.SourceMap is empty).
  • Coverage is 100.0% of statements (up from 79.3%); every parser/eval error-recovery and defensive branch is exercised, either through malformed-SCSS tests or via direct white-box drives of the defensive seams. The CI floor is 100%.

License

BSD-3-Clause. Copyright (c) 2026, the go-scss/scss authors.

Documentation

Overview

Package scss is a pure-Go (CGO-free) Sass/SCSS compiler whose output tracks Dart Sass, the canonical reference implementation. It supports both the SCSS and the indented (.sass) syntaxes and the "expanded" and "compressed" output styles.

The public surface intentionally mirrors the modern sass-embedded Dart Sass API shape (CompileString / Compile returning CSS + loaded URLs) so language bindings — such as the go-ruby-sass adapter — can wrap it directly.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type CompileResult

type CompileResult struct {
	CSS        string
	LoadedURLs []string
	SourceMap  string // source maps are not yet emitted (see package residuals)
}

CompileResult is the outcome of a successful compilation.

func Compile

func Compile(path string, opts *Options) (*CompileResult, error)

Compile reads and compiles a Sass/SCSS file.

func CompileString

func CompileString(source string, opts *Options) (*CompileResult, error)

CompileString compiles Sass/SCSS source text to CSS.

type Importer

type Importer func(url string) (source string, canonicalURL string, ok bool)

Importer resolves an import URL to source text and a canonical URL.

type Options

type Options struct {
	Syntax    Syntax
	Style     OutputStyle
	LoadPaths []string
	// Importer, when set, resolves @use/@forward/@import URLs. When nil, a
	// filesystem importer based on LoadPaths (and the entry file's directory)
	// is used. It receives only the URL; relative resolution is against the
	// configured load paths. For referrer-relative resolution (Dart Sass's
	// default behaviour, where a load resolves relative to the file issuing it),
	// set ImporterWithReferrer instead.
	Importer Importer
	// ImporterWithReferrer, when set, takes precedence over Importer and receives
	// the referrer (the canonical URL of the file issuing the load) so it can
	// resolve relative-to-referrer first, then load paths — matching Dart Sass.
	// The built-in filesystem importer used when both are nil is referrer-aware.
	ImporterWithReferrer ReferrerImporter
	// baseDir is the directory used to resolve relative imports (set internally
	// by Compile; may be set by callers of CompileString).
	BaseDir string
}

Options configures a compilation.

type OutputStyle

type OutputStyle int

OutputStyle selects the serialization style.

const (
	// Expanded is the human-readable multi-line style.
	Expanded OutputStyle = iota
	// Compressed is the minified style.
	Compressed
)

type ReferrerImporter

type ReferrerImporter func(url, referrer string, forImport bool) (source string, canonicalURL string, ok bool)

ReferrerImporter is the referrer-aware importer form. In addition to the URL being loaded, it receives the canonical URL of the stylesheet issuing the load — the file whose code is currently being evaluated (a module's own URL for its top-level rules, or a mixin/@content block's defining file for a dynamic load, such as meta.load-css, nested inside it). Mirroring Dart Sass's Importer.canonicalize(url, baseUrl:), an importer should resolve url relative to referrer first, then against its configured load paths. referrer is empty for a load issued by the entry stylesheet, which has no canonical URL.

forImport is true only for a legacy @import (Dart Sass's canonicalize(url, forImport:)); an importer should then prefer an import-only file — x.import.scss / _x.import.scss, or index.import.scss inside a directory — over the ordinary file of the same name. @use, @forward and meta.load-css pass false.

type Syntax

type Syntax int

Syntax selects the input grammar.

const (
	// SyntaxSCSS is the SCSS (braces/semicolons) grammar.
	SyntaxSCSS Syntax = iota
	// SyntaxIndented is the indented (.sass) grammar.
	SyntaxIndented
	// SyntaxCSS is plain CSS (parsed as SCSS without Sass features enabled).
	SyntaxCSS
)

Directories

Path Synopsis
cmd
scssc command
Command scssc compiles SCSS/Sass to CSS on stdout.
Command scssc compiles SCSS/Sass to CSS on stdout.
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL