Tag: Features

When Sass and New CSS Features Collide

Recently, CSS has added a lot of new cool features such as custom properties and new functions. While these things can make our lives a lot easier, they can also end up interacting with preprocessors, like Sass, in funny ways.

So this is going to be a post about the issues I’ve encountered, how I go around them, and why I still find Sass necessary these days.

The errors

If you’ve played with the new min() and max() functions, you may have ran into an error message like this when working with different units: “Incompatible units: vh and em.”

Screenshot. Shows the `Incompatible units: 'em' and 'vh'` error when trying to set `width: min(20em, 50vh)`.
An error when working with different types of units in the min()/ max() function

This is because Sass has its ownmin() function, and ignores the CSS min() function. Plus, Sass cannot perform any sort of computation using two values with units that don’t have a fixed relation between them.

For example, cm and in units have a fixed relation between them, so Sass can figure out what’s the result of min(20in, 50cm) and doesn’t throw an error when we try to use it in our code.

The same things goes for other units. Angular units, for example, all have a fixed relation between them: 1turn, 1rad or 1grad always compute to the same deg values. Same goes for 1s which is always 1000ms, 1kHz which is always 1000Hz, 1dppx which is always 96dpi, and 1in which is always 96px. This is why Sass can convert between them and mix them in computations and inside functions such as its own min() function.

But things break when these units don’t have a fixed relation between them (like the earlier case with em and vh units).

And it’s not just different units. Trying to use calc() inside min() also results in an error. If I try something like calc(20em + 7px), the error I get is, “calc(20em + 7px) is not a number for min.”

Screenshot. Shows the `'calc(20em + 7px)' is not a number for 'min'` error when trying to set `width: min(calc(20em + 7px), 50vh)`.
An error when using different unit values with calc() nested in the min()function

Another problem arises when we want to use a CSS variable or the result of a mathematical CSS function (such as calc(), min() or max()) in a CSS filter like invert().

In this case, we get told that “$ color: 'var(--p, 0.85) is not a color for invert.”

Screenshot. Shows the `$ color: 'var(--p, 0.85)' is not a color for 'invert'` error when trying to set `filter: invert(var(--p, .85))`.
var() in filter: invert() error

The same thing happens for grayscale(): “$ color: ‘calc(.2 + var(--d, .3))‘ is not a color for grayscale.”

Screenshot. Shows the `$ color: 'calc(.2 + var(--d, .3))' is not a color for 'grayscale'` error when trying to set `filter: grayscale(calc(.2 + var(--d, .3)))`.
calc() in filter: grayscale() error

opacity() causes the same issue: “$ color: ‘var(--p, 0.8)‘ is not a color for opacity.”

Screenshot. Shows the `$ color: 'var(--p, 0.8)' is not a color for 'opacity'` error when trying to set `filter: opacity(var(--p, 0.8))`.
var() in filter: opacity() error

However, other filter functions — including sepia(), blur(), drop-shadow(), brightness(), contrast() and hue-rotate()— all work just fine with CSS variables!

Turns out that what’s happening is similar to the min() and max() problem. Sass doesn’t have built-in sepia(), blur(), drop-shadow(), brightness(), contrast(), hue-rotate() functions, but it does have its own grayscale(), invert() and opacity() functions, and their first argument is a $ color value. Since it doesn’t find that argument, it throws an error.

For the same reason, we also run into trouble when trying to use a CSS variable that lists at least two hsl()or hsla() values.

Screenshot. Shows the `wrong number of arguments (2 for 3) for 'hsl'` error when trying to set `color: hsl(9, var(--sl, 95%, 65%))`.
var() in color: hsl() error.

On the flip side, color: hsl(9, var(--sl, 95%, 65%)) is perfectly valid CSS and works just fine without Sass.

The exact same thing happens with the rgb()and rgba() functions.

Screenshot. Shows the `$ color: 'var(--rgb, 128, 64, 64)' is not a color for 'rgba'` error when trying to set `color: rgba(var(--rgb, 128, 64, 64), .7)`.
var() in color: rgba() error.

Furthermore, if we import Compass and try to use a CSS variable inside a linear-gradient() or inside a radial-gradient(), we get another error, even though using variables inside conic-gradient() works just fine (that is, if the browser supports it).

Screenshot. Shows the At least two color stops are required for a linear-gradient error when trying to set background: linear-gradient(var(--c, pink), gold).
var() in background: linear-gradient() error.

This is because Compass comes with linear-gradient() and radial-gradient() functions, but has never added a conic-gradient() one.

The problems in all of these cases arise from Sass or Compass having identically-named functions and assuming those are what we intended to use in our code.

Drat!

The solution

The trick here is to remember that Sass is case-sensitive, but CSS isn’t.

That means we can write Min(20em, 50vh)and Sass won’t recognize it as its own min() function. No errors will be thrown and it’s still valid CSS that works as intended. Similarly, writing HSL()/ HSLA()/ RGB()/ RGBA() or Invert() allows us to avoid issues we looked at earlier.

As for gradients, I usually prefer linear-Gradient() and radial-Gradient() just because it’s closer to the SVG version, but using at least one capital letter in there works just fine.

But why?

Almost every time I tweet anything Sass-related, I get lectured on how it shouldn’t be used now that we have CSS variables. I thought I’d address that and explain why I disagree.

First, while I find CSS variables immensely useful and have used them for almost everything for the past three years, it’s good to keep in mind that they come with a performance cost and that tracing where something went wrong in a maze of calc() computations can be a pain with our current DevTools. I try not to overuse them to avoid getting into a territory where the downsides of using them outweigh the benefits.

Screenshot. Shows how `calc()` expressions are presented in DevTools.
Not exactly easy to figure out what’s the result of those calc() expressions.

In general, if it acts like a constant, doesn’t change element-to-element or state-to-state (in which case custom properties are definitely the way to go) or reduce the amount of compiled CSS (solving the repetition problem created by prefixes), then I’m going to use a Sass variable.

Secondly, variables have always been a pretty small portion of why I use Sass. When I started using Sass in late 2012, it was primarily for looping, a feature we still don’t have in CSS. While I’ve moved some of that looping to an HTML preprocessor (because it reduces the generated code and avoids having to modify both the HTML and the CSS later), I still use Sass loops in plenty of cases, like generating lists of values, stop lists inside gradient functions, lists of points inside a polygon function, lists of transforms, and so on.

Here’s an example. I used to generate n HTML items with a preprocessor. The choice of preprocessor matters less, but I’ll be using Pug here.

- let n = 12;  while n--   .item

Then I would set the $ n variable into the Sass (and it would have to be equal to that in the HTML) and loop up to it to generate the transforms that would position each item:

$ n: 12; $ ba: 360deg/$ n; $ d: 2em;  .item {   position: absolute;   top: 50%; left: 50%;   margin: -.5*$ d;   width: $ d; height: $ d;   /* prettifying styles */    @for $ i from 0 to $ n {     &:nth-child(#{$ i + 1}) {       transform: rotate($ i*$ ba) translate(2*$ d) rotate(-$ i*$ ba); 			       &::before { content: '#{$ i}' }     }   } }

However, this meant that I would have to change both the Pug and the Sass when changing the number of items, making the generated code very repetitive.

Screenshot. Shows the generated CSS, really verbose, almost completely identical transform declaration repeated for each item.
CSS generated by the above code

I have since moved to making Pug generate the indices as custom properties and then use those in the transform declaration.

- let n = 12;  body(style=`--n: $ {n}`)   - for(let i = 0; i < n; i++)     .item(style=`--i: $ {i}`)
$ d: 2em;  .item {   position: absolute;   top: 50%;   left: 50%;   margin: -.5*$ d;   width: $ d;   height: $ d;   /* prettifying styles */   --az: calc(var(--i)*1turn/var(--n));   transform: rotate(var(--az)) translate(2*$ d) rotate(calc(-1*var(--az)));   counter-reset: i var(--i); 	   &::before { content: counter(i) } }

This significantly reduces the generated code.

Screenshot. Shows the generated CSS, much more compact, no having almost the exact same declaration set on every element separately.
CSS generated by the above code

However, looping in Sass is still necessary if I want to generate something like a rainbow.

@function get-rainbow($ n: 12, $ sat: 90%, $ lum: 65%) {   $ unit: 360/$ n;   $ s-list: (); 	   @for $ i from 0 through $ n {     $ s-list: $ s-list, hsl($ i*$ unit, $ sat, $ lum)   } 	   @return $ s-list }  html { background: linear-gradient(90deg, get-rainbow()) }

Sure, I could generate it as a list variable from Pug, but doing so doesn’t take advantage of the dynamic nature of CSS variables and it doesn’t reduce the amount of code that gets served to the browser, so there’s no benefit coming out of it.

Another big part of my Sass (and Compass) use is tied to built-in mathematical functions (such as trigonometric functions), which are part of the CSS spec now, but not yet implemented in any browser. Sass doesn’t come with these functions either, but Compass does and this is why I often need to use Compass.

And, sure, I could write my own such functions in Sass. I did resort to this in the beginning, before Compass supported inverse trigonometric functions. I really needed them, so I wrote my own based on the Taylor series. But Compass provides these sorts of functions nowadays and they are better and more performant than mine.

Mathematical functions are extremely important for me as I’m a technician, not an artist. The values in my CSS usually result from mathematical computations. They’re not magic numbers or something used purely for aesthetics. A example is generating lists of clip paths points that create regular or quasi-regular polygons. Think about the case where we want to create things like non-rectangular avatars or stickers.

Let’s consider a regular polygon with vertices on a circle with a radius 50% of the square element we start from. Dragging the slider in the following demo allows us to see where the points are placed for different numbers of vertices:

Putting it into Sass code, we have:

@mixin reg-poly($ n: 3) {   $ ba: 360deg/$ n; // base angle   $ p: (); // point coords list, initially empty 	   @for $ i from 0 to $ n {     $ ca: $ i*$ ba; // current angle     $ x: 50%*(1 + cos($ ca)); // x coord of current point     $ y: 50%*(1 + sin($ ca)); // y coord of current point     $ p: $ p, $ x $ y // add current point coords to point coords list   } 	   clip-path: polygon($ p) // set clip-path to list of points }

Note that here we’re also making use of looping and of things such as conditionals and modulo that are a real pain when using CSS without Sass.

A slightly more evolved version of this might involve rotating the polygon by adding the same offset angle ($ oa) to the angle of each vertex. This can be seen in the following demo. This example tosses in a star mixin that works in a similar manner, except we always have an even number of vertices and every odd-indexed vertex is situated on a circle of a smaller radius ($ f*50%, where $ f is sub-unitary):

We can also have chubby stars like this:

Or stickers with interesting border patterns. In this particular demo, each sticker is created with a single HTML element and the border pattern is created with clip-path, looping and mathematics in Sass. Quite a bit of it, in fact.

Another example are these card backgrounds where looping, the modulo operation and exponential functions work together to generate the dithering pixel background layers:

This demo just happens to rely heavily on CSS variables as well.

Then there’s using mixins to avoid writing the exact same declarations over and over when styling things like range inputs. Different browsers use different pseudo-elements to style the components of such a control, so for every component, we have to set the styles that control its look on multiple pseudos.

Sadly, as tempting as it may be to put this in our CSS:

input::-webkit-slider-runnable-track,  input::-moz-range-track,  input::-ms-track { /* common styles */ }

…we cannot do it because it doesn’t work! The entire rule set is dropped if even one of the selectors isn’t recognized. And since no browser recognises all three of the above, the styles don’t get applied in any browser.

We need to have something like this if we want our styles to be applied:

input::-webkit-slider-runnable-track { /* common styles */ } input::-moz-range-track { /* common styles */ } input::-ms-track { /* common styles */ }

But that can mean a lot of identical styles repeated three times. And if we want to change, say, the background of the track, we need to change it in the ::-webkit-slider-runnable-track styles, in the ::-moz-range-track styles and in the ::-ms-track styles.

The only sane solution we have is to use a mixin. The styles get repeated in the compiled code because they have to be repeated there, but we don’t have to write the same thing three times anymore.

@mixin track() { /* common styles */ }  input {   &::-webkit-slider-runnable-track { @include track }   &::-moz-range-track { @include track }   &::-ms-track { @include track } }

The bottom line is: yes, Sass is still very much necessary in 2020.

The post When Sass and New CSS Features Collide appeared first on CSS-Tricks.

CSS-Tricks

, ,

Custom Styling Form Inputs With Modern CSS Features

It’s entirely possible to build custom checkboxes, radio buttons, and toggle switches these days, while staying semantic and accessible. We don’t even need a single line of JavaScript or extra HTML elements! It’s actually gotten easier lately than it has been in the past. Let’s take a look.

Here’s where we’ll end up:

Things sure have gotten easier than they were!

The reason is that we can finally style the ::before and ::after pseudo-elements on the <input> tag itself. This means we can keep and style an <input> and won’t need any extra elements. Before, we had to rely on the likes of an extra <div> or <span>, to pull off a custom design.

Let’s look at the HTML

Nothing special here. We can style our inputs with just this HTML:

<!-- Checkbox --> <input type="checkbox">  <!-- Radio --> <input type="radio">  <!-- Switch --> <input type="checkbox" class="switch">

That’s it for the HTML part, but of course it’s recommended to have name and id attributes, plus a matching <label> element:

<!-- Checkbox --> <input type="checkbox" name="c1" id="c1"> <label for="c1">Checkbox</label>  <!-- Radio --> <input type="radio" name="r1" id="r1"> <label for="r1">Radio</label>  <!-- Switch --> <input type="checkbox" class="switch" name="s1" id="s1"> <label for="s1">Switch</label>

Getting into the styling 

First of all, we check for the support of appearance: none;, including it’s prefixed companions. The appearance property is key because it is designed to remove a browser’s default styling from an element. If the property isn’t supported, the styles won’t apply and default input styles will be shown. That’s perfectly fine and a good example of progressive enhancement at play.

@supports(-webkit-appearance: none) or (-moz-appearance: none) {   input[type='checkbox'],   input[type='radio'] {     -webkit-appearance: none;     -moz-appearance: none;   } }

As it stands today, appearance  is a working draft, but here’s what support looks like:

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

Chrome Firefox IE Edge Safari
82* 74* No 79* TP*

Mobile / Tablet

Android Chrome Android Firefox Android iOS Safari
79* 68* 76* 13.3*

Like links, we’ve gotta consider different interactive states with form elements. We’ll consider these when styling our elements:

  • :checked
  • :hover
  • :focus
  • :disabled

For example, here’s how we can style our toggle input, create the knob, and account for the :checked state:

/* The toggle container */ .switch {   width: 38px;   border-radius: 11px; }  /* The toggle knob */ .switch::after {   left: 2px;   top: 2px;   border-radius: 50%;   width: 15px;   height: 15px;   background: var(--ab, var(--border));   transform: translateX(var(--x, 0)); }  /* Change color and position when checked */ .switch:checked {   --ab: var(--active-inner);   --x: 17px; }  /* Drop the opacity of the toggle knob when the input is disabled */ .switch:disabled:not(:checked)::after {   opacity: .6; }

We are using the <input> element like a container. The knob inside of the input is created with the ::after pseudo-element. Again, no more need for extra markup!

If you crack open the styles in the demo, you’ll see that we’re defining some CSS custom properties because that’s become such a nice way to manage reusable values in a stylesheet:

@supports(-webkit-appearance: none) or (-moz-appearance: none) {   input[type='checkbox'],   input[type='radio'] {     --active: #275EFE;     --active-inner: #fff;     --focus: 2px rgba(39, 94, 254, .25);     --border: #BBC1E1;     --border-hover: #275EFE;     --background: #fff;     --disabled: #F6F8FF;     --disabled-inner: #E1E6F9;   } }

But there’s another reason we’re using custom properties — they work well for updating values based on the state of the element! We won’t go into full detail here, but here’s an example how we can use custom properties for different states.

/* Default */ input[type='checkbox'], input[type='radio'] {   --active: #275EFE;   --border: #BBC1E1;   border: 1px solid var(--bc, var(--border)); }  /* Override defaults */ input[type='checkbox']:checked, input[type='radio']:checked {   --b: var(--active);   --bc: var(--active); }    /* Apply another border color on hover if not checked & not disabled */ input[type='checkbox']:not(:checked):not(:disabled):hover, input[type='radio']:not(:checked):not(:disabled):hover {   --bc: var(--border-hover); }

For accessibility, we ought to add a custom focus style. We are removing the default outline because it can’t be rounded like the rest of the things we’re styling. But a border-radius along with a box-shadow can make for a rounded style that works just like an outline.

input[type='checkbox'], input[type='radio'] {   --focus: 2px rgba(39, 94, 254, .25);   outline: none;   transition: box-shadow .2s; }  input[type='checkbox']:focus, input[type='radio']:focus {   box-shadow: 0 0 0 var(--focus); }

It’s also possible to align and style the <label> element which directly follows the <input> element in the HTML:

<input type="checkbox" name="c1" id="c1"> <label for="c1">Checkbox</label>
input[type='checkbox'] + label, input[type='radio'] + label {   display: inline-block;   vertical-align: top;   /* Additional styling */ }  input[type='checkbox']:disabled + label, input[type='radio']:disabled + label {     cursor: not-allowed; }

Here’s that demo again:

Hopefully, you’re seeing how nice it is to create custom form styles these days. It requires less markup, thanks to pseudo-elements that are directly on form inputs. It requires less fancy style switching, thanks to custom properties. And it has pretty darn good browser support, thanks to @supports.

All in all, this is a much more pleasant developer experience than we’ve had to deal with in the past!

The post Custom Styling Form Inputs With Modern CSS Features appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]

My Favorite Netlify Features

👋 Hey folks! Silvestar pitched this post to us because he is genuinely enthusiastic about JAMstack and all of the opportunities it opens up for front-end development. We wanted to call that out because, although some of the points in here might come across as sponsored content and Netlify is indeed a CSS-Tricks sponsor, it’s completely independent of Netlify.

Being a JAMstack developer in 2019 makes me feel like I am living in a wonderland. All these modern frameworks, tools, and services make our lives as JAMstack developers quite enjoyable. In fact, Chris would say they give us superpowers.

Yet, there is one particular platform that stands out with its formidable products and features — Netlify. You’re probably pretty well familiar with Netlify if you read CSS-Tricks regularly. There’s a slew of articles on it. There are even two CSS-Tricks microsites that use it.

This article is more of a love letter to Netlify and all of the great things it does. I decided to sit down and list my most favorite things about it. So that’s what I’d like to share with you here. Hopefully, this gives you a good idea not only what Netlify is capable of doing, but helps you get the most out of it as well.

You can customize your site’s Netlify subdomain.

When creating a new project on Netlify, you start by either:

  • choosing a repository from a Git provider, or
  • uploading a folder.

The project should be ready in a matter of minutes, and you could start configuring it for your needs right away. Start by choosing the site name.

The site name determines the default URL for your site. Only alphanumeric characters and hyphens are allowed.

Netlify randomly creates a default name for a new project. If you don’t like the name, choose your own and make it one that would be much easier for you to remember.

The “Site information” section of the Netlify dashboard.

For example, my site name is silvestarcodes, and I could access my site by visiting silvestarcodes.netlify.com.

You can manage all your DNS on Netlify.

If you are setting up an actual site, you would want to add a custom domain. From the domain management panel, go to the custom domains section, click on the “Add custom domain” button, enter your domain, and click the “Verify” button.

Now you have two options:

  1. Point your DNS records to Netlify load balancer IP address
  2. Let Netlify handle your DNS records

For the first option, you could read the full instructions in the official documentation for custom domains.

For the second option, you should add or update the nameservers on your domain registrar. If you didn’t buy the domain already, you could register it right from the dashboard.

Netlify has a service for provisioning DNS records called Netlify DNS.

Once you have configured the custom domain, you could handle your DNS records from the Netlify dashboard.

The “DNS” section of the Netlify dashboard.

If you want to set up a dev subdomain for your dev branch to preview development changes for your site, you could do it automatically. From the Domain Management section in the Settings section of your site, select the dev branch and Netlify would add a new subdomain dev for you automagically. Now you could see the previews by visiting dev subdomain.

The “Subdomains” section of the Netlify dashboard.

You could configure a subdomain for a different website. To achieve this, create a new Netlify site, enter a new subdomain as a custom domain, and Netlify would automatically add the records for you.

As an icing on the DNS management cake, Netlify lets you create Let’s Encrypt certificates for your domain automatically… for free.

You can inject snippets into pages, which is sort of like a Tag Manager.

Snippet injection is another excellent feature. I am using it mostly for inserting analytics, but you could use it for adding meta tags for responsive behavior, favicon tags, or Webmention.io tags.

The “Snippet injection” section of the Netlify dashboard.

When inserting snippets, you could choose to append the code fragment at the end of the <head> block, or at the end of the <body> block.

Every deploy has its own URL forever.

Netlify creates a unique preview link for every successful build. That means you could easily compare revisions made to your site. For example, here is the link to my website from January this year, and here is the link from January last year. Notice the style and content changes.

In his talk, Phil Hawksworth calls this feature immutable, atomic deploys.

They are immutable deployments that live on forever.
— Phil Hawksworth

I found this feature useful when completing tasks and sending the preview links to the clients. If there is a person in charge of handling Git-related tasks, like publishing to production, these preview links could be convenient to understand what to expect during the merge. You could even set up the preview builds for every pull request.

Netlify allows for the cleanest and most responsible A/B testing you can do.

If you ever wanted to run A/B tests on your site, you would find that Netlify makes running A/B tests quite straightforward. Split testing on Netlify allows you to display different versions of your website from different Git branches without any hackery.

The “Split testing” section of the Netlify dashboard.

Start by adding and publishing a separate branch with desired changes. From “Split testing” panel, select which branches to test, set a split percentage, and start the test. You could even set a variable in analytics code to track which branch is currently displayed. You might need to active branch deploys if you didn’t do this already.

Netlify’s Split Testing lets you divide traffic to your site between different deploys, straight from our CDN network, without losing any download performance, and without installing any third party JavaScript library.
Netlify documentation

I have been using A/B testing on my site for a few different features so far:

  • Testing different versions of contact forms
  • Displaying different versions of banners
  • Tracking user behavior, like heatmaps

If you want to track split testing information, you could set up the process environment variable for this purpose. You could learn more about it in the official documentation. The best part? Most A/B testing services use client-side JavaScript to do it, which is unreliable and not great for performance. Doing it at the load balancer level like this is so much better.

There are lots of options for notifications, like email and Slack.

If you want to receive a notification when something happens with your Netlify project, you could choose from a wide variety of notification options. I prefer getting an email for every successful or failed build.

The “Notifications” section of the Netlify dashboard.

If you are using Gmail, you could notice “See the changes live” link for every successful build when hovering your message in Gmail inbox. That means you could open a preview link without opening the email. There are other links like “See full deploy logs” when your build have any issues or “Check usage details” when your plan is near its limits. How awesome is that?

Netlify email notifications include a preview link.

If you want to set up a hook for third-party services, all you need is a URL (JWS secret token is optional). Slack hooks are built-in with Netlify and could be set up within seconds if you know your Slack incoming webhook URL.

Conclusion

All of the features mentioned above are part of the free Netlify plan. I cannot even imagine the effort invested in providing a seamless experience as it is now. But Netlify doesn’t stop there. They are introducing more and more new and shiny features, like Netlify Dev CLI for local development and deploy cancelations. Netlify has established as an undoubtedly game-changing platform in modern web development of static websites, and it is a big part of the growth and popularity of static sites.

The post My Favorite Netlify Features appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

New ES2018 Features Every JavaScript Developer Should Know

The ninth edition of the ECMAScript standard, officially known as ECMAScript 2018 (or ES2018 for short), was released in June 2018. Starting with ES2016, new versions of ECMAScript specifications are released yearly rather than every several years and add fewer features than major editions used to. The newest edition of the standard continues the yearly release cycle by adding four new RegExp features, rest/spread properties, asynchronous iteration, and Promise.prototype.finally. Additionally, ES2018 drops the syntax restriction of escape sequences from tagged templates.

These new changes are explained in the subsections that follow.

The Rest/Spread Properties

One of the most interesting features added to ES2015 was the spread operator. This operator makes copying and merging arrays a lot simpler. Rather than calling the concat() or slice() method, you could use the ... operator:

const arr1 = [10, 20, 30];  // make a copy of arr1 const copy = [...arr1];  console.log(copy);    // → [10, 20, 30]  const arr2 = [40, 50];  // merge arr2 with arr1 const merge = [...arr1, ...arr2];  console.log(merge);    // → [10, 20, 30, 40, 50]

The spread operator also comes in handy in situations where an array must be passed in as separate arguments to a function. For example:

const arr = [10, 20, 30]  // equivalent to // console.log(Math.max(10, 20, 30)); console.log(Math.max(...arr));    // → 30

ES2018 further expands this syntax by adding spread properties to object literals. With the spread properties you can copy own enumerable properties of an object onto a new object. Consider the following example:

const obj1 = {   a: 10,   b: 20 };  const obj2 = {   ...obj1,   c: 30 };  console.log(obj2);    // → {a: 10, b: 20, c: 30}

In this code, the ... operator is used to retrieve the properties of obj1 and assign them to obj2. Prior to ES2018, attempting to do so would throw an error. If there are multiple properties with the same name, the property that comes last will be used:

const obj1 = {   a: 10,   b: 20 };  const obj2 = {   ...obj1,   a: 30 };  console.log(obj2);    // → {a: 30, b: 20}

Spread properties also provide a new way to merge two or more objects, which can be used as an alternative to the Object.assign() method:

const obj1 = {a: 10}; const obj2 = {b: 20}; const obj3 = {c: 30};  // ES2018 console.log({...obj1, ...obj2, ...obj3});    // → {a: 10, b: 20, c: 30}  // ES2015 console.log(Object.assign({}, obj1, obj2, obj3));    // → {a: 10, b: 20, c: 30}

Note, however, that spread properties do not always produce the same result as Object.assign(). Consider the following code:

Object.defineProperty(Object.prototype, 'a', {   set(value) {     console.log('set called!');   } });  const obj = {a: 10};  console.log({...obj});     // → {a: 10}  console.log(Object.assign({}, obj));     // → set called! // → {}

In this code, the Object.assign() method executes the inherited setter property. Conversely, the spread properties simply ignore the setter.

It’s important to remember that spread properties only copy enumerable properties. In the following example, the type property won’t show up in the copied object because its enumerable attribute is set to false:

const car = {   color: 'blue' };  Object.defineProperty(car, 'type', {   value: 'coupe',   enumerable: false });  console.log({...car});    // → {color: "blue"}

Inherited properties are ignored even if they are enumerable:

const car = {   color: 'blue' };  const car2 = Object.create(car, {   type: {     value: 'coupe',     enumerable: true,   } });  console.log(car2.color);                      // → blue console.log(car2.hasOwnProperty('color'));    // → false  console.log(car2.type);                       // → coupe console.log(car2.hasOwnProperty('type'));     // → true  console.log({...car2});                       // → {type: "coupe"}

In this code, car2 inherits the color property from car. Because spread properties only copy the own properties of an object, color is not included in the return value.

Keep in mind that spread properties can only make a shallow copy of an object. If a property holds an object, only the reference to the object will be copied:

const obj = {x: {y: 10}}; const copy1 = {...obj};     const copy2 = {...obj};   console.log(copy1.x === copy2.x);    // → true

The x property in copy1 refers to the same object in memory that x in copy2 refers to, so the strict equality operator returns true.

Another useful feature added to ES2015 was rest parameters, which enabled JavaScript programmers to use ... to represent values as an array. For example:

const arr = [10, 20, 30]; const [x, ...rest] = arr;  console.log(x);       // → 10 console.log(rest);    // → [20, 30]

Here, the first item in arr is assigned to x, and remaining elements are assigned to the rest variable. This pattern, called array destructuring, became so popular that the Ecma Technical Committee decided to bring a similar functionality to objects:

const obj = {   a: 10,   b: 20,   c: 30 };  const {a, ...rest} = obj;  console.log(a);       // → 10 console.log(rest);    // → {b: 20, c: 30}

This code uses the rest properties in a destructuring assignment to copy the remaining own enumerable properties into a new object. Note that rest properties must always appear at the end of the object, otherwise an error is thrown:

const obj = {   a: 10,   b: 20,   c: 30 };  const {...rest, a} = obj;    // → SyntaxError: Rest element must be last element

Also keep in mind that using multiple rest syntaxes in an object causes an error, unless they are nested:

const obj = {   a: 10,   b: {     x: 20,     y: 30,     z: 40   } };  const {b: {x, ...rest1}, ...rest2} = obj;    // no error  const {...rest, ...rest2} = obj;    // → SyntaxError: Rest element must be last element

Support for Rest/Spread Properties

Chrome Firefox Safari Edge
60 55 11.1 No
Chrome Android Firefox Android iOS Safari Edge Mobile Samsung Internet Android Webview
60 55 11.3 No 8.2 60

Node.js:

  • 8.0.0 (requires the --harmony runtime flag)
  • 8.3.0 (full support)

Asynchronous Iteration

Iterating over a collection of data is an important part of programming. Prior to ES2015, JavaScript provided statements such as for, for...in, and while, and methods such as map(), filter(), and forEach() for this purpose. To enable programmers to process the elements in a collection one at a time, ES2015 introduced the iterator interface.

An object is iterable if it has a Symbol.iterator property. In ES2015, strings and collections objects such as Set, Map, and Array come with a Symbol.iterator property and thus are iterable. The following code gives an example of how to access the elements of an iterable one at a time:

const arr = [10, 20, 30]; const iterator = arr[Symbol.iterator]();    console.log(iterator.next());    // → {value: 10, done: false} console.log(iterator.next());    // → {value: 20, done: false} console.log(iterator.next());    // → {value: 30, done: false} console.log(iterator.next());    // → {value: undefined, done: true}

Symbol.iterator is a well-known symbol specifying a function that returns an iterator. The primary way to interact with an iterator is the next() method. This method returns an object with two properties: value and done. The value property contains the value of the next element in the collection. The done property contains either true or false denoting whether or not the end of the collection has reached.

By default, a plain object is not iterable, but it can become iterable if you define a Symbol.iterator property on it, as in this example:

const collection = {   a: 10,   b: 20,   c: 30,   [Symbol.iterator]() {     const values = Object.keys(this);     let i = 0;     return {       next: () => {         return {           value: this[values[i++]],           done: i > values.length         }       }     };   } };  const iterator = collection[Symbol.iterator]();    console.log(iterator.next());    // → {value: 10, done: false} console.log(iterator.next());    // → {value: 20, done: false} console.log(iterator.next());    // → {value: 30, done: false} console.log(iterator.next());    // → {value: undefined, done: true}

This object is iterable because it defines a Symbol.iterator property. The iterator uses the Object.keys() method to get an array of the object’s property names and then assigns it to the values constant. It also defines a counter variable and gives it an initial value of 0. When the iterator is executed it returns an object that contains a next() method. Each time the next() method is called, it returns a {value, done} pair, with value holding the next element in the collection and done holding a Boolean indicating if the iterator has reached the need of the collection.

While this code works perfectly, it’s unnecessarily complicated. Fortunately, using a generator function can considerably simplify the process:

const collection = {   a: 10,   b: 20,   c: 30,   [Symbol.iterator]: function * () {     for (let key in this) {       yield this[key];     }   } };  const iterator = collection[Symbol.iterator]();    console.log(iterator.next());    // → {value: 10, done: false} console.log(iterator.next());    // → {value: 20, done: false} console.log(iterator.next());    // → {value: 30, done: false} console.log(iterator.next());    // → {value: undefined, done: true}

Inside this generator, a for...in loop is used to enumerate over the collection and yield the value of each property. The result is exactly the same as the previous example, but it’s greatly shorter.

A downside of iterators is that they are not suitable for representing asynchronous data sources. ES2018’s solution to remedy that is asynchronous iterators and asynchronous iterables. An asynchronous iterator differs from a conventional iterator in that, instead of returning a plain object in the form of {value, done}, it returns a promise that fulfills to {value, done}. An asynchronous iterable defines a Symbol.asyncIterator method (instead of Symbol.iterator) that returns an asynchronous iterator.

An example should make this clearer:

const collection = {   a: 10,   b: 20,   c: 30,   [Symbol.asyncIterator]() {     const values = Object.keys(this);     let i = 0;     return {       next: () => {         return Promise.resolve({           value: this[values[i++]],            done: i > values.length         });       }     };   } };  const iterator = collection[Symbol.asyncIterator]();    console.log(iterator.next().then(result => {   console.log(result);    // → {value: 10, done: false} }));  console.log(iterator.next().then(result => {   console.log(result);    // → {value: 20, done: false}  }));  console.log(iterator.next().then(result => {   console.log(result);    // → {value: 30, done: false}  }));  console.log(iterator.next().then(result => {   console.log(result);    // → {value: undefined, done: true}  }));

Note that it’s not possible to use an iterator of promises to achieve the same result. Although a normal, synchronous iterator can asynchronously determine the values, it still needs to determine the state of “done” synchronously.

Again, you can simplify the process by using a generator function, as shown below:

const collection = {   a: 10,   b: 20,   c: 30,   [Symbol.asyncIterator]: async function * () {     for (let key in this) {       yield this[key];     }   } };  const iterator = collection[Symbol.asyncIterator]();    console.log(iterator.next().then(result => {   console.log(result);    // → {value: 10, done: false} }));  console.log(iterator.next().then(result => {   console.log(result);    // → {value: 20, done: false}  }));  console.log(iterator.next().then(result => {   console.log(result);    // → {value: 30, done: false}  }));  console.log(iterator.next().then(result => {   console.log(result);    // → {value: undefined, done: true}  }));

Normally, a generator function returns a generator object with a next() method. When next() is called it returns a {value, done} pair whose value property holds the yielded value. An async generator does the same thing except that it returns a promise that fulfills to {value, done}.

An easy way to iterate over an iterable object is to use the for...of statement, but for...of doesn’t work with async iterables as value and done are not determined synchronously. For this reason, ES2018 provides the for...await...of statement. Let’s look at an example:

const collection = {   a: 10,   b: 20,   c: 30,   [Symbol.asyncIterator]: async function * () {     for (let key in this) {       yield this[key];     }   } };  (async function () {   for await (const x of collection) {     console.log(x);   } })();  // logs: // → 10 // → 20 // → 30

In this code, the for...await...of statement implicitly calls the Symbol.asyncIterator method on the collection object to get an async iterator. Each time through the loop, the next() method of the iterator is called, which returns a promise. Once the promise is resolved, the value property of the resulting object is read to the x variable. The loop continues until the done property of the returned object has a value of true.

Keep in mind that the for...await...of statement is only valid within async generators and async functions. Violating this rule results in a SyntaxError.

The next() method may return a promise that rejects. To gracefully handle a rejected promise, you can wrap the for...await...of statement in a try...catch statement, like this:

const collection = {   [Symbol.asyncIterator]() {     return {       next: () => {         return Promise.reject(new Error('Something went wrong.'))       }     };   } };  (async function() {   try {     for await (const value of collection) {}   } catch (error) {     console.log('Caught: ' + error.message);   } })();  // logs: // → Caught: Something went wrong.

Support for Asynchronous Iterators

Chrome Firefox Safari Edge
63 57 12 No
Chrome Android Firefox Android iOS Safari Edge Mobile Samsung Internet Android Webview
63 57 12 No 8.2 63

Node.js:

  • 8.10.0 (requires the –harmony_async_iteration flag)
  • 10.0.0 (full support)

Promise.prototype.finally

Another exciting addition to ES2018 is the finally() method. Several JavaScript libraries had previously implemented a similar method, which proved useful in many situations. This encouraged the Ecma Technical Committee to officially add finally() to the specification. With this method, programmers will be able to execute a block of code regardless of the promise’s fate. Let’s look at a simple example:

fetch('https://www.google.com')   .then((response) => {     console.log(response.status);   })   .catch((error) => {      console.log(error);   })   .finally(() => {      document.querySelector('#spinner').style.display = 'none';   });

The finally() method comes in handy when you need to do some clean up after the operation has finished regardless of whether or not it succeeded. In this code, the finally() method simply hides the loading spinner after the data is fetched and processed. Instead of duplicating the final logic in the then() and catch() methods, the code registers a function to be executed once the promise is either fulfilled or rejected.

You could achieve the same result by using promise.then(func, func) rather than promise.finally(func), but you would have to repeat the same code in both fulfillment handler and rejection handler, or declare a variable for it:

fetch('https://www.google.com')   .then((response) => {     console.log(response.status);   })   .catch((error) => {      console.log(error);   })   .then(final, final);  function final() {   document.querySelector('#spinner').style.display = 'none'; }

As with then() and catch(), the finally() method always returns a promise, so you can chain more methods. Normally, you want to use finally() as the last chain, but in certain situations, such as when making a HTTP request, it’s a good practice to chain another catch() to deal with errors that may occur in finally().

Support for Promise.prototype.finally

Chrome Firefox Safari Edge
63 58 11.1 18
Chrome Android Firefox Android iOS Safari Edge Mobile Samsung Internet Android Webview
63 58 11.1 No 8.2 63

Node.js:

10.0.0 (full support)

New RegExp Features

ES2018 adds four new features to the RegExp object, which further improves JavaScript’s string processing capabilities. These features are as follows:

  • s (dotAll) flag
  • Named capture groups
  • Lookbehind assertions
  • Unicode property escapes

s (dotAll) Flag

The dot (.) is a special character in a regular expression pattern that matches any character except line break characters such as line feed (\n) or carriage return (\r). A workaround to match all characters including line breaks is to use a character class with two opposite shorthands such as [\d\D]. This character class tells the regular expression engine to find a character that’s either a digit (\d) or a non-digit (\D). As a result, it matches any character:

console.log(/one[\d\D]two/.test('one\ntwo'));    // → true

ES2018 introduces a mode in which the dot can be used to achieve the same result. This mode can be activated on per-regex basis by using the s flag:

console.log(/one.two/.test('one\ntwo'));     // → false console.log(/one.two/s.test('one\ntwo'));    // → true

The benefit of using a flag to opt in to the new behavior is backwards compatibility. So existing regular expression patterns that use the dot character are not affected.

Named Capture Groups

In some regular expression patterns, using a number to reference a capture group can be confusing. For example, take the regular expression /(\d{4})-(\d{2})-(\d{2})/ which matches a date. Because date notation in American English is different from British English, it’s hard to know which group refers to the day and which group refers to the month:

const re = /(\d{4})-(\d{2})-(\d{2})/; const match= re.exec('2019-01-10');  console.log(match[0]);    // → 2019-01-10 console.log(match[1]);    // → 2019 console.log(match[2]);    // → 01 console.log(match[3]);    // → 10

ES2018 introduces named capture groups which uses the (?<name>...) syntax. So, the pattern to match a date can be written in a less ambiguous manner:

const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/; const match = re.exec('2019-01-10');  console.log(match.groups);          // → {year: "2019", month: "01", day: "10"} console.log(match.groups.year);     // → 2019 console.log(match.groups.month);    // → 01 console.log(match.groups.day);      // → 10

You can recall a named capture group later in the pattern by using the \k<name> syntax. For example, to find consecutive duplicate words in a sentence, you can use /\b(?<dup>\w+)\s+\k<dup>\b/:

const re = /\b(?<dup>\w+)\s+\k<dup>\b/; const match = re.exec('Get that that cat off the table!');          console.log(match.index);    // → 4 console.log(match[0]);       // → that that

To insert a named capture group into the replacement string of the replace() method, you will need to use the $ <name> construct. For example:

const str = 'red & blue';  console.log(str.replace(/(red) & (blue)/, '$  2 & $  1'));     // → blue & red  console.log(str.replace(/(?<red>red) & (?<blue>blue)/, '$  <blue> & $  <red>'));     // → blue & red

Lookbehind Assertions

ES2018 brings lookbehind assertions to JavaScript, which have been available in other regex implementations for years. Previously, JavaScript only supported lookahead assertions. A lookbehind assertion is denoted by (?<=...), and enables you to match a pattern based on the substring that precedes the pattern. For example, if you want to match the price of a product in dollar, pound, or euro without capturing the currency symbol, you can use /(?<=$ |£|€)\d+(\.\d*)?/:

const re = /(?<=$  |£|€)\d+(\.\d*)?/;  console.log(re.exec('199'));      // → null  console.log(re.exec('$  199'));     // → ["199", undefined, index: 1, input: "$  199", groups: undefined]  console.log(re.exec('€50'));      // → ["50", undefined, index: 1, input: "€50", groups: undefined]

There is also a negative version of lookbehind, which is denoted by (?<!...). A negative lookbehind allows you to match a pattern only if it is not preceded by the pattern within the lookbehind. For example, the pattern /(?<!un)available/ matches the word available if it does not have a “un” prefix:

const re = /(?<!un)available/;  console.log(re.exec('We regret this service is currently unavailable'));     // → null  console.log(re.exec('The service is available'));              // → ["available", index: 15, input: "The service is available", groups: undefined]

Unicode Property Escapes

ES2018 provides a new type of escape sequence known as Unicode property escape, which provides support for full Unicode in regular expressions. Suppose you want to match the Unicode character ㉛ in a string. Although ㉛ is considered a number, you can’t match it with the \d shorthand character class because it only supports ASCII [0-9] characters. Unicode property escapes, on the other hand, can be used to match any decimal number in Unicode:

const str = '㉛';  console.log(/\d/u.test(str));    // → false console.log(/\p{Number}/u.test(str));     // → true

Similarly, if you want to match any Unicode word character, you can use \p{Alphabetic}:

const str = 'ض';  console.log(/\p{Alphabetic}/u.test(str));     // → true  // the \w shorthand cannot match ض   console.log(/\w/u.test(str));    // → false

There is also a negated version of \p{...}, which is denoted by \P{...}:

console.log(/\P{Number}/u.test('㉛'));    // → false console.log(/\P{Number}/u.test('ض'));    // → true  console.log(/\P{Alphabetic}/u.test('㉛'));    // → true console.log(/\P{Alphabetic}/u.test('ض'));    // → false

In addition to Alphabetic and Number, there are several more properties that can be used in Unicode property escapes. You can find a list of supported Unicode properties in the current specification proposal.

Support for New RegExp Features

Chrome Firefox Safari Edge
s (dotAll) Flag 62 No 11.1 No
Named Capture Groups 64 No 11.1 No
Lookbehind Assertions 62 No No No
Unicode Property Escapes 64 No 11.1 No
Chrome (Android) Firefox (Android) iOS Safari Edge Mobile Samsung Internet Android Webview
s (dotAll) Flag 62 No 11.3 No 8.2 62
Named Capture Groups 64 No 11.3 No No 64
Lookbehind Assertions 62 No No No 8.2 62
Unicode Property Escapes 64 No 11.3 No No 64

Node.js:

  • 8.3.0 (requires the –harmony runtime flag)
  • 8.10.0 (support for s (dotAll) flag and lookbehind assertions)
  • 10.0.0 (full support)

Template Literal Revision

When a template literal is immediately preceded by an expression, it is called a tagged template literal. A tagged template comes in handy when you want to parse a template literal with a function. Consider the following example:

function fn(string, substitute) {   if(substitute === 'ES6') {     substitute = 'ES2015'   }   return substitute + string[1]; }  const version = 'ES6'; const result = fn`$  {version} was a major update`;  console.log(result);    // → ES2015 was a major update

In this code, a tag expression — which is a regular function — is invoked and passed the template literal. The function simply modifies the dynamic part of the string and returns it.

Prior to ES2018, tagged template literals had syntactic restrictions related to escape sequences. A backslash followed by certain sequence of characters were treated as special characters: a \x interpreted as a hex escape, a \u interpreted as a unicode escape, and a \ followed by a digit interpreted as an octal escape. As a result, strings such as "C:\xxx\uuu" or "\ubuntu" were considered invalid escape sequences by the interpreter and would throw a SyntaxError.

ES2018 removes these restrictions from tagged templates and instead of throwing an error, represents invalid escape sequences as undefined:

function fn(string, substitute) {   console.log(substitute);    // → escape sequences:   console.log(string[1]);     // → undefined }  const str = 'escape sequences:'; const result = fn`$  {str} \ubuntu C:\xxx\uuu`;

Keep in mind that using illegal escape sequences in a regular template literal still causes an error:

const result = `\ubuntu`; // → SyntaxError: Invalid Unicode escape sequence

Support for Template Literal Revision

Chrome Firefox Safari Edge
62 56 11 No
Chrome Android Firefox Android iOS Safari Edge Mobile Samsung Internet Android Webview
62 56 11 No 8.2 62

Node.js:

  • 8.3.0 (requires the –harmony runtime flag)
  • 8.10.0 (full support)

Wrapping up

We’ve taken a good look at several key features introduced in ES2018 including asynchronous iteration, rest/spread properties, Promise.prototype.finally(), and additions to the RegExp object. Although some of these features are not fully implemented by some browser vendors yet, they can still be used today thanks to JavaScript transpilers such as Babel.

ECMAScript is rapidly evolving and new features are being introduced every so often, so check out the list of finished proposals for the full scope of what’s new. Are there any new features you’re particularly excited about? Share them in the comments!

The post New ES2018 Features Every JavaScript Developer Should Know appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,
[Top]