KV 存储 - 网络的第一个内置模块

在过去的十年中,浏览器供应商和 Web 性能专家一直认为 localStorage 运行缓慢,Web 开发者应该停止使用它。

公平地说,说这件事并没有错。localStorage 是一个会阻塞主线程的同步 API,因此,您每次访问该线程时都可能会阻止页面进入可交互状态。

问题在于 localStorage API 非常简单,localStorage 的唯一异步替代方案是 IndexedDB,它(现实情况)因其易用性或友好的 API 而闻名。

因此,开发者必须在难以使用和对性能不利的方面做出选择。虽然有些库提供 localStorage API 简单易用,同时在后台实际使用异步存储 API 的库,但在应用中添加其中一个库会产生文件大小,并且会占用您的性能预算

但是,如果能够借助 localStorage API 简单易用而获得异步存储 API 的性能而不用为文件大小支付费用,该怎么办?

嗯,很快就可以实现。Chrome 正在实验一项称为内置模块的新功能,我们计划推出的第一个功能是名为 KV Storage 的异步键值对存储模块。

不过,在详细介绍 KV 存储模块之前,我先解释一下“内置模块”的含义。

什么是内置模块?

内置模块与常规的 JavaScript 模块一样,只不过无需下载,因为它们是浏览器自带的。

与传统 Web API 一样,内置模块必须通过标准化流程,每个模块都有自己的规范,需要进行设计审核,并且有网页开发者和其他浏览器供应商的积极支持迹象才能推出。(在 Chrome 中,内置模块将遵循我们用于实现和发布所有新 API 的同一启动流程。)

与传统 Web API 不同,内置模块不会在全局作用域上公开,只能通过 import 获取。

不全局公开内置模块有许多好处:它们不会对启动新的 JavaScript 运行时环境(例如新标签页、工作器或 Service Worker)增加任何开销,而且除非实际导入,否则它们不会消耗任何内存或 CPU。此外,它们也不会面临与代码中定义的其他变量发生命名冲突的风险。

如需导入内置模块,请使用前缀 std:,后跟内置模块的标识符。例如,在受支持的浏览器中,您可以使用以下代码导入 KV Storage 模块(请参阅下文,了解如何在不受支持的浏览器中使用 KV Storage polyfill):

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

KV 存储模块

KV 存储模块在简洁性方面与 localStorage API 类似,但其 API 形状实际上更接近 JavaScript Map。它使用的是 get()set()delete(),而不是 getItem()setItem()removeItem()。它还具有 localStorage 不可用的其他类似映射的方法,如 keys()values()entries();并且与 Map 一样,其键不必是字符串。它们可以是任何结构化可序列化类型

Map 不同,所有 KV 存储方法都会返回 promise异步迭代器(因为本模块的主要要点是,它不是同步的,与 localStorage 相反)。如需详细了解完整的 API,您可以参阅规范

您可能已经从上面的代码示例中注意到,KV 存储模块有一个默认导出 storage 和一个名为“export StorageArea”的默认导出内容。

storage 是名为 'default'StorageArea 类的一个实例,开发者会在其应用代码中最常使用它。在需要进一步隔离的情况(例如,用于存储数据并且希望避免与通过默认 storage 实例存储的数据发生冲突的第三方库)中提供了 StorageArea 类。StorageArea 数据存储在名为 kv-storage:${name} 的 IndexedDB 数据库中,其中 name 是 StorageArea 实例的名称。

以下示例展示了如何在代码中使用 KV 存储模块:

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);
  });
};

main();

如果浏览器不支持内置模块,该怎么办?

如果您熟悉如何在浏览器中使用原生 JavaScript 模块,那么或许会知道(至少到目前为止)导入除网址以外的任何其他内容都会引发错误。std:kv-storage 不是有效网址。

这就引出了一个问题:我们是否必须等到所有浏览器都支持内置模块,才能在代码中使用它?幸运的是,答案是否定的!

