State of JS 2020

Following up on last year’s 2019 edition the results for the State of JS are in. 23,765 people answered the survey resulting in an overview of what’s hot and not for JavaScript and its ecosystem.

It’s great to see that language features like Destructuring, Nullish Coalescing and Optional Chaining seem to be common nowadays. However, just like last year, do take this all with a grain of salt though as 43% of the participants consider them to be experts in … CSS 🤨

State of JS 2020 →

☝️ For CSS a likewise survey was done: the State of CSS 2020

Update 2020-01-14: If you’re thinking the survey is too biased towards white male Americans, be sure to read State of JS 2020: Common Criticisms by author Sacha Greif.

10 Next.js Tips You Might Not Know!

Next.js can do a lot for you — which means there’s features you probably haven’t used! This video covers ten tips you might not know about Next.js.

Litepicker Date Range Picker

I like that the daterange needs to be entered in one single input, and that the rendered datepicker is used as a progressive enhancement on top.

Installation per NPM:

npm i litepicker

At its core, usage is really simple:

import Litepicker from 'litepicker';

const picker = new Litepicker({ element: document.getElementById('litepicker') });

Highly configurable too!

Litepicker — Date range picker – lightweight, no dependencies →

Using Nintendo Switch Joy-Con Controllers on the Web with the WebHID API

Thomas Steiner:

WebHID allows websites to access devices that use the human interface devices (HID) protocol via JavaScript. Here is a little Christmas present 🎄 to the community to celebrate the API approval: releasing Joy-Con WebHID, a WebHID “driver” for Nintendo Joy-Con controllers so you can use them in the browser. If you have Joy-Cons, be sure to check out the demo to get a feel for what is possible.

Installation per NPM:

npm install joy-con-webhid

Once a Joy-Con has been paired, you can listen to hidinput events:

joyCon.addEventListener('hidinput', ({ detail }) => {
    // Careful, this fires at ~60fps.
    console.log(`Input report from ${joyCon.device.productName}:`, detail);
});

💡 The code does not use on the Gamepad API but the WebHID API as the former does not have support for orientation, a feature the Joy-Con controllers use extensively.

Releasing Joy-Con WebHID →
Joy-Con WebHID Repo (GitHub) →

Writable Getters

Lea Verou:

A pattern that has come up a few times in my code is the following: an object has a property which defaults to an expression based on its other properties unless it’s explicitly set, in which case it functions like a normal property. Essentially, the expression functions as a default value.

Think of a post slug for example: if none is set, then one should be automatically generated from the title. But if one is explicitly set, then that one should be returned.

The easiest way to implement this is to delete the previously stored getter upon setting a value:

let lea = {
	name: "Lea Verou",
	get id() {
		return this.name.toLowerCase().replace(/\W+/g, "-");
	},
	set id(v) {
		delete this.id;
		return this.id = v;
	}
}

But that’s not really scalable and requires code duplication if you have multiple occurrences of this pattern. Lea provides us with some reusable code, so that we can eventually simply call this one-liner to achieve the same result:

makeGetterWritable(lea, "id", {enumerable: true});

Writable Getters →

HTML and CSS techniques to reduce your JavaScript

Anthony Ricaud, writing for the Web Performance Calendar, on the extra load that JavaScript can put on your site, and how you can replace some things with basic HTML and CSS:

Relying on solutions provided natively by browsers enables you to benefit at low cost from the expertise of the community creating web standards. These solutions generally have the advantage of using less code, thus reducing maintenance efforts for a development team (for example, no need to update the libraries used).

In this article, we will explore some of these native solutions that are available to the majority of your users.

It’s things like smooth scrolling, sticky items, carousels, etc. — those all can be implemented using pure CSS nowadays.

HTML and CSS techniques to reduce your JavaScript →

Turbo: The speed of a single-page web application without having to write any JavaScript

From the folks at Basecamp comes Turbo:

Turbo accelerates links and form submissions without requiring you to change your server-side generated HTML. It lets you carve up a page into independent frames, which can be lazy-loaded and operate as independent components. And finally, helps you make partial page updates using just HTML and a set of CRUD-like container tags. These three techniques reduce the amount of custom JavaScript that many web applications need to write by an order of magnitude.

Nice, but that “without requiring you to change your server-side generated HTML” part is not exactly true though, as you have to sprinkle some turbo-* custom elements and data-turbo-* attributes all over your markup. Once those are in place Turbo will do its thing and progressively enhance the experience.

If you want to do more than the features provided by Turbo you can bundle it up with Stimulus and Strada. All three together are published under the name Hotwire.

Turbo →

Injecting a JavaScript Attack Vector using CSS Custom Properties

Earlier this week I saw this tweet by Sansec float by:

This one’s pretty nice I must say: as the syntax for CSS Custom Properties is overly permissive (see here) you can use Custom Properties to store your JavaScript attack vector in. If you then use window.getComputedStyle to extract the contents of the Custom Property (see here) and combine it with a function constructor and an IIFE, it’s possible to execute it.

Here’s a pen that loads a remote confetti script using the method described:

Let this underline the importance of a Content Security Policy to prevent remote script loading script evaluation.

Update: Blocking this “hack” with a proper CSP

It took me some time to figure out — as I’m no CSP expert — but turns out the unsafe-inline keyword in the CSP’s source list is enough to block the execution of the JS-IN-CSS.

