生成 PWA 驱动的小组件
各种操作系统都有允许用户读取内容和执行任务的小组件仪表板。 例如,Android 主屏幕小组件、macOS 仪表板和今日面板小组件、Apple Touch Bar、Samsung 每日卡片、微型应用小组件以及智能watch应用伴侣。
在Windows 11上,小组件显示在小组件板中,从任务栏左侧打开:
在Windows 11中,渐进式Web 应用 (PWA) 可以定义小组件、更新小组件以及处理其中的用户交互。
需要为 PWA 生成自定义小组件
现有 PWA 不能像使用 Microsoft Edge 边栏一样简单地仪表板放入小组件中。 相反,你需要构建一个适合小组件主机的自定义小组件体验,该主机当前是Windows 11小组件板。 (将来可能会有其他小组件主机。) Windows 11小组件板要求使用自适应卡片模板而不是 HTML 和 JavaScript 生成小组件,因此必须独立于应用 UI 的其余部分设计小组件。
另请参阅:
若要生成 PWA 驱动的小组件并通过 Microsoft Store 交付它,不需要 C++/C# 代码。 生成小组件并可从公共终结点成功安装和运行小组件后,可以使用 PWABuilder.com 打包应用,并将应用寄送到 Microsoft 应用商店,而无需任何其他代码。 支持小组件的 PWA 必须可从公共终结点安装,因为 PWABuilder.com 不支持从 localhost 打包应用。
另请参阅:
安装 WinAppSDK 并启用开发人员模式
若要在本地计算机上启用开发和测试小组件,请执行以下操作:
安装 WinAppSDK 1.2。
在 Windows 11 中启用开发人员模式:
打开“设置”。
在 “查找设置” 文本框中,输入
developer
,然后单击“ 使用开发人员功能”。启用 开发人员模式:
定义小组件
使用清单成员在 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 屏幕截图项的字段支持 Windows 和 any 值。 大于 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。
上一节中定义的模板包含两个变量:song
和 artist
,它们包含在绑定表达式语法中:${}
。 小组件定义中的 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 小组件允许用户可视化当前歌曲并播放上一首或下一首歌曲。
如果尚未安装,请在 Windows 11 中安装 WinAppSDK 1.2 并启用开发人员模式。
转到 PWAmp 并在 Windows 11 上安装应用。
按 Windows 徽标键 + W 打开Windows 11小组件板。
单击“ 添加小组件 ”打开 小组件设置 屏幕,滚动到 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 对象 | 解析为 undefined 或 Error 的 Promise。 |
updateByTag(tag, payload) |
按标记更新小组件 | 小组件标记和 widgetPayload 对象 | 解析为 undefined 或 Error 的 Promise。 |
服务辅助角色全局对象还定义了以下事件:
widgetinstall
:小组件主机安装小组件时触发。widgetuninstall
:在小组件主机卸载小组件时触发。widgetresume
:当小组件主机恢复已安装小组件的呈现时触发,这在主机暂停小组件的呈现以保留资源后发生。widgetclick
:当用户运行其中一个小组件操作时触发。
有关随这些事件提供的对象的详细信息,请参阅下面的 widgetEvent 对象 和 widgetClickEvent 对象 定义。
widget 对象
每个小组件都表示为对象 widget
,该对象包含以下属性:
installable
:指示小组件是否可安装的布尔值。definition
: widgetDefinition 对象。instances
: widgetInstance 对象的 数组,表示小组件每个实例的当前状态。
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 对象
此对象作为参数传递给 、 widgetuninstall
和 widgetresume
类型的widgetinstall
服务辅助角色小组件事件的侦听器。
widgetinstall
对于 、 widgetuninstall
和 widgetresume
事件类型,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 |