vite ssr服务端渲染

阅读 Vue文档 这一章里有说过,vue是支持服务端渲染的。
通过createSSRApp创建vue组件实例,并使用renderToString将在服务器渲染好template并返回字符串结构,通过替换占位字符将渲染好的字符串输出到html上,这样的一个过程就实现了服务端渲染。
Vite 文档也提到了如何去渲染SSR并提供了相关示例

在这里插入图片描述
那么今天我们就按照官方给的示例来完成vue ssr的改造
使用Node Koa框架来做服务器,且使用vue全家桶(router,pinia)开发项目。
router配置这里需要注意,在服务器端路由模式为createMemoryHistory,在客户端则history or hash随意

const router = createRouter({
  routes,
  history: import.meta.env.SSR ? createMemoryHistory() : createWebHistory()
});

使用pinia则是为了预取数据,pinia文档中明确说明了支持SSR。而我们使用pinia则是在服务器端获取数据后 基于服务器时期和客户端时期是两个完全不同的环境,在客户端是没法访问到服务器时期的数据的。所以我们需要在服务器获取数据后保存到Pinia里,避免客户端再发起同样的请求。

在根目录的html文件里添加占位符(名字可以随意取 但是在server.js里replace替换的时候记得同步修改)
在这里插入图片描述
<!--preload-links--> 预加载的css style等资源
<!--pinia-state--> pinia里保存的数据
<!--ssr-outlet--> ssr渲染的节点
同时入口文件的地址应由main文件改成enter-client文件!!因为html是运行在浏览器里的,main文件下文已经改造成了通用逻辑,客户端真正的入口文件应该是enter-client。

开始

在这里插入图片描述

server.js

用于启动Node服务来实现服务端渲染,用到的Node模块有fs文件读取,path路径以及url处理,使用的node服务器则是Koa。

import Koa from "koa";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

const __dirname = path.dirname(fileURLToPath(import.meta.url));

const resolve = (p) => path.resolve(__dirname, p);

需要注意的是 要区分开发环境与生产环境。开发环境读取本地开发的代码,而生产环境则读取打包后的代码。

async function createServerApp(
  root = process.cwd(),
  isProd = process.env.NODE_ENV === "production"
) {
  const app = new Koa();
  const indexProd = isProd
    ? fs.readFileSync(resolve("./dist/client/index.html"), "utf-8")
    : fs.readFileSync(resolve("index.html"), "utf-8");
  const manifest = isProd
    ? fs.readFileSync(resolve("./dist/client/.vite/ssr-manifest.json"), "utf-8")
    : undefined;
  let vite;
  if (isProd) {
    // 压缩
    app.use((await import("koa-compress")).default());
    // 设置静态目录
    app.use(
      (await import("koa-static")).default(resolve("./dist/client"), {
        index: false,
      })
    );
  } else {
    // 开发环境
    vite = await (
      await import("vite")
    ).createServer({
      server: { middlewareMode: true },
      appType: "custom",
      // base,
    });
    app.use((await import("koa-connect")).default(vite.middlewares));
  }
  app.use(async (ctx) => {
    try {
      let template, render;
      if (isProd) {
        template = indexProd;
        render = (await import("./dist/server/entry-server.js")).render;
      } else {
        template = await vite.transformIndexHtml(ctx.originalUrl, indexProd);
        render = (await vite.ssrLoadModule("/src/entry-server.ts")).render;
      }
      const {
        html: appHtml,
        preloadLinks,
        piniaState,
      } = await render(ctx.originalUrl, manifest);
      const html = template
        .replace("<!--ssr-outlet-->", appHtml ?? "")
        .replace("<!--preload-links-->", preloadLinks ?? "")
        .replace("<!--pinia-state-->", piniaState ?? "");

      ctx.type = "text/html";
      ctx.body = html;
    } catch (e) {
      // 兜底 防止报错直接崩溃
      vite && vite.ssrFixStacktrace(e);
      ctx.status = 500;
      ctx.body = e.stack;
    }
  });
  return {
    app,
  };
}

开启服务

