Quasar CLI with Webpack - @quasar/app-webpack

为 SSR 编写通用的代码

编写通用(universal)或者叫同构(isomorphic)的代码是指编写的代码可以同时运行在服务端和客户端。但是由于用例和平台 API 的差异,我们的代码在不同的环境中运行时的行为不会完全相同。因此,下面我们将讨论您需要注意的关键事项。

服务端的响应性

在客户端渲染的应用中,每个用户都在自己的浏览器上有一个单独的、新的应用实例,在服务端渲染时,我们也希望如此:每个请求可以得到一个新的,独立的应用实例,从而不会发生跨请求状态污染。(如果我们用单个用户特定的数据对共享的单例状态进行修改,那么这个状态可能会意外地泄露给另一个用户的请求。我们把这种情况称为跨请求状态污染。)。

由于实际的渲染过程中需要明确指出渲染所需要使用的数据,因此我们还将在服务端进行“预先提取”数据(pre-fetching) - 这意味着当开始渲染时,我们的应用程序状态已经确定下来了,所以响应式数据在服务端是不必要的,默认情况下它是被禁用的。同时,在服务端禁用响应式数据也避免了性能消耗。

组件生命周期钩子

因为没有任何动态更新,所以在所有的 Vue 生命周钩子中,只有beforeCreatecreated 会在 SSR 的服务端被调用,也就是说,写在其他的生命周期(比如beforeMountmounted)中的代码只会在客户端被执行。

另外需要注意的是,您应该避免在beforeCreatecreated生命周期中使用会产生全局副作用的代码,这类副作用的常见例子是使用 setInterval 设置定时器。 我们可能会在客户端特有的代码中设置定时器,然后在 onBeforeUnmountonUnmounted 中清除。然而,由于 unmount 钩子不会在 SSR 期间被调用,所以定时器会永远存在。为了避免这种情况,请将含有副作用的代码放到 onMountedbeforeMount 中。

避免使用有状态的单例 Avoid Stateful Singletons

当我们写只在客户端的代码时,我们习惯了我们的代码总是在一个崭新的上下文环境中运行,然而一个 nodejs 服务是一个长时间运行的进程,当我们的代码生效后它会被驻留在内存中,这意味着如果您创建了一个单例对象,它会被分享到每个请求中。

所以 Quasar CLI 会为每个请求创建一个新的根 Vue 实例、它带有新的 Router 和新的 Store 实例。这类似于每个用户在自己的浏览器中使用应用程序的新实例。如果我们跨多个请求使用一个共享实例,就很容易导致跨请求状态污染。

与直接创建 Router 和 Store 实例不同,您可以公开一个工厂函数,它可以重复执行,为每个请求创建新的应用程序实例:

// src/router/index.js
export default function (/* { store, ssrContext } */) {
  const Router = new VueRouter({...})
  return Router
}
// src/store/index.js
export default function (/* { ssrContext } */) {
  const Store = new Vuex.Store({...})
  return Store
}

如果您在使用 Vuex modules 不要忘记将 state 作为一个函数导出,否则将会创建一个单例对象。

// src/store/myModule/state.js
export default () => ({
  ...
})

访问平台特有 API

通用代码不能访问平台特有的 API,如果您的代码直接使用了浏览器特有的全局变量,比如 windowdocument,他们会在 Node.js 运行时报错,反过来也一样。

对于在服务器和客户端之间共享,但使用了不同的平台 API 的任务,建议将平台特定的实现封装在一个通用的 API 中,或者使用能为您做这件事的库。例如您可以使用 Axios 在服务端和客户端使用相同的 API。 对于浏览器特有的 API,通常的方法是在仅客户端特有的生命周期钩子中惰性地访问它们,例如 onMounted。

Boot 文件

请注意,如果一个第三方库编写时没有考虑到通用性,那么要将它集成到一个 SSR 应用中可能会很棘手。您或许可以通过模拟一些全局变量来让它工作,但这只是一种 hack 手段并且可能会影响到其他库的环境检测代码。

当您通过 boot 文件添加一个第三方的库到您的项目中时,请考虑它是否可以同时在服务器和客户端上运行。如果它需要只在服务器上运行或只在客户端运行,那么请在quasar.config.js中指定它:

// quasar.config.js
return {
  // ...
  boot: [
    'some-boot-file', // 同时运行在服务端和客户端
    { path: 'some-other', server: false } // 只运行在客户端
    { path: 'third', client: false } //只运行在服务端
  ]
}

数据预取 Data Pre-Fetching and State

在 SSR 期间,我们实际上是在渲染应用程序的一个“快照”,所以如果应用程序依赖于一些异步数据,这些数据需要在我们开始渲染过程之前预取和解析。

Quasar CLI 提供了 PreFetch 特性可以帮您解决这个问题,请花费一些时间阅读此页面。


此页面中的部分内容引用自:vue 官文档 SSR 开发指南