Rust API

Entry points, options, the replaceable traits, and Cargo features.

Generated documentation is on docs.rs. This page is the shape of the API rather than its rustdoc.

Entry points

FunctionCompiles
from_string(input, &Options) -> Result<String>A string, named stdin
from_string_with_file_name(input, file_name, &Options) -> Result<String>A string, named as though it were that file
from_path(path, &Options) -> Result<String>A file, read through Options::fs

from_string_with_file_name matters for two reasons: relative imports resolve against the name's directory, and the syntax is inferred from its extension. An editor compiling an unsaved buffer wants both.

Result<T> is std::result::Result<T, Box<Error>>.

Options

Options is a consuming builder; every method returns it. Options::default() is expanded output, StdFs, StdLogger, no load paths, charset on, Unicode error messages on, warnings on, and syntax inferred from the file name.

MethodTypeDefaultEffect
fs&dyn FsStdFsWhere imports are read from
logger&dyn LoggerStdLoggerWhere @warn and @debug go
styleOutputStyleExpandedExpanded or Compressed
load_pathimpl AsRef<Path>noneAppend one load path
load_paths&[impl AsRef<Path>]noneAppend several
input_syntaxInputSyntaxinferredScss, Sass or Css, for the entry point only
allows_charsetbooltrueEmit @charset or a byte-order mark when output is non-ASCII
unicode_error_messagesbooltrueUse non-ASCII characters in error messages
quietboolfalseSilence @warn, @debug and deprecations
verboseboolfalseReport every deprecation warning, rather than counting those after the fifth of each kind

Traits

Fs

Where the compiler looks for imported files.

pub trait Fs: std::fmt::Debug {
    fn is_dir(&self, path: &Path) -> bool;
    fn is_file(&self, path: &Path) -> bool;
    fn read(&self, path: &Path) -> io::Result<Vec<u8>>;
    fn canonicalize(&self, path: &Path) -> io::Result<PathBuf> { /* identity */ }
}

read is synchronous, which is the constraint that shapes every embedder: an importer cannot await, so everything a compile might touch must already be reachable.

canonicalize decides module identity. The compiler uses its result as the key of the module cache, so an implementation that lets two spellings of one path survive will execute that module twice.

Three implementations ship:

TypeReads from
StdFsstd::fs. The default
MemoryFsAn in-memory map of path to contents
NullFsNothing. Every import fails to resolve

MemoryFs

MethodDoes
new()An empty filesystem
insert(path, contents)Add or replace a file. Paths are normalized, so ./a.scss and a.scss are one entry
len(), is_empty()How many files are stored
loaded_paths()The paths read so far, in order, one entry per read
clear_loaded_paths()Forget that record, to scope it to one compile

Directories are implied by the files in them: inserting a/b/c.scss makes a and a/b report true from is_dir, which is what the resolver needs to find a/b/_index.scss.

Logger

pub trait Logger: Debug {
    fn debug(&self, location: SpanLoc, message: &str);
    fn warn(&self, location: SpanLoc, message: &str);
    fn deprecation(&self, warning: &DeprecationWarning) { /* calls warn */ }
    fn repetitive_deprecations_omitted(&self, count: usize) { /* nothing */ }
}

StdLogger writes to standard error; NullLogger discards. Options::quiet stops events before they reach the logger at all.

deprecation receives a warning about a deprecated feature the stylesheet uses. Its default hands the message and location to warn, so a logger written before deprecation warnings existed still sees them. A DeprecationWarning carries:

MethodReturns
deprecation()The Deprecation, whose id() is the name dart-sass prints, such as bogus-combinators
message()The message alone, which can run over several lines
location()The SpanLoc the warning is about
formatted()The full text dart-sass prints: banner, message, source frame and location line

The same warning at the same place is reported once. After five of one deprecation the rest are counted instead, and repetitive_deprecations_omitted receives the count at the end of the compile; Options::verbose reports them all. Deprecation is #[non_exhaustive], so match it with a wildcard arm.

Errors

