SSR初体验-结合Vue3全家桶

news2025/1/18 12:02:20

SSR初体验

基础搭建

安装依赖
在这里插入图片描述
先开启一个服务器

let express = require("express");

let server = express();

server.get("/", (req, res) => {
	res.send(
		`
			Hello Node Server
		`
	);
});

server.listen(3000, () => {
	console.log("start node server on 3000");
});

配置wp.config.js

let path = require("path");
let nodeExternals = require("webpack-node-externals");

module.exports = {
	target: "node", //fs path就不会打包了
	mode: "development",
	entry: "./src/server/index.js",
	output: {
		filename: "server_bundle.js",
		path: path.resolve(__dirname, "../build/server"),
	},
	externals: [nodeExternals()], //排除掉node_module中的包
};

引入vue

在这里插入图片描述
创建App.vue

<template>
	<div class="app" style="border: 1px solid red">
		<h2>Vue3 app</h2>
		<div>{{ count }}</div>
		<button @click="addCounter">+1</button>
	</div>
</template>

<script setup>
	import { ref } from "vue";
	const count = ref(100);
	function addCounter() {
		count.value++;
	}
</script>

app.js

import { createSSRApp } from "vue";

import App from "./App.vue";

//这里为什么写一个函数来返回app实例
//通过函数返回app实例 可以保证每个请求都会返回一个新的app实例 避免跨请求状态的污染
export default function createApp() {
	let app = createSSRApp(App);
	return app;
}

index.js引入vue

let express = require("express");
let server = express();
import createApp from "../app";
import { renderToString } from "@vue/server-renderer";

server.get("/", async (req, res) => {
	let app = createApp();
	let appStringHtml = await renderToString(app);
	res.send(
		`
		<!DOCTYPE html>
		<html lang="en">
			<head>
				<meta charset="UTF-8" />
				<meta http-equiv="X-UA-Compatible" content="IE=edge" />
				<meta name="viewport" content="width=device-width, initial-scale=1.0" />
				<title>Document</title>
			</head>
			<body>
				<h1>Vue Serve Side Render</h1>
				<div id="app">
					${appStringHtml}
				</div>
			</body>
		</html>
		`
	);
});

server.listen(3000, () => {
	console.log("start node server on 3000");
});

修改server.config.js

let path = require("path");
let nodeExternals = require("webpack-node-externals");
let { VueLoaderPlugin } = require("vue-loader/dist/index");

module.exports = {
	target: "node", //fs path就不会打包了
	mode: "development",
	entry: "./src/server/index.js",
	output: {
		filename: "server_bundle.js",
		path: path.resolve(__dirname, "../build/server"),
	},
	module: {
		rules: [
			{
				test: /.\js$/,
				loader: "babel-loader",
				options: {
					presets: ["@babel/preset-env"],
				},
			},
			{
				test: /\.vue$/,
				loader: "vue-loader",
			},
		],
	},
	plugins: [new VueLoaderPlugin()], //对vue文件的打包
	resolve: {
		//添加了这些扩展明名之后 项目中导包如下的扩展名文件就不需要编写文件的后缀
		extensions: [".js", ".json", ".wasm", ".jsx", ".vue"],
	},
	externals: [nodeExternals()], //排除掉node_module中的包
};

npm run build:server 打包
npm run start 开启
访问http://localhost:3000/
在这里插入图片描述但此时是静态页面 无法交互 需要激活

hydration

import { createApp } from "vue";
import App from "../App.vue";

let app = createApp(App);

app.mount("#app");

client.config.js

let path = require("path");
let { VueLoaderPlugin } = require("vue-loader");
let { DefinePlugin } = require("webpack");