createServerApp().then(({ app }) => {
  app.listen(2000, () => {
    console.log(`[ssr server] run http://localhost:2000`);
  });
});

src 目录下改造:

main.js

导出通用的代码,前面提到的vue ssr是通过createSSRApp来创建实例。

这里的initialState就是服务器保存的对象
import.meta.env.SSR 则是vite自带的变量用于判断当前所处的环境

import { createSSRApp } from 'vue';
import App from './App.vue';
import router from './router';
import { createPinia } from 'pinia';
import './assets/styles';

export function createApp() {
  const app = createSSRApp(App);
  const store = createPinia();
  const initialState: {
    pinia: null | typeof store.state.value
  } = {
    pinia: null
  };
  app.use(router).use(store);
  if (import.meta.env.SSR) {
    initialState.pinia = store.state.value
  } else {
    store.state.value = window.__INITIAL_STATE__
  }
  return {
    app,
    router,
    store,
    initialState,
  }
}

entry-client.js

enter-client做的事情很简单,就是等路由处理好后挂载到页面上。

import { createApp } from './main';

const { app, router } = createApp();

router.isReady().then(() => app.mount('#app'))

entry-server.js

entry-server做的事情是调用renderToString于服务器环境去渲染好页面结构并返回字符串结构方便替换占位符。通过render方法把处理好的html节点,预渲染的资源以及pinia保存的数据return 出来。

import { renderToString } from 'vue/server-renderer';
import { createApp } from './main';
import { basename } from 'node:path';
import devalue from '@nuxt/devalue';

export async function render(path: string, manifest: any) {
  const { app, router, initialState } = createApp();
  router.push(path);
  await router.isReady();
  // 在这里预取数据并传回到pinia
  const ctx: any = {};
  const html = await renderToString(app, ctx);
  const preloadLinks = import.meta.env.PROD ? renderPreloadLinks(ctx.modules, manifest) : undefined;

  // https://pinia.vuejs.org/ssr/#state-hydration
  const piniaState = devalue(initialState.pinia)
  return {
    html,
    preloadLinks,
    piniaState,
  }
}

function renderPreloadLinks(modules: string[], manifest: any) {
  let links = ''
  const seen = new Set()
  modules.forEach((id) => {
    const files = manifest[id]
    if (files) {
      files.forEach((file: string) => {
        if (!seen.has(file)) {
          seen.add(file)
          const filename = basename(file)
          if (manifest[filename]) {
            for (const depFile of manifest[filename]) {
              links += renderPreloadLink(depFile)
              seen.add(depFile)
            }
          }
          links += renderPreloadLink(file)
        }
      })
    }
  })
  return links
}

function renderPreloadLink(file: string) {
  if (file.endsWith('.js')) {
    return `<link rel="modulepreload" crossorigin href="${file}">`
  } else if (file.endsWith('.css')) {
    return `<link rel="stylesheet" href="${file}">`
  } else if (file.endsWith('.woff')) {
    return ` <link rel="preload" href="${file}" as="font" type="font/woff" crossorigin>`
  } else if (file.endsWith('.woff2')) {
    return ` <link rel="preload" href="${file}" as="font" type="font/woff2" crossorigin>`
  } else if (file.endsWith('.gif')) {
    return ` <link rel="preload" href="${file}" as="image" type="image/gif">`
  } else if (file.endsWith('.jpg') || file.endsWith('.jpeg')) {
    return ` <link rel="preload" href="${file}" as="image" type="image/jpeg">`
  } else if (file.endsWith('.png')) {
    return ` <link rel="preload" href="${file}" as="image" type="image/png">`
  } else {
    // TODO
    return ''
  }
}

命令行配置

配置package.json 里的 scripts脚本命令

	"dev": "node server",
    "build": "npm run build:client && npm run build:server",
    "build:client": "vite build --ssrManifest --outDir dist/client",
    "build:server": "vite build --ssr src/entry-server.ts --outDir dist/server",
    "preview": "cross-env NODE_ENV=production node server",
    "start": "set NODE_ENV=production && node server"

