Interesting Webpack Plugins

I still keep looking for new items in the Webpack space that will make my code work better or will give me more information about it. I found two that are of particular interest to me.

Size plugin

Webpack’s size plugin Prints the gzipped sizes of your Webpack assets and the changes since the last build. This may not sound like much but it’s an important reminder during development. If your bundles grow every time you build your project it may end up hurting your performance budget and download speeds.

Using the plugin

First, install it using NPM

npm i -D size-plugin

Next, require the plugin at the top of your configuration file.

const SizePlugin = require('size-plugin');

Finally, add it to the plugins section of your configuration.

module.exports = {
  plugins: [
    new SizePlugin()
  ]
}

There is no additional configuration for this plugin.

Critters and CSS inlining

Critters from the Chrome team inlines your CSS into the HTML document, reducing the number of trips between client and server and improving your page speed and time to interactive. The plugin works differently from other options because it doesn’t use a headless browser to render content. Critters inlines all CSS rules used by your document, rather than only those needed for above-the-fold content.

npm i -D critters-webpack-plugin

Then, import Critters into your Webpack configuration and add it to your list of plugins:

// webpack.config.js
const Critters = require('critters-webpack-plugin');

module.exports = {
  plugins: [
    new Critters({
      // Outputs: 
      // <link rel="preload" onload="this.rel='stylesheet'">
      preload: 'swap',

      // Don't inline critical font-face rules,
      // but preload the font URLs:
      preloadFonts: true
    })
  ]
}

See the plugin’s README for more details and configuration parameters.

If you want to inline only above the fold content you may want to look at these alternatives to Critters.

Notes on upgrading to Babel 7

After a long time, Babel 7 has been released. If you use Babel at all, it’s important to do the following things to make sure you’re good to go:

  • Make sure that the project’s package.json file points to the right version of Babel
  • If you use yearly presets (ES2015 / ES2017) or the latest preset change to using preset-env
    • The yearly presets have been deprecated and will be removed in future versions of Babel
    • Preset-env produces a smaller transpiled file by only transpiling what’s needed for your target browsers
  • Note that all packages for version 7 have been scoped. This will require you to rename your Babel packages
    • babel-core is now @babel/core
    • babel-cli changed to @babel/cli
    • Safe bet is to replace babel- with @babel/

To make sure that you didn’t miss anything, you can run babel-upgrade which will automate some of the tasks we discussed… it helps to know what the automated tool does before the tool does it and, potentially, break things in unexpected ways.

You have two way to install babel-upgrade

  • Install globally and run
npm install babel-upgrade -g
babel-upgrade --write
  • Install it as dev-dependency and run it with npx (NPM 5.2 and later)
npm i -D babel-upgrade
npx babel-upgrade --write

See babel-upgrade’s README for more detailed instructions.

TODO with Indexed DB

IndexedDB (currently version 2) provides client-sided database storage for web applications. From the specification:

This document defines APIs for a database of records holding simple values and hierarchical objects. Each record consists of a key and some value. Moreover, the database maintains indexes over records it stores. An application developer directly uses an API to locate records either by their key or by using an index. A query language can be layered on this API. An indexed database can be implemented using a persistent B-tree data structure.

I’ve always found it hard to reason through IndexedDB, when it would be a good case to use it and whether it would be used on its own or in addition to other APIs.

This time I will try using raw IDB to create a TODO list browser-side. The idea is that this app will not use external dependencies, they will all be hosted on the same server as the application.

Project Structure

The idea of the Todo list is to create a local copy of a Todo database and let the user work with it in the local computer without having to be online to interact with it.

This is not meant for production. The very fact that it’ll work on an individual computer means that we need to be at that computer to make changes to the database.

But it’s an interesting exercise to learn about IDB, see how it works and see what the shortcomings (other than locality) may be.

Initializing the database

The first thing I do is to create a feature detection function that I will use shortly to make sure the browser supports IndexedDB before moving forward.

function idbOK() {
  return 'indexedDB' in window;)
}

We check if the browser supports IndexedDB and if it doesn’t then we bail early and log it to console. I might change the idbOK function to do the check and the bailout there.