module.exports = {
	target: "web",
	mode: "development",
	entry: "./src/client/index.js",
	output: {
		filename: "client_bundle.js",
		path: path.resolve(__dirname, "../build/client"),
	},
	module: {
		rules: [
			{
				test: /.\js$/,
				loader: "babel-loader",
				options: {
					presets: ["@babel/preset-env"],
				},
			},
			{
				test: /\.vue$/,
				loader: "vue-loader",
			},
		],
	},
	plugins: [
		new VueLoaderPlugin(),
		new DefinePlugin({
			__VUE_OPTIONS_API__: false,
			__VUE_PROD_DEVTOOLS__: false,
		}),
	], //对vue文件的打包
	resolve: {
		//添加了这些扩展明名之后 项目中导包如下的扩展名文件就不需要编写文件的后缀
		extensions: [".js", ".json", ".wasm", ".jsx", ".vue"],
	},
};


index.js

let express = require("express");
let server = express();
import createApp from "../app";
import { renderToString } from "@vue/server-renderer";

//部署静态资源
server.use(express.static("build"));

server.get("/", async (req, res) => {
	let app = createApp();
	let appStringHtml = await renderToString(app);
	res.send(
		`
		<!DOCTYPE html>
		<html lang="en">
			<head>
				<meta charset="UTF-8" />
				<meta http-equiv="X-UA-Compatible" content="IE=edge" />
				<meta name="viewport" content="width=device-width, initial-scale=1.0" />
				<title>Document</title>
			</head>
			<body>
				<h1>Vue Serve Side Render</h1>
				<div id="app">
					${appStringHtml}
				</div>
				<script src="/client/client_bundle.js"></script>
			</body>
		</html>
		`
	);
});

server.listen(3000, () => {
	console.log("start node server on 3000");
});

在这里插入图片描述
npm run build:server 打包
npm run build:client打包
npm run start 开启
访问http://localhost:3000/ 可以交互

在这里插入图片描述

结合vue-router

在这里插入图片描述

router/index.js

import { createRouter } from "vue-router";

const routes = [
	{
		path: "/",
		component: () => import("../views/home.vue"),
	},
	{
		path: "/about",
		component: () => import("../views/about.vue"),
	},
];

export default function (history) {
	return createRouter({
		history,
		routes,
	});
}

App.vue

<template>
	<div class="app" style="border: 1px solid red">
		<h2>Vue3 app</h2>
		<div>{{ count }}</div>
		<button @click="addCounter">+1</button>
		<div>
			<router-link to="/">
				<button>home</button>
			</router-link>
			<router-link to="/about">
				<button>about</button>
			</router-link>
		</div>
		<!-- 路由的占位 -->
		<router-view></router-view>
	</div>
</template>

<script setup>
	import { ref } from "vue";
	const count = ref(100);
	function addCounter() {
		count.value++;
	}
</script>

views/home.vue

<template>
	<div class="app" style="border: 1px solid green; margin: 10px">
		<h2>home</h2>
		<div>{{ count }}</div>
		<button @click="addCounter">+1</button>
	</div>
</template>

<script setup>
	import { ref } from "vue";
	const count = ref(200);
	function addCounter() {
		count.value++;
	}
</script>

client/index.js

import { createApp } from "vue";
import App from "../App.vue";
import createRouter from "../router";
import { createWebHistory } from "vue-router";

let app = createApp(App);

//安装路由插件
let router = createRouter(createWebHistory());
app.use(router);
router.isReady().then(() => {
	app.mount("#app");
});

server.js

let express = require("express");
let server = express();
import createApp from "../app";
import { renderToString } from "@vue/server-renderer";
import createRouter from "../router";
// 内存路由 -> node用
import { createMemoryHistory } from "vue-router";

//部署静态资源
server.use(express.static("build"));

