生成 PWA 驱动的小组件

各种操作系统都有允许用户读取内容和执行任务的小组件仪表板。 例如,Android 主屏幕小组件、macOS 仪表板和今日面板小组件、Apple Touch Bar、Samsung 每日卡片、迷你应用小组件和智能手表应用配套。

在 Windows 11 上,小组件显示在从任务栏左侧打开的小组件板中:

Windows 11 中的小组件板

在 Windows 11 中,渐进式 Web 应用 (PWA) 可以定义小组件、更新小组件并处理其中的用户交互。

需要为 PWA 生成自定义小组件

现有 PWA 不能像使用 Microsoft Edge 边栏一样简单地放置在小组件仪表板中。 相反,你需要构建适用于小组件主机(当前为 Windows 11 小组件板)的自定义小组件体验。 (将来可能会有其他小组件主机。) Windows 11 小组件板要求使用自适应卡片模板而不是 HTML 和 JavaScript 生成小组件,因此必须独立于应用 UI 的其余部分单独设计小组件。

另请参阅:

若要生成 PWA 驱动的小组件并通过Microsoft存储提供它,无需C++/C# 代码。 生成小组件并可从公共终结点成功安装和运行小组件后,可以使用 PWABuilder.com 打包应用,并将应用寄送到Microsoft存储,而无需任何其他代码。 支持小组件的 PWA 必须可从公共终结点安装,因为 PWABuilder.com 不支持从 localhost 打包应用。

另请参阅:

安装 WinAppSDK 并启用开发人员模式

若要在本地计算机上启用开发和测试小组件,请执行以下操作:

  • 安装 WinAppSDK 1.2

  • 在 Windows 11 中启用开发人员模式:

    1. 打开“设置”。

    2. “查找设置” 文本框中,输入 developer,然后单击“ 使用开发人员功能”。

    3. 启用 开发人员模式

      Windows 11 的开发人员设置

定义小组件

使用清单成员在 PWA 清单文件中 widgets 定义小组件。 此清单成员是一个数组,可以包含多个小组件定义。

{
  "name": "PWAmp",
  "description": "A music player app",
  "icons": [
    { "src": "img/icon-96.png", "sizes": "96x96" },
    { "src": "img/icon-128.png", "sizes": "128x128" },
    { "src": "img/icon-256.png", "sizes": "256x256" },
    { "src": "img/icon-512.png", "sizes": "512x512" }
  ],
  "widgets": [
    /* widget definitions go here */
  ]
}

数组中的每个 widgets 条目都包含多个字段,如下所示:

{
  ...
  "widgets": [
    {
      "name": "PWAmp mini player",
      "description": "widget to control the PWAmp music player",
      "tag": "pwamp",
      "template": "pwamp-template",
      "ms_ac_template": "widgets/mini-player-template.json",
      "data": "widgets/mini-player-data.json",
      "type": "application/json",
      "screenshots": [
        {
          "src": "./screenshot-widget.png",
          "sizes": "600x400",
          "label": "The PWAmp mini-player widget"
        }
      ],
      "icons": [
        {
          "src": "./favicon-16.png",
          "sizes": "16x16"
        }
      ],
      "auth": false,
      "update": 86400
    }
  ]
}

在上面的示例中,音乐播放器应用程序定义了一个微型播放器小组件。 Web 应用清单中的小组件定义具有以下必填字段和可选字段:

字段 说明 必需
name 显示给用户的小组件的标题。
short_name 名称的备用短版本。
description 小组件的作用说明。
icons 要用于小组件的图标数组。 如果缺失,则 icons 改用清单成员。 忽略大于 1024x1024 的图标。
screenshots 显示小组件外观的屏幕截图数组。 类似于 screenshot 清单成员platform屏幕截图项的字段支持 Windowsany 值。 大于 1024x1024 像素的图像将被忽略。 有关特定于 Windows 11 小组件板的屏幕截图要求,请参阅与小组件选取器集成中的屏幕截图图像要求
tag 用于引用 PWA 服务辅助角色中的小组件的字符串。
template 用于在操作系统小组件仪表板中显示小组件的模板。 注意:此属性目前仅供信息使用,不使用。 请参阅 ms_ac_template 下文。
ms_ac_template 自定义自适应卡片模板的 URL,用于在操作系统小组件仪表板中显示小组件。 请参阅下面的 定义小组件模板
data 可在其中找到用于填充模板的数据的 URL。 如果存在,则需要此 URL 才能返回有效的 JSON。
type 小组件数据的 MIME 类型。
auth 一个布尔值,指示小组件是否需要身份验证。
update 小组件更新的频率(以秒为单位)。 服务辅助角色中的代码必须执行更新;小组件不会自动更新。 请参阅 在运行时访问小组件实例
multiple 一个布尔值,指示是否允许小组件的多个实例。 默认为 true