// No support? Go in the corner and pout.
if (!idbOK()) {
  console.log('Indexed DB not supported');
}

Now we can start working on creating the database and setting up the store and the indices that we want to use in the database.

Because the database doesn’t exist the upgrade is needed and it’s only during the onupgradeneeded event that you can make changes to the databbase like setting it up for the first time.

We first check if there is a todos object store, if there isn’t one then we create it.

const todosdb = indexedDB.open('todosdb', 1);

todosdb.onupgradeneeded = function(e) {
  const db = e.target.result;
  console.log('running onupgradeneeded');

  if (!db.objectStoreNames.contains('todos')) {
    console.log('making a new object store');
    let store = db.createObjectStore('todos', {
        keyPath: 'id',
        autoIncrement: true,
    });

We set up indices for different fields in the database. These indices will help us when querying information later on.

    let titleIndex = store.createIndex('by_title', 'title', {'unique': false});
    let startIndex = store.createIndex('by_start', 'startDate');
    let completeIndex = store.createIndex('by_completed', 'complete');
  }
};

All functions related to IDB trigger success (onsuccess) and error (onerror) events.

todosdb.onsuccess = function(e) {
  console.log('running onsuccess');
  db = e.target.result;
  // console.dir(db.objectStoreNames);
};

todosdb.onerror = function(e) {
  console.log('onerror!');
  console.error(e);
};

I’ve moved this constant and event outside of functions to make sure that it works when calling a function.

const addTodoButton = document.querySelector('#addTodo');
addTodoButton.addEventListener('click', addTodo);

Adding Todos

Now that we’ve created the database we can add new todos.

The first part is to capture the values from the form we have in the web page we use to access the IDB content.

function addTodo() {
  const title = document.getElementById('title').value;
  const description = document.getElementById('description').value;
  const dateStarted = document.getElementById('dateStarted').value;
  const dateEnded = document.getElementById('dateEnded').value;
  const completed = document.getElementById('dateStarted').value;

We then create a transaction and store objects. This tells IDB what objectStore what we want to work with and that we want to work with a transaction. A transaction is an atomic unit of work, either it all succeeds and gets pushed to IDB or things fail and nothing gets pushed.

  // Get a transaction
  const transaction = db.transaction(['todos'], 'readwrite');
  // Ask for the objectStore
  const store = transaction.objectStore('todos');

Next, we define a todo object matching the elements we captured from the form with the names that we want to use in the database.

  // Define a todo
  const todo = {
    title: title,
    description: description,
    dateStarted: dateStarted,
    dateEnded: dateEnded,
    completed: completed,
  };

Lastly, we perform the addition using store.add and give success and error handlers in case we waant to do something special.

  // Perform the add
  let request = store.add(todo);

  request.onerror = function(e) {
    console.log('Error', e.target.error.name);
    // some type of error handler
  };

  request.onsuccess = function(e) {
    console.log('Woot! Did it');
  };
}

Show all records

Showing all the records combines retrieving them, creating a template for each instance and attaching the template to the page.

We first open the database and, on success, we create a transaction and select the object store and open a cursor to the object store.

function showAll() {
  let dbrequest = window.indexedDB.open('todosdb');

  dbrequest.onsuccess = function(e) {
    let transaction = db.transaction(['todos'], 'readonly');
    let request = transaction.objectStore('todos').openCursor();

We iterate over the content of the cursor. If it’s empty then we return since we don’t have any data to display.

    request.onsuccess = function(e) {
      let cursor = request.result || e.result;

      if (!cursor) {
        return;
      }

If the cursor is not empty we use it to iterate over the items in the database, populate the template defined in todoContent and attach it to the existing document.

Some of these values take advantage of the fact that we can use Javascript values inside the parameters; I’ve used ternary if operators to make sure that the dates values don’t return undefined if there is nothing there but output a blank space instead.

      let container = document.getElementById('todos-listing');

      // Define the content for each individual entry
      let todoContent = `<h2>${cursor.value.title}</h2>
      <div class="todo-desc">
        ${cursor.value.description ? cursor.value.description : ''}
      </div>
      Date Started:
        ${cursor.value.dateStarted ? cursor.value.dateStarted : ''}</p>
      <p>Date Finished:
        ${cursor.value.dateCompleted ? cursor.value.dateCompleted x: ''}</p>
      <p>Completed: ${cursor.value.completed}`;

      let element = document.createElement('div');
      element.innerHTML=todoContent;
      container.appendChild(element);
      cursor.continue();
    };

    request.onerror = function(e) {
      console.log('Error', e.target.error.name);
      // some type of error handler
    };
  };
}