server.get("/*", async (req, res) => {
	let app = createApp();

	//app 安装路由插件
	let router = createRouter(createMemoryHistory());
	app.use(router);
	await router.push(req.url || "/"); // / or /about 等待页面跳转好
	await router.isReady(); // 等待(异步)路由加载完成 再渲染页面

	let appStringHtml = await renderToString(app);
	res.send(
		`
		<!DOCTYPE html>
		<html lang="en">
			<head>
				<meta charset="UTF-8" />
				<meta http-equiv="X-UA-Compatible" content="IE=edge" />
				<meta name="viewport" content="width=device-width, initial-scale=1.0" />
				<title>Document</title>
			</head>
			<body>
				<h1>Vue Serve Side Render</h1>
				<div id="app">
					${appStringHtml}
				</div>
				<script src="/client/client_bundle.js"></script>
			</body>
		</html>
		`
	);
});

server.listen(3000, () => {
	console.log("start node server on 3000");
});

在这里插入图片描述

结合pinia

在这里插入图片描述
store/home.js

import { defineStore } from "pinia";

export const useHomeStore = defineStore("home", {
	state() {
		return {
			count: 1000,
		};
	},
	actions: {
		increment() {
			this.count++;
		},
		decrement() {
			this.count--;
		},
		// async fetchHomeData() {
		// 	let res = await axios.get();
		// 	this.homeInfo = res.data;
		// },
	},
});

client/index.js

import { createApp } from "vue";
import App from "../App.vue";
import createRouter from "../router";
import { createWebHistory } from "vue-router";
import { createPinia } from "pinia";

let app = createApp(App);

//安装路由插件
let router = createRouter(createWebHistory());
let pinia = createPinia();
app.use(router);
app.use(pinia);
router.isReady().then(() => {
	app.mount("#app");
});

server/index.js

let express = require("express");
let server = express();
import createApp from "../app";
import { renderToString } from "@vue/server-renderer";
import createRouter from "../router";
// 内存路由 -> node用
import { createMemoryHistory } from "vue-router";
import { createPinia } from "pinia";

//部署静态资源
server.use(express.static("build"));

server.get("/*", async (req, res) => {
	let app = createApp();

	//app 安装路由插件
	let router = createRouter(createMemoryHistory());
	app.use(router);
	await router.push(req.url || "/"); // / or /about 等待页面跳转好
	await router.isReady(); // 等待(异步)路由加载完成 再渲染页面

	//app 安装pinia插件
	let pinia = createPinia();
	app.use(pinia);

	let appStringHtml = await renderToString(app);
	res.send(
		`
		<!DOCTYPE html>
		<html lang="en">
			<head>
				<meta charset="UTF-8" />
				<meta http-equiv="X-UA-Compatible" content="IE=edge" />
				<meta name="viewport" content="width=device-width, initial-scale=1.0" />
				<title>Document</title>
			</head>
			<body>
				<h1>Vue Serve Side Render</h1>
				<div id="app">
					${appStringHtml}
				</div>
				<script src="/client/client_bundle.js"></script>
			</body>
		</html>
		`
	);
});

server.listen(3000, () => {
	console.log("start node server on 3000");
});

views/home.vue

<template>
	<div class="app" style="border: 1px solid green; margin: 10px">
		<h2>home</h2>
		<div>{{ count }}</div>
		<button @click="addCounter">+1</button>
	</div>
</template>

<script setup>
	import { storeToRefs } from "pinia";
	import { useHomeStore } from "../store/home";
	let homeStore = useHomeStore();
	let { count } = storeToRefs(homeStore);
	function addCounter() {
		count.value++;
	}
</script>

views/about.vue

<template>
	<div class="app" style="border: 1px solid blue; margin: 10px">
		<h2>about</h2>
		<div>{{ count }}</div>
		<button @click="addCounter">+1</button>
	</div>
</template>

<script setup>
	import { storeToRefs } from "pinia";
	import { useHomeStore } from "../store/home";
	let homeStore = useHomeStore();
	let { count } = storeToRefs(homeStore);
	function addCounter() {
		count.value++;
	}
</script>

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue3引入Element plus的详细步骤

目录 一、遇到问题 二、操作步骤 一、遇到问题 在用vue3去引用Element UI的时候&#xff0c;发现了白屏不能显示&#xff0c;一直检查是不是代码的问题。后面找到了问题的所在&#xff0c;原来是vue3对应兼容的是Element Plus&#xff0c;要去下载对应的Element plus版本来引…

