Fastify-vite。用SSR和客户端水化服务于Vite应用程序

1,265 阅读3分钟

Fastify是一个流行的Node.js的Web服务器框架,而Vite是一个构建工具--由Vue团队创建--截至最近,它提供了对服务器端渲染的试验性支持。将这两个工具连接在一起以获得服务器端的渲染和客户端的水化是很棘手的。

值得庆幸的是,有fastify-vite这个Fastify插件,可以让你更容易地在Fastify应用中使用Vite的所有优点。

在这篇博文中,我将展示如何开始使用fastify-vite,并解释它如何处理服务器端的渲染和资产捆绑。请记住,在写这篇文章的时候,fastify-vite的README说这个项目是 "高度实验性的,还没有准备好用于生产",所以不要把这个代码用于重要的生产应用

这篇博文假设你对Fastify语法Vue单文件组件有基本了解。

什么是fastify-vite?

首先,你需要安装一些依赖项。下面是一个最小的package.json ,以开始使用fastify-vite。你需要同时安装fastify-vite和fastify-vit-vue,因为fastify-vite被设计为与几个不同的前端框架一起工作。

{
  "dependencies": {
    "fastify": "3.17.0",
    "fastify-vite": "2.2.0-beta.5",
    "fastify-vite-vue": "2.2.0-beta.5"
  }
}

你需要创建六个文件,以便用fastify-vite建立一个最小的应用程序。

  1. server.js
  2. main.js
  3. base.vue
  4. routes.js
  5. views/index.vue
  6. entry/server.js

你可以在GitHub上找到完整的 "Hello, World "应用程序。应用程序的主要入口是server.js ,它负责设置一个Fastify服务器并配置它使用fastify-vite。

对于 "Hello, World "应用程序来说,以下是你需要用Fastify做的所有事情。Fastify-vite负责导入和执行其余的代码。

const fastify = require('fastify')()
const fastifyVite = require('fastify-vite')
const fastifyViteVue = require('fastify-vite-vue')

async function main () {
  await fastify.register(fastifyVite, {
    api: true,
    root: __dirname, // <-- fastify-vite looks for `main.js` in this directory
    renderer: fastifyViteVue,
  })

  return fastify
}

if (require.main === module) {
  fastifyVite.app(main, (fastify) => {
    fastify.listen(3000, (err, address) => {
      if (err) {
        console.error(err)
        process.exit(1)
      }
      console.log(`Server listening on ${address}`)
    })
  })
}

module.exports = main

Fastify-vite 在root 目录中寻找一个main.js 文件,它负责创建和配置Vue 应用程序。特别是,main.js 需要设置Vue Router。下面是main.js 的内容。

import { createSSRApp } from 'vue'
import { createMemoryHistory, createRouter, createWebHistory } from 'vue-router'
import { createHead } from '@vueuse/head'
import routes from './routes'

import base from './base.vue'

export function createApp (ctx) {
  const app = createSSRApp(base)
  const head = createHead()
  const history = import.meta.env.SSR
    ? createMemoryHistory()
    : createWebHistory()
  const router = createRouter({ history, routes })
  app.use(router)
  app.use(head)
  return { ctx, app, head, router }
}

首先,注意server.js 使用CommonJSrequire() ,但main.js 使用ESMimport

这是设计好的!Vite使用esbuild捆绑和编译main.js ,所以main.js 可以在浏览器和Node中运行。