This is the basic functionality corresponding to Create and Display in a CRUD application. Update and is more complicated as it requires creating a form where the user can edit the content of the todo and delete requires some way for the user to confirm that they want to delete the content.

Those two methods along with a way to update the database in place will come in another series of posts

CSS Houdini Properties & Values

If you haven’t read my prior articles about CSS Houdini, I’ll point you to the video below by Tab Atkins where he talks about Houdini as a (far) future combination of CSS and Javascript (he should know, he edits the Houdini specs and a whole bunch of other CSS specs for W3C).

This is not CSS in JS but a way to use JS to hook into the browser’s rendering lifecycle to add your own bits and pieces. With this we don’t have to wait for browser vendors to implement a feature, we can create our version that will fail gracefully if the browser doesn’t understand Houdini, calc or CSS variables.

Properties and values are CSS Variables in steroids. The spec is designed to address the shortcomings of variables as first implemented:

  • Custom properties allow for validation because we know what kind of value they have without having to parse and check for errors on every use
    • It’s not a string but a proper CSS Typed OM value
  • We can assign default/initial values
    • No manual error handling every time the property is used
    • Provides a sensible default when there is an error
  • We can decide if the property inherits down the cascade or not
    • Variables as currently defined always inherit
  • Allows for animating and transitioning of the custom properties
    • Values are known Typed OM values

Now that we know why these custom properties are important, let’s look at how they work.

Registering Properties

The first step in using custom properties is to define them in JavaScript.

CSS.registerProperty (case sensitive) does this by taking an object with the values we want to use for the property.

if ('registerProperty' in CSS) {
  CSS.registerProperty({
    name: '--my-custom-prop',
    syntax: '<color>',
    inherits: true,
    initialValue: 'black'
  });
}

There are 4 values that we need to pass to registerProperty:

The name is what we’ll use to reference the property. The two dashes at the beginning should be familiar from CSS variables and are required. This is how we’ll distinguish our custom variables and properties from what the CSS WG does and will do in the future.

Syntax indicates the possible syntaxes for the property. The following values are available in level 1 of the spec and matching corresponding units in CSS Values and Units Module Level 3

In addition, you can use the following modifiers or replacement tokens for the syntax:

  • * any value
  • | logical or (one or the other)
  • + one or more of the type specified
  • # one or more of the type specified separated by commas

You can create fairly complex syntax for your custom properties but until we become familiar with them, I advocate for the KISS (Keep It Simple Silly) principle.

Inherit tells the CSS parser if this custom rule should propagate down the cascade. Setting it to false gives us more power to style specific elements without being afraid to mess up elements further down the chain.

The final value is an initialValue. Use this to provide a sensible default for the property. We’ll analyze why this is important later.

That’s it… we now have a custom property.

Using custom properties

To demonstrate how to use Custom Properties we’ll reuse the --bg-color Javascript example and use it in several different elements.

CSS.registerProperty({
  name: '--bg-color',
  syntax: '<color>',
  inherits: false,
  initialValue: 'red'
});

The CSS will not be any different than if we used variables. But the things it does for free are much more interesting.

First we define common parameters to create 200px by 200px squares using div elements.

div {
  border: 1px solid black;
  height: 200px;
  width: 200px;
}

.smoosh1 and .smoosh2 set up colors other than the initial value and each has a different color to change on hover.

.smoosh1 {
  --bg-color: rebeccapurple;
  background: var(--bg-color);
  transition: --bg-color 0.3s linear;
  position: absolute;
  top: 50vh;
  left: 15vw;

  &:hover {
    --bg-color: orange;
  }
}

