KV Storage, the Web’s First Built-in Module  |  Web  |  Google Developers

Browser vendors and web performance experts have been saying for the better part
of the last decade that localStorage is
slow
,
and web developers should stop using it.

To be fair, the people saying this are not wrong. localStorage is a
synchronous API that blocks the main thread, and any time you access it you
potentially prevent your page from being interactive.

The problem is the localStorage API is just so temptingly simple, and the only
asynchronous alternative to localStorage is
IndexedDB,
which (let’s face it) is not known for its ease of use or welcoming API.

So developers are left with a choice between something hard to use and something
bad for performance. And while there are libraries that offer the simplicity
of the localStorage API while actually using asynchronous storage APIs under
the hood, including one of those libraries in your app has a file-size cost and
can eat into your performance
budget
.

But what if it were possible to get the performance of an asynchronous storage
API with the simplicity of the localStorage API, without having to pay the
file size cost?

Well, now there is. Chrome is experimenting with a new feature called built-in
modules
, and the
first one we’re planning to ship is an asynchronous key/value storage module
called KV Storage

But before I get into the details of the KV Storage module, let me explain
what I mean by built-in modules.

What are built-in modules?

Built-in modules
are just like regular JavaScript
modules,
except that they don’t have to be downloaded because they ship with the browser.

Like traditional web APIs, built-in modules must go through a standardization
process and have well-defined specifications, but unlike traditional web APIs,
they’re not exposed on the global scope—they’re only available via
imports.

Not exposing built-in modules globally has a lot of advantages: they won’t add
any overhead to starting up a new JavaScript runtime context (e.g. a new tab,
worker, or service worker), and they won’t consume any memory or CPU unless
they’re actually imported. Furthermore, they don’t run the risk of naming
collisions with other variables defined in your code.

To import a built-in module you use the prefix std: followed by the built-in
module’s identifier. For example, in supported
browsers
, you could import the KV Storage module with the following code
(see below for how to use a KV Storage polyfill in unsupported
browsers
):

import {storage, StorageArea} from 'std:kv-storage';

The KV Storage module

The KV Storage module is similar in its simplicity to the localStorage
API
, but
its API shape is actually closer to a
JavaScript Map.
Instead of getItem(),
setItem(),
and removeItem(),
it has get(),
set(),
and delete().
It also has other map-like methods not available to localStorage, like
keys(),
values(), and
entries(),
and like Map, its keys do not have to be strings. They can be any
structured-serializable type.

Unlike Map, all KV Storage methods return either
promises or
async iterators (since the
main point of this module is it’s not synchronous, in contrast to
localStorage). To see the full API in detail, you can refer to the
specification.

As you may have noticed from the code example above, the KV Storage module has
two named exports: storage and StorageArea.

storage is an instance of the StorageArea class with the name 'default',
and it’s what developers will use most often in their application code. The
StorageArea class is provided for cases where additional isolation is needed
(e.g. a third-party library that stores data and wants to avoid conflicts with
data stored via the default storage instance). StorageArea data is stored in
an IndexedDB database with the name kv-storage:${name}, where name is the name
of the StorageArea instance.

Here’s an example of how to use the KV Storage module in your code:

import {storage} from 'std:kv-storage';

const main = async () => {
  const oldPreferences = await storage.get('preferences');

  document.querySelector('form').addEventListener('submit', () => {
    const newPreferences = Object.assign({}, oldPreferences, {
      // Updated preferences go here...
    });

    await storage.set('preferences', newPreferences);
  });
};

main();

What if a browser doesn’t support a built-in module?

If you’re familiar with using native JavaScript modules in browsers, you
probably know that (at least up until now) importing anything other than a URL
will generate an error. And std:kv-storage is not a valid URL.

So that raises the question: do we have to wait until all browsers support
built-in module before we can use it in our code?

Thankfully, the answer is no! You can actually use built-in modules in your
code today, with the help of another new feature called
import maps.

Import maps

Import maps are essentially a mechanism
by which developers can alias import identifiers to one or more alternate
identifiers.

This is powerful because it gives you a way to change (at runtime) how a
browser resolves a particular import identifier across your entire application.

In the case of built-in modules, this allows you to reference a polyfill of the
module in your application code, but a browser that supports the built-in module
can load that version instead!

Here’s how you would declare an import map to make this work with the KV Storage
module:


The key point in the above code is the URL /path/to/kv-storage-polyfill.mjs
is being mapped to two different resources: std:kv-storage and then the
original URL again, /path/to/kv-storage-polyfill.mjs.

So when the browser encounters an import statement referencing that URL
(/path/to/kv-storage-polyfill.mjs), it first tries to load std:kv-storage,
and if it can’t then it falls back to loading
/path/to/kv-storage-polyfill.mjs.

Again, the magic here is that the browser doesn’t need to support import maps
or built-in modules for this technique to work since the URL being passed to
the import statement is the URL for the polyfill. The polyfill is not actually a
fallback, it’s the default. The built-in module is a progressive enhancement!

What about browsers that don’t support modules at all?

In order to use import maps to conditionally load built-in modules, you have to
actually use import statements, which also means you have to use module
scripts
, i.e.
.

Currently, more than 80% of browsers support
modules
, and for browsers that don't,
you can use the module/nomodule
technique

to serve a legacy bundle to older browsers. Note that when generating your
nomodule build, you'll need to include all polyfills because you know for sure
that browsers that don't support modules will definitely not support built-in
modules.

KV Storage demo

To illustrate that it's possible to use built-in modules today while still
supporting older browsers, I've put together a
demo
that incorporates all the techniques described above:

  • Browsers that support modules, import maps, and the built-in module do not
    load any unneeded code.
  • Browsers that support modules and import maps but do not support the built-in
    module load the KV Storage
    polyfill
    (via the
    browser's module loader).
  • Browsers that support modules but do not support import maps also load the
    KV Storage polyfill (via the browser's module loader)
  • Browsers that do not support modules at all get the KV Storage polyfill in
    their legacy bundle (loaded via ).

The demo is hosted on Glitch, so you can view its
source
.
I also have a detailed explanation of the implementation in the
README.
Feel free to take a look if you're curious to see how it's built.

In order to actually see the native built-in module in action, you have to load
the demo in Chrome 74 (currently Chrome Dev or Canary) with the experimental web
platform features flag turned on
(chrome://flags/#enable-experimental-web-platform-features).

You can verify that the built-in module is being loaded because you won't see
the polyfill script in the source panel in DevTools; instead you'll see the
built-in module version (fun fact: you can actually inspect the module's source
code or even put breakpoints in it!):


The KV Storage module source in Chrome DevTools

Please give us feedback

This introduction should have given you a taste of what's possible with built-in
modules. And hopefully you're excited! We'd really love for developers to try
out the KV Storage module (as well as all the new features discussed here) and
give us feedback.

Here are the GitHub links where you can give us feedback for each of the
features mentioned in this article:

If your site currently uses localStorage, you should try switching to the KV
Storage API, and if you sign up for the KV Storage origin
trial
, you can
actually deploy your changes today! All your users should benefit from better
performance, and Chrome 74+ users won't have to pay any extra download cost.

Read More

Advertisements

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.