main.js 文件导入了两个新文件:routes.jsbase.vuebase.vue 文件负责设置Vue应用程序的基本布局,包括设置 [router-view](https://router.vuejs.org/api/#router-view)组件,如下图所示。

<template>
  <router-view v-slot="{ Component }">
    <h1>My App</h1>
    <component :key="route.path" :is="Component" />
  </router-view>
</template>

<script>
import { useRoute } from 'vue-router'
export default {
  setup () {
    return { route: useRoute() }
  }
}
</script>

接下来是routes.js 文件。这个文件负责为你的应用程序导出所有的路由。

main.js 文件将这些路由添加到Vue Router,而entry/server.js 将把这些路由添加到Fastify。fastify-vit-vue包有一个loadRoutes() 函数,可以将路由转换为Vue Router和Fastify的正确格式。

为了导入多个路由,你可以使用Vite的[globEager()](https://vitejs.dev/guide/features.html#glob-import)函数,但下面的例子使用了vanillaimport ,以避免引入不必要的新概念。

import { loadRoutes } from 'fastify-vite-vue/app'
import * as index from './views/index.vue'

export default loadRoutes({ './views/index.vue': index })

最后,entry/server.js 是fastify-vite的服务器端入口点。它负责创建一个服务器端的渲染函数和导出路由。你可以把这个函数看作是标准的模板。

import { createApp } from '../main'
import { createRenderFunction } from 'fastify-vite-vue/server'
import routes from '../routes'

export default {
  routes,
  render: createRenderFunction(createApp),
}

一旦你运行node ./server.js ,并访问 [http://localhost:3000/](http://localhost:3000/),你应该看到下面的HTML输出。

<div id="app">
  <h1>My App</h1>

  <h2>index.vue</h2>
</div>

用fastify-vite进行服务器端渲染

fastify-vite最大的好处是,它为你设置了服务器端渲染和完整的客户端水化功能。用Vue进行纯粹的服务器端渲染很容易,但要让客户端接上服务器离开的地方是很棘手的。

Fastify-vite可以确保从客户端到服务器的无缝衔接,而无需太多的额外工作。下面是使用fastify-vite的服务器端渲染的工作原理。

首先,你需要添加fastify-api插件。Fastify-vite与fastify-api配合得很好,允许你的Vue组件在服务器端渲染过程中提出API请求,因此你的路由不必向服务器提出API请求在 [mounted()](https://masteringjs.io/tutorials/vue/mounted)钩子来获取数据。

{
  "dependencies": {
    "fastify": "3.17.0",
    "fastify-api": "0.2.0",
    "fastify-vite": "2.2.0-beta.5",
    "fastify-vite-vue": "2.2.0-beta.5"
  }
}

接下来,你需要在server.js 文件中添加一个API端点。这个端点将回传它收到的任何msg 。很简单,但足以说明fastify-vite将能够渲染index.vue ,而无需向/echo

const fastify = require('fastify')()
const fastifyVite = require('fastify-vite')
const fastifyViteVue = require('fastify-vite-vue')
const fastifyApi = require('fastify-api')

async function main () {
  await fastify.register(fastifyApi)
  await fastify.register(fastifyVite, {
    api: true,
    root: __dirname,
    renderer: fastifyViteVue,
  })

  fastify.api(({ post }) => ({
    echo: post('/echo/:msg', ({ msg }, req, reply) => { // <-- new API endpoint `echo`
      reply.send({ msg })
    })
  }))

  return fastify
}

接下来,在index.vue 上添加数据获取功能。fastify-vit-vue包有一个漂亮的useHydration() 钩子。它采用一个负责加载初始数据的getData() 函数,并注入一个$api 对象,让你访问echo API端点。

注意:不要直接使用HTTP客户端,如Axios或fetch() 。使用$api ,因为fastify-vit-vue足够聪明,可以在服务器端跳过HTTP请求。

<template>
  <h2>{{ctx.$loading ? 'Loading...' : ctx.$data.result.body}}</h2>
</template>

<script>
import { useHydration } from 'fastify-vite-vue/client'

export const path = '/'

export async function getData ({ req, $api }) {
  return {
    result: await $api.echo({
      msg: 'Hello from API method',
    }),
  }
}
export default {
  async setup () {
    const ctx = await useHydration({ getData })
    return { ctx }
  }
}
</script>

为了使客户端应用程序完全水化,你需要在entry/client.js 中创建一个客户端入口点,使应用程序水化。下面是entry/client.js

import { createApp } from '../main'
import { hydrate } from 'fastify-vite-vue/client'
const { app, router } = createApp()

hydrate(app)

// Wait until router is ready before mounting to ensure hydration match
router.isReady().then(() => app.mount('#app'))

当你打开 [http://localhost:3000](http://localhost:3000)时,注意到index.vue 包含了来自echo 的响应,而没有向/echo 提出任何HTTP请求。

Index.vue Echo Response

然而,假设你添加了一个新的视图--views/hello.vue --它有一个Vue[router-link](https://masteringjs.io/tutorials/vue/router-link)返回到索引。

<template>
  <router-link to="/">Back to Home</router-link>
</template>

<script>
export const path = '/hello'
</script>

导航到 [http://localhost:3000/hello](http://localhost:3000/hello),点击该链接,仔细观察网络标签,当 [http://localhost:3000](http://localhost:3000)加载时的网络标签。你会看到getData() 现在向/echo API端点发出了一个单独的HTTP请求。

Separate HTTP Request

结论

Fastify-vite是一个强大的Fastify插件,它可以帮助你将Vite的强大功能整合到你的Fastify应用中,包括完全的服务器端渲染支持。

尽管fastify-vite还不能用于生产,但你应该在你正在开发的任何Fastify应用程序上亲自试用。它使服务器端的渲染和客户端的水化变得更加容易!

The postFastify-vite:用SSR和客户端水化服务Vite应用程序,首先出现在LogRocket博客上。