Skip to page content
Quasar CLI with Vite - @quasar/app-vite

SSR 配合 PWA 客户端接管

使用 Quasar CLI,您可以构建 SSR + PWA 终极组合的应用。为了给 SSR 构建启用 PWA,您需要先编辑 /quasar.config 文件:

/quasar.config file

return {
  // ...
  ssr: {
    /**
     * 是否由 PWA 接管客户端,否则为 SPA。
     * @default false
     */
    pwa?: boolean;

    /**
     * 使用 SSR+PWA 时,这是 PWA 客户端回退的
     * index html 文件名。仅用于生产环境。
     *
     * 不要使用 index.html 作为名称,否则会导致 SSR 出错!
     *
     * @default ssg.pwaOfflineHtmlFilename(如已配置),否则 'offline.html'
     */
    pwaOfflineHtmlFilename?: string;

    /**
     * 扩展/配置 Workbox GenerateSW 选项
     * 指定将应用在 `pwa > extendPWAGenerateSWOptions()` 之上的
     * Workbox 选项。
     *
     * https://developer.chrome.com/docs/workbox/the-ways-of-workbox/
     *
     * 可以是异步的。可以直接修改 "config" 参数,
     * 或返回一个新对象与默认配置合并。
     */
    extendSSRGenerateSWOptions?: (
      config: GenerateSWOptions
    ) => void | GenerateSWOptions | Promise<void | GenerateSWOptions>;

    /**
     * 扩展/配置 Workbox InjectManifest 选项
     * 指定将应用在 `pwa > extendPWAInjectManifestOptions()` 之上的
     * Workbox 选项。
     *
     * https://developer.chrome.com/docs/workbox/the-ways-of-workbox/
     *
     * 可以是异步的。可以直接修改 "config" 参数,
     * 或返回一个新对象与默认配置合并。
     */
    extendSSRInjectManifestOptions?: (
      config: InjectManifestOptions
    ) => void | InjectManifestOptions | Promise<void | InjectManifestOptions>;
  }
}

导航是如何被处理的

新客户端的第一个请求由 web 服务器处理,因此 SSR 提供初始页面内容,同时 service worker 也在此过程中被安装。

从那以后,service worker 会接管。在两种 Workbox 模式的默认配置下,Quasar 会把离线外壳注册为导航回退,它在你的 service worker 中最终会呈现为:

Generated service worker

registerRoute(
  new NavigationRoute(createHandlerBoundToURL('offline.html'), {
    denylist: [/* ... */]
  })
)

createHandlerBoundToURL() 直接从预缓存(precache)响应,从不接触网络,而 NavigationRoute 会匹配每一个导航请求。换句话说,此后每一次页面加载都会从缓存提供离线外壳,web 服务器根本不会被访问。这正是让应用秒开并能离线工作的原因,也是这里所说的「客户端接管」的含义。

离线外壳是一个客户端渲染的外壳,因此在这些页面加载中:

  • 没有服务端渲染的标记,也没有序列化状态,这意味着不会发生 store 水合
  • preFetch 特性 钩子会在客户端而非服务端运行
  • 任何只存在于服务端的东西(ssrContext、它的请求和响应对象、你 boot 文件中的服务端部分)都不会参与
TIP

这只涉及整页加载。应用内导航早已由客户端的 Vue Router 处理,而你的 API 调用仍会照常走网络。

让服务端渲染每一次导航

如果你希望 SSR web 服务器继续提供导航,而离线外壳只在网络不可用时才介入,那么请退出导航回退,改为把离线外壳用作某个网络策略的回退。

对于 GenerateSW Workbox 模式:

/quasar.config file

ssr: {
  pwa: true,

  extendSSRGenerateSWOptions (workboxConfig) {
    delete workboxConfig.navigateFallback
    delete workboxConfig.navigateFallbackDenylist

    workboxConfig.runtimeCaching = [
      ...(workboxConfig.runtimeCaching || []),
      {
        urlPattern: ({ request }) => request.mode === 'navigate',
        handler: 'NetworkOnly',
        options: {
          precacheFallback: { fallbackURL: 'offline.html' }
        }
      }
    ]
  }
}

对于 InjectManifest Workbox 模式,则改为编辑你自己的 service worker:

/src-pwa/sw/custom-sw file

import { NetworkOnly } from 'workbox-strategies'
import { PrecacheFallbackPlugin } from 'workbox-precaching'

// ...replaces the default NavigationRoute:
registerRoute(
  new NavigationRoute(
    new NetworkOnly({
      plugins: [
        new PrecacheFallbackPlugin({
          fallbackURL: import.meta.env.QUASAR_PWA_FALLBACK_HTML
        })
      ]
    }),
    {
      denylist: [
        new RegExp(import.meta.env.QUASAR_PWA_SERVICE_WORKER_REGEX),
        /workbox-(.)*\.js$/
      ]
    }
  )
)

此时导航又重新依赖网络,因此你可能还想启用 Workbox 的 导航预加载(navigationPreload: true),以避免每次导航都要承担 service worker 的启动耗时。

有关 PWA 的更多信息,请前往 PWA 简介 并阅读整个 PWA 指南章节。