.smoosh2 {
  --bg-color: teal;
  background: var(--bg-color);
  transition: --bg-color 0.3s linear;
  position: absolute;
  top: 20em;
  left: 45em;

  &:hover {
    --bg-color: pink;
  }
}

.smoosh3 was set up with a wrong type of color (1 is not a valid CSS color). In normal CSS the rule would be ignored and there would be no background color. Because we added an initialValue to the property, it’ll take this value instead of giving an error.

.smoosh3 {
  --bg-color: 1;
  background: var(--bg-color);
  transition: --bg-color 0.3s linear;
  position: absolute;
  top: 5em;
  left: 35em;

  &:hover {
    --bg-color: lightgrey;
  }
}

You can see the full working demo in Codepen

Why is this important?

There are a couple of reasons that make custom properties particularly useful.

Validation

As we saw in the last section, custom properties allow for default initial values that give developers a way to avoid errors or unexpected behavior.

If we define the following custom property:

CSS.registerProperty({
  name: '--bg-color',
  syntax: '<color>',
  inherits: false,
  initialValue: 'red'
});

And mistakenly use the property with the following declaration below:

.dark-theme-section {
  --bg-color: 1;
}

We would expect to get an error or, in CSS, to ignore the rule altogether. But, as the demo showed, the CSS will use the initial value.

We can also test what the value is using Javascript.

const section = document.querySelector('.smoosh3');
const styles = getComputedStyle(section);
const themeColor = styles.getPropertyValue('--bg-color');

console.log(themeColor); // "red"

Animations

CSS variables don’t provide good support for animation because the browser doesn’t know what type of value it has.

Because we assign a value to the property now the parser knows what to do with the property when we ask to animate it.

The Codepen Demo shows how the animation works.

We could do further modularization in the animation itself by creating properties for the animation parameters or the positioning of each individual content… but, for now, baby steps are OK.

Links and Resources

Design for the lower end of the spectrum: Part 3

Ideas for client-side

So how do we address some problems of the web? There are aspects that go beyond PWAs and contribute to the bloat of the web and, I would imagine, the large size of apps.

For each of the problems listed in the Developer Facing Problems there is a solution that will work whether we use a build system or not. I avoided build system-based solutions as I don’t want to give the impression that a build system is required, just the extra effort of running the apps either locally or in the cloud.

Image sizes and download times

A solution for image sizes passes through responsive images (either picture or srcset) and image compression with tools like imagemin.

We don’t need to send the same high DPI retina image to all browser; we can choose what type of image to send and browsers are smart enough to know which one to use for each browser and screen size (if we set them up correctly).

This is what a responsive image looks like:

<img src="small.jpg"
     srcset=" large.jpg 1024w,
              medium.jpg 640w,
              small.jpg 320w"
     alt="A rad wolf">

Both srcset, nd sizes (a related specification) are part of the HTML specification and is supported in all major browsers so there should be no problem in using it across devices. If a browser doesn’t understand the srcset attribute it will just use src like any browser did until we got responsive images… they will get something, not just the optimized version of the image we wanted to give them.

Cloudinary has made available a Responsive Image Generator that allows you to create responsive images and their corresponding CSS styles in the cloud and then download it to use it on your project during development.

Javascript issues

Problems with Javascript size and download time can be solved by bundling content and using tools like Webpack or Rollup to create and bundle, split into smaller pieces, tree shake the result so it’ll only use what is actually needed, and load only assets for a specific page or route (lazy loading).

If we send large bundles down to the users, it’ll take a while to parse the code we send. This will not hurt our high-end devices but it will be much longer for mid and lower-end phones we’re likely to see in rural areas or for people who can’t afford the latest and greatest.

Parse times for a 1MB bundle of JavaScript across desktop & mobile devices of differing classes. From JavaScript Start-up Performance by Addy Osmani.

Mid and Lower level devices in other countries may be different and have different specs, usually less powerful than what we normally see.

Meeting users where they are: using their language

We can also provide content in the users’ target language and take advantage of technologies that have been on the web for a while. Web fonts used responsibly and with proper fallbacks, give you a device independent way to give users content in the language they use every day without major changes to the structure of your CSS.