As a reminder, here are the four allowed keywords:

  • 'none', as you might expect, matches nothing.
  • 'self' matches the current origin, but not its subdomains.
  • 'unsafe-inline' allows inline JavaScript and CSS.
  • 'unsafe-eval' allows text-to-JavaScript mechanisms like eval.

I first thought unsafe-inline would be insufficient here as the code does not call eval, but apparently a function constructor is (correctly!) considered equally harmful, and therefore also blocked.

Here’s an updated demo that blocks the script evaluation:

See the Pen
Injecting a JavaScript attack vector using CSS Custom Properties (with CSP)
by Bramus (@bramus)
on CodePen.

The CSP used is this one:

<meta
    http-equiv="Content-Security-Policy"
    content="script-src https://cpwebassets.codepen.io https://cdpn.io https://cdn.jsdelivr.net 'unsafe-inline';"
>

It works as follows:

  • https://cpwebassets.codepen.io and https://cdpn.io are there for the CodePen demo to work
  • https://cdn.jsdelivr.net is there to allow legitimate loading of scripts — such as a jQuery you might need — from that CDN.
  • unsafe-inline is the one that prevents the execution of the JS-IN-CSS defined script by blocking the call to the function constructor

That calls for confetti! 🤪

Did this help you out? Like what you see?
Thank me with a coffee.

I don't do this for profit but a small one-time donation would surely put a smile on my face. Thanks!

☕️ Buy me a Coffee (€3)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.

content-visiblity: auto; vs. jumpy scrollbars, a solution

As warned in content-visibility: the new CSS property that boosts your rendering performance you need to be careful with applying content-visibility: auto; on each and every element as the scrollbar might get jumpy.

This is because elements will be rendered as they scroll into the viewport and will be hidden as they scroll out of the viewport, thereby affecting the height of the rendered page, and thus also affecting the scrollbar.

ℹ️ Apart from a jumpy scrollbar it can also negatively affect accessibility when you include headings and landmark elements inside of regions styled with content-visibility: auto;. See Content-visibility and Accessible Semantics for details.

Now, thanks to infinite scroll we are — or at least I am — kind of used to the thumb part of the scrollbar shrinking and jumping back up a bit on the scrollbar track as you scroll down. What we’re not used to is the thumb part jump forwards on the scroll track as you scroll down. This is because elements that slide out of the viewport will no long be rendered — as that’s what content-visibility: auto; does — and the scrollbar is (by default) only calculated against the rendered elements.


Elements can become non-rendered elements as they scroll out of the viewport,
thanks to content-visibility: auto; doing its thing.

To cater for this jumpy behavior you should use contain-intrinsic-size so space for an element is reserved when it’s not being rendered. However, it is not always possible to know a box its dimensions in advance. Looking for a way to automatically reserve space for previously rendered elements, Alex Russel created a little script for it.

One challenge with naive application of content-visibility, though, is the way that it removes elements from the rendered tree once they leave the viewport — particularly as you scroll downward. If the scroll position depends on elements above the currently viewable content “accordion scrollbars” can dance gleefully as content-visibility: auto does its thing.

In a first version of the script he applied content-visibility: visible on each element from the moment it had appeared on screen. To detect this an IntersectionObserver is used. While this does prevent the scrollbar thumb from jumping forwards as you scroll down, it will make the page slow again as that content remains rendered (even though it’s off-screen).

A second version of the script takes a different approach and calculates the contain-intrinsic-size to apply based on the element’s dimensions. That way elements that passed by once now have a correct contain-intrinsic-size set, and can safely be hidden again as content-visibility: auto does its job.

let spaced = new WeakMap();
let reserveSpace = (el, rect) => {
    let old = spaced.get(el);
    // Set intrinsic size to prevent jumping.
    if (!old || rectNotEQ(old, rect)) {
        spaced.set(el, rect);
        el.attributeStyleMap.set(
        "contain-intrinsic-size",
        `${rect.width}px ${rect.height}px`
        );
    }
};

Additionally he also added a ResizeObserver to cater for resize events.

Resize-Resilient `content-visiblity` Fixes →

🤔 Clever script indeed, yet I cannot help but think: this should be possible without the needs for this extra script. What if a value like contain-intrinsic-size: auto; would be allowed, and do exactly as the script Alex built does?

UPDATE 2020.12.21: I’ve created an issue on GitHub that proposes contain-intrinsic-size: auto;. Let’s see where this goes …

UPDATE 2021.01.13: Looks like support for contain-intrinsic-size: auto; will land in the spec!

Deep Dive into Page Lifecycle API

As the name suggests, the Page Lifecycle API exposes the web page lifecycle hooks to JavaScript. However, it isn’t an entirely new concept. Page Visibility API existed for some time, revealing some of the page visibility events to JavaScript.
However, if you happen to choose between these two, it’s worth mentioning some of the limitations of the Page Visibility API.

  • It only provides visible and hidden states of a web page.
  • It can not capture pages discarded by the operating system (Android, IOS, and the latest Windows systems can terminated background processes to preserve system resources).

Let’s take a look at the page lifecycle states exposed by the Page Lifecycle API.

To implement the Page Lifecycle API and help overcome browser inconsistencies, there’s PageLifecycle.js that will come in handy:

import lifecycle from '/path/to/page-lifecycle.mjs';

lifecycle.addEventListener('statechange', function(event) {
  console.log(event.oldState, event.newState);
});

Deep Dive into Page Lifecycle API →
The Page Lifecycle API →
PageLifecycle.js →