定义小组件模板

为了使小组件易于创建和适应各种操作系统小组件仪表板,将使用模板显示它们。 存在两种类型的模板:

  • 泛型模板,由其名称使用 template 字段定义。
  • 自定义模板,由其 URL 使用自定义模板字段定义。

目前,仅支持自定义自适应卡片模板。 自适应卡片是一种开放卡片交换格式,可用于以通用且一致的方式交换 UI 内容。 请参阅 自适应卡片概述

若要在 Windows 11 上定义自定义自适应卡片模板,请使用 ms_ac_template Web 应用清单中的小组件定义中的 字段。 虽然 template 当前未使用 ,但它是必填字段。

{
  ...
  "template": "pwamp-template",
  "ms_ac_template": "widgets/mini-player.json",
  ...
}

字段 ms_ac_template 值应该是模板文件的有效 URL。

下面是自适应卡片模板的示例:

{
  "type": "AdaptiveCard",
  "body": [
    {
      "type": "TextBlock",
      "size": "Medium",
      "text": "Now playing...",
      "horizontalAlignment": "Center"
    },
    {
      "type": "TextBlock",
      "spacing": "Large",
      "weight": "Bolder",
      "horizontalAlignment": "Center",
      "text": "${song}, by ${artist}",
    }
  ],
  "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
  "version": "1.5"
}

若要了解详细信息,请参阅 自适应卡片模板化

接下来,需要将数据绑定到模板。

将数据绑定到模板

模板声明小组件的用户界面。 然后,数据将填充此用户界面。

若要将数据绑定到模板,请在小组件定义中使用 data 字段。 应将此字段设置为返回有效 JSON 数据的 URL。

上一节中定义的模板包含两个变量:songartist,它们包含在绑定表达式语法中:${}。 小组件定义中的 URL 返回 data 的数据应包含这些变量的值。

下面是 URL 可能返回的内容 data 的示例:

{
  "song": "I Will Always Love You",
  "artist": "Whitney Houston"
}

定义小组件操作

如果希望小组件允许用户执行任务,请定义支持操作的模板。

下面是自定义自适应卡片模板中定义的操作示例:

{
  "type": "AdaptiveCard",
  "body": [
    {
      "type": "TextBlock",
      "size": "Medium",
      "text": "Now playing...",
      "horizontalAlignment": "Center"
    },
    {
      "type": "TextBlock",
      "spacing": "Large",
      "weight": "Bolder",
      "horizontalAlignment": "Center",
      "text": "${song}, by ${artist}",
    }
  ],
  "actions": [
    {
      "type": "Action.Execute",
      "title": "Previous",
      "verb": "previous-song"
    },
    {
      "type": "Action.Execute",
      "title": "Next",
      "verb": "next-song"
    }
  ],
  "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
  "version": "1.5"
}

请注意上述 verb JSON 模板中的 字段。 在服务辅助角色代码中处理小组件操作时,将使用它。 请参阅 处理小组件操作

在运行时访问小组件实例

可以访问小组件,并从 PWA 服务辅助角色代码更新它们。 在运行时访问小组件在以下情况下很有用:

服务辅助角色有权访问 self.widgets 对象和多个小组件事件,这些事件共同构成了一个 API,用于在运行时响应更改和访问小组件。

以下部分提供了代码示例。 有关 API 的参考,请参阅 服务辅助角色 API 参考

在安装时呈现小组件

安装 PWA 后,应用在其清单中定义的小组件将添加到小组件仪表板,但尚未安装。 仅当用户选择从仪表板添加小组件时,才会安装小组件。

安装小组件时,不会使用 ms_ac_template 小组件定义的 和 data 字段自动呈现该小组件。

若要呈现小组件,请侦 widgetinstall 听服务辅助角色中的 事件,并使用 widgets.updateByTag 函数更新小组件:

// Listen to the widgetinstall event.
self.addEventListener("widgetinstall", event => {
  // The widget just got installed, render it using renderWidget.
  // Pass the event.widget object to the function.
  event.waitUntil(renderWidget(event.widget));
});

async function renderWidget(widget) {
  // Get the template and data URLs from the widget definition.
  const templateUrl = widget.definition.msAcTemplate;
  const dataUrl = widget.definition.data;

  // Fetch the template text and data.
  const template = await (await fetch(templateUrl)).text();
  const data = await (await fetch(dataUrl)).text();

  // Render the widget with the template and data.
  await self.widgets.updateByTag(widget.definition.tag, {template, data});
}

更新服务辅助角色更新上的小组件

当 PWA 中的服务辅助角色代码发生更改时,浏览器会检测到该更改,安装新的服务辅助角色,然后激活服务辅助角色。