实际上,只要有一款浏览器支持内置模块,那么(我们正对另一项名为导入映射的功能)进行实验,您就可以使用这些模块了。

导入地图

导入映射本质上是一种机制,开发者可通过该机制将导入标识符的别名设置为一个或多个备用标识符。

此功能非常强大,因为它可以让您(在运行时)更改浏览器在整个应用中解析特定导入标识符的方式。

对于内置模块,这允许您在应用代码中引用该模块的 polyfill,但支持内置模块的浏览器可以改为加载该版本!

下面展示了如何声明导入映射,以使其适用于 KV 存储模块:

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

<!-- 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` ...
</script>

上述代码中的要点是,网址 /path/to/kv-storage-polyfill.mjs 被映射到两个不同的资源:std:kv-storage,然后再次映射到原始网址 /path/to/kv-storage-polyfill.mjs

因此,当浏览器遇到引用该网址的 import 语句 (/path/to/kv-storage-polyfill.mjs) 时,会先尝试加载 std:kv-storage,如果无法加载,则回退到加载 /path/to/kv-storage-polyfill.mjs

同样,此处的魔力是,由于传递到 import 语句的网址是 polyfill 的网址,因此浏览器无需支持导入映射或内置模块,即可让此方法生效。polyfill 实际上并不是回退,而是默认值。内置模块是一项渐进式增强功能!

那会如何处理根本不支持模块的浏览器呢?

为了使用导入映射有条件地加载内置模块,您必须实际使用 import 语句,这也意味着您必须使用模块脚本,即 <script type="module">

目前,超过 80% 的浏览器支持模块;如果浏览器不支持,您可以使用 module/nomodule 技术提供旧版软件包。请注意,生成 nomodule build 时,您需要包含所有 polyfill,因为您知道不支持模块的浏览器肯定不支持内置模块。

KV 存储演示

为了说明可以在仍然支持旧版浏览器的情况下使用内置模块,我制作了一个演示,该演示融合了上述所有技术,并且可以在当今所有浏览器中运行:

  • 支持模块、导入地图和内置模块的浏览器不会加载任何不需要的代码。
  • 如果浏览器支持模块并导入地图,但不支持内置模块,则会(通过浏览器的模块加载器)加载 KV Storage polyfill
  • 支持模块但不支持导入映射的浏览器也会加载 KV Storage polyfill(通过浏览器的模块加载器)。
  • 完全不支持模块的浏览器会在其旧版软件包(通过 <script nomodule> 加载)中使用 KV Storage polyfill。

此演示版托管在 Glitch 上,因此您可以查看其源代码。我还在 README 中提供了关于实现的详细说明。如果您想了解其构建方式,请随时看一看。

为了实际看到原生内置模块的实际效果,您必须在 Chrome 74 或更高版本中加载演示,并启用实验性 Web 平台功能标志 (chrome://flags/#enable-experimental-web-platform-features)。

您可以验证内置模块是否正在加载,因为您不会在开发者工具中的源代码面板中看到 polyfill 脚本;相反,您看到的是内置模块版本(有趣的事实:您可以实际检查模块的源代码,甚至在其中放置断点!):

Chrome DevTools 中的 KV 存储模块源代码

请向我们提供反馈

在本简介中,您应该已经了解了内置模块可以实现哪些功能。也希望你喜欢!我们非常期待开发者试用 KV 存储模块(以及此处讨论的所有新功能),并向我们提供反馈。

您可以通过以下 GitHub 链接向我们提供针对本文中提及的每个功能的反馈:

如果您的网站目前使用 localStorage,您应尝试切换到 KV Storage API,看看它是否满足您的所有需求。如果您注册参加 KV Storage 源试用,现在就可以部署这些功能。所有用户都能受益于更好的存储性能,而 Chrome 74 及更高版本的用户无需支付任何额外的下载费用。