Error (the crate's SassError) implements Display, which produces the formatted block the command line prints. Error::kind consumes it and returns ErrorKind:

VariantCarries
ParseErrormessage, a SpanLoc, and whether Unicode messages are allowed
IoErrorThe entry point could not be read
FromUtf8ErrorA file was not valid UTF-8

Imports that cannot be found are ParseErrors pointing at the @use or @import, not IoErrors.

SpanLoc comes from the codemap crate, which the crate re-exports. Its lines and columns are 0-based; the formatted block and every editor count from one.

The include! macro

static CSS: &str = accent_sass::include!("../static/_index.scss");

Requires the macro feature. Compiles at build time with default options except output style, which is compressed. Tracked with include_str! so incremental rebuilds notice a changed partial; the nightly feature uses proc_macro::tracked_path instead, which is more robust.

Cargo features

FeatureDefaultEffect
commandlineyesBuild the binary, using clap
randomyesmath.random(), random(), string.unique-id(), unique-id()
macronoThe accent_sass::include! macro
nightlynoLet include! use proc_macro::tracked_path
wasm-exportsnoThe JavaScript API for a wasm32-unknown-unknown build
wasi-exportsnoA C ABI for embedding a wasm32-wasip1 module in a host

Turning off random removes four Sass functions from the build. A stylesheet that calls one then fails to compile, which is a compile error rather than a missing feature, so turn it off deliberately.

Custom builtin functions are not reachable here

accent_sass_compiler has a fifth feature, custom-builtin-fns, which is on by default for that crate and gates Options::add_custom_fn. The accent-sass crate depends on the compiler with default-features = false and forwards no such feature, so add_custom_fn and the Builtin type it takes cannot be reached through accent-sass as published. Depending on accent_sass_compiler directly is the only way to them today, against a crate this project documents as an internal.

The WASI C ABI

wasi-exports exposes a C ABI for wasm32-wasip1, for a plugin host that instantiates the compiler once and compiles many stylesheets without paying process startup for each.

Strings cross as UTF-8 in linear memory: allocate, write, call, read the result, free. A result is three 32-bit words -- status, pointer, length.

ExportDoes
accent_sass_alloc(len)Reserve len bytes in the guest and return the pointer
accent_sass_dealloc(ptr, len)Release what accent_sass_alloc returned
accent_sass_compile_string(ptr, len)Compile the source at that range, with defaults
accent_sass_compile_path(ptr, len)Compile the file at that guest path, with defaults
accent_sass_result_free(res)Release a result
accent_sass_options_new()A handle, filled in by the setters below
accent_sass_options_free(opts)Release a handle
accent_sass_compile_string_with_options(ptr, len, opts)As above, with a handle
accent_sass_compile_path_with_options(ptr, len, opts)As above, with a handle

Status words:

ConstantValueMeaning
ACCENT_SASS_OK0The call succeeded
ACCENT_SASS_REJECTED1A null handle, or a value the ABI does not define
ACCENT_SASS_NOT_UTF82The bytes were not valid UTF-8

The setters are named after dart-sass's JavaScript API wherever the two have the same knob, so this ABI and the JavaScript API do not drift apart:

Setterdart-sassValues
accent_sass_options_set_stylestyle0 expanded, 1 compressed
accent_sass_options_set_syntaxsyntax0 scss, 1 indented, 2 css
accent_sass_options_add_load_pathloadPathsOne path per call
accent_sass_options_set_charsetcharsetNon-zero is true. Default true
accent_sass_options_set_alert_asciialertAsciiNon-zero is true. Default false
accent_sass_options_set_quiet--Non-zero is true. Default false

A value the ABI does not define is refused rather than rounded to a default, so a host that ignores the status word keeps what it had. A handle is reusable and carries no borrowed state: set it up once and compile a whole theme through it.

Load paths are guest paths. Under WASI a path is readable only if a preopen covers it, so a host wanting /shared on the load path must map a directory onto that name when it instantiates the module.

Both release profiles set panic = "abort", so a panic reaches the host as a trap and leaves the instance unusable. A host compiling untrusted input should be ready to discard the instance.