发生这种情况时,请务必更新可能已在运行的任何小组件实例。 在发出服务辅助角色 activate 事件之前,小组件可能已安装。 若要避免显示空小组件,请在事件发生时 activate 更新小组件

// Update the widgets to their initial states
// when the service worker is activated.
self.addEventListener("activate", event => {
  event.waitUntil(updateWidgets());
});

async function updateWidgets() {
  // Get the widget that match the tag defined in the web app manifest.
  const widget = await self.widgets.getByTag("pwamp");
  if (!widget) {
    return;
  }

  // Using the widget definition, get the template and data.
  const template = await (await fetch(widget.definition.msAcTemplate)).text();
  const data = await (await fetch(widget.definition.data)).text();

  // Render the widget with the template and data.
  await self.widgets.updateByTag(widget.definition.tag, {template, data});
}

处理小组件操作

如果小组件模板包含操作,则用户可以通过单击呈现的小组件中的按钮来运行这些操作。 有关如何在模板中定义操作的信息,请参阅 定义小组件操作

当用户运行小组件操作时, widgetclick 会在 PWA 服务辅助角色中触发事件。 若要处理用户操作,请侦听事件:

self.addEventListener('widgetclick', (event) => {
  switch (event.action) {
    case 'previous-song':
      // Application logic to play the previous song...
      break;
    case 'next-song':
      // Application logic to play the next song...
      break;
  }
});

为简洁起见,上面的代码片段中未显示实际的应用程序代码。 previous-song收到 或 next-song 操作后,可能需要使用 Client.postMessage 将消息发送到应用,以告知应用应开始播放上一首或下一首歌曲。

请注意, action 传递给上述事件侦听器的 对象的 属性 widgetEvent 与在小组件模板的 字段中定义的 action.verb 字符串匹配。

有关事件 widgetclick 以及可从该事件访问哪些信息的详细信息,请参阅下面的 服务辅助角色 API 参考

更新应用程序更改小组件

在前面的部分中,你已了解如何在发生特定小组件事件、小组件操作和服务辅助角色更新时更新小组件。 当应用程序中发生某些事情、发生推送通知时,或者定期更新小组件也很有用。

在本部分中,你将了解如何使用定期后台同步 API 定期更新小组件。 有关定期后台同步 API 的详细信息,请参阅 使用定期后台同步 API 定期获取新内容

在以下代码片段中,事件侦听器用于响应应用程序小组件的各种生命周期事件。 检测到小组件安装时,将注册定期同步,并且检测到小组件删除时,定期同步将取消注册。

定期发生同步事件时,将使用 widgets.updateByTag 函数更新小组件实例。

self.addEventListener("widgetinstall", event => {
  event.waitUntil(onWidgetInstall(event.widget));
});

self.addEventListener("widgetuninstall", event => {
  event.waitUntil(onWidgetUninstall(event.widget));
});

async function onWidgetInstall(widget) {
  // Register a periodic sync, if this wasn't done already.
  // We use the same tag for the sync registration and the widget to
  // avoid registering several periodic syncs for the same widget.
  const tags = await self.registration.periodicSync.getTags();
  if (!tags.includes(widget.definition.tag)) {
    await self.registration.periodicSync.register(widget.definition.tag, {
      minInterval: widget.definition.update
    });
  }

  // And also update the instance.
  await updateWidget(widget);
}

async function onWidgetUninstall(widget) {
  // On uninstall, unregister the periodic sync.
  // If this was the last widget instance, then unregister the periodic sync.
  if (widget.instances.length === 1 && "update" in widget.definition) {
    await self.registration.periodicSync.unregister(widget.definition.tag);
  }
}

// Listen to periodicsync events to update all widget instances
// periodically.
self.addEventListener("periodicsync", async event => {
  const widget = await self.widgets.getByTag(event.tag);

  if (widget && "update" in widget.definition) {
    event.waitUntil(updateWidget(widget));
  }
});

async function updateWidget(widget) {
  // Get the template and data URLs from the widget definition.
  const templateUrl = widget.definition.msAcTemplate;
  const dataUrl = widget.definition.data;

  // Fetch the template text and data.
  const template = await (await fetch(templateUrl)).text();
  const data = await (await fetch(dataUrl)).text();

  // Render the widget with the template and data.
  await self.widgets.updateByTag(widget.definition.tag, {template, data});
}

演示应用

PWAmp 是定义小组件的音乐播放器 PWA 演示应用程序。 PWAmp 小组件允许用户可视化当前歌曲并播放上一首或下一首歌曲。

  1. 如果尚未安装,请在 Windows 11 中安装 WinAppSDK 1.2 并启用开发人员模式。

  2. 转到 PWAmp 并在 Windows 11 上安装应用。

  3. 按 Windows 徽标键 + W 打开 Windows 11 小组件板。

  4. 单击“ 添加小组件 ”打开 小组件设置 屏幕,滚动到 PWAmp 微型播放器 小组件并添加它。

  5. 关闭 小组件设置 屏幕。 PWAmp 微型播放器现在显示在小组件板中。

