SSR初体验-结合Vue3全家桶

SSR初体验

基础搭建

安装依赖
SSR初体验-结合Vue3全家桶_第1张图片
先开启一个服务器

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

SSR初体验-结合Vue3全家桶_第2张图片
创建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(
		`
		
		
			
				
				
				
				Document
			
			
				

Vue Serve Side Render

${appStringHtml}
`
); }); 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/
SSR初体验-结合Vue3全家桶_第3张图片但此时是静态页面 无法交互 需要激活

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(
		`
		
		
			
				
				
				
				Document
			
			
				

Vue Serve Side Render

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

SSR初体验-结合Vue3全家桶_第4张图片
npm run build:server 打包
npm run build:client打包
npm run start 开启
访问http://localhost:3000/ 可以交互

SSR初体验-结合Vue3全家桶_第5张图片

结合vue-router

SSR初体验-结合Vue3全家桶_第6张图片

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(
		`
		
		
			
				
				
				
				Document
			
			
				

Vue Serve Side Render

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

SSR初体验-结合Vue3全家桶_第7张图片

结合pinia

SSR初体验-结合Vue3全家桶_第8张图片
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(
		`
		
		
			
				
				
				
				Document
			
			
				

Vue Serve Side Render

${appStringHtml}
`
); }); 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>

SSR初体验-结合Vue3全家桶_第9张图片

你可能感兴趣的:(SSR,javascript,webpack,vue.js)