Rsass 0.26.0 released

Posted 2022-09-18 20:04. Tagged , , , , .

More than three months after the last release, is is now the time to announce rsass 0.26.0. There is a bunch of breaking changes and a bunch of improvements. The changelog has the whole (too long) list.

But this time, some of the changes may go a bit deeper. As usual, the breaking changes is breaking mainly to users who in some way modify the global context from rust code, maybe by providing their own builtin functions or maybe just by inserting a global variable.

Also this time, there is an improved way of calling ructe from a cargo build.rs program, see the Cargo section below.

Builtin functions and their arguments

A BuiltinFn is now a dyn Fn(&ResolvedArgs) -> Result<Value, CallError> + Send + Sync (earlier, the argument was a ScopeRef and the error type was rsass::Error). The ResolvedArgs type provides functions get and get_map to get an argument converted to a specific type, either by TryFrom<Value> for the type or by a given conversion function. If the conversion fails, the (display formatted) error will be combined with the argument name into a CallError::BadArgument.

There is also similar get_opt and get_opt_map for optional arguments (i.e. arguments where null values are allowed).

If something other than argument parsing can go wrong in your function, creating an Invalid::AtError (and a CallError from that) is probably the best way to handle that.

Contexts and files

The easiest way to get some css data is still like this:

let css = compile_scss_path("mystyle.scss", Format::default())?;

The body of that function now looks like this:

let (context, source) = FsContext::for_path(path)?;

Here, FsContext is a specialization of Context for loading files from the file system. Apart from loading files, the Context also provides the global Scope, which can be modified to provide extra “builtin” variables, functions or modules.

For a full example, see the rust_functions.rs test. Here’s a tiny example providing the global variable $project while transforming path.

let (mut context, source) = FsContext::for_path(path)?;
context.get_scope().define("project".into(), "My project name".into())?;


When running ructe from a cargo build.rs program, the following way of calling rsass can be used:

let (context, sass) = CargoContext::for_path("res/app.scss")?;

This doesn’t look like a big change, but using a CargoContext (which is just type alias for a Context where the loader is a CargoLoader) have two benefits: First, the path is resolved relative to the crate root (the directory containing your Cargo.toml) rather than the current working directory.

Secondly, both for the given path and for any paths loaded when handling the scss (due to e.g. @use or @import), the CargoLoader emitts special messages telling cargo that those files are used in the build, so the build.rs program gets reexecuted if needed when rebuilding the project.


Updating ructe to 0.15.0 includes an update of rsass to 0.26.0. Ructe also declares a built-in function, static_name, that takes the source name of a static file and returns an url name, i.e. a name including a content hash for cache-busting.

Error handling

Earlier versions of rsass had an rsass::Error enum with lots of variants that was returned from most functions in the crate. In this version, there is still an Error enum, but it has fewer variants (and the fallback variant with just a message string is used a lot less, even though some uses still remains). Many of the old variants are now covered by Error::Invalid(Invalid, SourcePos), which holds an Invalid (what went wrong) and a SourcePos (where it happened).

Internally, some functions now returns a Result<T, Invalid> to be combined with a source position as the error propagates. Other functions have other specific error types, such as ScopeError and the CallError mentioned above.

More to come

Rsass is far from done. Lots of things remains before I’ll call it 1.0. Probably the biggest one is placeholder selectors and the @extend directive. To be able to do that, there are two things missing; selector functions and a css data tree.

Many selector functions require a deeper understanding of css selectors than what is currently encoded, to be able to answer questions like “do this complex selector select a proper subset of what that complex selector selects”. I’m not yet sure how a css selector should be represented in rsass to be able to do that efficiently.

Css output is almost just a list of rules, but there is also @ directives, such as @media, which contains another list of rules and possibly @ directives. Currently, rsass has a data type for a single css rule, but when a rule is completed it is written to an output buffer, and that buffer is the only representation of the completed css. To be able to find earlier rules to extend, a full data tree is needed. That should also provide some nice separation between the transformation (sass to css) and the formatting (css data model to css text buffer).

So, lots more to come at https://github.com/kaj/rsass. I hope to see you there!


Write a comment

Basic markdown is accepted.

Your name (or pseudonym).

Not published, except as gravatar.

Your presentation / homepage (if any).