渐进式 Web 应用

编辑页面

了解如何为 Expo 网站添加渐进式 Web 应用支持。


For the complete documentation index, see llms.txt. Use this Use this file to discover all available pages.

渐进式 Web 应用(简称 PWA)是可以安装到用户设备上并可离线使用的网站。我们建议尽可能构建原生应用,因为它们具有最佳的离线支持,但对于桌面用户来说,PWA 也是一个很好的选择。

图标

Expo CLI 会根据 app.json 中的 web.favicon 字段自动生成 favicon.ico 文件。

{ "web": { "favicon": "./assets/favicon.png" } }

另外,你也可以在 public 目录中创建一个 favicon.ico 文件来手动指定图标。

清单文件

PWA 可以通过 manifest 文件进行配置,该文件描述应用的名称、图标和其他元数据。

1

public/manifest.json 中创建一个 PWA manifest:

{ "short_name": "Expo App", "name": "Expo Router Sample", "icons": [ { "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" }, { "src": "logo192.png", "type": "image/png", "sizes": "192x192" }, { "src": "logo512.png", "type": "image/png", "sizes": "512x512" } ], "start_url": ".", "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" }

2

logo192.pnglogo512.png 文件是在应用安装到用户设备上时会使用的图标。它们也应该添加到 public 目录中。

public
manifest.jsonPWA 清单
logo192.png192x192 图标
logo512.png512x512 图标

3

现在在你的 HTML 文件中链接 manifest。这里的方法取决于你网站的输出模式(在 app.json 中由 web.output 指示——默认值为 single)。

如果你使用的是单页应用,可以先在 public/index.html 中创建一个模板 HTML,然后在你的 HTML 文件中链接 manifest:

Terminal
npx expo customize public/index.html

然后在 <head> 标签中添加 manifest:

<link rel="manifest" href="/manifest.json" />

如果你使用静态渲染或服务端渲染,HTML 入口可以在 src/app/+html.tsx 中动态创建。这里我们通过向 <head> 组件添加一个 <link> 标签来链接 manifest:

src/app/+html.tsx
import { ScrollViewStyleReset } from 'expo-router/html'; import type { PropsWithChildren } from 'react'; // 此文件仅用于 Web,并用于在静态渲染期间为每个 // Web 页面配置根 HTML。 // 此函数中的内容仅在 Node.js 环境中运行, // 无法访问 DOM 或浏览器 API。 export default function Root({ children }: PropsWithChildren) { return ( <html lang="en"> <head> <meta charSet="utf-8" /> <meta httpEquiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> {/* 链接 PWA manifest 文件。 */} <link rel="manifest" href="/manifest.json" /> {/* 禁用 Web 上的 body 滚动。这会让 ScrollView 组件的行为更接近原生端。 不过,在移动端 Web 上,保留 body 滚动通常也很有用。如果你想启用它,请删除这一行。 */} <ScrollViewStyleReset /> {/* 添加任何你希望在 Web 上全局可用的额外 <head> 元素... */} </head> <body>{children}</body> </html> ); }

Service workers

Service worker 主要用于为网站添加离线支持。Google 的 Workbox 是为网站添加 service worker 的最佳方式。请参考 使用 Workbox CLI 的指南,并且其中凡是提到 “build script(构建脚本)” 的地方,都改为使用 npx expo export -p web

添加 service worker 时请务必小心,因为它们已知会在 Web 上引发意外行为。如果你不小心发布了一个激进缓存你网站的 service worker,用户将无法轻松请求更新。若想获得最佳的移动端离线体验,请使用 Expo 创建原生应用。与带有 service worker 的网站不同,原生应用可以通过应用商店更新来清除缓存体验。这类似于重置用户的原生浏览器(如果 service worker 足够激进,他们可能不得不这样做)。更多信息请参见 为什么 service worker 不是最佳选择

例如,下面是一个设置 Workbox 的可能流程:

1

使用以下命令创建一个新项目:

Terminal
npm create expo -t tabs my-app

cd my-app

2

现在在 HTML 文件中注册 service worker。这里的方法取决于你网站的输出模式(在 app.json 中由 web.output 指示——默认值为 single)。

接下来在根 index.html 中添加一个 service worker 注册脚本。

如果尚不存在,先在 public/index.html 中创建一个模板 HTML:

Terminal
npx expo customize public/index.html

然后在 <head> 标签中创建 service worker 注册脚本:

<script> if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker .register('/sw.js') .then(registration => { console.log('Service Worker registered with scope:', registration.scope); }) .catch(error => { console.error('Service Worker registration failed:', error); }); }); } </script>

接下来,为应用创建一个根 HTML 文件并添加 service worker 注册脚本:

src/app/+html.tsx
import { ScrollViewStyleReset } from 'expo-router/html'; import type { PropsWithChildren } from 'react'; // 此文件仅用于 Web,并用于在静态渲染期间为每个 // Web 页面配置根 HTML。 // 此函数中的内容仅在 Node.js 环境中运行, // 无法访问 DOM 或浏览器 API。 export default function Root({ children }: PropsWithChildren) { return ( <html lang="en"> <head> <meta charSet="utf-8" /> <meta httpEquiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> {/* 初始化 service worker。 */} <script dangerouslySetInnerHTML={{ __html: sw }} /> {/* 禁用 Web 上的 body 滚动。这会让 ScrollView 组件的行为更接近原生端。 不过,在移动端 Web 上,保留 body 滚动通常也很有用。如果你想启用它,请删除这一行。 */} <ScrollViewStyleReset /> {/* 添加任何你希望在 Web 上全局可用的额外 <head> 元素... */} </head> <body>{children}</body> </html> ); } const sw = ` if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/sw.js').then(registration => { console.log('Service Worker registered with scope:', registration.scope); }).catch(error => { console.error('Service Worker registration failed:', error); }); }); } `;

3

现在在运行向导之前先构建网站:

Terminal
npx expo export -p web

4

运行向导命令,将 dist 设为应用的根目录,并对其他所有选项使用默认值:

Terminal
npx workbox-cli wizard

? What is the root of your web app (that is which directory do you deploy)? dist/? Which file types would you like to precache? js, html, ttf, ico, json? Where would you like your service worker file to be saved? dist/sw.js? Where would you like to save these configuration options? workbox-config.js? Does your web app manifest include search parameter(s) in the 'start_url', other than 'utm_' or 'fbclid' (like '?source=pwa')? No

5

最后,运行 npx workbox-cli generateSW workbox-config.js 以生成 service worker 配置。

从现在开始,你可以在 package.json 中添加一个构建脚本,以正确顺序运行这两个脚本:

package.json
{ "scripts": { "build:web": "expo export -p web && npx workbox-cli generateSW workbox-config.js" } }

6

如果你托管了你的网站并使用 Chrome 访问,可以在 Chrome DevTools 中通过进入 Application > Service Workers 来检查 service worker。