Skip to content

Sass 3.3 (RC1) - #1989

Merged
kaelig merged 5 commits into
masterfrom
sass33
Oct 15, 2013
Merged

Sass 3.3 (RC1)#1989
kaelig merged 5 commits into
masterfrom
sass33

Conversation

@kaelig

@kaelig kaelig commented Oct 14, 2013

Copy link
Copy Markdown
Contributor

Sass 3.3 is a major upgrade of Sass, and it brings support for Sourcemaps. The RC1 was just released and it's ready to use.

Source maps won't work for now for two reasons:

  • we store the CSS in localStorage, meaning it's impossible for Chrome to know when a stylesheet has been updated
  • still in dev: sourcemaps don't point to an existing file. eg: /*# sourceMappingURL=football.min.css.map */ (the file has no hash)

@phamann's work (in an unreleased branch) will address those issues, enabling us to have a much faster front-end development process (native live reload in Chrome!)

Sass Changelog

3.3.0

Using & in SassScript

For a long time, Sass has supported a special
{file:SASS_REFERENCE.md#parent-selector "parent selector", &}, which is used
when nesting selectors to describe how a nested selector relates to the
selectors above it. Until now, this has only been usable in selectors, but now
it can be used in SassScript as well.

In a SassScript expression, & refers to the current parent selector. It's a
comma-separated list of space-separated lists. For example:

.foo.bar .baz.bang, .bip.qux {
  $selector: &;
}

The value of $selector is now ((".foo.bar" ".baz.bang"), ".bip.qux"). The
compound selectors are quoted here to indicate that they're strings, but in
reality they would be unquoted.

The SassScript & may be used in selectors using #{} interpolation. By
treating it as a SassScript value, you can do different things with it than you
can when treating it as a selector. When & is used as a selector, it can only
appear at the beginning of a compound selector, similarly to a type selector
like a or h1. When used with #{}, it can go anywhere. For example:

.badge {
  @at-root #{&}-info { ... }
  @at-root #{&}-header { ... }
}

Produces:

.badge-info { ... }
.badge-header { ... }

@at-root

What's that @at-root thing in the previous example? It's a way to tell Sass
that you don't want that selector to be nested. When you use & as a selector,
Sass can tell automatically that you don't want nesting, but when you use it
with #{} you have to be explicit. After all, you might have put it in a
variable, returned it from a function, or turned it into a string and reversed
the characters.

In addition to using @at-root on a single selector, you can also use it on a
whole block of them. For example:

.badge {
  @at-root {
    #{&}-info { ... }
    #{&}-header { ... }
  }
}

Also produces:

.badge-info { ... }
.badge-header { ... }

@at-root (without: ...) and @at-root (with: ...)

By default, @at-root just excludes selectors to allow #{&} to work
similarly to just including & in a selector. However, it's also
possible to use @at-root to move outside of nested directives such
as @media as well. For example:

@media print {
  .page {
    width: 8in;
    @at-root (without: media) {
      color: red;
    }
  }
}

produces:

@media print {
  .page {
    width: 8in;
  }
}
.page {
  color: red;
}

You can use @at-root (without: ...) to move outside of any
directive. You can also do it with multiple directives separated by a
space: @at-root (without: media supports) moves outside of both
@media and @supports queries.

There are two special values you can pass to @at-root. "rule" refers
to normal CSS rules; @at-root (without: rule) is the same as
@at-root with no query. @at-root (without: all) means that the
styles should be moved outside of all directives and CSS rules.

If you want to specify which directives or rules to include, rather
than listing which ones should be excluded, you can use with instead
of without. For example, @at-root (with: rule) will move outside
of all directives, but will preserve any CSS rules.

Source Maps

Sass now has the ability to generate standard JSON source maps of a format
that will soon be supported in most major browsers. These source maps tell the
browser how to find the Sass styles that caused each CSS style to be generated.
They're much more fine-grained than the old Sass-specific debug info that was
generated; rather than providing the source location of entire CSS rules at a
time, source maps provide the source location of each individual selector and
property.

Source maps can be generated by passing the --sourcemap flag to the sass
executable, by passing the {file:SASS_REFERENCE.md#sourcemap-option :sourcemap
option} to {Sass::Plugin}, or by using the
{Sass::Engine#render_with_sourcemap} method. By default, Sass assumes that
the source stylesheets will be made available on whatever server you're using,
and that their relative location will be the same as it is on the local
filesystem. If this isn't the case, you'll need to make a custom class that
extends {Sass::Importers::Base} or {Sass::Importers::Filesystem} and overrides
{Sass::Importers::Base#public_url #public_url}.

Thanks to Alexander Pavlov for implementing this.

SassScript Maps

SassScript has a new data type: maps. These are associations from SassScript
values (often strings, but potentially any value) to other SassScript values.
They look like this:

$map: (key1: value1, key2: value2, key3: value3);

Unlike lists, maps must always be surrounded by parentheses. () is now an
empty map in addition to an empty list.

Maps will allow users to collect values into named groups and access those
groups dynamically. For example, you could use them to manage themes for your
stylesheet:

$themes: (
  mist: (
    header: #DCFAC0,
    text:   #00968B,
    border: #85C79C
  ),
  spring: (
    header: #F4FAC7,
    text:   #C2454E,
    border: #FFB158
  ),
  // ...
);

@mixin themed-header($theme-name) {
  h1 {
    color: map-get(map-get($themes, $theme-name), header);
  }
}

There are a variety of functions for working with maps:

  • The {Sass::Script::Functions#map_get map-get($map, $key) function} returns
    the value in the map associated with the given key. If no value is found, it
    returns null.
  • The {Sass::Script::Functions#map_merge map-merge($map1, $map2) function}
    merges two maps together into a new map. If there are any conflicts, the
    second map takes precedence, making this a good way to modify values in a map
    as well.
  • The {Sass::Script::Functions#map_keys map-keys($map) function} returns all
    the keys in a map as a comma-separated list.
  • The {Sass::Script::Functions#map_values map-values($map) function} returns
    all the values in a map as a comma-separated list.
  • The {Sass::Script::Functions#map_has_key map-has-key($map, $key) function}
    returns whether or not a map contains a pair with the given key.

All the existing list functions also work on maps, treating them as lists of
pairs. For example, nth((foo: 1, bar: 2), 1) returns foo 1. Maps can also be
used with @each, using the new multiple assignment feature (see below):

@each $header, $size in (h1: 2em, h2: 1.5em, h3: 1.2em) {
  #{$header} {
    font-size: $size;
  }
}

Produces:

h1 {
  font-size: 2em;
}

h2 {
  font-size: 1.5em;
}

h3 {
  font-size: 1.2em;
}

Variable Keyword Arguments

Maps can be passed as variable arguments, just like lists. For example, if
$map is (alpha: -10%, "blue": 30%), you can write scale-color($color, $map...) and it will do the same thing as scale-color($color, $alpha: -10%, $blue: 30%). To pass a variable argument list and map at the same time, just do
the list first, then the map, as in fn($list..., $map...).

You can also access the keywords passed to a function that accepts a variable
argument list using the new {Sass::Script::Functions#keywords keywords($args)
function}. For example:

@function create-map($args...) {
  @return keywords($args);
}

create-map($foo: 10, $bar: 11); // returns (foo: 10, bar: 11)

Lists of Pairs as Maps

The new map functions work on lists of pairs as well, for the time being. This
feature exists to help libraries that previously used lists of pairs to simulate
maps. These libraries can now use map functions internally without introducing
backwards-incompatibility. For example:

$themes: (
  mist (
    header #DCFAC0,
    text   #00968B,
    border #85C79C
  ),
  spring (
    header #F4FAC7,
    text   #C2454E,
    border #FFB158
  ),
  // ...
);

@mixin themed-header($theme-name) {
  h1 {
    color: map-get(map-get($themes, $theme-name), header);
  }
}

Since it's just a migration feature, using lists of pairs in place of maps is
already deprecated. Library authors should encourage their users to use actual
maps instead.

Smaller Improvements

  • listen is now a standard Gem dependency.
    It's no longer bundled with Sass.
  • Sass now has numerous functions for working with strings:
    {Sass::Script::Functions#str_length str-length} will return the length of a
    string; {Sass::Script::Functions#str_insert str-insert} will insert one
    string into another; {Sass::Script::Functions#str_index str-index} will
    return the index of a substring within another string;
    {Sass::Script::Functions#str_slice str-slice} will slice a substring from a
    string; {Sass::Script::Functions#to_upper_case to-upper-case} will
    transform a string to upper case characters; and
    {Sass::Script::Functions#to_lower_case to-lower-case} will transform a
    string to lower case characters.
  • A {Sass::Script::Functions#list_separator list-separator} function has been
    added to determine what separator a list uses. Thanks to Sam
    Richard
    .
  • Custom Ruby functions can now access the global environment, which
    allows them the same power as Sass-based functions with respect to
    reading and setting variables defined elsewhere in the stylesheet.
  • The set-nth($list, $n, $value) function lets you construct a new
    list based on $list, with the nth element changed to the value
    specified.
  • Add "grey" and "transparent" as recognized SassScript colors. Thanks to Rob
    Wierzbowski
    .
  • Add a function {Sass::Script::Functions#unique_id unique-id()} that will
    return a CSS identifier that is unique within the scope of a single CSS file.
  • Allow negative indices into lists when using nth().
  • You can now detect the presence of a Sass feature using the new function
    feature-exists($feature-name). There are no detectable features in this
    release, this is provided so that subsequent releases can begin to
    use it. Additionally, plugins can now expose their functionality
    through feature-exists by calling Sass.add_feature(feature_name). Features
    exposed by plugins must begin with a dash to distinguish them from
    official features.
  • It is now possible to determine the existence of different Sass
    constructs using these new functions:
    • variable-exists($name) checks if a variable resolves in the
      current scope.
    • global-variable-exists($name) checks if a global variable of the
      given name exists.
    • function-exists($name) checks if a function exists.
    • mixin-exists($name) checks if a mixin exists.
  • You can call a function by name by passing the function name to the
    call function. For example, call(nth, a b c, 2) returns b.
  • Comments following selectors in the indented syntax will be correctly
    converted using sass-convert.
  • @each now supports "multiple assignment", which makes it easier to iterate
    over lists of lists. If you write @each $var1, $var2, $var3 in a b c, d e f, g h i, the elements of the sub-lists will be assigned individually to the
    variables. $var1, $var2, and $var3 will be a, b and c; then d,
    e, and f; and then g, h, and i. For more information, see
    {file:SASS_REFERENCE.md#each-multi-assign the @each reference}.
  • There is a new {Sass::Script::Value::Helpers convenience API} for creating
    Sass values from within ruby extensions.
  • The if() function now only evaluates the argument corresponding to
    the value of the first argument.
  • Comma-separated lists may now have trailing commas (e.g. 1, 2, 3,). This also allows you to use a trailing comma to distinguish a
    list with a single element from that element itself -- for example,
    (1,) is explicitly a list containing the value 1.
  • All directives that are nested in CSS rules or properties and that
    contain more CSS rules or properties are now bubbled up through
    their parent rules.

Backwards Incompatibilities -- Must Read!

  • Sass will now throw an error when @extend is used to extend a selector
    outside the @media context of the extending selector. This means the
    following will be an error:

    @media screen {
      .foo { @extend .bar; }
    }
    .bar { color: blue; }
    
  • Sass will now throw an error when an @extend that has no effect is used. The
    !optional flag may be used to avoid this behavior for a single @extend.

  • Sass will now throw an error when it encounters a single @import statement
    that tries to import more than one file. For example, if you have @import "screen" and both screen.scss and _screen.scss exist, a warning will be
    printed.

  • grey and transparent are no longer interpreted as strings; they're now
    interpreted as colors, as per the CSS spec.

  • The automatic placement of the current working directory onto the Sass
    load path is now deprecated as this causes unpredictable build
    processes. If you need the current working directory to be available,
    set SASSPATH=. in your shell's environment.

  • Sass::Compiler.on_updating_stylesheet has been removed.

  • Sass::Plugin.options= has been removed.

  • Sass::Script::Number::PRECISION has been removed.

  • Many classes in the {Sass::Script} have been rearranged. All the value
    classes have been moved into {Sass::Script::Value} (e.g.
    {Sass::Script::Value::Color}, {Sass::Script::Value::String}, etc). Their
    base class is now {Sass::Script::Value::Base} instead of
    Sass::Script::Literal. All the parse tree classes have been moved into
    {Sass::Script::Tree} (e.g. {Sass::Script::Tree::Node},
    {Sass::Script::Tree::Operation}, etc).

    The old names will continue to work for the next couple releases, but they
    will be removed eventually. Any code using them should upgrade to the new
    names.

  • As part of a migration to cleaner variable semantics, assigning to
    global variables in a local context by default is deprecated. If
    there's a global variable named $color and you write $color: blue within a CSS rule, Sass will now print a warning; in the
    future, it will create a new local variable named $color. You may
    now explicitly assign to global variables using the !global flag;
    for example, $color: blue !global will always assign to the global
    $color variable.

@phamann

phamann commented Oct 15, 2013

Copy link
Copy Markdown
Contributor

So as I told you yesterday, I'm slightly concerned about using a "release candidate" in PROD. I've learnt the hard way in the past around things like this. Especially as we don't check the contents of the compiled source from our preprocessors.

On the other hand, I do like the idea of live reload in Chrome and making our workflow faster. Therefore if we were to use this, I would make sure we do a full regression on this branch to catch any incosistencies.

One things that stood out to me from the release notes was:

Sass will now throw an error when @extend is used to extend a selector
outside the @media context of the extending selector.

I believe we are still doing this in a few places, but we have been silencing the warnings in the past.

@kaelig

kaelig commented Oct 15, 2013

Copy link
Copy Markdown
Contributor Author

Thanks for the feedback, these are completely legitimate concerns.

In this pull request I've made the problematic extends optional (using the !optional declaration).

I ran a diff between the CSS in master and this branch, and found no differences (so, no need for a regression test).

I hope this dissipates any concerns you had.

@phamann

phamann commented Oct 15, 2013

Copy link
Copy Markdown
Contributor

I ran a diff between the CSS in master and this branch, and found no differences (so, no need for a regression test).

Yes this dissipates my concerns 😉

👍 SHIP IT

kaelig added a commit that referenced this pull request Oct 15, 2013
@kaelig
kaelig merged commit e4a364e into master Oct 15, 2013
@kaelig
kaelig deleted the sass33 branch October 15, 2013 12:58
kaelig added a commit that referenced this pull request Oct 15, 2013
This reverts commit e4a364e, reversing
changes made to 1c8a770.
kaelig added a commit that referenced this pull request Oct 15, 2013
Revert "Merge pull request #1989 from guardian/sass33"
kaelig added a commit that referenced this pull request Oct 17, 2013
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants