为什么捐赠
API 浏览器
升级指南
NEW!
quasar.config 文件
将项目转换为 CLI with Vite
浏览器兼容性
支持 TypeScript
目录结构
命令列表
CSS 预处理器
路由
延迟加载 - 代码分割
处理资产
引导文件
预取功能
API 代理
处理 Vite
处理 process.env
使用 Pinia 进行状态管理
使用 Vuex 进行状态管理
代码检查器
测试 & 审计
开发移动应用程序
Ajax 请求
向公众开放开发服务器
使用 Vite 的 Quasar CLI - @quasar/app-vite
SSR Web 服务器

请注意,您生成的 /src-ssr 包含一个名为 server.js 的文件。此文件定义了您的 SSR Web 服务器是如何创建、管理和提供的。您可以开始侦听端口或为您的无服务器基础架构提供处理程序。这取决于您。

解剖

/src-ssr/server.[js|ts] 文件是一个简单的 JavaScript/Typescript 文件,它启动您的 SSR Web 服务器并定义 Web 服务器如何启动和处理请求,以及它导出了什么(如果有导出)。

警告

/src-ssr/server.[js|ts] 文件用于 DEV 和 PROD,因此请谨慎配置它。要区分这两种状态,可以使用 process∙env∙DEVprocess∙env∙PROD

/**
 * More info about this file:
 * https://v2.quasar.dev/quasar-cli-vite/developing-ssr/ssr-webserver
 *
 * Runs in Node context.
 */

/**
 * Make sure to yarn/npm/pnpm/bun install (in your project root)
 * anything you import here (except for express and compression).
 */
import express from 'express'
import compression from 'compression'

/**
 * Create your webserver and return its instance.
 * If needed, prepare your webserver to receive
 * connect-like middlewares.
 *
 * Should NOT be async!
 */
export function create (/* { ... } */) {
  const app = express()

  // attackers can use this header to detect apps running Express
  // and then launch specifically-targeted attacks
  app.disable('x-powered-by')

  // place here any middlewares that
  // absolutely need to run before anything else
  if (process.env.PROD) {
    app.use(compression())
  }

  return app
}

/**
 * You need to make the server listen to the indicated port
 * and return the listening instance or whatever you need to
 * close the server with.
 *
 * The "listenResult" param for the "close()" definition below
 * is what you return here.
 *
 * For production, you can instead export your
 * handler for serverless use or whatever else fits your needs.
 */
export async function listen ({ app, port, isReady, ssrHandler }) {
  await isReady()
  return await app.listen(port, () => {
    if (process.env.PROD) {
      console.log('Server listening at port ' + port)
    }
  })
}

/**
 * Should close the server and free up any resources.
 * Will be used on development only when the server needs
 * to be rebooted.
 *
 * Should you need the result of the "listen()" call above,
 * you can use the "listenResult" param.
 *
 * Can be async.
 */
export function close ({ listenResult }) {
  return listenResult.close()
}

const maxAge = process.env.DEV
  ? 0
  : 1000 * 60 * 60 * 24 * 30

/**
 * Should return middleware that serves the indicated path
 * with static content.
 */
export function serveStaticContent (path, opts) {
  return express.static(path, {
    maxAge,
    ...opts
  })
}

const jsRE = /\.js$/
const cssRE = /\.css$/
const woffRE = /\.woff$/
const woff2RE = /\.woff2$/
const gifRE = /\.gif$/
const jpgRE = /\.jpe?g$/
const pngRE = /\.png$/

/**
 * Should return a String with HTML output
 * (if any) for preloading indicated file
 */
export function renderPreloadTag (file) {
  if (jsRE.test(file) === true) {
    return `<link rel="modulepreload" href="${file}" crossorigin>`
  }

  if (cssRE.test(file) === true) {
    return `<link rel="stylesheet" href="${file}">`
  }

  if (woffRE.test(file) === true) {
    return `<link rel="preload" href="${file}" as="font" type="font/woff" crossorigin>`
  }

  if (woff2RE.test(file) === true) {
    return `<link rel="preload" href="${file}" as="font" type="font/woff2" crossorigin>`
  }

  if (gifRE.test(file) === true) {
    return `<link rel="preload" href="${file}" as="image" type="image/gif">`
  }

  if (jpgRE.test(file) === true) {
    return `<link rel="preload" href="${file}" as="image" type="image/jpeg">`
  }

  if (pngRE.test(file) === true) {
    return `<link rel="preload" href="${file}" as="image" type="image/png">`
  }

  return ''
}

提示

请记住,无论 listen() 函数返回什么(如果有),都会从您的构建 dist/ssr/index.js 中导出。如果您需要无服务器架构,可以返回您的 ssrHandler。

参数

export function <functionName> ({
  app, port, isReady, ssrHandler,
  resolve, publicPath, folders, render, serve
}) => {

详细说明对象

{
  app,     // Expressjs app instance (or whatever you return from create())

  port,    // on production: process∙env∙PORT or quasar.config file > ssr > prodPort
           // on development: quasar.config file > devServer > port

  isReady, // Function to call returning a Promise
           // when app is ready to serve clients

  ssrHandler, // Prebuilt app handler if your serverless service
              // doesn't require a specific way to provide it.
              // Form: ssrHandler (req, res, next)
              // Tip: it uses isReady() under the hood already

  // all of the following are the same as
  // for the SSR middlewares (check its docs page);
  // normally you don't need these here
  // (use a real SSR middleware instead)
  resolve: {
    urlPath(path)
    root(arg1, arg2),
    public(arg1, arg2)
  },
  publicPath, // String
  folders: {
    root,     // String
    public    // String
  },
  render(ssrContext),
  serve: {
    static(path, opts),
    error({ err, req, res })
  }
}

使用

警告

  • 如果您从 node_modules 中导入任何内容,请确保该软件包在 package.json > “dependencies” 中指定,而不是在“devDependencies” 中指定。
  • 通常这不是添加中间件的地方(但您可以这样做)。使用 SSR 中间件 添加中间件。您也可以配置 SSR 中间件,使其仅在开发环境或生产环境中运行。

替换 express.js

您可以用任何其他兼容 connect API 的服务器替换默认的 Express.js Node 服务器。只需确保首先 yarn/npm 安装其软件包。

src-ssr/server.[js|ts]

import connect from 'connect'

export function create (/* { ... } */) {
  const app = connect()

  // place here any middlewares that
  // absolutely need to run before anything else
  if (process.env.PROD) {
    app.use(compression())
  }

  return app
}

监听端口

这是在 Quasar CLI 项目中添加 SSR 支持时获得的默认选项。它开始监听配置的端口(process∙env∙PORT 或 quasar.config 文件 > ssr > prodPort)。

src-ssr/server.[js|ts]

export async function listen ({ app, port, isReady }) {
  await isReady()
  return await app.listen(port, () => {
    if (process.env.PROD) {
      console.log('Server listening at port ' + port)
    }
  })
}

无服务器

如果您有无服务器基础设施,则通常需要导出一个处理程序,而不是开始监听端口。

假设您的无服务器服务要求您

module.exports.handler = __your_handler__

那么您需要做的是

src-ssr/server.[js|ts]

export async function listen ({ app, port, ssrHandler }) {
  if (process.env.DEV) {
    await isReady()
    return await app.listen(port, () => {
      if (process.env.PROD) {
        console.log('Server listening at port ' + port)
      }
    })
  }
  else { // in production
    // "ssrHandler" is a prebuilt handler which already
    // waits for all the middlewares to run before serving clients

    // whatever you return here is equivalent to module.exports.<key> = <value>
    return { handler: ssrHandler }
  }
}

请注意,提供的 ssrHandler 是一个函数,形式为:(req, res, next) => void。如果您需要导出形式为 (event, context, callback) => void 的处理程序,那么您很可能需要使用 serverless-http 软件包(见下文)。

示例:serverless-http

您需要手动 yarn/npm 安装 serverless-http 软件包。

src-ssr/server.[js|ts]

import serverless from 'serverless-http'

export async function listen (({ app, port, ssrHandler }) => {
  if (process.env.DEV) {
    await isReady()
    return await app.listen(port, () => {
      // we're ready to serve clients
    })
  }
  else { // in production
    return { handler: serverless(ssrHandler) }
  }
})

示例:Firebase 函数

src-ssr/server.[js|ts]

import * as functions from 'firebase-functions'

export async function listen (({ app, port, ssrHandler }) => {
  if (process.env.DEV) {
    await isReady()
    return await app.listen(port, () => {
      // we're ready to serve clients
    })
  }
  else { // in production
    return {
      handler: functions.https.onRequest(ssrHandler)
    }
  }
})