通过dev命令启动开发环境
生产环境则需要先build构建完后调用start开启服务

本篇代码仓库地址 github仓库直达

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/452807.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

发送短信验证码

​​​​​​【短信验证码-快速报备签名】三网短信接口-短信-短信验证码-短信服务-三网短信接口-短信-三网短信【最新版】_商业智能_电商_金融-云市场-阿里云阿里云云市场提供 专注企业短信服务10年运营与技术积累&#xff0c;稳定、安全、快速。服务&#xff0c;建站服务&…

【word技巧】word文件如何设置打开密码?可以试试这两种方法!

Word文件想要设置密码打开文件&#xff0c;我们可以给文件设置一个打开密码&#xff0c;这样只有知道密码的人才能够打开查看文件&#xff0c;今天分享两个word文件设置打开密码的方法。 一、 打开word文档后&#xff0c;点击【文件】-【信息】-【保护文档】这里有很多选项&a…

excel统计分析——一元直线回归

参考资料&#xff1a;生物统计学 两个具有因果关系的协变量如果呈直线关系&#xff0c;可以用直线回归模型来分析两个变量的关系。直线回归&#xff08;linear regression&#xff09;是回归分析中最简单的类型&#xff0c;建立直线回归方程并经检验证明两个变量存在直线回归关…

Altium Designer怎么设置默认原理图纸张大小

Altium Designer怎么设置默认原理图纸张大小 绘制原理图时我们需要设置好原理图图纸大小&#xff0c;建议大家可以将默认原理图图纸设置为A3&#xff0c;A3图纸大小可以容纳下大部分原理图&#xff0c;这样就不用每次画原理图前去修改图纸大小&#xff0c;可以提高设计效率。 …

Redis底层数据结构之Hash

文章目录 1. Redis底层hash编码格式2. Redis 6源码分析3. Redis 7源码分析 1. Redis底层hash编码格式 在redis6中hash的编码格式分别是ziplist&#xff08;压缩列表&#xff09;和hashtable&#xff0c;但在redis7中hash的编码格式变为了listpack&#xff08;紧凑列表&#xf…

如何不依赖Unity直接解压unitypackage的内容

使用场景 我们都知道unity的资源导出是导出成.unitypackage文件,如果要里面的内容,得打开Unity,将unitypackage导入进去才能看到里面的内容。 但是很多时候我们下了几十个unitypackage资源包,又不清楚好不好用,而且导入之后编译特别慢,unity又不提供批量解压的功能,所…

好消息!电商平台订单API同步订单详情信息免申请审核调用指南!

淘宝开放平台订单类API 测试key获取 拼多多开放平台订单API列表 custom-自定义API操作 taobao.custom/pinduoduo.custom 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secretString是调用密钥api_nameString是API接口名称&…

day08-Mybatis入门

MyBatis 是一款优秀的 持久层 框架&#xff0c;用于简化 JDBC 的开发。 官网&#xff1a;https://mybatis.org/mybatis-3/zh/index.html 一、快速入门 1.1 Mybatis 操作数据库的步骤 准备工作(创建 springboot 工程、数据库表 user、实体类 User)引入 Mybatis 的相关依赖&…

基于Qt 和python 的自动升级功能

需求&#xff1a; 公司内部的一个客户端工具&#xff0c;想加上一个自动升级功能。 服务端&#xff1a; 1&#xff0c;服务端使用python3.7 &#xff0c;搭配 fastapi 和uvicorn 写一个简单的服务&#xff0c;开出一个get接口&#xff0c;用于客户端读取安装包的版本&#…

北京市办理大兴道路运输许可证所需条件及注意事项

尊敬的客户&#xff1a; 感谢您选择北京经典世纪集团有限公司作为您的信任合作伙伴。我们从多个角度&#xff0c;为您详细解析办理大兴道路运输许可证所需的条件及注意事项&#xff0c;以便您轻松高效地完成相关手续。&#xff08;游览器搜经典世纪胡云帅&#xff09;。 我们…

Android7.1 ANR error 弹窗处理

