SSR初体验-结合Vue3全家桶

这篇文章详细介绍了如何使用Vue.js进行服务器端渲染(SSR)的设置,包括基础的Express服务器搭建、Webpack配置、Vue组件的创建以及Vue-Router的集成。此外,还展示了如何结合Pinia进行状态管理,以实现跨请求状态的隔离和交互功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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>

在这里插入图片描述

### 创建 OE 光盘镜像文件 #### 准备工作 为了创建光盘镜像 (OE) 文件,需要准备 UltraISO 软件以及目标光盘或已有的 ISO 文件。如果目的是基于现有数据创建一个新的 ISO 文件,则应确保源数据的完整性和准确性。 #### 启动 UltraISO 并加载内容 启动 UltraISO 应用程序之后,可以通过多种方式来获取要转换成 ISO 的内容: - 如果是从物理光盘创建 ISO 文件,在菜单栏选择 `文件` -> `打开磁盘映像` 或者直接点击工具条上的相应图标,接着按照向导操作选取对应的驱动器号完成读取过程[^1]。 - 若是依据其他类型的存储介质比如 USB 设备或是已经存在于计算机中的文件夹结构构建 ISO 映像,则应该通过 `新建` 功能并指定根目录来进行设置[^2]。 ```bash # 假设当前路径为欲打包成ISO的目标文件夹所在位置 ultraiso-cli.exe /new /output="path_to_save.iso" "target_folder" ``` 此命令行适用于批处理脚本调用 UltraISO CLI 版本来自动化生成 ISO 流程;实际 GUI 操作中无需执行上述代码片段而是手动配置参数。 #### 编辑与调整项目属性 一旦载入了所需的内容到 UltraISO 工作区,可以对其进行必要的修改以满足特定需求,例如更改卷标、添加/移除文件等。特别注意的是当涉及到 Windows 安装媒体定制时可能还需要遵循某些特殊指导方针,如删除 sources 目录下的 ei.cfg 文件以便支持多版本安装选项的选择。 #### 输出最终的 ISO 文件 确认所有的改动无误后,转至 `动作` (`Action`) 下拉列表里的 `保存为标准ISO映像文件...` 来确定输出的具体细节,包括但不限于保存的位置、名称及压缩级别等设定项。完成后单击 OK 即可开始编译过程直至新创制好的 ISO 文件完全写出完毕。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_聪明勇敢有力气

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值