Browser vendors and web performance experts have been saying for the better part
of the last decade that localStorage is
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 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
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, one of those libraries in your has a file-size cost and
can eat into your performance

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, soon there may be. Chrome is
with a new feature known as built-in
, and the
first one we’re planning to ship is an asynchronous key/value storage
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
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 — each will have its own specification that requires a design
and positive signs of
support from both web developers and other browser vendors before it can ship.
(In Chrome, built-in modules will follow the same launch
we use to implement
and ship all new APIs.)

Unlike traditional web APIs, built-in modules are not exposed on the global
scope — they’re only available via

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
, you could import the KV Storage module with the following code
(see below for how to use a KV Storage polyfill in unsupported

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

The KV Storage module

The KV Storage module is similar in its simplicity to the localStorage
, but
its API shape is actually closer to a
JavaScript Map.
Instead of getItem(),
and removeItem(),
it has get(),
and delete().
It also has other map-like methods not available to localStorage, like
values(), and
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

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', async () => {
    const newPreferences = Object.assign({}, oldPreferences, {
      // Updated preferences go here...

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


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 a
built-in module before we can use it in our code?
Thankfully, the answer is no!

You can actually use built-in modules as soon as even one browser supports them
thanks to the help of another feature we’re
with called import maps.

Import maps

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

This is powerful because it gives you a way to (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

<!-- The import map is inlined into your page -->
<script type="importmap">
  "imports": {
    "/path/to/kv-storage-polyfill.mjs": [

<!-- Then any module scripts with import statements use the above map -->
<script type="module">
  import {storage} from '/path/to/kv-storage-polyfill.mjs';

  // Use `storage` ...

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

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
, i.e.
<script type="module">.

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

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

KV Storage demo

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

  • 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
    (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 <script nomodule>).

The demo is hosted on Glitch, so you can view its
I also have a detailed explanation of the implementation in the
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

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  - kv storage devtools source - the Web&#8217;s First Built-in Module  |  Web  |  Google Developers

Please give us feedback

This introduction should have given you a taste of what may be 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 to see if it meets all your needs. And if you sign up for the KV
Storage origin
, you can
actually deploy these features today. All your users should benefit from better
storage performance, and Chrome 74+ users won’t have to pay any extra download

Source link


Please enter your comment!
Please enter your name here