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":
- The imported URL begins with
http://
orhttps://
. - The imported URL ends with
.css
. - The imported URL is syntactically defined as a
url()
. - The argument has a media query and/or a supports query.
Note that this means that imports that explicitly end with
.css
will continue to be treated as plain CSS@import
rules, rather than importing stylesheets as CSS. - The imported URL begins with
-
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 isfile
, otherwise null. -
Let
bases
be a list beginning withroot
if it's non-null, followed by the absolutefile:
URLs of all import paths. -
For each
base
inbases
:-
Let
url
be the result of parsingargument
as a URL withbase
as the base URL.If this returns a failure, throw that failure.
-
If
url
's scheme is notfile
, return null. -
Let
resolved
be the result of resolvingurl
. -
If
resolved
is null:-
Let
index
bedirname(url)
+"index/"
+basename(url)
. -
Set
resolved
to the result of resolvingindex
.
-
-
If
resolved
is still null, continue to the next loop. -
Let
text
be the contents of the file atresolved
. -
Let
ast
be:- The result of parsing
text
as SCSS ifresolved
ends in.scss
. - The result of parsing
text
as the indented syntax ifresolved
ends in.sass
. - The result of parsing
text
as CSS ifresolved
ends in.css
.
The algorithm for resolving a
file:
URL guarantees thatresolved
will have one of these extensions. - The result of parsing
-
Return
ast
with the canonical URLresolved
.
-
-
Return null.
This 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 resolvingurl
for partials. -
Let
sass
be the result of resolvingurl
+".sass"
for partials. -
Let
scss
be the result of resolvingurl
+".scss"
for partials. -
If neither
sass
norscss
are null, throw an error. -
If exactly one of
sass
andscss
is null, return the other one. -
Return the result of resolving
url
+".css"
for partials.
This 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
, returnurl
.Otherwise return null.
-
-
Let
partial
bedirname(url)
+"_"
+basename(url)
. -
If a file exists on disk at both
url
andpartial
, throw an error. -
If a file exists on disk at
url
, returnurl
. -
If a file exists on disk at
partial
, returnpartial
. -
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
@import
URLs).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
@while
-
An
@import
that contains interpolation in theurl()
, 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
@function
or 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:
- At-rule values (including
@media
queries) - Declaration names
- Declaration values
- Style rule selectors
- At-rule values (including
-
All SassScript operations except for:
/
not
or
and
Note 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
@import
s that don't produce errors should be parsed as static CSS imports. -
The tokens
not
,or
,and
, andnull
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 absolutefile:
URL corresponding topath
. -
Let
text
be the contents of the file atpath
. -
Let
ast
be:- The result of parsing
text
as the indented syntax ifurl
ends in.sass
. - The result of parsing
text
as CSS ifurl
ends in.css
. - The result of parsing
text
as SCSS otherwise.
The algorithm for resolving a
file:
URL guarantees thatresolved
will have one of these extensions. - The result of parsing
-
Return
ast
with the canonical URLurl
.
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
, andand
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
.