Sass Blog https://sass-lang.com/blog 2021年11月21日T00:15:00+00:00 New JS API Release Candidate is Live https://sass-lang.com/blog/new-js-api-release-candidate 2021年11月21日T00:15:00+00:00 2022年01月05日T05:12:05+00:00 Natalie 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&rsquo;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&rsquo;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&rsquo;ll officially consider the old API to be deprecated. Since it&rsquo;s still widely-used, we&rsquo;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 API https://sass-lang.com/blog/request-for-comments-new-js-api 2021年08月05日T23:30:00+00:00 2022年01月05日T05:12:05+00:00 Natalie Weizenbaum <p>I&rsquo;m excited to officially unveil something that&rsquo;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&rsquo;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&rsquo;s why we call it the &ldquo;Node Sass API&rdquo;!) 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&rsquo;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&rsquo;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&rsquo;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">&lt;</span><span class="s1">'sync'</span><span class="o">&gt;</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">&lt;</span><span class="s1">'sync'</span><span class="o">&gt;</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">&lt;</span><span class="s1">'async'</span><span class="o">&gt;</span> <span class="p">)</span><span class="err">:</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">CompileResult</span><span class="o">&gt;</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">&lt;</span><span class="s1">'async'</span><span class="o">&gt;</span> <span class="p">)</span><span class="err">:</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">CompileResult</span><span class="o">&gt;</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>&#39;compressed&#39;</code> or <code>&#39;expanded&#39;</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>&#39;scss&#39;</code> (the default), <code>&#39;indented&#39;</code>, or <code>&#39;css&#39;</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&rsquo;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&rsquo;s passed:</p> <ul> <li>The warning message</li> <li>A flag indicating whether it&rsquo;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&rsquo;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 &ldquo;quiet mode&rdquo; 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&rsquo;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&rsquo;s loaded from the filesystem, the canonical URL will be the absolute <code>file:</code> URL of the physical file on disk. If it&rsquo;s generated in-memory, the importer should choose a custom URL scheme to guarantee that its canonical URLs don&rsquo;t conflict with any other importer&rsquo;s.</p> <p>For example, if you&rsquo;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&rsquo;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 &quot;variables&quot;</code>, it&rsquo;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&rsquo;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&rsquo;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&rsquo;s <code>load()</code>; otherwise, it returns <code>null</code>.</p></li> <li><p>If the old stylesheet&rsquo;s importer didn&rsquo;t recognize the URL, it&rsquo;s passed to all the <code>importers</code>&lsquo; 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&rsquo;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>&#39;scss&#39;</code>, <code>&#39;indented&#39;</code>, or <code>&#39;css&#39;</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&rsquo;t require the caller to implement <code>load()</code>, since that&rsquo;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&rsquo;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&rsquo;s function type is very similar to the old API&rsquo;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">=&gt;</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&lt;Value&gt;</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&rsquo;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">&lt;</span><span class="nx">Value</span><span class="o">&gt;</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">&lt;</span><span class="nx">Value</span><span class="p">,</span> <span class="nx">Value</span><span class="o">&gt;</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&rsquo;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&rsquo;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&rsquo;s built-in immutability of all its types. Although these values can&rsquo;t be modified directly, their APIs make it easy and efficient to create new values with changes applied.</p></li> <li><p>Sass&rsquo;s list-indexing conventions are different than JavaScript&rsquo;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 &ldquo;falsey&rdquo; 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&rsquo;re being passed the arguments you expect, and to throw an idiomatic error if you&rsquo;re not. They&rsquo;re particularly useful for TypeScript users since they&rsquo;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&rsquo;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&rsquo;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&rsquo;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&rsquo;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">&lt;</span><span class="kr">string</span><span class="o">&gt;</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">&lt;</span><span class="kr">string</span><span class="o">&gt;</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">&lt;</span><span class="kr">string</span><span class="o">&gt;</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">&lt;</span><span class="kr">string</span><span class="o">&gt;</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">&lt;</span><span class="kr">string</span><span class="o">&gt;</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">&lt;</span><span class="kr">string</span><span class="o">&gt;</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">&lt;</span><span class="kr">string</span><span class="o">&gt;</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">&lt;</span><span class="kr">string</span><span class="o">&gt;</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">&lt;</span><span class="kr">string</span><span class="o">&gt;</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">&lt;</span><span class="kr">string</span><span class="o">&gt;</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">&lt;</span><span class="kr">string</span><span class="o">&gt;</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">&lt;</span><span class="kr">string</span><span class="o">&gt;</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&rsquo;s notion of indexes differs from Sass&rsquo;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">&lt;</span><span class="nx">Value</span><span class="o">&gt;</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">&lt;</span><span class="nx">Value</span><span class="p">,</span> <span class="nx">Value</span><span class="o">&gt;</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">&lt;</span><span class="nx">Value</span><span class="p">,</span> <span class="nx">Value</span><span class="o">&gt;</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&rsquo;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&rsquo;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&rsquo;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-fibers https://sass-lang.com/blog/node-fibers-discontinued 2021年03月26日T23:00:00+00:00 2022年01月05日T05:12:05+00:00 Natalie 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&rsquo;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&rsquo;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&rsquo;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&rsquo;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&rsquo;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&rsquo;s fault: since those interfaces weren&rsquo;t part of V8&rsquo;s public API, they were under no obligation to keep them stable. Sometimes in software that&rsquo;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&rsquo;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&rsquo;s at all possible to make the plugins you pass in to Sass synchronous, you can use the <code>renderSync()</code> method which doesn&rsquo;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&rsquo;s not ready for prime-time yet, the Sass team is working on a project called &ldquo;embedded Dart Sass&rdquo;. 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&rsquo;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&rsquo;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&rsquo;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&rsquo;s.</p> <p>At the moment, the Sass team is focused on Embedded Sass, so we don&rsquo;t have the spare bandwidth to dive into worker threads as an alternative. That said, we&rsquo;d be happy to help a motivated user implement this. If you&rsquo;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&rsquo;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&rsquo;ve expressed willingness to help an engineer who&rsquo;s willing to give it a shot.</p> <p>This isn&rsquo;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&rsquo;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&rsquo;re interested, please don&rsquo;t hesitate to <a href="mailto:nweiz@google.com">reach out</a>!</p> Request for Comments: First-Class Calc https://sass-lang.com/blog/request-for-comments-first-class-calc 2021年03月15日T09:35:00+00:00 2022年01月05日T05:12:05+00:00 Natalie 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&rsquo;s parser, since we don&rsquo;t have to support the specific <code>calc()</code> microsyntax, and it&rsquo;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&rsquo;s parser, users can&rsquo;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&rsquo;s no way to manipulate it with Sass the way you can manipulate a plain number.</p> <p>We&rsquo;re looking to change that with a new proposal we call &ldquo;First-Class Calc&rdquo;. 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 &ldquo;calculation&rdquo;. This data type represents mathematical expressions that can&rsquo;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&rsquo;t be used interchangeably with numbers (you can&rsquo;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&rsquo;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&rsquo;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&rsquo;s open for comments and revisions for the next month, so if you&rsquo;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 Deprecated https://sass-lang.com/blog/libsass-is-deprecated 2020年10月26日T20:00:00+00:00 2022年01月05日T05:12:05+00:00 Natalie Weizenbaum <p>After much discussion among the Sass core team, we&rsquo;ve come to the conclusion that it&rsquo;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&rsquo;s been clear that there&rsquo;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&rsquo;ve hoped to see this pattern turn around, even the excellent work of long-time LibSass contributors Michael Mifsud and Marcel Greter couldn&rsquo;t keep up with the fast pace of language development in both CSS and Sass.</p> <p>I&rsquo;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&rsquo;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&rsquo;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&rsquo;t work</a> and assuming Sass as a whole is at fault, when in fact it&rsquo;s only LibSass that doesn&rsquo;t support that feature.</p> <p>Official support for LibSass doesn&rsquo;t just cause pain for individual users. Because LibSass doesn&rsquo;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&rsquo;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&rsquo;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&rsquo;re choosing to use the term &ldquo;deprecated&rdquo; 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&rsquo;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&rsquo;s written in C++, it&rsquo;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&rsquo;s comparable to Dart Sass&rsquo;s command-line executable).</p></li> </ul> <p>We&rsquo;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&rsquo;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&rsquo;re using the SassC command-line interface, you can switch to <a href="https://sass-lang.com/documentation/cli/dart-sass">Dart Sass&rsquo;s CLI</a>. Note that this doesn&rsquo;t have exactly the same interface as SassC, so you may need to change a few flags.</p> <p>If you&rsquo;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&rsquo;s put so much time and energy into LibSass and Node Sass over the years. It will always be a towering achievement, and Sass&rsquo;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&rsquo;ll always consider themselves part of the Sass community going forward.</p> Request for Comments: HWB Functions https://sass-lang.com/blog/request-for-comments-hwb-functions 2020年10月07日T00:00:00+00:00 2022年01月05日T05:12:05+00:00 Natalie 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&rsquo;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&rsquo;t mean we can&rsquo;t add a few new color goodies.</p> <p>Today we&rsquo;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&rsquo;ll be able to write colors in HWB syntax and adjust their whiteness and blackness the same way you can adjust a color&rsquo;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&rsquo;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&rsquo;re locked in by browsers, and if Sass ends up supporting something different than the browsers themselves that&rsquo;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&rsquo;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 &ldquo;hue&rdquo; 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>

AltStyle によって変換されたページ (->オリジナル) /