为什么提升客户服务是长期成功的关键

当今互联网&#xff0c;服务越来约趋向个人化&#xff0c;但在这个在线互动的时代&#xff0c;当涉及到客户支持时&#xff0c;这种个人联系的感觉可能很难形成。当事情出错时&#xff0c;当客户需要支持时&#xff0c;个人联系的感觉最为强烈。在不远的过去&#xff0c;客户支…

网络安全如何入门?有哪些学习误区?

那年我高三毕业的时候要填志愿前几天 我妈问我想学什么专业。 我说&#xff0c;想学网络设计、或者计算机、网络安全工程师 那时候还比较年轻&#xff0c;也对网络&#xff0c;计算机这方面感兴趣嘛 于是我妈和我爸决定让我学网管。 我说不想做网管&#xff0c;想直接成为一…

给想涨薪和正在学习Android的朋友们一些建议

前言 相信很多从事Android开发工作的朋友&#xff0c;在入职一年后会有申请涨薪的想法&#xff0c;但由于某些原因&#xff0c;公司拒绝了您的加薪申请&#xff0c;在我看来&#xff0c;出现这种情况主要有两种原因&#xff1a;第一个原因可能是你在工作中就只知道埋头苦干&am…

81-82-83-84-85-86 - 文件系统设计与实现

---- 整理自狄泰软件唐佐林老师课程 查看所有文章链接&#xff1a;&#xff08;更新中&#xff09;深入浅出操作系统 - 目录 文章目录1. 问题1.1 硬盘上最最最简单的文件系统支持方式1.2 改进思路1.3 更多细节问题1.4 文件系统概要设计1.5 硬盘数据逻辑示意图1.6 硬盘数据物理组…

文心一言 VS chatgpt (8)-- 算法导论2.3 5~6题

五、回顾查找问题(参见练习 2.1-3)&#xff0c;注意到&#xff0c;如果序列 A 已排好序&#xff0c;就可以将该序列的中点与v进行比较。根据比较的结果&#xff0c;原序列中有一半就可以不用再做进一步的考虑了。二分查找算法重复这个过程&#xff0c;每次都将序列剩余部分的规…

数据结构之七大排序

数据结构之七大排序&#x1f506;排序的概念及其运用排序的概念常见的排序算法&#x1f506;插入排序直接插入排序希尔排序&#x1f506;选择排序直接选择排序堆排序&#x1f506;交换排序冒泡排序快排&#x1f506;归并排序&#x1f506;非比较排序&#x1f506;结语&#x1f…

深度探索list

1.list的基本组成 list是一个双向链表&#xff0c;它的基本组成就是 成员作用prev指针指向上一个元素next指针指向下一个元素data用来保存数据2.list的迭代器 由于人们一般习惯于&#xff1a;迭代器是找到下一个元素&#xff0c;迭代器–是找到上一个元素。在双向链表list中…

C++的命名空间

C和C语言是有一些相似的地方的&#xff0c;而且C就是C语言的改进版本&#xff0c;所以学习C也得学习C语言&#xff0c;但是他们又是有很多不同的地方 下面我们就看一下C的命名空间 我们首先看一下 如果是这一段代码&#xff0c;那么这里输出的是多少呢&#xff1f; 很好这里输…

Nacos服务端服务注册源码分析 - 篇四

Nacos服务端服务注册源码分析 - 篇四 服务端调用接口 嗨 ~~~ 上班除了无聊的摸鱼&#xff0c;我还学了一个新技能&#xff0c;偷偷写博客。。。。 我们先回忆一下之前的三篇文章 &#x1f550;Nacos 客户端服务注册源码分析-篇一 &#x1f551;Nacos 客户端服务注册源码分析…

路由器的两种工作模式及快速通过express搭建微型服务器流程,解决刷新页面服务端404的问题

