A look at web workers

Web Workers (workers for short) are a way to create multi-threaded applications in Javascript and get around the language’s single-threaded application model.

According to Surma:

Web Workers, also called “Dedicated Workers”, are JavaScript’s take on threads. JavaScript engines have been built with the assumption that there is a single thread, and consequently, there is no concurrent access JavaScript object memory, which absolves the need for any synchronization mechanism. If regular threads with their shared memory model got added to JavaScript it would be disastrous, to say the least. Instead, we have been given Web Workers, which are an entire JavaScript scope running on a separate thread, without any shared memory or shared values. To make these completely separated and isolated JavaScript scopes work together you have postMessage(), which allows you to trigger a message event in the other JavaScript scope together with the copy of a value you provide (copied using the structured clone algorithm).

So, the idea is that we get one way to run computationally expensive tasks off the main execution thread, improving the application’s performance and responsiveness by making the main thread do less work.

In a script/module hosted on the main page, in this example using a script tag, we create a worker and provide functions for it to communicate with the main thread.

<script>
const supportsWorker = 'Worker' in window;

if (!supportsWorker) {
    console.log('Web Workers not supported');
  // Implement a fallback strategy
} else {
    const worker = new Worker('echoWorker.js');
    const result = document.querySelector('#result');

    // post message to worker
    worker.postMessage('<h1>Message sent to the worker</h1>');

    worker.onmessage = event => {
        result.innerHTML = event.data;
    }
}
</script>

echoWorker will send back whatever message it received from the main thread. It will do this by reacting to messages using the onmessage event and posting the content of the event back too the main thread using postMessage.

onmessage = (e) => {
  console.log('Echoing message we got from main script');
  postMessage(e.data);
};

The combination offers interesting possibilities an example that I found interesting is converting Markdown to HTML using a worker.

The code in the main page is similar to the echo example. The main difference is we’re sending a Markdown file in the postMessage to the worker and taking the result of processing that file as the result.

<div id='result'></div>
<script>
  const supportsWorker = 'Worker' in window;

  if (!supportsWorker) {
    // If the browser doesn't support workers bail
    console.log('Web Workers not supported');
  } else {
    // Create the worker
    const worker = new Worker('./markdownWorker.js');
    const result = document.querySelector('#result');

    // post message to worker with the file to use
    worker.postMessage('./content2.md');

    worker.onmessage = event => {
      result.innerHTML = event.data;
    }
  }
</script>

I’ve broken the markdownWorker.js file into multiple sections to make the explanation easier.

The worker uses third-party resources via importScripts. In this case, we load the Remarkable Markdown Parser and the Highlight.js syntax highlighter.

importScripts(
  'https://cdn.jsdelivr.net/npm/[email protected]/dist/remarkable.js',
  'https://cdn.jsdelivr.net/npm/[email protected]/highlight.pack.min.js');

Everything that we want to react to happens inside the onmessage event.

We first create and configure a Remarkable instance. The highlight configuration uses Highlight.js to insert the highlighting classes and attributes that will display the highlighted code.

self.onmessage = (event) => {
  const md = new Remarkable('full', {
    html: true,
    linkify: true,
    typographer: true,
    // Set highlight options for highlight.js
    highlight: function(str, lang) {
      if (lang && hljs.getLanguage(lang)) {
        try {
          return hljs.highlight(lang, str).value;
        } catch (err) {}
      }

      try {
        return hljs.highlightAuto(str).value;
      } catch (err) {}

      return '';
    },
  });

The final element of the worker is a fetch promise chain. The chain does the following:

  1. It fetches the file indicated by event.data
  2. Converts the downloaded data into text
  3. Renders the text file into Markdown using the Remarkable instance we defined earlier
  4. Sends the data back to the main script using postMessage

If there are any errors the catch block triggers and we report it to the console.

  fetch(event.data)
  .then((response) => {
    // Convert the response to text
    return response.text();
  })
  .then((content) => {
    let transformedSource = md.render(content);
    postMessage(transformedSource);
  })
  .catch((err) => {
    console.log('There\'s been a problem completing your request: ', err);
  });
};

