24: PWA 和原生应用程序

一个 Phlo 应用程序已经是一个网站。相同的构建可以作为可安装的渐进式 Web 应用程序发布,无需额外的工具,并且相同的 Web 输出可以被包装在一个轻量级的原生外壳中,以便于应用商店。没有单独的“移动构建”:一个源,两个额外的分发形态。

24.1: 清单

PWA 需要一个描述应用的清单:名称、图标、启动方式。Phlo 提供一个 manifest 资源,该资源负责路由和服务;你的应用只需声明主体。在你的资源 (data/app.json) 中列出 manifest,然后从 app.phlo 中注入主体:

prop %manifest.body => arr(
	id: '/',
	name: %app->title,
	short_name: 'App',
	description: %app->description,
	start_url: '/',
	scope: '/',
	display: 'standalone',
	orientation: 'any',
	theme_color: %app->themeColor,
	background_color: '#0e131b',
	icons: [
		obj(src: '/icons/app-192.png', sizes: '192x192', type: 'image/png', purpose: 'any'),
		obj(src: '/icons/app-512.png', sizes: '512x512', type: 'image/png', purpose: 'any'),
		obj(src: '/icons/app-192.png', sizes: '192x192', type: 'image/png', purpose: 'maskable'),
		obj(src: '/icons/app-512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable'),
	],
)

该资源以 application/manifest+json 的形式提供 GET /manifest.json,并设置了短期缓存,而 %manifest->head 为您的 head view 渲染 <link rel="manifest"> 标签。由于 body 是一个懒加载的 prop,它在请求时读取实时应用状态(每种语言的 name,当前主题颜色)。

一个拥有多个 manifest 的应用(专业版,按部分的 PWA)保持自己的 routes,并通过相同的助手提供每个 body;当没有声明 %manifest.body 时,资源的默认 route 会让位:

route GET pro manifest.json => manifest::output(manifests::body(pro: true))

两个主体字段决定了可安装性:display: 'standalone' 和一对可遮罩图标是使 Chrome 和 Safari 提供安装提示的关键。没有这两者,清单是有效的,但应用程序永远无法变得可安装。同时,明确设置 id'/'):它将已安装的身份锚定到源,因此稍后更改 start_url 不会将已安装的应用程序变成另一个应用程序。

24.2: 服务工作者

可安装也意味着具备离线功能。通过一个 route 提供 worker 本身,以便其缓存名称可以携带应用版本,在每次部署时使旧缓存失效:

route GET sw.js {
	%res->header('Cache-Control', 'no-cache')
	output(strtr((string)file_get_contents(data.'sw.js.tpl'), ['{{VERSION}}' => %app->version]), type: 'text/javascript')
}

output() 是通用的响应助手:它设置内容类型和 Content-Length,自动对数组进行 JSON 编码,并处理下载的 Content-Disposition。仅在 output() 无法覆盖的情况下直接使用 %res->,例如这里的额外 Cache-Control 头。

模板在安装时缓存应用程序外壳,在激活时丢弃过期缓存,并在获取时优先使用网络进行导航,同时提供离线回退到缓存的外壳:

const CACHE = 'app-{{VERSION}}'
const SHELL = ['/', '/app.js?{{VERSION}}', '/app.css?{{VERSION}}', '/manifest.json']

self.addEventListener('install', e => {
	e.waitUntil(caches.open(CACHE).then(c => c.addAll(SHELL)).then(() => self.skipWaiting()))
})
self.addEventListener('activate', e => {
	e.waitUntil(caches.keys()
		.then(keys => Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k))))
		.then(() => self.clients.claim()))
})
self.addEventListener('fetch', e => {
	if (e.request.method !== 'GET') return
	if (e.request.mode === 'navigate') return e.respondWith(fetch(e.request).catch(() => caches.match('/')))
})

课程。 一个通用的 fetch 处理程序会破坏 SPA 引擎。Phlo 的 .async 导航直接从 apply() 响应中读取和写入 DOM;它从不期望返回完整的 HTML 页面。Cache.match 忽略头部,因此如果 worker 为每个对 / 的请求提供缓存的 / 外壳,异步请求将收到一个完整的 HTML 文档,而它期望的是一个 DOM 补丁,从而导致页面自身损坏。通过检查引擎在每个异步调用中发送的头部,明确排除异步流量: ```js if (e.request.headers.get('X-Requested-With') === 'phlo') return


不使用 `respondWith` 的情况下,浏览器会正常处理请求,不使用缓存。相同的原则适用于任何必须始终实时访问网络的端点:WebSocket 升级、字节范围媒体请求、任何缓存会拦截并静默损坏的内容。

早期注册它:

if ('serviceWorker' in navigator) navigator.serviceWorker.register('/sw.js')

24.3: Capacitor 添加了什么

PWA 涵盖了安装到主屏幕、离线外壳和推送通知(如果你添加的话),所有这些都来自同一个部署。 Capacitor 解决了浏览器标签无法做到的事情:应用商店列表,以及后台浏览器标签无法可靠访问的本地 API,例如后台地理定位、在操作系统强制挂起后台标签时仍能存活的唤醒锁,或本地文本转语音。

Capacitor 不会将你的应用程序的副本打包到二进制文件中。它指向一个轻量级的本地外壳,指向你正在运行的网站:

{
	"appId": "com.example.app",
	"appName": "App",
	"webDir": "www",
	"server": { "url": "https://app.example.com", "cleartext": false }
}

二进制文件是一个 WebView,加载您已实时部署的 Phlo 应用:无需单独的移动构建管道来保持同步,也没有过时的捆绑资产。以正常方式发布更改(请参见部署章节),每个已安装的副本在下次启动时都会自动更新,完全像一个网站。

根据需要添加平台和原生插件:

npm install @capacitor/core @capacitor/android @capacitor/geolocation
npx cap add android
npx cap sync
npx cap open android

从您的 Phlo 前端脚本中调用本地插件,就像调用任何其他浏览器 API 一样:这是页面在本地外壳中运行时可以有条件地访问的另一种功能。

import { Geolocation } from '@capacitor/geolocation'
const pos = await Geolocation.getCurrentPosition()

npx cap open android 在 Android Studio 中打开生成的项目,以进行商店签名构建。对于 iOS,cap add ios 的路径相同,前提是有 Mac 和 Xcode;配置和插件的故事是相同的,只有本地项目有所不同。

24.4: 一个应用,三种形状

一个 Phlo 应用首先是一个网站。它通过清单和服务工作者变得可安装,没有构建步骤的变化。它通过 Capacitor 成为一个商店列表,没有应用的分叉,没有第二个代码库需要保持同步,只是一个指向相同部署的薄壳,以及在浏览器确实无法跟随的地方桥接的小型原生插件。浏览器标签、已安装的 PWA 和原生二进制文件是同一个应用在一个分发光谱上的三个点,而不是三个需要维护的东西。

我们使用必要的cookie来使该网站正常工作。在您的许可下,我们还使用分析工具来改善网站。