First declare the language in the root element of your page, usually html:

<!-- provides a default language -->
<html lang="en-US" dir="ltr">

and later in the document, we add content in traditional Chinese, like this:

<!-- Later in the document -->
<p lang="zh-Hans">朋消然,地正成信青約屋角來邊還部下賽清受光可,綠不女外!
權來星加智有花個費主母:機爭理陸行吃洋然答辦清大旅動節活性眼還起就情相蘭要運見……
都靜內率機足轉</p>

We can use the following CSS to indicate what fonts we want to use for each case. We can go further and indicate multiple language fonts for different versions or dialects of the language. In this case, we’ve indicated different fonts for traditional and simplified Chinese characters.

body {
  font-family: "Times New Roman", serif;
}

:lang(zh-Hant) {
  font-family: Kai, KaiTi, serif;
}

:lang(zh-Hans) {
  font-family: DFKai-SB, BiauKai, serif;
}

You can also use attribute selectors that exactly match
the value of a language attribute ([lang = "..."]) or one that only matches the beginning of the value of a language attribute (lang |= "..."]).

<p lang="en-uk">Content written in British English</p>
<p lang="en-au">G'Day Mate</a>
<p lang="es">Buenos días</a>
<p lang="es-cl">Hola</a>
*[lang="en-uk"] {
  color: white;
  background: black
}

*[lang="es-cl"] {
  color: white;
  background: rebeccapurple;
}

*[lang|="es"] {
  text-align: end;
}

Finally, you can always create classes for the languages that you want to use and add them to the elements using that language.

.en-uk {
  color: white;
  background: black
}

.es-cl {
  color: white;
  background: rebeccapurple;
}

.es {
  text-align: end;
}

There’s another aspect of working with non-latin, non-western languages: writing direction.

If you’ve seen Hebrew or Arabic text you’ll notice that is written in the opposite direction than English: right to left, top to bottom.

There are other languages that write top to bottom, and either left to right or right to left.

There is CSS that will make vertical languages is not hard but it’s not something that we’re used to seeing in most western content.

<div class="japanese-vertical">
  <p>こいけそ個課チクテャあえむ派たケトリネちたつよめ'
  ねかへゆと都ヌミニセメ無かカルウャ「ウつ巣根都阿瀬個そ」
  るるうほさフソメふ毛さ名ゃみゃ日シセウヒニウっれ根離名屋エコネヌ。
  こっつろてとねつぬなつさそやほっツカトル夜知め鵜舳屋遊夜鵜区せすの
  ぬ雲るきな屋素夜差っメケセア都毛阿模模派知ンカシけか瀬
  列野毛よいーソコ区屋ろぬゅき</p>
</div>
.japanese-vertical {
  writing-mode: vertical-rl;
}

You can see an example combining Latin and Japanese text in this demo page

Depending on your target audiences and the languages they use, this may be all you need to do. But there are other languages that use different directions and vertical alignments.

The first step, always, is to localize your UI but the localizing the content is not too far away.

Conclusion: Where do we go from here?

Perhaps the hardest part is to move away from a one size fits all model and ask ourselves the question: “Does the web work well for what I’m trying to do?”

Think about it… the web has no app store and no install friction. All that your users need is visit the URL in a browser that supports the PWA technologies, and voila, they are using a PWA and using the technologies without having to do anything else but use their web browser. When they have engaged with the app enough times they can install it to the device (desktop or mobile) without having to follow the app store procedure and update the app with a large download every time the developer makes changes.

Once they are there the APIs that make up a PWA give you features that look like native but work on the browser and make the experience better for all your users, not just those on mobile connections.

And you’re not required to use all the APIs that make a PWA. For example, in my Layout experiments I chose to implement a service worker to make sure that users get a consistent experience after their first visit without using a manifest or any other PWA API or technology.

I’m not saying not to implement native applications but to evaluate your target audience and requirements before deciding if web or native is better for your application and your objectives.

And, whether you decide the web is best for your project or not, you owe it to your users to optimize your content as much as possible.

Links, References, Tools