Android7.1 ANR error 弹窗处理 问题描述解决方法 郑重声明:本人原创博文&#xff0c;都是实战&#xff0c;均经过实际项目验证出货的 转载请标明出处:攻城狮2015 Platform: Rockchip OS:Android 7.1.2 Kernel: 3.10 问题描述 有时会用到第三方apk&#xff0c;内置到系统中&…

Linux从0到1——Linux环境基础开发工具的使用(上)

Linux从0到1——Linux环境基础开发工具的使用&#xff08;上&#xff09; 1. Linux软件包管理器yum1.1 yum介绍1.2 用yum来下载软件1.3 更新yum源 2. Linux编辑器&#xff1a;vi/vim2.1 vim的基本概念2.2 vim的基本操作2.3 vim正常模式命令集2.4 vim底行模式命令集2.5 视图模式…

【全志H616】-2 写一个自己的串口

【全志H616】-2 写一个自己的串口 1、基本命令 重启 sudo rebootLinux系统下一个文件夹的文件复制到另一个文件夹下 cp flags.c /home/user05/lab09/flags_revised.c //复制当前文件夹下的 flags.c 文件到 lab09 文件夹下flags_recised.c 文件cp oled_demo.c /home/orangep…

在图片上进行标记

文章目录 需求分析 需求 底图是一张图片&#xff0c;要在图上做标记&#xff0c;对标记的位置有交互行为鼠标滚顶页面&#xff0c;标记位置不发生变化页面发生缩放&#xff0c;标记位置不发生变化 分析 <template><divv-loading"loading"class"point-m…

什么是智慧公厕?对公共厕所智能实时监测管理控制,城市管理更高效智能

公共厕所一直以来都是城市管理的难题之一&#xff0c;但随着智慧科技的发展和应用&#xff0c;智慧公厕成为了解决这一问题的利器。智慧公厕是一种信息化的新型公共厕所&#xff0c;通过全面感知平台实时监测公共厕所的使用状态&#xff0c;并将数据转化为可视、可算、可管的数…

读取txt文件并统计每行最长的单词以及长度

读取txt文件并统计每行最长的单词以及长度 题目 在 D:\\documant.txt 文本中,文件中有若干行英文文本,每行英文文本中有若干个单词&#xff0c;每个单词不会跨行出现每行至多包含100个字符,要求编写一个程序,处理文件,分析各行中的单词,找到每行中的最长单词&#xff0c;分别…

互联网剧本杀小程序,如何创新发展提高收益

近年来&#xff0c;剧本杀深受年轻人的喜欢&#xff0c;一度成为了大众的社交方式&#xff0c;剧本杀为年轻人提供了一个全新的娱乐游戏和社交为一体的模式。 不过随着剧本杀市场入局的人越来越多&#xff0c;市场的发展也迎来了“拐点”&#xff0c;剧本杀逐渐趋向高质量发展…

190基于matlab的tfrSTFT时频分布图

基于matlab的tfrSTFT时频分布图&#xff0c;计算时间序列的STFT时频分布图&#xff0c;得到瞬时频率。通过GUI可以调节图像的展示样式。程序已调通&#xff0c;可直接运行。 190 STFT时频分布图 瞬时频率 能量谱 (xiaohongshu.com)

openGauss使用BenchmarkSQL进行性能测试(上)

一、前言 本文提供openGauss使用BenchmarkSQL进行性能测试的方法和测试数据报告。 BenchmarkSQL&#xff0c;一个JDBC基准测试工具&#xff0c;内嵌了TPC-C测试脚本&#xff0c;支持很多数据库&#xff0c;如PostgreSQL、Oracle和Mysql等。 TPC-C是专门针对联机交易处理系统…

软考高项总结:第16章采购管理(一)

一、管理基础 1、项目采购管理包括从项目团队外部采购或获取所需产品、服务或成果的各个过程。例如合同、订购单、协议备忘录(MOA)和服务水平协议(SLA)。被授权采购项目所需货物、服务的人员可以是项目团队、管理层或组织采购部的成员。 2、协议可以是合同、服务水平协议(SL…
最新文章