前端开发工具构建工具插件系统【免费下载链接】wxt⚡ Next-gen Web Extension Framework项目地址https://gitcode.com/gh_mirrors/wx/wxt点击查看免费下载导读wxt-dev/storage在 WXT 项目中以wxt/storage模块内置是对浏览器扩展chrome.storage/browser.storage原生 API 的一层简化封装是 WXT 生态中负责数据持久化的核心模块。它为local、session、sync、managed四个存储区域提供统一、类型安全、且支持元数据、快照、批量操作与版本迁移的存储抽象。阅读本文后你将掌握如何安装与引入该模块、如何设计带类型的存储 Key、如何利用 Watcher 监听存储变化、如何使用defineItem定义带默认值/版本迁移的存储项以及如何用批量 API 与快照 API 构建高效可靠的扩展数据层。模块定位它解决什么问题浏览器原生的browser.storageAPI 功能完备但存在几个日常开发中的痛点Key 是裸字符串、没有类型约束数据与附加信息如版本号、修改时间需要手工约定存储位置跨脚本background、content、popup共享同一份存储时缺少统一的读写封装监听变化需要手工管理onChanged监听器。WXT Storage 正是为这些痛点而生的简化封装其核心能力包括统一命名空间所有 Key 必须使用area:key形式明确指定存储区域类型安全通过 TypeScript 泛型与存储项storage item抽象让读写操作的类型在编译期即被校验元数据为每个 Key 附加key $元数据对象内置版本号v追踪版本迁移defineItem支持声明式迁移函数在扩展更新时自动升级存量数据快照与批量操作支持整个存储区域快照、恢复以及按区域合并的批量读写监听基于onChanged的watch/unwatch支持按 Key 精确监听。在仓库中该包位于 packages/storage核心实现集中在 packages/storage/src/index.ts约 1000 行并配有覆盖各存储区域与各 API 行为的完整测试套件 packages/storage/src/tests/index.test.ts1600 行。它同时以 npm 包wxt-dev/storage见 packages/storage/package.json当前版本 v1.2.9对外发布。安装与引入在 WXT 项目中使用零安装该模块已内置在 WXT 中无需额外安装import { storage } from wxt/storage;如果你开启了 auto-importsWXT 默认storage会被自动导入连 import 语句都可以省略。从源码看WXT 在 packages/wxt/src/core/resolve-config.ts 中配置了默认的 unimport preset将storage以及StorageArea、WxtStorage、WxtStorageItem、StorageItemKey、StorageAreaChanges、MigrationError等类型一并自动导入来源为 packages/wxt/src/utils/storage.ts该文件仅一行核心逻辑export * from wxt-dev/storage即 WXT 直接复导出该包。在非 WXT 项目中使用独立安装在没有 WXT 的纯扩展项目中可以通过任意包管理器安装npm i wxt-dev/storage pnpm add wxt-dev/storage yarn add wxt-dev/storage bun add wxt-dev/storageimport { storage } from wxt-dev/storage;注意该包依赖wxt-dev/browser提供的跨浏览器browser全局对象见 packages/storage/package.json 的 dependencies因此在非 WXT 环境中需要自行保证浏览器 API 全局可用。前置条件Storage 权限与运行环境必须声明storage权限使用该 API 前必须在 manifest 中声明storage权限。在 WXT 中通过 wxt.config.ts 配置export default defineConfig({ manifest: { permissions: [storage], }, });从源码 packages/storage/src/index.ts 可见createDriver在真正访问存储区域前会做三重校验browser.runtime不存在非扩展环境、browser.storage不存在缺权限、对应区域不存在如browser.storage.managed未定义时都会抛出明确错误。运行环境限制wxt/storage必须在 Web 扩展环境中加载若在构建阶段或 Node 测试环境中直接引入会抛出wxt/storage must be loaded in a web extension environment的错误。测试时需正确 mockwxt/browser仓库自身的测试即通过webext-core/fake-browser模拟浏览器环境见测试文件开头的fakeBrowser.reset()。基础用法Key 必须带区域前缀所有存储 Key 必须以其所属存储区域作为前缀WXT Storage 支持四种区域前缀对应存储区域典型用途local:browser.storage.local持久化本地数据默认无上限session:browser.storage.session会话级数据浏览器重启或扩展重载后清空sync:browser.storage.sync随浏览器账号同步的数据有配额限制managed:browser.storage.managed由管理员策略提供的只读数据// ❌ 缺少区域前缀运行时会抛错 await storage.getItem(installDate); // ✅ 正确写法 await storage.getItem(local:installDate);从源码 packages/storage/src/index.ts 看resolveKey通过查找第一个冒号:切分区域与 Key因此 Key 内部再包含冒号是允许的测试用例storage.getItem(local:some:key)也验证了这一点。Key 的类型被定义为模板字面量类型StorageItemKey \${StorageArea}:${string}见 [packages/storage/src/index.ts](https://link.gitcode.com/i/076d9de1fe4d75458da308430cb277d0#L931-L932)无效区域在编译期即会报错运行时也会抛出Invalid area xxx. Options: local, session, sync, managed。类型参数大部分方法支持泛型参数指定值的类型await storage.getItemnumber(local:installDate); await storage.watchnumber( local:installDate, (newInstallDate, oldInstallDate) { // ... }, ); await storage.getMeta{ v: number }(local:installDate);对于一次性字段或通用辅助函数这种写法足够但官方推荐使用下文介绍的defineItem以获得更强的类型安全与复用性。Watcher监听存储变化storage.watch为单个 Key 注册监听器当该 Key 的值发生变化时回调(newValue, oldValue)const unwatch storage.watchnumber(local:counter, (newCount, oldCount) { console.log(Count changed:, { newCount, oldCount }); });调用返回的unwatch函数即可移除监听器const unwatch storage.watch(...); // 稍后某个时刻... unwatch();其底层实现packages/storage/src/index.ts在browser.storage[area].onChanged上注册监听器并做了两件事仅当变化 Key 与监听 Key 一致时才触发使用dequal深度比较新旧值值未实际变化如重复写入相同值时不触发回调。测试用例覆盖了不同 Key 不触发相同值不触发值变化触发unwatch 后不再触发四种情形。此外还提供storage.unwatch()一次性移除所有监听器这在测试或页面卸载场景中很实用。元数据MetadataWXT Storage 允许为每个 Key 关联一组元数据存储位置约定为key $。元数据适合存放版本号、最后修改时间等与主值相关的附加信息。await Promise.all([ storage.setItem(local:preference, true), storage.setMeta(local:preference, { lastModified: Date.now() }), ]);多次调用setMeta时不同属性会合并而非覆盖await storage.setMeta(local:preference, { lastModified: Date.now() }); await storage.setMeta(local:preference, { v: 2 }); await storage.getMeta(local:preference); // { v: 2, lastModified: 1703690746007 }删除元数据时可以整体删除也可以按属性名删除// 删除全部属性 await storage.removeMeta(local:preference); // 只删除 lastModified 属性 await storage.removeMeta(local:preference, lastModified); // 删除多个属性 await storage.removeMeta(local:preference, [lastModified, v]);从源码 packages/storage/src/index.ts 可以看到元数据的几个关键实现细节getMetaKey将key映射为key $作为元数据存储键mergeMeta合并新旧元数据对象新值中为null/undefined的属性会被删除若元数据值不是对象例如被意外写成了标量读取时会被视为{}读取不存在的元数据时返回空对象{}。定义存储项Defining Storage Items反复书写同一个 Key 和类型参数容易出错、也难以维护。storage.defineItem可以把 Key、类型、默认值等集中定义为一个存储项storage item后续所有操作都围绕该对象进行// utils/storage.ts const showChangelogOnUpdate storage.defineItemboolean( local:showChangelogOnUpdate, { fallback: true, }, );之后使用存储项替代storage变量await showChangelogOnUpdate.getValue(); await showChangelogOnUpdate.setValue(false); await showChangelogOnUpdate.removeValue(); const unwatch showChangelogOnUpdate.watch((newValue) { // ... });存储项提供的完整 API对应WxtStorageItem接口见 packages/storage/src/index.ts方法/属性说明key创建时传入的存储 KeyfallbackdefaultValue已废弃语义相同值缺失时getValue返回的默认值getValue()读取最新值getMeta()读取元数据setValue(value)写入值传入null/undefined等价于删除setMeta(properties)写入元数据属性合并语义removeValue(opts?)删除值{ removeMeta: true }可连带删除元数据removeMeta(properties?)删除全部或指定元数据属性watch(cb)监听值变化回调值会自动套用 fallbackmigrate()手动执行迁移扩展更新时自动执行通常无需手动调用类型层面未传fallback/init时getValue()返回TValue | null传了非空fallback、defaultValue或init函数后返回非空类型测试文件 packages/storage/src/tests/index.test.ts 的types分组用expectTypeOf验证了这些类型推断规则。版本迁移Versioning存储数据的 schema 会随业务演进WXT Storage 内置了声明式版本迁移机制。定义第一个版本时从version: 1开始后续升级时递增版本号并补充对应版本的迁移函数。以一个被忽略网站列表为例v1 时它只是一组字符串type IgnoredWebsiteV1 string; export const ignoredWebsites storage.defineItemIgnoredWebsiteV1[]( local:ignoredWebsites, { fallback: [], version: 1, }, );v2 时我们希望每个网站带id于是版本升到 2并新增迁移函数将字符串数组映射为带 id 的对象数组import { nanoid } from nanoid; type IgnoredWebsiteV1 string; interface IgnoredWebsiteV2 { id: string; website: string; } export const ignoredWebsites storage.defineItemIgnoredWebsiteV2[]( local:ignoredWebsites, { fallback: [], version: 2, migrations: { // v1 - v2 时执行 2: (websites: IgnoredWebsiteV1[]): IgnoredWebsiteV2[] { return websites.map((website) ({ id: nanoid(), website })); }, }, }, );v3 时再增加enabled字段迁移函数链式累积每个版本一个迁移import { nanoid } from nanoid; type IgnoredWebsiteV1 string; interface IgnoredWebsiteV2 { id: string; website: string; } interface IgnoredWebsiteV3 { id: string; website: string; enabled: boolean; } export const ignoredWebsites storage.defineItemIgnoredWebsiteV3[]( local:ignoredWebsites, { fallback: [], version: 3, migrations: { // v1 - v2 时执行 2: (websites: IgnoredWebsiteV1[]): IgnoredWebsiteV2[] { return websites.map((website) ({ id: nanoid(), website })); }, // v2 - v3 时执行 3: (websites: IgnoredWebsiteV2[]): IgnoredWebsiteV3[] { return websites.map((website) ({ ...website, enabled: true })); }, }, }, );内部实现上版本号存放在元数据的v字段中即key $对象里的v属性。实际开发中往往是在 schema 需要变更时才想起加版本。好在给未版本化的存储项补版本非常简单WXT 在找不到旧版本号时默认按 v1 处理因此只需把version设为 2 并为2提供一个迁移函数即可export const ignoredWebsites storage.defineItemstring[]( local:ignoredWebsites, { fallback: [], }, );import { nanoid } from nanoid; // 补上第一版的类型定义 type IgnoredWebsiteV1 string; interface IgnoredWebsiteV2 { id: string; website: string; } export const ignoredWebsites storage.defineItemIgnoredWebsiteV2[]( local:ignoredWebsites, { fallback: [], version: 2, migrations: { // v1 - v2 时执行 2: (websites: IgnoredWebsiteV1[]): IgnoredWebsiteV2[] { return websites.map((website) ({ id: nanoid(), website })); }, }, }, );迁移的执行时机与源码行为从源码 packages/storage/src/index.ts 可以确认迁移机制的完整行为调用即检查只要storage.defineItem被调用且传入了migrations迁移流程立即异步启动migrationsDonePromise。getValue、setValue、getMeta等方法内部都会先await migrationsDone确保迁移完成后才读写数据版本递增链式执行迁移按currentVersion 1 ... targetVersion逐个执行迁移函数返回值作为下一个迁移的输入缺失的迁移函数会被跳过测试用例验证了只提供 v1、v3 迁移时v2 缺失可正常跳过单个迁移失败会抛出MigrationError携带 Key 与失败版本号构造见 packages/storage/src/index.ts版本回退检测存量版本大于目标版本时抛出Version downgrade detected (v2 - v1) for local:count版本下限version不能小于 1否则定义时即抛错首写补版本对于带版本号但值尚不存在的项首次setValue时会同时写入{ v: targetVersion }元数据测试验证了这一只补一次的行为debug: true开启后会在控制台输出迁移过程的console.debug日志[wxt-dev/storage] Running storage migration for ...等四类日志onMigrationComplete全部迁移完成后回调(migratedValue, targetVersion)。测试套件中versioning分组packages/storage/src/tests/index.test.ts用大量用例逐一验证了上述行为包括连续迁移 v1→v2→v3 得到 12、空项不迁移、版本不变不迁移等。默认值fallback 与 initdefineItem提供两种互补的默认值机制fallback值缺失时getValue()返回该值而非null适合设置项这类不必落盘的默认值const theme storage.defineItem(local:theme, { fallback: dark, }); const allowEditing storage.defineItem(local:allow-editing, { fallback: true, });init定义后立即将值初始化并写入存储仅当值不存在时适合只初始化一次的数据const userId storage.defineItem(local:user-id, { init: () globalThis.crypto.randomUUID(), }); const installDate storage.defineItem(local:install-date, { init: () new Date().getTime(), });源码 packages/storage/src/index.ts 对init做了并发保护用superlock的withLock()包裹初始化逻辑保证同一 JS 上下文内多次并发getValue()只执行一次init值被删除后下一次getValue()会重新初始化。测试init option分组验证了只调用一次 init、避免竞态以及删除后重新初始化的行为。批量操作Bulk Operations逐 Key 读写会产生大量存储调用批量 API 通过按存储区域合并调用显著降低开销。WXT Storage 提供五个批量方法getItems一次性读取多个值getMetas一次性读取多个项的元数据setItems一次性写入多个值setMetas一次性写入多个项的元数据removeItems一次性删除多个值可连带元数据。这些 API 均支持字符串 Key 与已定义的存储项混用const userId storage.defineItem(local:userId); await storage.setItems([ { key: local:installDate, value: Date.now() }, { item: userId, value: generateUserId() }, ]);源码中批量 API 的实现值得注意packages/storage/src/index.ts按区域分组先将入参按local/session/sync/managed分组再对每个区域只发一次底层调用测试用例断言了跨区域批量读取时local与session各自只调用一次get保持入参顺序getItems/getMetas的返回数组顺序与入参顺序严格一致语义一致批量set中值为null/undefined的项等价于删除setMetas会先读取各 Key 已有元数据再合并写入避免覆盖未提及的属性注意单个setItem写入的值会整体覆盖因此值与版本元数据一起写的场景在defineItem内部被拆成两次独立调用见源码注释说明。批量 API 的类型签名与更多示例可参考WxtStorage接口注释packages/storage/src/index.ts。快照与恢复Snapshot Restoresnapshot返回某个存储区域的完整数据不含区域前缀可直接落盘或导出restoreSnapshot则把快照写回// 导出 local 区域全部数据 const snapshot await storage.snapshot(local); // 排除某些 Key连同其元数据 const snapshot await storage.snapshot(local, { excludeKeys: [secretKey], }); // 恢复快照 await storage.restoreSnapshot(local, snapshot);从源码 packages/storage/src/index.ts 可见snapshot的excludeKeys会同时剔除对应 Key 及其元数据key$restoreSnapshot直接对底层区域执行set(data)因此快照中的元数据是整体覆盖而非合并测试restoreSnapshot分组对此有专门用例而快照中不存在的存量 Key 不会被删除仅覆盖快照内出现的值。这一组 API 很适合做配置导出/导入、用户数据备份等场景。在真实扩展中的典型用法在 WXT 自带的示例扩展 packages/wxt-demo 中可以看到最朴素的用法——background 入口在浏览器启动时记录会话开始时间// packages/wxt-demo/src/entrypoints/background.ts export default defineBackground(() { // ... storage.setItem(session:startTime, Date.now()); });对应测试 packages/wxt-demo/src/entrypoints/tests/background.test.ts 则断言storage.getItem(session:startTime)有值——这正是session:区域会话级持久化语义的体现普通local数据会跨浏览器会话残留而会话数据在扩展重载/浏览器重启后自动清空非常适合记录本次运行状态。完整的存储数据层示例综合以上能力一个类型安全、带版本迁移的存储数据层可以这样组织// utils/storage.ts import { nanoid } from nanoid; // 1) 设置项带 fallback无需落盘 export const theme storage.defineItemlight | dark(local:theme, { fallback: dark, }); // 2) 只初始化一次的数据 export const installDate storage.defineItemnumber(local:install-date, { init: () new Date().getTime(), }); // 3) 会演进的数据带版本迁移 interface BookmarkV1 { url: string; } interface BookmarkV2 extends BookmarkV1 { id: string; createdAt: number; } export const bookmarks storage.defineItemBookmarkV2[](local:bookmarks, { fallback: [], version: 2, migrations: { 2: (items: BookmarkV1[]): BookmarkV2[] items.map((item) ({ ...item, id: nanoid(), createdAt: Date.now(), })), }, });// entrypoints/background.ts export default defineBackground(() { // 监听主题变化实时同步到各个页面 theme.watch((newTheme, oldTheme) { console.log(Theme changed:, oldTheme, -, newTheme); }); // 批量导出用户数据 browser.runtime.onMessage.addListener(async (msg) { if (msg.type export) { const data await storage.snapshot(local); return { data }; } }); });小结WXT Storage 通过区域前缀 Key 泛型 存储项抽象三重设计把浏览器扩展存储从裸字符串字典升级为可维护、可演进、类型安全的数据层watch/unwatch解决状态同步元数据与v版本字段支撑 schema 演进fallback/init覆盖默认值场景批量 API 与快照 API 兼顾性能与数据迁移。需要深入源码的读者可以从三个文件入手核心实现 packages/storage/src/index.ts、行为契约测试 packages/storage/src/tests/index.test.ts、以及 WXT 侧的集成入口 packages/wxt/src/utils/storage.ts。赞分享前端开发工具构建工具插件系统【免费下载链接】wxt⚡ Next-gen Web Extension Framework项目地址https://gitcode.com/gh_mirrors/wx/wxt点击查看免费下载相关推荐WXT 扩展消息传递Messaging实战指南原生 API 与类型安全封装方案WXT 扩展消息传递Messaging实战指南原生 API 与类型安全封装方案 WXT 是一款面向 Web Extension 的下一代开发框架其文档在前端开发工具构建工具插件系统把Fay开源数字人框架变成你的业务伙伴快速部署与交互完整指南把Fay开源数字人框架变成你的业务伙伴快速部署与交互完整指南 Fay数字人是一个帮助数字人模型或大语言模型openai兼容、deepseek连通业务系统的前端开发工具构建工具插件系统WXT 国际化i18n实战指南从原生 browser.i18n 到 wxt-dev/i18n 类型安全封装WXT 国际化i18n实战指南从原生 browser.i18n 到 wxt dev/i18n 类型安全封装 本指南以 WXT 仓库中的 i18n 官方文前端开发工具构建工具插件系统上一篇AMD Ryzen调试工具SMUDebugTool3步掌握硬件调校释放处理器全部潜力下一篇5分钟掌握AMD Ryzen调试神器SMUDebugTool让你的处理器发挥全部潜力创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
大麦自动抢票:从克隆到跑通只需三条命令 大麦自动抢票:从克隆到跑通只需三条命令 【免费下载链接】ticket-purchase 大麦自动抢票,支持人员、城市、日期场次、价格选择 项目地址: https://gitcode.com/GitHub_Trending/ti/ticket-purchase
周五晚上八点整,大麦网开票倒计时跳… · 2026/9/24 15:46:08
OpenLayers 入门背景指南:模块化架构、公共 API 与浏览器支持详解 前端GIS数据可视化 【免费下载链接】openlayers OpenLayers 项目地址: https://gitcode.com/gh_mirrors/op/openlayers 点击查看 免费下载 OpenLayers 是一个模块化、高性能、功能丰富的开源 JavaScript 地图库,用于在 Web 页面中展示地理空间数据并与之… · 2026/9/24 15:46:08
Astrid 内核架构决策记录(ADR-K1~K7)全解析:保护域、能力对象、撤销、故障端点、调度与审计排序的设计取舍 【免费下载链接】astrid Astrid is a portable, capability-secure operating system for composable software. 项目地址: https://gitcode.com/gh_mirrors/astrid2/astrid 点击查看 免费下载 导读
本文是 Astrid 原生内核(astrid-native-kernel&… · 2026/9/24 15:45:49
【Dify】36氪新闻热榜智能自动化采集与AI处理 实时掌握热点新闻已成为信息时代的重要能力,自动化技术和AI智能体正推动新闻获取方式变革。
本文介绍如何通过Dify等自动化工具,实现36氪新闻热榜的批量采集、智能摘要和定制化输出,适用于信息收集、内容创作、行业分析等场景。 文章目录 36氪新闻热榜智能自动化 核心模型 … · 2026/9/24 16:16:34
Smithbox完全入门:一站式搞定9款FromSoftware游戏MOD编辑的终极工具指南 Smithbox完全入门:一站式搞定9款FromSoftware游戏MOD编辑的终极工具指南 【免费下载链接】Smithbox Smithbox is a modding tool for Elden Ring, Armored Core VI, Sekiro, Dark Souls 3, Dark Souls 2, Dark Souls, Bloodborne and Demons Souls. 项目地址: htt… · 2026/9/24 16:16:02
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程 简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13
1D-CNN时间序列建模实战:从Conv1d原理到工业落地 简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26
柔软的L:汉语语流中被忽视的舌肌张力控制 1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44