accepted/css-imports.md
This proposal covers a long-awaited Sass feature: the ability to import plain
CSS files from Sass. Although the original plan was to wait on this support
until the module system was in place and support CSS imports only with
@use, it has become desirable to support them sooner in order to have
compatibility with the existing LibSass implementation. See
Background for more details.
Historically, the reference implementations of Sass—first Ruby Sass, then Dart Sass—only supported importing other Sass files. However, LibSass supported importing CSS files as well, interpreting them as though they were SCSS. Although this technically violated the implementation guide's prohibition on unilaterally extending the language, these CSS imports were useful and were widely adopted in the Node.js community.
This became particularly clear when, at the language team's urging, LibSass added deprecation warnings for CSS imports and users were left without a suitable replacement. The language team came together to discuss the problem, and decided to move towards allowing CSS imports but forbidding the use of non-CSS features in the imported files. This proposal describes the specifics of that idea.
LibSass's behavior at time of writing is to import files with the extension
.css at the same precedence level as those with the .scss and .sass
extensions, and to throw an error if an import is ambiguous between a .css
file and a .scss or .sass file.
This section is non-normative.
This proposal seeks to strike a balance between preserving compatibility with
LibSass's existing behavior, and moving towards a more principled scheme for
loading CSS. This is particularly important as we intend to allow @use to load
CSS files without Sass features, so we want the existing CSS loading support to
be as similar as possible.
Locating CSS files for import works similarly under this proposal as it does in
LibSass currently: a relative .css file takes precedence over files with any
extension on the load path, a .css file earlier on the load path takes
precedence over a file with any extension later on the load path, and foo.css
takes precedence over index/foo.scss.
The only difference in loading scheme occurs when an import is ambiguous between
a .css file and a .scss or .sass file at the same path. LibSass currently
produces an error here, but in order to maximize compatibility with existing
Dart Sass (and Ruby Sass) behavior, this proposal has the .scss or .sass
file taking precedence. This is not a breaking change to LibSass's behavior,
since it only applies in situations that would previously have produced an
error.
This proposal diverges significantly from LibSass in parsing the imported CSS
files, though: it forbids all use of SCSS features in the parsed files. Most
SCSS features produce errors (rather than compiling to plain, likely-invalid
CSS) in order to help users who accidentally wrote SCSS in their CSS realize
what's going wrong. However, features like @import that overlap with plain CSS
continue to be rendered as CSS.
In order to avoid a sudden backwards-incompatible change in LibSass, this also includes a proposal for a set of deprecation warnings that can be added to LibSass's existing behavior to steer users away from using Sass features in their imported CSS without entirely breaking their build process.
The basename of a URL is the final component of that URL's path.
The dirname of a URL is the prefix of that URL up to, but not including, the beginning of its basename.
The canonical URL of a stylesheet is a URL associated with that stylesheet that represents the location from which it was loaded.
The canonical URL for stylesheets are set by the algorithms for loading an import and loading an entrypoint path.
This proposal defines a new algorithm for
handling an @import rule. It is intended to
replace the existing algorithm. I've also taken this opportunity to more
explicitly specify the behavior of @import rules.
This proposal omits importers, which while important, are out of scope. Importers should be considered to effectively replace the algorithm for loading an import, possibly with another algorithm that calls the existing algorithm to handle filesystem imports.
Other than support for importing plain CSS, this algorithm is designed to accurately capture the current behavior of Ruby Sass and Dart Sass (when only filesystem importers are involved). It closely matches the implementation of Dart Sass, whereas Ruby Sass treats imported URLs as plain strings and operates on filesystem paths rather than
file:URLs.
This proposal also defines a new algorithm for loading an entrypoint path, which is also intended to replace the existing algorithm. This defines how a Sass implementation should compile a file passed on the command line or through a programming language API.
To evaluate an @import rule:
For each of that rule's arguments:
If any of the following are true, the argument is considered "plain CSS":
http:// or https://..css.url().Note that this means that imports that explicitly end with
.csswill continue to be treated as plain CSS@importrules, rather than importing stylesheets as CSS.
If the argument is "plain CSS":
Evaluate any interpolation it contains.
Add an @import with the evaluated string, media query, and/or supports
query to the CSS AST.
Otherwise, let stylesheet be the result of
loading the imported string.
If this returns null, throw an error.
If an AST with the same canonical URL as stylesheet is currently being
evaluated, throw an error.
Evaluate stylesheet in the global scope.
This algorithm takes a string, argument, and returns a Sass stylesheet.
Let root be the current stylesheet's canonical URL if its scheme is
file, otherwise null.
Let bases be a list beginning with root if it's non-null, followed by the
absolute file: URLs of all import paths.
For each base in bases:
Let url be the result of parsing argument as a URL with base as
the base URL.
If this returns a failure, throw that failure.
If url's scheme is not file, return null.
Let resolved be the result of resolving url.
If resolved is null:
Let index be dirname(url) + "index/" +
basename(url).
Set resolved to the result of
resolving index.
If resolved is still null, continue to the next loop.
Let text be the contents of the file at resolved.
Let ast be:
text as SCSS if resolved ends in .scss.text as the indented syntax if resolved ends in
.sass.text as CSS if resolved ends
in .css.The algorithm for resolving a
file:URL guarantees thatresolvedwill have one of these extensions.
Return ast with the canonical URL resolved.
Return null.
file: URLThis algorithm takes a URL, url, whose scheme must be file and returns
either another URL that's guaranteed to point to a file on disk or null.
If url ends in .scss or .sass, return the result of
resolving url for partials.
Let sass be the result of
resolving url + ".sass" for partials.
Let scss be the result of
resolving url + ".scss" for partials.
If neither sass nor scss are null, throw an error.
If exactly one of sass and scss is null, return the other one.
Return the result of
resolving url + ".css" for partials.
file: URL for partialsThis algorithm takes a URL, url, whose scheme must be file and returns
either another URL that's guaranteed to point to a file on disk or null.
If url's basename begins with "_":
If a file exists on disk at url, return url.
Otherwise return null.
Let partial be dirname(url) + "_" +
basename(url).
If a file exists on disk at both url and partial, throw an error.
If a file exists on disk at url, return url.
If a file exists on disk at partial, return partial.
Return null.
This algorithm takes a string, text, and returns a Sass abstract syntax tree.
This algorithm is designed with two goals in mind:
CSS imported from Sass should be as compatible with standard CSS as possible. In some cases we err even more towards CSS compatibility than SCSS does, because the CSS being imported is likely not written by someone who knows to avoid things that Sass interprets specially (such as certain
@importURLs).We should provide clear and eager feedback to users who accidentally try to use Sass features in CSS imports. We don't allow these features, and we want users to know that through error messages rather than digging through generated CSS only to find that Sass features were passed through unmodified. This is a particular concern because LibSass has historically allowed the use of Sass features in CSS imports.
The algorithm for parsing text as CSS works like parsing text as SCSS, with some modifications. The following productions should produce errors:
Any at-rules that are defined in Sass and not in plain CSS. At the time of writing, this means:
@at-root@content@debug@each@error@extend@for@function@if@include@mixin@return@warn@whileAn @import that contains interpolation in the url(), the media query, or
the supports query.
An @import that appears within a style rule or at-rule.
An @import with more than one argument.
A declaration followed by an open curly brace (that is, a nested declaration).
A style rule appearing within another style rule.
The parent selector &, either in a selector or a declaration value.
Placeholder selectors.
All built-in functions, excluding the following:
rgb()rgba()hsl()hsla()grayscale()invert()alpha()opacity()Note that user-defined functions are not forbidden, whether they're defined using
@functionor through a host language API.
Any function called with keyword arguments or variable-length arguments.
Interpolation anywhere its contents would be evaluated. At the time of writing, this means:
@media queries)All SassScript operations except for:
/notorandNote that although unary
-is forbidden, the-that appears at the beginning of a number literal is part of that literal and thus allowed.
Parentheses in declaration values that aren't part of a CSS production.
Map literals.
The empty list literal (,).
Uses or declarations of Sass variables.
//-style ("silent") comments.
In addition, some productions should be parsed differently than they would be in SCSS:
All functions that don't produce errors should be parsed as plain CSS functions, regardless of whether a Sass function with that name is defined.
All @imports that don't produce errors should be parsed as static CSS
imports.
The tokens not, or, and, and null should be parsed as unquoted
strings.
The
/operation should be parsed as normal. Because variables, parentheses, functions that return numbers, and all other arithmetic expressions are disallowed, it will always compile to slash-separated values rather than performing division.
This algorithm takes a string, path, that represents a file on the filesystem.
It returns a Sass stylesheet.
Let url be the absolute file: URL corresponding to path.
Let text be the contents of the file at path.
Let ast be:
text as the indented syntax if url ends in
.sass.text as CSS if url ends in
.css.text as SCSS otherwise.The algorithm for resolving a
file:URL guarantees thatresolvedwill have one of these extensions.
Return ast with the canonical URL url.
Because Dart Sass has no existing behavior for CSS imports–they'll just fail to find a file to import–it can just add the new behavior without need for a deprecation process. But LibSass parses its CSS imports as SCSS, which means it allows Sass features that this proposal forbids. In order to help users migrate away from using these features without a sudden breakage, LibSass should continue to parse CSS imports as SCSS, while emitting deprecation warnings for:
All productions that would produce an error according to the algorithm for parsing text as CSS.
All invocations of user-defined functions, whether they're defined using
@function or through a host language API.
The not, or, and and operations.
The value null.
Where possible, LibSass should avoid emitting multiple deprecation warnings for the same expression.
For example, if a user writes
-$var, producing a warning for the use of the variable already makes it clear that Sass features are in use. There's no additional value in emitting a warning for the unary minus operation.
These deprecation warnings should indicate that Sass features will cease to be
allowed in files named .css in the future, and suggest that users either use
plain CSS or rename their files to .scss.