history模式与hash模式 首先这个#叫做hash&#xff0c;最大的特点就是不会随的http请求&#xff0c;发给服务器。 默认的模式是hash模式&#xff0c;如果想要修改&#xff0c;可以在router里面的index.js中配置mode属性&#xff0c; 它们俩直接的区别最明面上的有没有#和hist…

Python第三方库安装

看见更大的Python世界 Python社区PyPI The Python Package Index PyPI: Python Package Index PSF维护的展示全球Python计算生态的主站 学会检索并利用PyPI&#xff0c;找到合适的第三方库开发程序 实例&#xff1a;开发与区块链相关的程序 第1步&#xff1a;在pypi.org…

【服务器数据恢复】EVA存储数据硬盘掉线导致LUN不可用的数据恢复

服务器数据恢复环境&#xff1a; HP-EVA存储环境&#xff1a;EVA某型号控制器EVA扩展柜FC硬盘。 服务器故障&#xff1a; EVA存储中两块磁盘掉线导致存储中某些LUN丢失不可用。 服务器数据恢复过程&#xff1a; 1、首先对故障存储中所有磁盘做物理故障检测&#xff0c;经过…

在Spring Boot微服务使用RedisTemplate操作Redis

记录&#xff1a;400 场景&#xff1a;在Spring Boot微服务使用RedisTemplate操作Redis缓存和队列。 使用ValueOperations操作Redis String字符串&#xff1b;使用ListOperations操作Redis List列表&#xff0c;使用HashOperations操作Redis Hash哈希散列&#xff0c;使用SetO…

基于LNMP架构搭建网站

一、编译安装Nginx 服务 1、编译安装Nginx 服务的操作步骤 systemctl stop firewalld systemctl disable firewalld setenforce 01.1 安装依赖包 yum -y install pcre-devel zlib-devel gcc gcc-c make1.2 创建运行用户 useradd -M -s /sbin/nologin nginx1.3 编译安装 cd…

Claude注册安装教程【403 Forbidden】

Claude注册安装教程 尝试注册Claude的兄弟需要注意&#xff0c;最后一步需要科学上网 本来打算看看csdn&#xff0c;结果可能是时效性&#xff0c;和我情况不一样 按照他们的意思&#xff0c;点击add a stack 就进去了&#xff0c;我却被403 这个时候我就搜索stack&#xff0c;…

钢网是SMT生产使用的一种工具,如何制作?

钢网是SMT生产使用的一种工具&#xff0c;其主要功能是将锡膏准确地涂敷在有需要焊接的PCB焊盘上。 钢网的好坏&#xff0c;直接影响印刷工作的质量&#xff0c;目前一般使用的金属钢网&#xff0c;是由薄薄的、带有小孔的金属板制作成的&#xff0c;在开孔处&#xff0c;锡膏…

React中Context的使用,跨组件传值

目录Context 是什么&#xff1f;使用ContextXxx.Provider正式使用Context中的数据Context 是什么&#xff1f; Context 提供了一个无需为每层组件手动添加 props&#xff0c;就能在组件树间进行数据传递的方法。 在一个典型的 React 应用中&#xff0c;数据是通过 props 属性自…

容器镜像的设计原理

1 概述&#xff1a; 1.1 历史概要 2016年&#xff0c;Docker制定了镜像规范v2&#xff0c;并在Docker 1.10中实现了这个规范。镜像规范v2分为Schema 1和Schema 2。 Schema 1主要兼容使用v1规范的Docker客户端&#xff08;从2017年2月起&#xff0c;镜像规范v1不再被Registry支…

云计算基础——云服务

目录 云服务概述 云服务简介 云服务的产生和发展 云服务产生的前提&#xff1a; 接入云端的主要前端工具&#xff1a; 云服务的优缺点 优点 缺点 云服务的类型 SaaS PaaS IaaS 云部署模型 云服务概述 云服务简介 云计算通过使计算分布在大量的分布式计算机上&…