Some things to consider

While workers have access to most APIs and features available to the main thread they are not fully equivalent.

The biggest issue, for me, is that you can’t directly manipulate the DOM from inside Workers (since the global element is DedicatedWorkerGlobalScope and not the window global scope)

See Functions and classes available to workers for a list of functions and APIs that work inside a worker.

Links and resources

Web Bundles as content packages

A web bundle, formally known as a Bundled HTTP Exchanges is a set of resources packaged together for distribution with a .wbn extension and application/webbundle mime type.

The bundle contains multiple resources that make up your website. These range from HTML, CSS and Javascript to svg, audio and other elements necessary to make your site or applicatioon work.

Bundles don’t depend on users accessing the site or app once before it will work offlline. The bundle contains everything necessary for the site or application to run offline.

Bundles retain the interactivity of the original site because it allows Javascript to run.

Having a single package for an application opens new avenues for distribution. You could share your content from USB sticks or other promotional media… The team at the Chrome Developer Summit had a USB stick hidden inside a Chrome Offlline Dinsoaur shape.

Getting Started

Currently the easiest way to create unsigned web bundles is to use the go/bundle CLI. To install the CLI (and Go if necessary) follow the instructions in Building Web Bundles

Once you have the go/bundle installed build your content and run the following command; make sure you replace the -baseURL and primaryURL values with valid URLs that work for your site or application.

gen-bundle \
-dir docs \
-baseURL https://layout-experiments.firebaseapp.com/ \
-primaryURL https://layout-experiments.firebaseapp.com/ \
-headerOverride 'Access-Control-Allow-Origin: *' \
-o layout.wbn

The layout.wbn is the bundle ready to open.

Chrome version and flags

Web bundles will only work on Chrome 80, in Canary at the time this post is written. The feature is on an Origin Trial so you shouldn’t rely on it for production.

Open chrome://flags/#web-bundles and set the flag to enabled.

Now you can open the Bundle in Chrome.

Taking size into account…

One thing to consider is the size of the bundles. A bundle for Layout experiments is 21MB while a bundle for Google’s web.dev is around 212MB.

One solution to this is to use a combination of the Network Information API and offline events to only display download data when in WIFI connectivity.

In an ideal world, the code might look like this:

if ('connection' in navigator) {
  if ((navigator.connection.type === 'wifi') ||
     (navigator.connection.type === 'ethernet')) {
    // display dowonload bundle section
    console.log('Using wifi or Ethernet');
  } else {
    console.log('Not on Wifi or Ethernet');
  }
}

Unfortunately the reality is far from ideal. As currently implemented the API doesn’t reflect the actual connection but reports unknown for all connections. This defeats the purpose of using it.

Resources

display: flow-root, a clearfix replacement

display: flow-root

The post talks about elements of a W3C candidate recommendation, CSS Display Module Level 3. It’s possible but highly unlikely, that things will change before the recommendation is finalized.

Most developers (myself included) will be familiar with some values for the display property:

  • block
  • inline
  • inline-block
  • grid
  • flex

And we use these in most of our everyday design, dating to the “age of floats” design philosophy. To see the full list check out the MDN article on the display property.

Using flow-root. The end of clearfix?

Rachel Andrew’s The end of the clearfix hack? explains a new way to avoid the clear fix hack.

From Clearfix: A Lesson in Web Development Evolution

The clearfix, for those unaware, is a CSS hack that solves a persistent bug that occurs when two floated elements are stacked next to each other. When elements are aligned this way, the parent container ends up with a height of 0, and it can easily wreak havoc on a layout. All you might be trying to do is position a sidebar to the left of your main content block, but the result would be two elements that overlap and collapse on each other. To complicate things further, the bug is inconsistent across browsers. The clearfix was invented to solve all that.

The idea is that using the ::after pseudo element we add attributes that fill force the clearing of the floats and get us the layout we want.

The most basic clearfix looks like this:

.container::after {
  content: "";
  display: block;
  clear: both;
}

display: flow-root replaces the clearfix with a single attribute. it creates a new block formatting context for the element using flow layout formatting, fixing the layout without needing additional attributes.

Using flow-root the element itself now looks like this:

.container {
  display: flow-root;
}

We don’t need to use the ::after pseudo-element to generate a clearfix anymore.

To see what issues display: flow-root solves see this Codepen from Rachel Andrew.

Browser support

Browser support for display:flow-root is not quite there yet. Current (non-Chromium versions) of Edge and iOS Safari support the feature, although desktop Safari does.

Data on support for the flow-root feature across the major browsers from caniuse.com
Data on support for the flow-root feature across the major browsers from caniuse.com

Until support is normalized code defensively and either use @support to target browsers that support the feature and use the clearfix for browsers that don’t support either @supports or display: flow-root

Adding transparency to hex colors

One thing hidden somewhere in the CSS specifications is the fact that you can use four or eight digits to represent a hexadecimal color, three or six to represent the color and one or two to represent the alpha transparency. For clarity’s sake, the post will only cover the eight-digit version.

What is this?

If you’re familiar with the RGB/RGBA color space in CSS you will see two different types of color, one solid color and one with some level of transparency.

RGB colors use values from 0 to 255 or 1 to 100% to represent color and values from 0 to 1 to represent transparency.

The example below shows an example of RGB and RGBA colors and how they look in the browser.

Partially transparent: rgba(6,52,164, .45)

Fully Opaque: rgba(6,52,164, 1)

Hex and Hex + Transparency Colors

For most developers (myself among them), Hexadecimal (hex) colors are the first type of colors we’ve used with CSS.

It came as a surprise that you could expand the format to include alpha channel transparency.

The following examples show three of the syntaxes available for Hex colors:

  1. The traditional 6 digit syntax
  2. The “new” 8 color syntax using full opacity
  3. The “new” syntax using transparency

Fully Opaque:; #0634a4

Fully Opaque (with trasparency): #0634a4ff

Partially transparent: #0634a466

The hardest part of this is to figure out how transparency works in hexadecimal numbers.

Remember that RGBA colors work with transparency between 0 and 1, but working with values from 00 to FF is conceptually harder (at least for me). It’ll take a lot of trial and error to get the numbers right.

Browser support

Browser support is pretty good with Edge, the last outlier coming into the fold once the Chromium version of Edge goes into regular release.


Data on support for the css-rrggbbaa feature across the major browsers from caniuse.com

Difference between inline and inline-block

As I was working on a side project, the same one where I was using buttons I saw examples using display: inline-block used in the button styles.

Why should I use inline-block instead of inline styles? What difference does it make?

Difference between inline-block and inline

display: inline-block respect top and bottom margins & paddings. display: inline, doesn’t.

In the example below, we use both inline and inline-block elements in the same paragraph to illustrate the process.

<p>Cheese and wine ricotta danish fontina.
Brie cheesy grin paneer squirty cheese taleggio
cheesecake <span class="inline-block-box">goat
taleggio</span> <span class="inline-box">goat
taleggio</span>. Bavarian
bergkase emmental fromage cheesecake
cheese slices cheesy grin queso caerphilly.</p>

The styles for the span elements are identical except for the display attribute.

Again, the difference is that inline-block will honor dimensions and padding but inline will not.

span.inline-block-box {
  display: inline-block;
  width: 100px;
  height: 100px;
  padding: 1em;
  background-color: rebeccapurple;
  color: white;
}

span.inline-box {
  display: inline;
  width: 100px;
  height: 100px;
  padding: 1em;
  background-color: rebeccapurple;
  color: white;
}

You can see multiple examples using the styles described above in Codepen: https://codepen.io/caraya/pen/wvvpEQo

The first example uses inline-block for two span elements.

The second example uses inline for both span elements

The last example combines the two. It uses inline-block for the first span and inline for the second.

Why would we use inline-block?

There may be times when we want to style inline elements with attributes that will only work with inline-block elements.