Sass Bloghttps://sass-lang.com/blog2021年11月21日T00:15:00+00:00New JS API Release Candidate is Livehttps://sass-lang.com/blog/new-js-api-release-candidate2021年11月21日T00:15:00+00:002022年01月05日T05:12:05+00:00Natalie Weizenbaum<p>The new JavaScript API that we <a href="https://sass-lang.com/blog/request-for-comments-new-js-api">announced a few months ago</a> is now fully
implemented in Dart Sass and ready for you to try! The new API is designed to be
more idiomatic, performant, and usable than the old one, and we hope it’ll be
adopted swiftly by tooling packages.</p>
<p>Because this is such a substantial addition, we want to give users a chance to
kick the tires a bit before we set it in stone, so we’ve released it as a release
candidate in Dart Sass 1.45.0-rc.1. Download it, try it out, and let us know
what you think by <a href="https://github.com/sass/sass/issues/new">filing issues</a> or <a href="https://twitter.com/SassCSS">sending us a tweet</a>. Unless major changes
are necessary, we plan to make a stable release some time next week.</p>
<h2 id="how-to-use-it">
<a class="anchor" href="#how-to-use-it"><span class="visuallyhidden">How to use it permalink</span></a>How to use it</h2>
<p>The new API comes with four new entrypoint functions: <code>compile()</code> and
<code>compileAsync()</code> take Sass file paths and return the result of compiling them to
CSS, while <code>compileString()</code> and <code>compileStringAsync()</code> take a
string of Sass source and compile it to CSS. Unlike the old API, the async
functions all return <code>Promise</code>s. As with the old API, the synchronous functions
are substantially faster than their async counterparts, so we recommend using
them if at all possible.</p>
<pre class="highlight javascript"><code><span class="kr">const</span> <span class="nx">sass</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">'sass'</span><span class="p">);</span>
<span class="kr">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="nx">sass</span><span class="p">.</span><span class="nx">compileString</span><span class="p">(</span><span class="err">`</span>
<span class="nx">h1</span> <span class="p">{</span>
<span class="nx">font</span><span class="o">-</span><span class="nx">size</span><span class="err">:</span> <span class="mi">40</span><span class="nx">px</span><span class="p">;</span>
<span class="nx">code</span> <span class="p">{</span>
<span class="nx">font</span><span class="o">-</span><span class="nx">face</span><span class="err">:</span> <span class="nx">Roboto</span> <span class="nx">Mono</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span><span class="err">`</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nx">css</span><span class="p">);</span>
</code></pre>
<p>Check out <a href="/documentation/js-api">the API docs</a> for more details on the API, including the brand-new
importer and custom function APIs.</p>
<h2 id="what-about-the-old-api">
<a class="anchor" href="#what-about-the-old-api"><span class="visuallyhidden">What about the old API? permalink</span></a>What about the old API?</h2>
<p>Once the new API has a stable release, we’ll officially consider the old API to
be deprecated. Since it’s still widely-used, we’ll continue to maintain it for a
good long while going forward. Expect it to start printing a deprecation warning
in a year or so, and to be disabled for good once we release Dart Sass 2.0.0.</p>
Request for Comments: New JS APIhttps://sass-lang.com/blog/request-for-comments-new-js-api2021年08月05日T23:30:00+00:002022年01月05日T05:12:05+00:00Natalie Weizenbaum<p>I’m excited to officially unveil something that’s been in the works for quite a
while now: a (proposal for a) brand new JavaScript API for Sass. This API has
been redesigned from the ground up based on lessons learned from both the Node
Sass API and various other historical Sass APIs in other languages through the
years, and it addresses many of the shortcomings of the existing API.</p>
<p>The API has four main components, all of which I’ll cover in this post:</p>
<ul>
<li><a href="#compilation">The core compilation API</a></li>
<li><a href="#loggers">The logger API</a></li>
<li><a href="#importers">The importer API</a></li>
<li><a href="#functions">The function API</a></li>
</ul>
<p>As you read on, remember that this API is still just a proposal. We want to hear
from you, our users, whether it meets your needs and how we can improve it
before we lock it in to a full release. So go ahead and make your voices known
<a href="https://github.com/sass/sass/issues/new">on the issue tracker</a>!</p>
<h2 id="why-a-new-api">
<a class="anchor" href="#why-a-new-api"><span class="visuallyhidden">Why a New API? permalink</span></a>Why a New API?</h2>
<p>The existing JavaScript API is showing its age. It predates Dart Sass, having
been originally designed for the <code>node-sass</code> package, which wrapped the
now-<a href="/libsass">deprecated</a> implementation. (That’s why we call it the “Node Sass
API”!) It grew organically and often messily along with LibSass, and ended up
with more than a few awkward legacy behaviors. Many of these behaviors are more
of a pain for implementation than anything else, but a few of them made life
quite difficult:</p>
<ul>
<li><p>The importer API was built around file paths rather than URLs, and was tightly
coupled to the physical filesystem. This made it impossible to override <em>all</em>
file-based loads and present a fully virtual filesystem, and caused custom
Node importers to interact poorly with the new <a href="https://sass-lang.com/blog/the-module-system-is-launched">module system</a>.</p></li>
<li><p>The function API was built around mutable value objects, which runs counter to
Sass’s immutable nature. It also provided no utility methods (such as looking
up a key in a map) to make it easier to implement idiomatic custom functions,
and didn’t provide access to crucial information about values such as whether
strings were quoted.</p></li>
<li><p>All of the asynchronous functions were callback-based rather than
promise-based.</p></li>
</ul>
<p>The new API addresses these issues and more with a modern, idiomatic API that
will make working with Sass from JS a breeze.</p>
<h2 id="compilation">
<a class="anchor" href="#compilation"><span class="visuallyhidden">Compilation permalink</span></a>Compilation</h2>
<p>At the heart of the API are four functions that do the actual Sass compilation,
two synchronous and two asynchronous. They’re presented here in TypeScript
syntax to clarify exactly what they take and return, but you can always call
them from plain JS:</p>
<pre class="highlight typescript"><code><span class="kd">function</span> <span class="nx">compile</span><span class="p">(</span>
<span class="nx">path</span><span class="err">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="nx">options</span><span class="p">?:</span> <span class="nx">Options</span><span class="o"><</span><span class="s1">'sync'</span><span class="o">></span>
<span class="p">)</span><span class="err">:</span> <span class="nx">CompileResult</span><span class="p">;</span>
<span class="kd">function</span> <span class="nx">compileString</span><span class="p">(</span>
<span class="nx">source</span><span class="err">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="nx">options</span><span class="p">?:</span> <span class="nx">StringOptions</span><span class="o"><</span><span class="s1">'sync'</span><span class="o">></span>
<span class="p">)</span><span class="err">:</span> <span class="nx">CompileResult</span><span class="p">;</span>
<span class="kd">function</span> <span class="nx">compileAsync</span><span class="p">(</span>
<span class="nx">path</span><span class="err">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="nx">options</span><span class="p">?:</span> <span class="nx">Options</span><span class="o"><</span><span class="s1">'async'</span><span class="o">></span>
<span class="p">)</span><span class="err">:</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">CompileResult</span><span class="o">></span><span class="p">;</span>
<span class="kd">function</span> <span class="nx">compileStringAsync</span><span class="p">(</span>
<span class="nx">source</span><span class="err">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="nx">options</span><span class="p">?:</span> <span class="nx">StringOptions</span><span class="o"><</span><span class="s1">'async'</span><span class="o">></span>
<span class="p">)</span><span class="err">:</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">CompileResult</span><span class="o">></span><span class="p">;</span>
</code></pre>
<p>The <code>compile()</code> and <code>compileAsync()</code> functions load a Sass file from a path on
disk, whereas <code>compileString()</code> and <code>compileStringAsync()</code> compile Sass source
code passed in as a string. All these take the following options:</p>
<ul>
<li><code>alertAscii</code>: Whether errors and warnings should use only ASCII characters (as
opposed to, for example, Unicode box-drawing characters).</li>
<li><code>alertColor</code>: Whether errors and warnings should use terminal colors.</li>
<li><code>loadPaths</code>: A list of file paths to use to look up files to load, just like
<code>includePaths</code> in the old API.</li>
<li><code>importers</code>: A list of <a href="#importers">custom importers</a> to use to load Sass
source files.</li>
<li><code>functions</code>: An object whose keys are Sass function signatures and whose
values are <a href="#functions">custom functions</a>.</li>
<li><code>quietDeps</code>: Whether to silence deprecation warnings in dependencies.</li>
<li><code>logger</code>: The <a href="#loggers">custom logger</a> to use to emit warnings and debug
messages.</li>
<li><code>sourceMap</code>: Whether to generate a source map during compilation.</li>
<li><code>style</code>: The output style, <code>'compressed'</code> or <code>'expanded'</code>.</li>
<li><code>verbose</code>: Whether to emit every deprecation warning encountered.</li>
</ul>
<p>The <code>compileString()</code> and <code>compileStringAsync()</code> functions take a few additional
options:</p>
<ul>
<li><code>syntax</code>: The syntax of the file, <code>'scss'</code> (the default), <code>'indented'</code>, or
<code>'css'</code>.</li>
<li><code>url</code>: The <a href="#canonicalizing">canonical URL</a> of the file.</li>
<li><code>importer</code>: The <a href="#importers">custom importer</a> to treat as the file’s source.
If this is passed, this importer will be used to resolve relative loads from
this stylesheet.</li>
</ul>
<p>All these functions return an object with the following fields:</p>
<ul>
<li><code>css</code>: The compiled CSS, as a string.</li>
<li><code>loadedUrls</code>: All the URLs loaded during the compilation, in no particular
order.</li>
<li><code>sourceMap</code>: The source map for the file if <code>sourceMap: true</code> was passed, as
a decoded object.</li>
</ul>
<p>As with the Node Sass API, the synchronous functions will be substantially
faster than their asynchronous counterparts. Unfortunately the new API will not
support the <code>fibers</code> option for speeding up asynchronous compilation, since <a href="/blog/node-fibers-discontinued">the
<code>fibers</code> package has been discontinued</a>.</p>
<h2 id="loggers">
<a class="anchor" href="#loggers"><span class="visuallyhidden">Loggers permalink</span></a>Loggers</h2>
<p>The logger API gives you more fine-grained control over how and when warnings
and debug messages are emitted. Unlike other aspects of this proposal, a
<code>logger</code> option will also be added to the <em>old</em> API to allow you to control your
messages there without needing to upgrade to the new API immediately.</p>
<p>A logger implements the following interface:</p>
<pre class="highlight typescript"><code><span class="kr">interface</span> <span class="nx">Logger</span> <span class="p">{</span>
<span class="nx">warn</span><span class="p">?(</span>
<span class="nx">message</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="nx">options</span><span class="err">:</span> <span class="p">{</span>
<span class="nl">deprecation</span><span class="p">:</span> <span class="kr">boolean</span><span class="p">;</span>
<span class="nx">span</span><span class="p">?:</span> <span class="nx">SourceSpan</span><span class="p">;</span>
<span class="nx">stack</span><span class="p">?:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">)</span><span class="err">:</span> <span class="k">void</span><span class="p">;</span>
<span class="nx">debug</span><span class="p">?(</span>
<span class="nx">message</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="nx">options</span><span class="err">:</span> <span class="p">{</span><span class="nl">span</span><span class="p">:</span> <span class="nx">SourceSpan</span><span class="p">}</span>
<span class="p">)</span><span class="err">:</span> <span class="k">void</span><span class="p">;</span>
<span class="p">}</span>
</code></pre>
<p>The <code>warn</code> function handles warnings, including both warnings from the compiler
itself and from <code>@warn</code> rules. It’s passed:</p>
<ul>
<li>The warning message</li>
<li>A flag indicating whether it’s specifically a deprecation warning</li>
<li>A span indicating where the warning was located, if it comes from a specific
location</li>
<li>The Sass stack trace at the point at which the warning was encountered, if it
was encountered during execution</li>
</ul>
<p>The <code>debug</code> function handles only <code>@debug</code> rules, and is just passed the message
and the rule’s span. For more information on the <code>SourceSpan</code> type, see <a href="https://github.com/sass/sass/tree/main/proposal/js-logger.d.ts">the
Logger proposal</a>.</p>
<p>Sass will also provide a built-in logger, <code>Logger.silent</code>, that never emits any
messages. This will allow you to easily run Sass in “quiet mode” where no
warnings are ever surfaced.</p>
<h2 id="importers">
<a class="anchor" href="#importers"><span class="visuallyhidden">Importers permalink</span></a>Importers</h2>
<p>Rather than modeling importers as single-function callbacks, the new API models
them as objects that expose two methods: one that <em>canonicalizes</em> a URL, and one
that <em>loads</em> a canonical URL.</p>
<pre class="highlight typescript"><code><span class="c1">// Importers for compileAsync() and compileStringAsync() are the same, except</span>
<span class="c1">// they may return Promises as well.</span>
<span class="kr">interface</span> <span class="nx">Importer</span> <span class="p">{</span>
<span class="nx">canonicalize</span><span class="p">(</span>
<span class="nx">url</span><span class="err">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="nx">options</span><span class="err">:</span> <span class="p">{</span><span class="nl">fromImport</span><span class="p">:</span> <span class="kr">boolean</span><span class="p">}</span>
<span class="p">)</span><span class="err">:</span> <span class="nx">URL</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
<span class="nx">load</span><span class="p">(</span><span class="nx">canonicalUrl</span><span class="err">:</span> <span class="nx">URL</span><span class="p">)</span><span class="err">:</span> <span class="nx">ImporterResult</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
<span class="p">}</span>
</code></pre>
<p>Note that even stylesheets that are loaded directly from the filesystem through
<code>compile()</code> or <code>loadPaths</code> are treated as though they’re loaded by an importer.
This built-in filesystem importer canonicalizes all paths to <code>file:</code> URLs, and
loads those URLs from the physical filesystem.</p>
<h3 id="canonicalizing">
<a class="anchor" href="#canonicalizing"><span class="visuallyhidden">Canonicalizing permalink</span></a>Canonicalizing</h3>
<p>The first step determines the canonical URL for a stylesheet. Each stylesheet
has exactly one canonical URL that in turn refers to exactly one stylesheet. The
canonical URL must be absolute, including a scheme, but the specific structure
is up to the importer. In most cases, the stylesheet in question will exist on
disk and the importer will just return a <code>file:</code> URL for it.</p>
<p>The <code>canonicalize()</code> method takes a URL string that may be either relative or
absolute. If the importer recognizes that URL, it returns a corresponding
absolute URL (including a scheme). This is the <em>canonical URL</em> for the
stylesheet in question. Although the input URL may omit a file extension or
an initial underscore, the canonical URL must be fully resolved.</p>
<p>For a stylesheet that’s loaded from the filesystem, the canonical URL will be
the absolute <code>file:</code> URL of the physical file on disk. If it’s generated
in-memory, the importer should choose a custom URL scheme to guarantee that
its canonical URLs don’t conflict with any other importer’s.</p>
<p>For example, if you’re loading Sass files from a database, you might use the
scheme <code>db:</code>. The canonical URL for a stylesheet associated with key <code>styles</code>
in the database might be <code>db:styles</code>.</p>
<p>This function also takes a <code>fromImport</code> option that indicates whether the
importer is being invoked from an <code>@import</code> rule (as opposed to <code>@use</code>,
<code>@forward</code>, or <code>meta.load-css()</code>).</p>
<p>Having a canonical URL for each stylesheet allows Sass to ensure that the
same stylesheet isn’t loaded multiple times in the new module system.</p>
<h4 id="canonicalizing-relative-loads">
<a class="anchor" href="#canonicalizing-relative-loads"><span class="visuallyhidden">Canonicalizing Relative Loads permalink</span></a>Canonicalizing Relative Loads</h4>
<p>When a stylesheet tries to load a relative URL, such as <code>@use "variables"</code>, it’s
not clear from the document itself whether that refers to a file that exists
relative to the stylesheet or to another importer or load path. Here’s how the
importer API resolves that ambiguity:</p>
<ul>
<li><p>First, the relative URL is resolved relative to the canonical URL of the
stylesheet that contained the <code>@use</code> (or <code>@forward</code> or <code>@import</code>). For
example, if the canonical URL is <code>file:///path/to/my/_styles.scss</code>, then the
resolved URL will be <code>file:///path/to/my/variables</code>.</p></li>
<li><p>This URL is then passed to the <code>canonicalize()</code> method of the importer that
loaded the old stylesheet. (That means it’s important for your importers to
support absolute URLs!) If the importer recognizes it, it returns the
canonical value which is then passed to that importer’s <code>load()</code>; otherwise,
it returns <code>null</code>.</p></li>
<li><p>If the old stylesheet’s importer didn’t recognize the URL, it’s passed to all
the <code>importers</code>‘ canonicalize functions in the order they appear in <code>options</code>,
then checked for in all the <code>loadPaths</code>. If none of those recognizes it, the
load fails.</p></li>
</ul>
<p>It’s important that local relative paths take precedence over other importers or
load paths, because otherwise your local stylesheets could get unexpectedly
broken by a dependency adding a file with a conflicting name.</p>
<h3 id="loading">
<a class="anchor" href="#loading"><span class="visuallyhidden">Loading permalink</span></a>Loading</h3>
<p>The second step actually loads the text of the stylesheet. The <code>load()</code>
method takes a canonical URL that was returned by <code>canonicalize()</code> and
returns the contents of the stylesheet at that URL. This is only called once
per compilation for each canonical URL; future loads of the same URL will
re-use either the existing module (for <code>@use</code> and <code>@forward</code>) or the parse
tree (for <code>@import</code>).</p>
<p>The <code>load()</code> method returns an object with the following fields:</p>
<ul>
<li><code>css</code>: The text of the loaded stylesheet.</li>
<li><code>syntax</code>: The syntax of the file: <code>'scss'</code>, <code>'indented'</code>, or <code>'css'</code>.</li>
<li><code>sourceMapUrl</code>: An optional browser-accessible <code>URL</code> to include in source maps
when referring to this file.</li>
</ul>
<h3 id="fileimporter">
<a class="anchor" href="#fileimporter"><span class="visuallyhidden">FileImporter permalink</span></a><code>FileImporter</code>
</h3>
<p>This proposal also adds a special type of importer known as a <code>FileImporter</code>.
This importer makes the common case of redirecting loads to somewhere on the
physical filesystem easier. It doesn’t require the caller to implement
<code>load()</code>, since that’s always going to be the same for files on disk.</p>
<pre class="highlight typescript"><code><span class="kr">interface</span> <span class="nx">FileImporter</span> <span class="p">{</span>
<span class="nx">findFileUrl</span><span class="p">(</span>
<span class="nx">url</span><span class="err">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="nx">options</span><span class="err">:</span> <span class="p">{</span><span class="nl">fromImport</span><span class="p">:</span> <span class="kr">boolean</span><span class="p">}</span>
<span class="p">)</span><span class="err">:</span> <span class="nx">FileImporterResult</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
<span class="p">}</span>
</code></pre>
<p>The <code>findFileUrl()</code> method takes a relative URL and returns an object with the
following fields:</p>
<ul>
<li><code>url</code>: The absolute <code>file:</code> URL of the file to load. This URL doesn’t need to
be fully canonicalized: the Sass compiler will take care of resolving
partials, file extensions, index files, and so on.</li>
<li><code>sourceMapUrl</code>: An optional browser-accessible <code>URL</code> to include in source maps
when referring to this file.</li>
</ul>
<h2 id="functions">
<a class="anchor" href="#functions"><span class="visuallyhidden">Functions permalink</span></a>Functions</h2>
<p>The new function API’s function type is very similar to the old API’s:</p>
<pre class="highlight typescript"><code><span class="kd">type</span> <span class="nx">CustomFunctionCallback</span> <span class="o">=</span> <span class="p">(</span><span class="nx">args</span><span class="err">:</span> <span class="nx">Value</span><span class="p">[])</span> <span class="o">=></span> <span class="nx">Value</span><span class="p">;</span>
</code></pre>
<p>The only differences are:</p>
<ul>
<li>Async functions return a <code>Promise<Value></code> rather than calling a callback.</li>
<li>The value types themselves are different.</li>
</ul>
<p>The second point is pretty substantial, though! The new value types are much
more fleshed out than the old versions. Let’s start with the parent class:</p>
<pre class="highlight typescript"><code><span class="kd">abstract</span> <span class="kr">class</span> <span class="nx">Value</span> <span class="p">{</span>
<span class="cm">/**
* Returns the values of `this` when interpreted as a list.
*
* - For a list, this returns its elements.
* - For a map, this returns each of its key/value pairs as a `SassList`.
* - For any other value, this returns a list that contains only that value.
*/</span>
<span class="nx">get</span> <span class="nx">asList</span><span class="p">()</span><span class="err">:</span> <span class="nx">List</span><span class="o"><</span><span class="nx">Value</span><span class="o">></span><span class="p">;</span>
<span class="cm">/** Whether `this` is a bracketed Sass list. */</span>
<span class="nx">get</span> <span class="nx">hasBrackets</span><span class="p">()</span><span class="err">:</span> <span class="kr">boolean</span><span class="p">;</span>
<span class="cm">/** Whether `this` is truthy (any value other than `null` or `false`). */</span>
<span class="nx">get</span> <span class="nx">isTruthy</span><span class="p">()</span><span class="err">:</span> <span class="kr">boolean</span><span class="p">;</span>
<span class="cm">/** Returns JS's null if this is `sassNull`, or `this` otherwise. */</span>
<span class="nx">get</span> <span class="nx">realNull</span><span class="p">()</span><span class="err">:</span> <span class="kc">null</span> <span class="o">|</span> <span class="nx">Value</span><span class="p">;</span>
<span class="cm">/** If `this` is a list, return its separator. Otherwise, return `null`. */</span>
<span class="nx">get</span> <span class="nx">separator</span><span class="p">()</span><span class="err">:</span> <span class="nx">ListSeparator</span><span class="p">;</span>
<span class="cm">/**
* Converts the Sass index `sassIndex` to a JS index into the array returned
* by `asList`.
*
* Sass indices start counting at 1, and may be negative in order to index
* from the end of the list.
*/</span>
<span class="nx">sassIndexToListIndex</span><span class="p">(</span><span class="nx">sassIndex</span><span class="err">:</span> <span class="nx">Value</span><span class="p">)</span><span class="err">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="cm">/**
* Returns `this` if it's a `SassBoolean`, and throws an error otherwise.
*
* The `name` parameter is used for error reporting. It should match the name
* of a parameter passed to the custom function (without the `$`).
*/</span>
<span class="nx">assertBoolean</span><span class="p">(</span><span class="nx">name</span><span class="p">?:</span> <span class="kr">string</span><span class="p">)</span><span class="err">:</span> <span class="nx">SassBoolean</span><span class="p">;</span>
<span class="cm">/**
* Returns `this` if it's a `SassColor`, and throws an error otherwise.
*
* The `name` parameter is used for error reporting. It should match the name
* of a parameter passed to the custom function (without the `$`).
*/</span>
<span class="nx">assertColor</span><span class="p">(</span><span class="nx">name</span><span class="p">?:</span> <span class="kr">string</span><span class="p">)</span><span class="err">:</span> <span class="nx">SassColor</span><span class="p">;</span>
<span class="cm">/**
* Returns `this` if it's a `SassFunction`, and throws an error otherwise.
*
* The `name` parameter is used for error reporting. It should match the name
* of the parameter passed to the custom function (without the `$`).
*/</span>
<span class="nx">assertFunction</span><span class="p">(</span><span class="nx">name</span><span class="p">?:</span> <span class="kr">string</span><span class="p">)</span><span class="err">:</span> <span class="nx">SassFunction</span><span class="p">;</span>
<span class="cm">/**
* Returns `this` if it's a `SassMap` (or converts it to a `SassMap` if it's
* an empty list), and throws an error otherwise.
*
* The `name` parameter is used for error reporting. It should match the name
* of the parameter passed to the custom function (without the `$`).
*/</span>
<span class="nx">assertMap</span><span class="p">(</span><span class="nx">name</span><span class="p">?:</span> <span class="kr">string</span><span class="p">)</span><span class="err">:</span> <span class="nx">SassMap</span><span class="p">;</span>
<span class="cm">/**
* Returns `this` if it's a `SassNumber`, and throws an error otherwise.
*
* The `name` parameter is used for error reporting. It should match the name
* of a parameter passed to the custom function (without the `$`).
*/</span>
<span class="nx">assertNumber</span><span class="p">(</span><span class="nx">name</span><span class="p">?:</span> <span class="kr">string</span><span class="p">)</span><span class="err">:</span> <span class="nx">SassNumber</span><span class="p">;</span>
<span class="cm">/**
* Returns `this` if it's a `SassString`, and throws an error otherwise.
*
* The `name` parameter is used for error reporting. It should match the name
* of a parameter passed to the custom function (without the `$`).
*/</span>
<span class="nx">assertString</span><span class="p">(</span><span class="nx">name</span><span class="p">?:</span> <span class="kr">string</span><span class="p">)</span><span class="err">:</span> <span class="nx">SassString</span><span class="p">;</span>
<span class="cm">/**
* Returns the value of `this` if it can be interpreted as a map.
*
* - If this is a map, returns its contents.
* - If this is an empty list, returns an empty map.
* - Otherwise, returns `null`.
*/</span>
<span class="nx">tryMap</span><span class="p">()</span><span class="err">:</span> <span class="nx">OrderedMap</span><span class="o"><</span><span class="nx">Value</span><span class="p">,</span> <span class="nx">Value</span><span class="o">></span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
<span class="cm">/** Returns whether `this == other` in SassScript. */</span>
<span class="nx">equals</span><span class="p">(</span><span class="nx">other</span><span class="err">:</span> <span class="nx">Value</span><span class="p">)</span><span class="err">:</span> <span class="kr">boolean</span><span class="p">;</span>
<span class="p">}</span>
</code></pre>
<p>There are a couple important things to note here:</p>
<ul>
<li><p>Because CSS doesn’t have a strong syntactic differentiation between a single
element and a list containing one element, any Sass value may be treated as
though it’s a list. The <code>Value</code> makes it easy to follow this convention by
making the <code>asList()</code>, <code>hasBrackets()</code>, and <code>separator()</code> getters available
for every <code>Value</code>.</p></li>
<li><p>The list returned this was and the map returned by <code>asMap()</code> are immutable
types from the <a href="https://immutable-js.com/"><code>immutable</code> package</a>. This reflects Sass’s built-in
immutability of all its types. Although these values can’t be modified
directly, their APIs make it easy and efficient to create new values with
changes applied.</p></li>
<li><p>Sass’s list-indexing conventions are different than JavaScript’s. The
<code>sassIndexToListIndex()</code> function makes it easy to convert from Sass index to
JS index.</p></li>
<li><p>In Sass, any value may be used in a boolean context, with <code>false</code>
and <code>null</code> counting as “falsey” values. The <code>isTruthy</code> getter makes this
convention easy to follow.</p></li>
<li><p>The <code>assert*()</code> functions make it easy to ensure that you’re being passed the
arguments you expect, and to throw an idiomatic error if you’re not. They’re
particularly useful for TypeScript users since they’ll automatically narrow
the type of the <code>Value</code>.</p></li>
</ul>
<p>Most Sass values have their own subclasses, but there are three singleton values
that are just available as constants: <code>sassTrue</code>, <code>sassFalse</code>, and <code>sassNull</code>
represent Sass’s <code>true</code>, <code>false</code>, and <code>null</code> values respectively.</p>
<h3 id="colors">
<a class="anchor" href="#colors"><span class="visuallyhidden">Colors permalink</span></a>Colors</h3>
<p>The new API’s <code>SassColor</code> class provides access to colors in RGB, HSL, and HWB
format. As with built-in Sass color functions, any attribute can be accessed on
any color regardless of how it was initially created.</p>
<pre class="highlight typescript"><code><span class="kr">class</span> <span class="nx">SassColor</span> <span class="k">extends</span> <span class="nx">Value</span> <span class="p">{</span>
<span class="cm">/** Creates an RGB color. */</span>
<span class="k">static</span> <span class="nx">rgb</span><span class="p">(</span>
<span class="nx">red</span><span class="err">:</span> <span class="kr">number</span><span class="p">,</span>
<span class="nx">green</span><span class="err">:</span> <span class="kr">number</span><span class="p">,</span>
<span class="nx">blue</span><span class="err">:</span> <span class="kr">number</span><span class="p">,</span>
<span class="nx">alpha</span><span class="p">?:</span> <span class="kr">number</span>
<span class="p">)</span><span class="err">:</span> <span class="nx">SassColor</span><span class="p">;</span>
<span class="cm">/** Creates an HSL color. */</span>
<span class="k">static</span> <span class="nx">hsl</span><span class="p">(</span>
<span class="nx">hue</span><span class="err">:</span> <span class="kr">number</span><span class="p">,</span>
<span class="nx">saturation</span><span class="err">:</span> <span class="kr">number</span><span class="p">,</span>
<span class="nx">lightness</span><span class="err">:</span> <span class="kr">number</span><span class="p">,</span>
<span class="nx">alpha</span><span class="p">?:</span> <span class="kr">number</span>
<span class="p">)</span><span class="err">:</span> <span class="nx">SassColor</span><span class="p">;</span>
<span class="cm">/** Creates an HWB color. */</span>
<span class="k">static</span> <span class="nx">hwb</span><span class="p">(</span>
<span class="nx">hue</span><span class="err">:</span> <span class="kr">number</span><span class="p">,</span>
<span class="nx">whiteness</span><span class="err">:</span> <span class="kr">number</span><span class="p">,</span>
<span class="nx">blackness</span><span class="err">:</span> <span class="kr">number</span><span class="p">,</span>
<span class="nx">alpha</span><span class="p">?:</span> <span class="kr">number</span>
<span class="p">)</span><span class="err">:</span> <span class="nx">SassColor</span><span class="p">;</span>
<span class="cm">/** The color's red channel. */</span>
<span class="nx">get</span> <span class="nx">red</span><span class="p">()</span><span class="err">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="cm">/** The color's green channel. */</span>
<span class="nx">get</span> <span class="nx">green</span><span class="p">()</span><span class="err">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="cm">/** The color's blue channel. */</span>
<span class="nx">get</span> <span class="nx">blue</span><span class="p">()</span><span class="err">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="cm">/** The color's hue. */</span>
<span class="nx">get</span> <span class="nx">hue</span><span class="p">()</span><span class="err">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="cm">/** The color's saturation. */</span>
<span class="nx">get</span> <span class="nx">saturation</span><span class="p">()</span><span class="err">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="cm">/** The color's lightness. */</span>
<span class="nx">get</span> <span class="nx">lightness</span><span class="p">()</span><span class="err">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="cm">/** The color's whiteness. */</span>
<span class="nx">get</span> <span class="nx">whiteness</span><span class="p">()</span><span class="err">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="cm">/** The color's blackeness. */</span>
<span class="nx">get</span> <span class="nx">blackness</span><span class="p">()</span><span class="err">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="cm">/** The color's alpha channel. */</span>
<span class="nx">get</span> <span class="nx">alpha</span><span class="p">()</span><span class="err">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="cm">/**
* Returns a copy of `this` with the RGB channels updated to match `options`.
*/</span>
<span class="nx">changeRgb</span><span class="p">(</span><span class="nx">options</span><span class="err">:</span> <span class="p">{</span>
<span class="nx">red</span><span class="p">?:</span> <span class="kr">number</span><span class="p">;</span>
<span class="nx">green</span><span class="p">?:</span> <span class="kr">number</span><span class="p">;</span>
<span class="nx">blue</span><span class="p">?:</span> <span class="kr">number</span><span class="p">;</span>
<span class="nx">alpha</span><span class="p">?:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">})</span><span class="err">:</span> <span class="nx">SassColor</span><span class="p">;</span>
<span class="cm">/**
* Returns a copy of `this` with the HSL values updated to match `options`.
*/</span>
<span class="nx">changeHsl</span><span class="p">(</span><span class="nx">options</span><span class="err">:</span> <span class="p">{</span>
<span class="nx">hue</span><span class="p">?:</span> <span class="kr">number</span><span class="p">;</span>
<span class="nx">saturation</span><span class="p">?:</span> <span class="kr">number</span><span class="p">;</span>
<span class="nx">lightness</span><span class="p">?:</span> <span class="kr">number</span><span class="p">;</span>
<span class="nx">alpha</span><span class="p">?:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">})</span><span class="err">:</span> <span class="nx">SassColor</span><span class="p">;</span>
<span class="cm">/**
* Returns a copy of `this` with the HWB values updated to match `options`.
*/</span>
<span class="nx">changeHwb</span><span class="p">(</span><span class="nx">options</span><span class="err">:</span> <span class="p">{</span>
<span class="nx">hue</span><span class="p">?:</span> <span class="kr">number</span><span class="p">;</span>
<span class="nx">whiteness</span><span class="p">?:</span> <span class="kr">number</span><span class="p">;</span>
<span class="nx">blackness</span><span class="p">?:</span> <span class="kr">number</span><span class="p">;</span>
<span class="nx">alpha</span><span class="p">?:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">})</span><span class="err">:</span> <span class="nx">SassColor</span><span class="p">;</span>
<span class="cm">/** Returns a copy of `this` with `alpha` as its alpha channel. */</span>
<span class="nx">changeAlpha</span><span class="p">(</span><span class="nx">alpha</span><span class="err">:</span> <span class="kr">number</span><span class="p">)</span><span class="err">:</span> <span class="nx">SassColor</span><span class="p">;</span>
<span class="p">}</span>
</code></pre>
<h3 id="numbers">
<a class="anchor" href="#numbers"><span class="visuallyhidden">Numbers permalink</span></a>Numbers</h3>
<p>The <code>SassNumber</code> class stores its numerator and denominator units as arrays
rather than strings. In addition, it provides methods for asserting that it has
specific units (<code>assertNoUnits()</code>, <code>assertUnit()</code>) and for converting it to
specific units (<code>convert()</code>, <code>convertToMatch()</code>, <code>convertValue()</code>,
<code>convertValueToMatch()</code>, <code>coerce()</code>, <code>coerceValue()</code>, <code>coerceValueToMatch()</code>).</p>
<p>Sass’s numeric logic is also subtly different from JS, since Sass considers
numbers that differ by less than the 10th decimal digit to be identical. This
API provides a number of methods that help convert between this and JavaScript’s
numeric logic.</p>
<pre class="highlight typescript"><code><span class="kr">class</span> <span class="nx">SassNumber</span> <span class="k">extends</span> <span class="nx">Value</span> <span class="p">{</span>
<span class="cm">/** Creates a Sass number with no units or a single numerator unit. */</span>
<span class="kd">constructor</span><span class="p">(</span><span class="nx">value</span><span class="err">:</span> <span class="kr">number</span><span class="p">,</span> <span class="nx">unit</span><span class="p">?:</span> <span class="kr">string</span><span class="p">);</span>
<span class="cm">/** Creates a Sass number with multiple numerator and/or denominator units. */</span>
<span class="k">static</span> <span class="nx">withUnits</span><span class="p">(</span>
<span class="nx">value</span><span class="err">:</span> <span class="kr">number</span><span class="p">,</span>
<span class="nx">options</span><span class="p">?:</span> <span class="p">{</span>
<span class="nx">numeratorUnits</span><span class="p">?:</span> <span class="kr">string</span><span class="p">[]</span> <span class="o">|</span> <span class="nx">List</span><span class="o"><</span><span class="kr">string</span><span class="o">></span><span class="p">;</span>
<span class="nx">denominatorUnits</span><span class="p">?:</span> <span class="kr">string</span><span class="p">[]</span> <span class="o">|</span> <span class="nx">List</span><span class="o"><</span><span class="kr">string</span><span class="o">></span><span class="p">;</span>
<span class="p">}</span>
<span class="p">):</span> <span class="nx">SassNumber</span><span class="p">;</span>
<span class="cm">/** This number's value. */</span>
<span class="nx">get</span> <span class="nx">value</span><span class="p">():</span> <span class="kr">number</span><span class="p">;</span>
<span class="cm">/**
* Whether `value` is an integer according to Sass's numeric logic.
*
* The integer value can be accessed using `asInt`.
*/</span>
<span class="nx">get</span> <span class="nx">isInt</span><span class="p">():</span> <span class="kr">boolean</span><span class="p">;</span>
<span class="cm">/**
* If `value` is an integer according to Sass's numeric logic, returns the
* corresponding JS integer, or `null` if `value` isn't an integer.
*/</span>
<span class="nx">get</span> <span class="nx">asInt</span><span class="p">():</span> <span class="kr">number</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
<span class="cm">/** This number's numerator units. */</span>
<span class="nx">get</span> <span class="nx">numeratorUnits</span><span class="p">():</span> <span class="nx">List</span><span class="o"><</span><span class="kr">string</span><span class="o">></span><span class="p">;</span>
<span class="cm">/** This number's denominator units. */</span>
<span class="nx">get</span> <span class="nx">denominatorUnits</span><span class="p">():</span> <span class="nx">List</span><span class="o"><</span><span class="kr">string</span><span class="o">></span><span class="p">;</span>
<span class="cm">/** Whether `this` has numerator or denominator units. */</span>
<span class="nx">get</span> <span class="nx">hasUnits</span><span class="p">():</span> <span class="kr">boolean</span><span class="p">;</span>
<span class="cm">/**
* If `value` is an integer according to Sass's numeric logic, returns the
* corresponding JS integer, or throws an error if `value` isn't an integer.
*
* The `name` parameter is used for error reporting. It should match the name
* of the parameter passed to the custom function (without the `$`).
*/</span>
<span class="nx">assertInt</span><span class="p">(</span><span class="nx">name</span><span class="p">?:</span> <span class="kr">string</span><span class="p">):</span> <span class="kr">number</span><span class="p">;</span>
<span class="cm">/**
* If `value` is between `min` and `max` according to Sass's numeric logic,
* returns it clamped to that range. Otherwise, throws an error.
*
* The `name` parameter is used for error reporting. It should match the name
* of the parameter passed to the custom function (without the `$`).
*/</span>
<span class="nx">assertInRange</span><span class="p">(</span><span class="na">min</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="na">max</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="nx">name</span><span class="p">?:</span> <span class="kr">string</span><span class="p">):</span> <span class="kr">number</span><span class="p">;</span>
<span class="cm">/**
* Returns `this` if it has no units. Otherwise, throws an error.
*
* The `name` parameter is used for error reporting. It should match the name
* of a parameter passed to the custom function (without the `$`).
*/</span>
<span class="nx">assertNoUnits</span><span class="p">(</span><span class="nx">name</span><span class="p">?:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">SassNumber</span><span class="p">;</span>
<span class="cm">/**
* Returns `this` if it has `unit` as its single (numerator) unit. Otherwise,
* throws an error.
*
* The `name` parameter is used for error reporting. It should match the name
* of a parameter passed to the custom function (without the `$`).
*/</span>
<span class="nx">assertUnit</span><span class="p">(</span><span class="nx">name</span><span class="p">?:</span> <span class="na">stringunit</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">SassNumber</span><span class="p">;</span>
<span class="cm">/** Returns whether `this` has the single numerator unit `unit`. */</span>
<span class="nx">hasUnit</span><span class="p">(</span><span class="na">unit</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="kr">boolean</span><span class="p">;</span>
<span class="cm">/** Returns whether this number's units are compatible with `unit`. */</span>
<span class="nx">compatibleWithUnit</span><span class="p">(</span><span class="na">unit</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="kr">boolean</span><span class="p">;</span>
<span class="cm">/**
* If this number's units are compatible with `newNumerators` and
* `newDenominators`, returns a new number with those units that's equal to
* `this`. Otherwise, throws an error.
*
* Note that unitless numbers are only compatible with other unitless numbers.
*/</span>
<span class="nx">convert</span><span class="p">(</span>
<span class="na">newNumerators</span><span class="p">:</span> <span class="kr">string</span><span class="p">[]</span> <span class="o">|</span> <span class="nx">List</span><span class="o"><</span><span class="kr">string</span><span class="o">></span><span class="p">,</span>
<span class="na">newDenominators</span><span class="p">:</span> <span class="kr">string</span><span class="p">[]</span> <span class="o">|</span> <span class="nx">List</span><span class="o"><</span><span class="kr">string</span><span class="o">></span>
<span class="p">):</span> <span class="nx">SassNumber</span><span class="p">;</span>
<span class="cm">/**
* If this number's units are compatible with `other`'s, returns a new number
* with `other`'s units that's equal to `this`. Otherwise, throws an error.
*
* Note that unitless numbers are only compatible with other unitless numbers.
*/</span>
<span class="nx">convertToMatch</span><span class="p">(</span><span class="na">other</span><span class="p">:</span> <span class="nx">SassNumber</span><span class="p">):</span> <span class="nx">SassNumber</span><span class="p">;</span>
<span class="cm">/** Equivalent to `convert(newNumerators, newDenominators).value`. */</span>
<span class="nx">convertValue</span><span class="p">(</span>
<span class="na">newNumerators</span><span class="p">:</span> <span class="kr">string</span><span class="p">[]</span> <span class="o">|</span> <span class="nx">List</span><span class="o"><</span><span class="kr">string</span><span class="o">></span><span class="p">,</span>
<span class="na">newDenominators</span><span class="p">:</span> <span class="kr">string</span><span class="p">[]</span> <span class="o">|</span> <span class="nx">List</span><span class="o"><</span><span class="kr">string</span><span class="o">></span>
<span class="p">):</span> <span class="kr">number</span><span class="p">;</span>
<span class="cm">/** Equivalent to `convertToMatch(other).value`. */</span>
<span class="nx">convertValueToMatch</span><span class="p">(</span><span class="na">other</span><span class="p">:</span> <span class="nx">SassNumber</span><span class="p">):</span> <span class="kr">number</span><span class="p">;</span>
<span class="cm">/**
* Like `convert()`, but if `this` is unitless returns a copy of it with the
* same value and the given units.
*/</span>
<span class="nx">coerce</span><span class="p">(</span>
<span class="na">newNumerators</span><span class="p">:</span> <span class="kr">string</span><span class="p">[]</span> <span class="o">|</span> <span class="nx">List</span><span class="o"><</span><span class="kr">string</span><span class="o">></span><span class="p">,</span>
<span class="na">newDenominators</span><span class="p">:</span> <span class="kr">string</span><span class="p">[]</span> <span class="o">|</span> <span class="nx">List</span><span class="o"><</span><span class="kr">string</span><span class="o">></span>
<span class="p">):</span> <span class="nx">SassNumber</span><span class="p">;</span>
<span class="cm">/**
* Like `convertToMatch()`, but if `this` is unitless returns a copy of it
* with the same value and `other`'s units.
*/</span>
<span class="nx">coerceToMatch</span><span class="p">(</span><span class="na">other</span><span class="p">:</span> <span class="nx">SassNumber</span><span class="p">):</span> <span class="nx">SassNumber</span><span class="p">;</span>
<span class="cm">/** Equivalent to `coerce(newNumerators, newDenominators).value`. */</span>
<span class="nx">coerceValue</span><span class="p">(</span>
<span class="na">newNumerators</span><span class="p">:</span> <span class="kr">string</span><span class="p">[]</span> <span class="o">|</span> <span class="nx">List</span><span class="o"><</span><span class="kr">string</span><span class="o">></span><span class="p">,</span>
<span class="na">newDenominators</span><span class="p">:</span> <span class="kr">string</span><span class="p">[]</span> <span class="o">|</span> <span class="nx">List</span><span class="o"><</span><span class="kr">string</span><span class="o">></span>
<span class="p">):</span> <span class="kr">number</span><span class="p">;</span>
<span class="cm">/** Equivalent to `coerceToMatch(other).value`. */</span>
<span class="nx">coerceValueToMatch</span><span class="p">(</span><span class="na">other</span><span class="p">:</span> <span class="nx">SassNumber</span><span class="p">):</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">}</span>
</code></pre>
<h3 id="strings">
<a class="anchor" href="#strings"><span class="visuallyhidden">Strings permalink</span></a>Strings</h3>
<p>The <code>SassString</code> class provides access to information about whether or not the
string is quoted. As with lists, JS’s notion of indexes differs from Sass’s, so
it also provides the <code>sassIndexToStringIndex()</code> method to convert a JS index
into a Sass index.</p>
<pre class="highlight typescript"><code><span class="kr">class</span> <span class="nx">SassString</span> <span class="k">extends</span> <span class="nx">Value</span> <span class="p">{</span>
<span class="cm">/** Creates a string with the given `text`. */</span>
<span class="kd">constructor</span><span class="p">(</span>
<span class="nx">text</span><span class="err">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="nx">options</span><span class="p">?:</span> <span class="p">{</span>
<span class="cm">/** @default true */</span>
<span class="na">quotes</span><span class="p">:</span> <span class="kr">boolean</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">);</span>
<span class="cm">/** Creates an empty string`. */</span>
<span class="k">static</span> <span class="nx">empty</span><span class="p">(</span><span class="nx">options</span><span class="p">?:</span> <span class="p">{</span>
<span class="cm">/** @default true */</span>
<span class="na">quotes</span><span class="p">:</span> <span class="kr">boolean</span><span class="p">;</span>
<span class="p">}):</span> <span class="nx">SassString</span><span class="p">;</span>
<span class="cm">/** The contents of `this`. */</span>
<span class="nx">get</span> <span class="nx">text</span><span class="p">():</span> <span class="kr">string</span><span class="p">;</span>
<span class="cm">/** Whether `this` has quotes. */</span>
<span class="nx">get</span> <span class="nx">hasQuotes</span><span class="p">():</span> <span class="kr">boolean</span><span class="p">;</span>
<span class="cm">/** The number of Unicode code points in `text`. */</span>
<span class="nx">get</span> <span class="nx">sassLength</span><span class="p">():</span> <span class="kr">number</span><span class="p">;</span>
<span class="cm">/**
* Converts the Sass index `sassIndex` to a JS index into `text`.
*
* Sass indices start counting at 1, and may be negative in order to index
* from the end of the list. In addition, Sass indexes strings by Unicode code
* point, while JS indexes them by UTF-16 code unit.
*/</span>
<span class="nx">sassIndexToStringIndex</span><span class="p">(</span><span class="na">sassIndex</span><span class="p">:</span> <span class="nx">Value</span><span class="p">):</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">}</span>
</code></pre>
<h3 id="lists">
<a class="anchor" href="#lists"><span class="visuallyhidden">Lists permalink</span></a>Lists</h3>
<p>As mentioned above, most list functions are on the <code>Value</code> superclass to make it
easy to follow the Sass convention of treating all values as lists. However, the
<code>SassList</code> class can still be constructed to make new lists:</p>
<pre class="highlight typescript"><code><span class="kr">class</span> <span class="nx">SassList</span> <span class="k">extends</span> <span class="nx">Value</span> <span class="p">{</span>
<span class="cm">/** Creates a Sass list with the given `contents`. */</span>
<span class="kd">constructor</span><span class="p">(</span>
<span class="nx">contents</span><span class="err">:</span> <span class="nx">Value</span><span class="p">[]</span> <span class="o">|</span> <span class="nx">List</span><span class="o"><</span><span class="nx">Value</span><span class="o">></span><span class="p">,</span>
<span class="nx">options</span><span class="p">?:</span> <span class="p">{</span>
<span class="cm">/** @default ',' */</span>
<span class="nx">separator</span><span class="p">?:</span> <span class="nx">ListSeparator</span><span class="p">;</span>
<span class="cm">/** @default false */</span>
<span class="nx">brackets</span><span class="p">?:</span> <span class="kr">boolean</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">);</span>
<span class="cm">/** Creates an empty Sass list. */</span>
<span class="k">static</span> <span class="nx">empty</span><span class="p">(</span><span class="nx">options</span><span class="p">?:</span> <span class="p">{</span>
<span class="cm">/** @default null */</span>
<span class="nx">separator</span><span class="p">?:</span> <span class="nx">ListSeparator</span><span class="p">;</span>
<span class="cm">/** @default false */</span>
<span class="nx">brackets</span><span class="p">?:</span> <span class="kr">boolean</span><span class="p">;</span>
<span class="p">}):</span> <span class="nx">SassList</span><span class="p">;</span>
<span class="p">}</span>
</code></pre>
<h3 id="maps">
<a class="anchor" href="#maps"><span class="visuallyhidden">Maps permalink</span></a>Maps</h3>
<p>The <code>SassMap</code> class simply exposes its contents as an <code>OrderedMap</code> from the
<a href="https://immutable-js.com/"><code>immutable</code> package</a>.</p>
<pre class="highlight typescript"><code><span class="kr">class</span> <span class="nx">SassMap</span> <span class="k">extends</span> <span class="nx">Value</span> <span class="p">{</span>
<span class="cm">/** Creates a Sass map with the given `contents`. */</span>
<span class="kd">constructor</span><span class="p">(</span><span class="nx">contents</span><span class="err">:</span> <span class="nx">OrderedMap</span><span class="o"><</span><span class="nx">Value</span><span class="p">,</span> <span class="nx">Value</span><span class="o">></span><span class="p">);</span>
<span class="cm">/** Creates an empty Sass map. */</span>
<span class="k">static</span> <span class="nx">empty</span><span class="p">()</span><span class="err">:</span> <span class="nx">SassMap</span><span class="p">;</span>
<span class="cm">/** Returns this map's contents. */</span>
<span class="nx">get</span> <span class="nx">contents</span><span class="p">()</span><span class="err">:</span> <span class="nx">OrderedMap</span><span class="o"><</span><span class="nx">Value</span><span class="p">,</span> <span class="nx">Value</span><span class="o">></span><span class="p">;</span>
<span class="p">}</span>
</code></pre>
<h3 id="functions">
<a class="anchor" href="#functions"><span class="visuallyhidden">Functions permalink</span></a>Functions</h3>
<p>The <code>SassFunction</code> class is fairly restrictive: it just allows a new first-class
function to be created with a synchronous callback. These functions can’t be
invoked by custom functions—but they still provide more power than the old API!</p>
<pre class="highlight typescript"><code><span class="kr">class</span> <span class="nx">SassFunction</span> <span class="k">extends</span> <span class="nx">Value</span> <span class="p">{</span>
<span class="cm">/**
* Creates a Sass function value with the given `signature` that calls
* `callback` when it's invoked.
*/</span>
<span class="kd">constructor</span><span class="p">(</span>
<span class="nx">signature</span><span class="err">:</span> <span class="kr">string</span><span class="p">,</span>
<span class="nx">callback</span><span class="err">:</span> <span class="nx">CustomFunctionCallback</span>
<span class="p">);</span>
<span class="p">}</span>
</code></pre>
<h2 id="more-information">
<a class="anchor" href="#more-information"><span class="visuallyhidden">More Information permalink</span></a>More Information</h2>
<p>If you want to know more about these proposals and see their most up-to-date
forms, they’re available on GitHub to view in full:</p>
<ul>
<li><a href="https://github.com/sass/sass/tree/main/proposal/new-js-api.d.ts">Compile API proposal</a></li>
<li><a href="https://github.com/sass/sass/blob/main/proposal/js-logger.d.ts">Logger proposal</a></li>
<li><a href="https://github.com/sass/sass/blob/main/proposal/new-js-importer.d.ts">Importer proposal</a></li>
<li><a href="https://github.com/sass/sass/blob/main/proposal/new-function-and-values-api.d.ts">Functions and values proposal</a></li>
</ul>
<p>We’re eager for feedback, so please <a href="https://github.com/sass/sass/issues/new">let us know what you think</a>! The proposals
in question will be open for at least a month after this blog post goes live,
and possibly more depending on how lively the discussion around them is.</p>
The Discontinuation of node-fibershttps://sass-lang.com/blog/node-fibers-discontinued2021年03月26日T23:00:00+00:002022年01月05日T05:12:05+00:00Natalie Weizenbaum<p>We have recently received the unfortunate but not entirely surprising news that
<a href="https://github.com/laverdet/node-fibers/commit/8f2809869cc92c28c92880c4a38317ae3dbe654d">the <code>node-fibers</code> package has reached its end-of-life</a> and will not be updated
for compatibility with Node 16. Dart Sass has historically allowed JavaScript
users to pass in <code>node-fibers</code> to improve the performance of the asynchronous
<code>render()</code> method, but going forward this will unfortunately no longer be an
option in Node 16 and on.</p>
<p>There are a number of <a href="#reclaiming-performance">alternative options</a> for reclaiming this lost
performance, some of them which are available today, some which are in
development, and some which are theoretical but could be made real with pull
requests from users like you. Sadly, none of the options that are ready today
are drop-in solutions with the same level of ease-of-use as <code>node-fibers</code>, so if
that performance is crucial to you we recommend staying on Node 14 for the time
being.</p>
<h2 id="what-happened">
<a class="anchor" href="#what-happened"><span class="visuallyhidden">What Happened? permalink</span></a>What Happened?</h2>
<p>In order to understand how we got here, it’s important to know two pieces of
history. First, why does Dart Sass use <code>node-fibers</code> in the first place? And
second, why is <code>node-fibers</code> dying?</p>
<p><em>This section is fairly technical, so feel free to <a href="#reclaiming-performance">skip ahead</a> if you don’t care
about the gory details.</em></p>
<h3 id="fibers-in-sass">
<a class="anchor" href="#fibers-in-sass"><span class="visuallyhidden">Fibers in Sass permalink</span></a>Fibers in Sass</h3>
<p>Dart Sass inherited its <a href="/documentation/js-api">JavaScript API</a> from the now-deprecated <a href="https://www.npmjs.com/package/node-sass">Node Sass</a>.
This API has two main functions for compiling Sass files: <code>renderSync()</code> which
synchronously returned the compiled CSS, and <code>render()</code> which instead takes a
callback to which it passes the compiled CSS asynchronously. Only <code>render()</code>
allowed asynchronous plugins, including widely-used importers such as webpack’s
<a href="https://www.npmjs.com/package/sass-loader"><code>sass-loader</code></a>, so <code>render()</code> became very widely used in practice.</p>
<p>For Node Sass, the performance difference between <code>render()</code> and <code>renderSync()</code>
was negligible, because it was built on C++ code which had few restrictions on
how it handled asynchrony. However, Dart Sass runs as pure JavaScript, which
makes it subject to JavaScript’s strict async rules. Asynchrony in JavaScript is
<em>contagious</em>, which means that if any function (such as an importer plugin) is
asynchronous, then everything that calls it must be asynchronous, and so on
until the entire program is asynchronous.</p>
<p>And asynchrony in JavaScript isn’t free. Every asynchronous function call has to
allocate callbacks, store them somewhere, and take a trip back to the event loop
before invoking those callbacks, and that all takes time. In fact, it takes
enough time that the asynchronous <code>render()</code> in Dart Sass tends to be 2-3x
slower than <code>renderSync()</code>.</p>
<p>Enter fibers. Fibers are a very cool concept, available in languages like Ruby
and C++, that give the programmer more control over asynchronous functions. They
can even allow a chunk of synchronous code (such as the Sass compiler) to call
asynchronous callbacks (such as the webpack plugin). The <code>node-fibers</code> package
did some arcane magick with the V8 virtual machine to implement Fibers in
JavaScript, which allowed Dart Sass to use the fast synchronous code to
implement the asynchronous <code>render()</code> API. And for a time, it was great.</p>
<h3 id="the-death-of-fibers">
<a class="anchor" href="#the-death-of-fibers"><span class="visuallyhidden">The Death of Fibers permalink</span></a>The Death of Fibers</h3>
<p>Unfortunately, the arcane magick that <code>node-fibers</code> used involved accessing some
parts of V8 that were not officially part of its public API. There was no
guarantee that the interfaces they were using would stay the same from release
to release, and indeed they tended to change fairly regularly. For a long time,
those changes were small enough that it was possible to release a new version of
<code>node-fibers</code> that supported them, but with Node.js 16 the luck ran out.</p>
<p>The latest version of V8 involves some major overhauls to its internals. These
will eventually allow it to implement some cool improvements, so its hard to
begrudge, but a side effect is that the APIs <code>node-fibers</code> was using are
completely gone without an obvious replacement. This is no one’s fault: since
those interfaces weren’t part of V8’s public API, they were under no obligation
to keep them stable. Sometimes in software that’s just the way things go.</p>
<h2 id="reclaiming-performance">
<a class="anchor" href="#reclaiming-performance"><span class="visuallyhidden">Reclaiming Performance permalink</span></a>Reclaiming Performance</h2>
<p>There are a few options for getting back the performance that’s lost by no
longer being able to pass <code>node-fibers</code> to <code>sass.render()</code>. In order from
nearest to longest term:</p>
<h3 id="avoid-asynchronous-plugins">
<a class="anchor" href="#avoid-asynchronous-plugins"><span class="visuallyhidden">Avoid Asynchronous Plugins permalink</span></a>Avoid Asynchronous Plugins</h3>
<p>This is something you can do today. If it’s at all possible to make the plugins
you pass in to Sass synchronous, you can use the <code>renderSync()</code> method which
doesn’t need fibers to go fast. This may require rewriting some existing
plugins, but it will pay dividends immediately.</p>
<h3 id="embedded-dart-sass">
<a class="anchor" href="#embedded-dart-sass"><span class="visuallyhidden">Embedded Dart Sass permalink</span></a>Embedded Dart Sass</h3>
<p>While it’s not ready for prime-time yet, the Sass team is working on a project
called “embedded Dart Sass”. This involves running Dart Sass as a <em>subprocess</em>,
rather than a library, and communicating with it using a special protocol. This
provides several important improvements over the existing alternatives:</p>
<ul>
<li><p>Unlike running <code>sass</code> from the command line, this will still work with plugins
like the webpack importer. In fact, we plan to match the existing JavaScript
API as closely as possible. This will probably run asynchronous plugins <em>even
faster</em> than synchronous ones.</p></li>
<li><p>Unlike the existing JS-compiled version, this will use the Dart VM. Due to the
more static nature of the Dart language, the Dart VM runs Sass substantially
faster than Node.js, which will provide about a 2x speed improvement for large
stylesheets.</p></li>
</ul>
<p>The Node.js host for Embedded Sass is still in active development, but there’s
<a href="https://www.npmjs.com/package/sass-embedded">a beta release</a> available (with minimal features) if you want to kick the
tires.</p>
<h3 id="worker-threads">
<a class="anchor" href="#worker-threads"><span class="visuallyhidden">Worker Threads permalink</span></a>Worker Threads</h3>
<p>We’ve explored the possibility of running the pure-JS Dart Sass in a Node.js
worker thread. Worker threads work a bit like fibers in that they make it
possible for synchronous code to wait for asynchronous callbacks to run.
Unfortunately, they’re also <em>extremely</em> restrictive about what sorts of
information can be passed across the thread boundary, which makes it much harder
to use them to wrap a complex API like Sass’s.</p>
<p>At the moment, the Sass team is focused on Embedded Sass, so we don’t have the
spare bandwidth to dive into worker threads as an alternative. That said, we’d
be happy to help a motivated user implement this. If you’re interested, follow
up on <a href="https://github.com/sass/dart-sass/issues/868">the GitHub issue</a>!</p>
<h3 id="reanimating-node-fibers">
<a class="anchor" href="#reanimating-node-fibers"><span class="visuallyhidden">Reanimating node-fibers permalink</span></a>Reanimating <code>node-fibers</code>
</h3>
<p>There’s one other potential solution, although it would take true dedication to
turn into reality. It would in principle be possible to add a new API to V8 that
would <em>officially</em> support the hooks <code>node-fibers</code> needs to do its good work.
This would allow the package to return gloriously to life and Sass to make
<code>render()</code> fast on into the future.</p>
<p>The Sass team has contacted both the V8 team and the owner of <code>node-fibers</code>, and
both of them are amenable to this idea in principle. While neither one has the
time to see it through to completion themselves, they’ve expressed willingness
to help an engineer who’s willing to give it a shot.</p>
<p>This isn’t a contribution for the faint of heart, though: it requires knowledge
of C++, a willingness to learn at least the basics of the <code>node-fibers</code> codebase
and V8’s isolate APIs, and skills in both API design and human interaction to
negotiate a stable API that will meet the needs of <code>node-fibers</code> <em>and</em> that the
V8 team feels comfortable committing to maintain. But if you’re interested,
please don’t hesitate to <a href="mailto:nweiz@google.com">reach out</a>!</p>
Request for Comments: First-Class Calchttps://sass-lang.com/blog/request-for-comments-first-class-calc2021年03月15日T09:35:00+00:002022年01月05日T05:12:05+00:00Natalie Weizenbaum<p>One of the absolutely most-requested features in Sass is the ability to more
easily work with <code>calc()</code> expressions. These expressions have historically been
parsed opaquely: between the parentheses, you can put any text at all, and
Sass will just treat it as an unquoted string. This has simplified Sass’s
parser, since we don’t have to support the specific <code>calc()</code> microsyntax, and
it’s meant that we automatically support new features like the use of <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties">CSS
variables</a> within <code>calc()</code>.</p>
<p>However, it comes at a substantial usability cost as well. Because each <code>calc()</code>
is totally opaque to Sass’s parser, users can’t simply use Sass variables in
place of values; they have to <a href="https://sass-lang.com/documentation/interpolation">interpolate</a> variables explicitly. And once a
<code>calc()</code> expression has been created, there’s no way to manipulate it with Sass
the way you can manipulate a plain number.</p>
<p>We’re looking to change that with a new proposal we call “First-Class Calc”.
This proposal changes <code>calc()</code> (and other supported mathematical functions) from
being parsed as unquoted strings to being parsed in-depth, and sometimes
(although not always) producing a new data type known as a “calculation”. This
data type represents mathematical expressions that can’t be resolved at
compile-time, such as <code>calc(10% + 5px)</code>, and allows those expressions to be
combined gracefully within further mathematical functions.</p>
<p>To be more specific: a <code>calc()</code> expression will be parsed according to the <a href="https://drafts.csswg.org/css-values-3/#calc-syntax">CSS
syntax</a>, with additional support for Sass variables, functions, and (for
backwards compatibility) interpolation. Sass will perform as much math as
possible at compile-time, and if the result is a single number it will return it
as a normal Sass number type. Otherwise, it will return a calculation that
represents the (simplified) expression that can be resolved in the browser.</p>
<p>For example:</p>
<ul>
<li><p><code>calc(1px + 10px)</code> will return the number <code>11px</code>.</p></li>
<li><p>Similarly, if <code>$length</code> is <code>10px</code>, <code>calc(1px + $length)</code> will return <code>11px</code>.</p></li>
<li><p>However, <code>calc(1px + 10%)</code> will return the calc <code>calc(1px + 10%)</code>.</p></li>
<li><p>If <code>$length</code> is <code>calc(1px + 10%)</code>, <code>calc(1px + $length)</code> will return
<code>calc(2px + 10%)</code>.</p></li>
<li><p>Sass functions can be used directly in <code>calc()</code>, so <code>calc(1% +
math.round(15.3px))</code> returns <code>calc(1% + 15px)</code>.</p></li>
</ul>
<p>Note that calculations cannot generally be used in place of numbers. For
example, <code>1px + calc(1px + 10%)</code> will produce an error, as will
<code>math.round(calc(1px + 10%))</code>. This is because calculations can’t be used
interchangeably with numbers (you can’t pass a calculation to <code>math.sqrt()</code>), so
we want to make sure mathematical functions are explicit about whether or not
they support calculations by either wrapping all of their math in <code>calc()</code> or
using normal Sass arithmetic.</p>
<p>For backwards compatibility, <code>calc()</code> expressions that contain interpolation
will continue to be parsed using the old highly-permissive syntax, although this
behavior will eventually be deprecated and removed. These expressions will still
return calculation values, but they’ll never be simplified or resolve to plain
numbers.</p>
<h2 id="let-us-know-what-you-think">
<a class="anchor" href="#let-us-know-what-you-think"><span class="visuallyhidden">Let us know what you think! permalink</span></a>Let us know what you think!</h2>
<p>If you’re interested in learning more about this proposal, <a href="https://github.com/sass/sass/tree/main/proposal/first-class-calc.md">read it in full</a> on
GitHub. It’s open for comments and revisions for the next month, so if you’d
like to see something change please <a href="https://github.com/sass/sass/issues/new">file an issue</a> and we can discuss it!</p>
LibSass is Deprecatedhttps://sass-lang.com/blog/libsass-is-deprecated2020年10月26日T20:00:00+00:002022年01月05日T05:12:05+00:00Natalie Weizenbaum<p>After much discussion among the Sass core team, we’ve come to the conclusion
that it’s time to officially declare that LibSass and the packages built on top
of it, including Node Sass, are deprecated. For several years now, it’s been
clear that there’s simply not enough engineering bandwidth behind LibSass to
keep it up-to-date with the latest developments in the Sass language (for
example, the most recent new language feature was added in <a href="https://github.com/sass/libsass/releases/tag/3.5.5">November 2018</a>). As
much as we’ve hoped to see this pattern turn around, even the excellent work of
long-time LibSass contributors Michael Mifsud and Marcel Greter couldn’t keep up
with the fast pace of language development in both CSS and Sass.</p>
<p>I’ll go into detail about what this means below, but here are the major points:</p>
<ul>
<li><p>We no longer recommend LibSass for new Sass projects. Use <a href="https://sass-lang.com/dart-sass">Dart Sass</a> instead.</p></li>
<li><p>We recommend all existing LibSass users make plans to eventually move onto
Dart Sass, and that all Sass libraries make plans to eventually drop support
for LibSass.</p></li>
<li><p>We’re no longer planning to add any new features to LibSass, including
compatibility with new CSS features.</p></li>
<li><p>LibSass and Node Sass will continue to be maintained indefinitely on a
best-effort basis, including fixing major bugs and security issues and
maintaining compatibility with the latest Node versions.</p></li>
</ul>
<h2 id="why-deprecate">
<a class="anchor" href="#why-deprecate"><span class="visuallyhidden">Why deprecate? permalink</span></a>Why deprecate?</h2>
<p>For several years now, Sass has managed to exist in an ambiguous kind of state
where LibSass was an officially-supported implementation in theory, but its
feature surface was static in practice. As time has gone on, it’s becoming
increasingly clear that this state causes substantial concrete problems for Sass
users. For example, we regularly see users confused as to why <a href="https://github.com/sass/sass/issues/2849">plain-CSS <code>min()</code>
and <code>max()</code> don’t work</a> and assuming Sass as a whole is at fault, when in fact
it’s only LibSass that doesn’t support that feature.</p>
<p>Official support for LibSass doesn’t just cause pain for individual users.
Because LibSass doesn’t support the <a href="https://sass-lang.com/blog/the-module-system-is-launched">Sass module system</a> that launched last
year, major shared Sass libraries have been unable to use it for fear that their
downstream users would be incompatible. By clearly indicating that all Sass
users should eventually move off of LibSass, we hope to make it more feasible
for these library authors to use more modern features.</p>
<p>LibSass has even inhibited the development of the Sass language itself. We’ve
been unable to move forward with the proposal for <a href="https://github.com/sass/sass/blob/main/accepted/slash-separator.md">treating <code>/</code> as a separator</a>
because any code they’d write would either produce deprecation warnings in Dart
Sass or fail to compile in LibSass. By marking LibSass as deprecated, this
becomes much more feasible, and Sass becomes much better at supporting the
latest versions of CSS.</p>
<h2 id="what-does-deprecated-mean">
<a class="anchor" href="#what-does-deprecated-mean"><span class="visuallyhidden">What does "deprecated" mean? permalink</span></a>What does "deprecated" mean?</h2>
<p>We’re choosing to use the term “deprecated” because it carries a lot of weight
in the programming community, and provides a strong signal that users should
start planning to move away from LibSass. However, it doesn’t mean that the
project is entirely dead. Michael Mifsud, the lead maintainer of LibSass and
Node Sass, has affirmed that he plans to continue maintenance on the same level
as the past few years. This means that although there will be no more features
added (and as such LibSass will slowly drift further and further out of
compatibility with the latest CSS and Sass syntax), there will continue to be
maintenance releases indefinitely.</p>
<h2 id="what-about-portability-and-performance">
<a class="anchor" href="#what-about-portability-and-performance"><span class="visuallyhidden">What about portability and performance? permalink</span></a>What about portability and performance?</h2>
<p>LibSass today has two major benefits over Dart Sass:</p>
<ul>
<li><p><strong>Portability</strong>: since it’s written in C++, it’s easy to embed LibSass within
other programming languages and provide a native-feeling API.</p></li>
<li><p><strong>Performance</strong>: calling out to LibSass via the C++ API is very fast relative
to the speeds of code written directly in scripting languages. In particular,
this means LibSass is substantially faster in JavaScript than Dart
Sass-compiled-to-JS (although it’s comparable to Dart Sass’s command-line
executable).</p></li>
</ul>
<p>We’re working on addressing both of those with the <a href="https://github.com/sass/embedded-protocol">Sass embedded protocol</a>,
which runs a Sass compiler as a subprocess that can communicate with any host
language via message-passing. The embedded protocol supports all the features of
a native Sass API, including the ability to define custom importers and Sass
functions, while also providing the high performance of the CLI app. Dart Sass
has already implemented the compiler side of the embedded protocol, and a
JavaScript host for it is in active development.</p>
<h2 id="how-do-i-migrate">
<a class="anchor" href="#how-do-i-migrate"><span class="visuallyhidden">How do I migrate? permalink</span></a>How do I migrate?</h2>
<p>If you’re a user of Node Sass, migrating to Dart Sass is straightforward: just
replace <code>node-sass</code> in your <code>package.json</code> file with <code>sass</code>. Both packages
expose the same JavaScript API.</p>
<p>If you’re using the SassC command-line interface, you can switch to <a href="https://sass-lang.com/documentation/cli/dart-sass">Dart Sass’s
CLI</a>. Note that this doesn’t have exactly the same interface as SassC, so you
may need to change a few flags.</p>
<p>If you’re using LibSass through a wrapper library in another language, you can
either switch to the Dart Sass CLI or ask the maintainer of the LibSass wrapper
to convert it to a host for the <a href="https://github.com/sass/embedded-protocol">Sass embedded protocol</a>. The embedded protocol
allows any language to provide a native API that calls out to Dart Sass.</p>
<p>Please note that because activity on LibSass has been low for several years, it
has a number of outstanding bugs and behavioral variations from the Sass spec.
You may need to make minor updates to stylesheets to make them compatible with
Dart Sass. See <a href="https://github.com/sass/libsass/issues?q=is%3Aopen+is%3Aissue+label%3A%22Compatibility+-+P1+%E2%9A%A0%EF%B8%8F%22">this list of major compatibility issues</a> for reference.</p>
<h2 id="thank-you">
<a class="anchor" href="#thank-you"><span class="visuallyhidden">Thank you permalink</span></a>Thank you</h2>
<p>Finally, I want to thank everyone who’s put so much time and energy into LibSass
and Node Sass over the years. It will always be a towering achievement, and
Sass’s popularity outside of the Ruby community is undoubtedly due in large part
to its existence. Many people have tried to implement Sass only to find that the
language is much deeper and more complex than they expected, and LibSass alone
among all of those implementations managed to become fully-featured enough to
provide real value for thousands if not millions of users. These maintainers
deserve to be proud of that work, and I hope they’ll always consider themselves
part of the Sass community going forward.</p>
Request for Comments: HWB Functionshttps://sass-lang.com/blog/request-for-comments-hwb-functions2020年10月07日T00:00:00+00:002022年01月05日T05:12:05+00:00Natalie Weizenbaum<p>The CSS working group has been up to all sorts of exciting stuff recently in the
<a href="https://www.w3.org/TR/css-color-4/">Color Level 4</a> spec, and the Sass team is starting to think about how to
integrate those cool new features into Sass’s color model. We need more time to
hammer out exactly the right designs for complex features like the Lab color
space, but that doesn’t mean we can’t add a few new color goodies.</p>
<p>Today we’re announcing a proposal for one such feature: built-in Sass functions
for <a href="https://www.w3.org/TR/css-color-4/#the-hwb-notation">HWB</a> colors! Once this proposal (drafted by Sass core team member <a href="https://www.miriamsuzanne.com/">Miriam
Suzanne</a>) is accepted and implemented, you’ll be able to write colors in HWB
syntax and adjust their whiteness and blackness the same way you can adjust a
color’s saturation and lightness today.</p>
<h2 id="the-functions">
<a class="anchor" href="#the-functions"><span class="visuallyhidden">The Functions permalink</span></a>The Functions</h2>
<p>Here are the new and improved functions this proposal adds:</p>
<h3 id="color-hwb">
<a class="anchor" href="#color-hwb"><span class="visuallyhidden">color.hwb() permalink</span></a><code>color.hwb()</code>
</h3>
<p>The <code>color.hwb()</code> function defines a color using its hue, whiteness, and
blackness. Like the existing <code>rgb()</code> and <code>hsl()</code> functions, It can either use
the space-separated syntax defined in <a href="https://www.w3.org/TR/css-color-4/#the-hwb-notation">the spec</a> (<code>hwb(270 20% 40%)</code>) or
the more Sass-y comma-separated syntax (<code>hwb(270, 20%, 40%)</code>). Because HWB
colors use the same sRGB colorspace as all other Sass color values, colors
created this way are fully compatible with all existing Sass color functions and
will be emitted as their RGB equivalents for maximum browser compatibility.</p>
<p>Note that <em>unlike</em> <code>rgb()</code> and <code>hsl()</code>, the proposal doesn’t add this function
to the global scope yet. This is because Sass has a policy of never adding
support for new CSS syntax before at least one browser implements it. Specs have
a tendency to change until they’re locked in by browsers, and if Sass ends up
supporting something different than the browsers themselves that’s bad news!</p>
<h3 id="color-whiteness-and-color-blackness">
<a class="anchor" href="#color-whiteness-and-color-blackness"><span class="visuallyhidden">color.whiteness() and color.blackness() permalink</span></a><code>color.whiteness()</code> and <code>color.blackness()</code>
</h3>
<p>These functions work like the <code>color.saturation()</code> and <code>color.lightness()</code>
functions do for HSL colors. They even work for colors that weren’t created with
<code>color.hwb()</code>, so you can use them to check how pale or dark any color is.</p>
<p>Because HWB colors have the same notion of “hue” as HSL colors, the existing
<code>color.hue()</code> function already works perfectly!</p>
<h3 id="color-scale-color-adjust-and-color-change">
<a class="anchor" href="#color-scale-color-adjust-and-color-change"><span class="visuallyhidden">color.scale(), color.adjust(), and color.change() permalink</span></a><code>color.scale()</code>, <code>color.adjust()</code>, and <code>color.change()</code>
</h3>
<p>All three color modification functions now support <code>$whiteness</code> and <code>$blackness</code>
arguments. If you want a color (again no matter how it was created) to be 20%
whiter, just pass it to <code>color.scale($color, $whiteness: 20%)</code> and there you go!</p>
<h2 id="let-us-know-what-you-think">
<a class="anchor" href="#let-us-know-what-you-think"><span class="visuallyhidden">Let us know what you think! permalink</span></a>Let us know what you think!</h2>
<p>If you’re interested in learning more about this proposal, <a href="https://github.com/sass/sass/tree/main/proposal/color-4-hwb.md">read it in full</a> on
GitHub. It’s open for comments and revisions for the next month, so if you’d
like to see something change please <a href="https://github.com/sass/sass/issues/new">file an issue</a> and we can discuss it!</p>