PWAmp 小组件显示用于播放上一首或下一首歌曲的当前歌曲和按钮。

Windows 小组件板,PWAmp 演示应用旁边。小组件板包含 PWAmp 微型播放器小组件,显示 PWAmp 应用中播放的当前歌曲

服务辅助角色 API 参考

服务辅助角色全局对象 (或 ServiceWorkerGlobalScope) 包含一个 widgets 属性,该属性公开了以下基于 Promise 的方法:

方法 说明 参数 返回值
getByTag(tag) 按标记获取小组件。 小组件标记 一个 Promise,解析为与标记匹配 的小组件对象 ,或 undefined
getByInstanceId(id) 按实例 ID 获取小组件。 小组件实例 ID 解析为相应 小组件对象的 Promise,或 undefined
getByHostId(id) 按主机 ID 获取小组件。 主机 ID 在该主机中找到 的小组件对象的 数组。
matchAll(options) 通过匹配选项获取小组件。 widgetOptions 对象 一个 Promise,它解析为符合options条件的小组件对象数组。
updateByInstanceId(id, payload) 按实例 ID 更新小组件。 实例 ID 和 widgetPayload 对象 解析为 undefinedError的 Promise。
updateByTag(tag, payload) 按标记更新小组件。 小组件标记和 widgetPayload 对象 解析为 undefinedError的 Promise。

服务辅助角色全局对象还定义了以下事件:

  • widgetinstall:小组件主机安装小组件时触发。
  • widgetuninstall:小组件主机卸载小组件时触发。
  • widgetresume:当小组件主机恢复已安装小组件的呈现时触发,这在主机暂停小组件的呈现以保留资源后发生。
  • widgetclick:当用户运行某个小组件操作时触发。

有关随这些事件提供的对象的详细信息,请参阅下面的 widgetEvent 对象widgetClickEvent 对象

widget 对象

每个小组件都表示为对象 widget ,该对象包含以下属性:

widgetOptions 对象

使用 matchAll(options) 获取多个小组件时,需要一个 widgetOptions 对象来筛选要返回的小组件。 对象 widgetOptions 包含以下属性,所有这些属性都是可选的:

  • installable:一个布尔值,指示返回的小组件是否应可安装。
  • installed:一个布尔值,指示返回的小组件是否安装在小组件主机中。
  • tag:用于按标记筛选返回的小组件的字符串。
  • instanceId:用于按实例 ID 筛选返回的小组件的字符串。
  • hostId:用于按小组件主机 ID 筛选返回的小组件的字符串。

widgetPayload 对象

创建或更新小组件实例时,服务辅助角色必须发送填充小组件所需的模板和数据。 模板和数据称为 有效负载。 对象 widgetPayload 包含以下属性:

  • template:用作呈现小组件的字符串模板。 这是自适应卡片模板的字符串化 JSON。
  • data:数据(作为字符串)与小组件模板一起使用。 此数据可以是字符串化的 JSON 数据。

widgetInstance 对象

此对象表示小组件主机中小组件的给定实例,并包含以下属性:

  • id:用于引用实例的内部 GUID 字符串。
  • host:指向已安装此实例的小组件主机的内部指针。
  • updated:一个 Date 对象,表示上次将数据发送到实例的时间。
  • payload:一个 widgetPayload 对象 ,表示发送到此实例的最后一个有效负载。

widgetDefinition 对象

此对象表示小组件的原始定义,可在 PWA 清单文件中找到。 此对象的属性与上面 “定义小组件”中列出的属性匹配。

widgetEvent 对象

此对象作为参数传递给 、 widgetuninstallwidgetresume类型的widgetinstall服务辅助角色小组件事件的侦听器。

widgetinstall对于 、 widgetuninstallwidgetresume 事件类型,widgetEvent对象具有以下属性:

属性 说明 类型
widget 触发事件的小组件实例。 控件
instanceId 小组件实例 ID。 String
hostId 小组件主机 ID。 String

widgetClickEvent 对象

此对象作为参数传递给类型的 widgetclick服务辅助角色小组件事件的侦听器。 可以使用 打开应用的窗口以 widgetclick 响应事件 clients.openWindow()

对象 widgetClickEvent 具有以下属性:

属性 说明 类型
action 触发事件的操作,如小组件模板的字段所定义 actions.verb 。 请参阅 定义小组件操作 String
widget 触发事件的小组件实例。 widgetInstance
hostId 小组件主机 ID。 String
instanceId 小组件实例 ID。 String