vite2.0项目配置ssr服务端渲染的探索(七)
目前我们的应用大多是spa单页面应用为主,关于单页面和多页面的概念和区别各自的优势,大家可以参考别的文章!
现在主要研究下怎么在vite里面实现服务端的渲染!
注意下面两张图片
单页面效果


客户端渲染的源码

服务端渲染的源码

可以很清楚的看到二者的区别!
那么我们该如何来实现这种服务端的渲染呢!
宇宙门: vite的ssr官网.

作为探索,我们暂时忽略它的友情提示!
1 根目录下面新建一个server.js,内容基本可以用vite的github上面的文件源码,我们暂时不做改动
宇宙门:vite的ssr—github地址
yarn add express
// @ts-check
const fs = require("fs");
const path = require("path");
const express = require("express");const isTest = process.env.NODE_ENV === "test" || !!process.env.VITE_TEST_BUILD;async function createServer(root = process.cwd(),isProd = process.env.NODE_ENV === "production"
) {const resolve = (p) => path.resolve(__dirname, p);const indexProd = isProd? fs.readFileSync(resolve("dist/client/index.html"), "utf-8"): "";const manifest = isProd? // @ts-ignorerequire("./dist/client/ssr-manifest.json"): {};const app = express();/*** @type {import('vite').ViteDevServer}*/let vite;if (!isProd) {vite = await require("vite").createServer({root,logLevel: isTest ? "error" : "info",server: {middlewareMode: "ssr",watch: {// During tests we edit the files too fast and sometimes chokidar// misses change events, so enforce polling for consistencyusePolling: true,interval: 100,},},});// use vite's connect instance as middlewareapp.use(vite.middlewares);} else {app.use(require("compression")());app.use(require("serve-static")(resolve("dist/client"), {index: false,}));}app.use("*", async (req, res) => {try {const url = req.originalUrl;let template, render;if (!isProd) {// always read fresh template in devtemplate = fs.readFileSync(resolve("index.html"), "utf-8");template = await vite.transformIndexHtml(url, template);render = (await vite.ssrLoadModule("/src/entry-server.js")).render;} else {template = indexProd;render = require("./dist/server/entry-server.js").render;}const [appHtml, preloadLinks] = await render(url, manifest);const html = template.replace(``, preloadLinks).replace(``, appHtml);res.status(200).set({ "Content-Type": "text/html" }).end(html);} catch (e) {vite && vite.ssrFixStacktrace(e);console.log(e.stack);res.status(500).end(e.stack);}});return { app, vite };
}if (!isTest) {createServer().then(({ app }) =>app.listen(3000, () => {console.log("http://localhost:3000");}));
}// for test use
exports.createServer = createServer;
2.接着改造根目录下面的index.html文件,也是我们的静态模板文件
<html lang="en"><head><meta charset="UTF-8" /><link rel="icon" href="/favicon.ico" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Vite Apptitle> //这里是我们需要替换的内容可以随意写,跟替换的时候保持一致head><body><div id="app"> //这里是我们需要替换的内容可以随意写,跟替换的时候保持一致div><script type="module" src="/src/main.js">script>body>
html>
3.src/router/index.js
/* import { createRouter, createWebHistory } from "vue-router";const routes = [{path: "/",name: "Home",component: () => import("@/views/Home.vue"),},{path: "/about/:id",name: "About",component: () => import("@/views/About.vue"),},
];export default createRouter({history: createWebHistory(),routes,
});*/import {createMemoryHistory,createRouter as _createRouter,createWebHistory,
} from "vue-router";// Auto generates routes from vue files under ./pages
// https://vitejs.dev/guide/features.html#glob-import
///glob后面是我们页面文件模板
const pages = import.meta.glob("../views/*.vue");///
const routes = Object.keys(pages).map((path) => {const name = path.match(/\.\/views(.*)\.vue$/)[1].toLowerCase();return {path: name === "/home" ? "/" : name,//这里的导入也要注意路径component: pages[path], // () => import('./pages/*.vue')};
});export function createRouter() {return _createRouter({// use appropriate history implementation for server/client// import.meta.env.SSR is injected by Vite.history: import.meta.env.SSR ? createMemoryHistory() : createWebHistory(),routes,});
}
4.main.js入口文件的改造
/* import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";createApp(App).use(router).use(store).mount("#app");*/import App from "./App.vue";
import { createSSRApp } from "vue";
import { createRouter } from "./router";// SSR requires a fresh app instance per request, therefore we export a function
// that creates a fresh app instance. If using Vuex, we'd also be creating a
// fresh store here.
export function createApp() {const app = createSSRApp(App);const router = createRouter();app.use(router);return { app, router };
}
5.src/entry-client.js
import { createApp } from "./main";const { app, router } = createApp();// wait until router is ready before mounting to ensure hydration match
router.isReady().then(() => {app.mount("#app");
});
6.src/entry-server.js
yarn add @vue/server-renderer
import { createApp } from "./main";
import { renderToString } from "@vue/server-renderer";export async function render(url, manifest) {const { app, router } = createApp();// set the router to the desired URL before renderingrouter.push(url);await router.isReady();// passing SSR context object which will be available via useSSRContext()// @vitejs/plugin-vue injects code into a component's setup() that registers// itself on ctx.modules. After the render, ctx.modules would contain all the// components that have been instantiated during this render call.const ctx = {};const html = await renderToString(app, ctx);// the SSR manifest generated by Vite contains module -> chunk/asset mapping// which we can then use to determine what files need to be preloaded for this// request.const preloadLinks = renderPreloadLinks(ctx.modules, manifest);return [html, preloadLinks];
}function renderPreloadLinks(modules, manifest) {let links = "";const seen = new Set();modules.forEach((id) => {const files = manifest[id];if (files) {files.forEach((file) => {if (!seen.has(file)) {seen.add(file);links += renderPreloadLink(file);}});}});return links;
}function renderPreloadLink(file) {if (file.endsWith(".js")) {return `${file}">`;} else if (file.endsWith(".css")) {return `${file}">`;} else if (file.endsWith(".woff")) {return ` ${file}" as="font" type="font/woff" crossorigin>`;} else if (file.endsWith(".woff2")) {return ` ${file}" as="font" type="font/woff2" crossorigin>`;} else if (file.endsWith(".gif")) {return ` ${file}" as="image" type="image/gif">`;} else if (file.endsWith(".jpg") || file.endsWith(".jpeg")) {return ` ${file}" as="image" type="image/jpeg">`;} else if (file.endsWith(".png")) {return ` ${file}" as="image" type="image/png">`;} else {// TODOreturn "";}
}
7.package.json的改造
..."dev": "node server","build:client": "vite build --outDir dist/client","build:server": "vite build --outDir dist/server --ssr src/entry-server.js "...
到这里,我们就实现了vite项目的ssr服务端渲染!当然里面还有vuex还没有集成进来,大家可以尝试着自己去加进来玩玩!
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
