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中的包
};
<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/
但此时是静态页面 无法交互 需要激活
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");
});
npm run build:server 打包
npm run build:client打包
npm run start 开启
访问http://localhost:3000/ 可以交互
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");
});
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>