前言大家好,我是 易,在上一篇文章中,我们有讲到《如何使用 vite+vue3+ts+pinia+vueuse 打造前端企业级项目》,能看的出来很多同学喜欢,今天给大家带来爆肝许久的 如何使用vite 打造前端 SSR 企业级项目
,希望大家能喜欢!如果大家对 Vite 感兴趣可以去看看专栏: 《Vite 从入门到精通》了解 SSR什么是 SSR服务器端渲染(Server-Side Rendering)是指由服务端完成页面的 HTML 结构拼接的页面处理技术,发送到浏览器,然后为其绑定状态与事件,成为完全可交互页面的过程。
简单理解就是html是由服务端写出,可以动态改变页面内容,即所谓的动态页面早年的 php、asp 、jsp 这些 Server page 都是 SSR 的为什么使用 SSR网页内容在服务器端渲染完成,一次性传输到浏览器,所以 。
首屏加载速度非常快;有利于SEO,因为服务器返回的是一个完整的 html,在浏览器可以看到完整的 dom,对于爬虫、百度搜索等引擎就比较友好;快速查看github 仓库地址长话短说,直接开干 ~建议包管理器使用优先级:pnpm > yarn > npm > cnpm
一、初始化项目pnpm create vite koa2-ssr-vue3-ts-pinia — –template vue-ts 复制代码集成基本配置由于本文的重点在于 SSR 配置,为了优化读者的观感体验,所以项目的
基本配置就不做详细介绍,在我上一篇文章《手把手教你用 vite+vue3+ts+pinia+vueuse 打造企业级前端项目》中已详细介绍,大家可以自行查阅修改 tsconfig.json :查看代码修改
vite.config.ts:查看代码集成 eslint 和 prettier 统一代码质量风格的:查看教程集成 commitizen 和 husky 规范 git 提交:查看教程到这里我们项目的基本框架都搭建完成啦~
二、修改客户端入口修改 ~/src/main.tsimport { createSSRApp } from”vue”; import App from”./App.vue”; // 为了保证数据的互不干扰,每次请求需要导出一个新的实例
exportconst createApp = () => { const app = createSSRApp(App); return { app }; } 复制代码新建 ~/src/entry-client.ts
import { createApp } from”./main”const { app } = createApp(); app.mount(“#app”); 复制代码修改 ~/index.html
的入口 …
> … 复制代码到这里你运行 pnpm run dev ,发现页面中还是可以正常显示,因为到目前只是做了一个文件的拆分,以及更换了 createSSRApp 方法;三、创建开发服务器
使用 Koa2安装 koa2pnpm i koa –save && pnpm i @types/koa –save-dev 复制代码安装中间件 koa-connectpnpm i koa-connect
–save 复制代码使用:新建 ~/server.js备注:因为该文件为 node 运行入口,所以用 js 即可,如果用 ts 文件,需单独使用 ts-node 等去运行,导致程序变复杂const Koa = require(koa); (async () => { const app = new Koa(); app.use(async (ctx) => { ctx.body = `
koa2 + vite + ts + vue3 + vue-router<
h1style=”text-align: center;”>使用 koa2 + vite + ts + vue3 + vue-router 集成前端 SSR 企业级项目
>`; }); app.listen(9000, () => { console.log(server is listening in 9000); }); })(); 复制代码
运行 node server.js结果:
渲染替换成项目根目录下的index.html修改 server.js 中的 ctx.body 返回的是 index.htmlconst fs = require(fs); const path = require
(path); const Koa = require(koa); (async () => { const app = new Koa(); // 获取 index.html
const template = fs.readFileSync(path.resolve(__dirname, index.html), utf-8); app.use(async (
ctx) => { ctx.body = template; }); app.listen(9000, () => { console.log(
server is listening in 9000); }); })(); 复制代码运行 node server.js后, 我们就会看到返回的是空白内容的 index.html 了,但是我们需要返回的是
vue 模板 ,那么我们只需要做个 正则的替换给 index.html 添加 标记
=”UTF-8″ />
/>koa2 + vite + ts + vue3
src=”/src/entry-client.ts”> 复制代码修改 server.js 中的 ctx.body// other code … (async () => { const app = new Koa(); // 获取index.html const template = fs.readFileSync(path.resolve(__dirname, index.html), utf-8); app.use(async (ctx) => { let vueTemplate =
现在假装这是一个vue模板; // 替换 index.html 中的 标记 let html = template.replace(
, vueTemplate); ctx.body = html; }); app.listen(9000, () => { console.log(server is listening in 9000); }); })(); 复制代码
运行 node server.js后,我们就会看到返回的 变量 vueTemplate 内容那么到现在服务已正常启动了,但是我们试想一下,我们页面模板使用的是 vue,并且 vue 返回的是一个 vue 实例模板
,所以我就要把这个 vue 实例模板 转换成 可渲染的 html,那么 @vue/server-renderer 就应运而生了四、新增服务端入口因为 vue 返回的是 vue 实例模板 而不是 可渲染的 html
,所以我们需要使用 @vue/server-renderer 进行转换安装 @vue/server-rendererpnpm i @vue/server-renderer –save 复制代码新建
~/src/entry-server.tsimport { createApp } from./main; import { renderToString } from@vue/server-renderer
; exportconst render = async () => { const { app } = createApp(); // 注入vue ssr中的上下文对象const renderCtx: {modules?:
string[]} = {} let renderedHtml = await renderToString(app, renderCtx) return { renderedHtml }; } 复制代码
那么如何去使用 entry-server.ts 呢,到这里就需要 vite 了五、注入vite修改 ~/server.jsconst fs = require(fs) const path = require
(path) const Koa = require(koa) const koaConnect = require(koa-connect) const vite = require(vite) ;
(async () => { const app = new Koa(); // 创建 vite 服务const viteServer = await vite.createServer(
{ root: process.cwd(), logLevel: error, server: { middlewareMode: true
, }, }) // 注册 vite 的 Connect 实例作为中间件(注意:vite.middlewares 是一个 Connect 实例) app.use(
koaConnect(viteServer.middlewares)) app.use(async ctx => { try { // 1. 获取index.html
let template = fs.readFileSync(path.resolve(__dirname, index.html), utf-8); // 2. 应用 Vite HTML 转换。
这将会注入 Vite HMR 客户端, template = await viteServer.transformIndexHtml(ctx.path, template)
// 3. 加载服务器入口, vite.ssrLoadModule 将自动转换const { render } = await viteServer.ssrLoadModule(/src/entry-server.ts
) // 4. 渲染应用的 HTMLconst { renderedHtml } = await render(ctx, {}) const html = template.replace(
, renderedHtml) ctx.type = text/html ctx.body = html }
catch (e) { viteServer && viteServer.ssrFixStacktrace(e) console.log(e.stack) ctx.
throw(500, e.stack) } }) app.listen(9000, () => { console.log(server is listening
in 9000); }); })() 复制代码运行 node server.js 就可以看到返回的 App.vue 模板中的内容了,如下图
并且我们 右键查看显示网页源代码,也会看到渲染的正常 html
>
/>koa2 + vite + ts + vue3
>Hello Vue 3 + TypeScript + Vite Recommended IDE setup:
VSCode +
target=”_blank”data-v-469af010>Volar
See README.md> for more information.
>Vue 3 Docs
count is: 0 Editdata-v-469af010>components/HelloWorld.vue to test hot module replacement.
复制代码到这里我们就已经在 开发环境 已经正常的渲染了,但我们想一下,在
生产环境 我们应该怎么做呢,因为咱们不可能直接在 生产环境 运行使用 vite 吧!所以咱们接下来处理如何在 生产环境 运行吧六、添加开发环境为了将 SSR 项目可以在生产环境运行,我们需要:正常构建生成一个
客户端构建包;再生成一个 SSR 构建,使其通过 require() 直接加载,这样便无需再使用 Vite 的 ssrLoadModule;修改 package.json...
{
"scripts"
: {
// 开发环境"dev": "node server-dev.js",
// 生产环境"server": "node server-prod.js",
// 构建"build"
: "pnpm build:client && pnpm build:server",
"build:client": "vite build --outDir dist/client",
"build:server": "vite build --ssr src/entry-server.js --outDir dist/server",
},
}
...
复制代码修改 server.js
为 server-dev.js运行 pnpm run build 构建包新增 server-prod.js注意:为了处理静态资源,需要在此新增 koa-send 中间件: pnpm i koa-send --save
const Koa = require(koa);
const sendFile = require(koa-send);
const path = require(path);
const fs =
require(fs);
const resolve = (p) => path.resolve(__dirname, p);
const clientRoot = resolve(dist/client
);
const template = fs.readFileSync(resolve(dist/client/index.html), utf-8);
const render = require(./dist/server/entry-server.js
).render;
const manifest = require(./dist/client/ssr-manifest.json);
(async () => {
const app =
new Koa();
app.use(async (ctx) => {
// 请求的是静态资源if (ctx.path.startsWith(/assets)) {
await sendFile(ctx, ctx.path, { root: clientRoot });
return;
}
const [ appHtml ] =
await render(ctx, manifest);
const html = template
.replace(, appHtml
);
ctx.type = text/html;
ctx.body = html;
});
app.listen(8080, () => console
.log(started server on http://localhost:8080));
})();
复制代码到这里,我们在 开发环境 和 生成环境 已经都可以正常访问了,那么是不是就万事无忧了呢?
为了用户的更极致的用户体验,那么 预加载 就必须要安排了七、预加载我们知道 vue 组件在 html 中渲染时都是动态去生成的对应的 js 和 css 等;那么我们要是在用户获取 服务端模板 (也就是执行
vite build 后生成的 dist/client 目录) 的时候,直接在 html 中把对应的 js 和 css 文件预渲染了,这就是 静态站点生成(SSG) 的形式闲话少说,明白道理了之后,直接开干 ~。
生成预加载指令:在 package.json 中的 build:client 添加 --ssrManifest 标志,运行后生成 ssr-manifest.json...
{
"scripts": {
...
"build:client": "vite build --ssrManifest --outDir dist/client",
...
},
}
...
复制代码在 entry-sercer.ts
中添加解析生成的 ssr-manifest.json 方法exportconst render = async (
ctx: ParameterizedContext,
manifest: Record<
string, string[]>
): Promise => {
const { app } = createApp();
console.log(ctx, manifest, );
const renderCtx: { modules?: string[] } = {};
const renderedHtml = await renderToString(app, renderCtx);
const preloadLinks = renderPreloadLinks(renderCtx.modules, manifest);
return [renderedHtml, preloadLinks];
};
/**
* 解析需要预加载的链接
* @param modules
* @param manifest
* @returns string
*/functionrenderPreloadLinks
(
modules: undefined | string[],
manifest: Record
): string{
let links = ;
const seen = new Set();
if (modules === undefined) thrownewError();
modules.forEach((id) => {
const files = manifest[id];
if (files) {
files.forEach((file) => {
if (!seen.has(file)) {
seen.add(file);
links += renderPreloadLink(file);
}
});
}
});
return links;
}
/**
* 预加载的对应的地址
* 下面的方法只针对了 js 和 css,如果需要处理其它文件,自行添加即可
* @param file
* @returns string
*/
functionrenderPreloadLink(file: string): string{
if (file.endsWith(.js)) {
return``;
} elseif (file.endsWith(.css)) {
return``
;
} else {
return ;
}
}
复制代码给 index.html 添加 标记改造 server-prod.js
...
(async () => {
const app = new Koa();
app.use(async (ctx) => {
...
const [appHtml, preloadLinks] = await render(ctx, manifest);
const html = template
.replace(
, preloadLinks)
.replace(, appHtml);
// do something
});
app.listen(8080, () => console.log(started server on http://localhost:8080));
})();
复制代码
运行 pnpm run build && pnpm run serve 就可正常显示了到这里基本的渲染就完成了,因为我们是需要在浏览器上渲染的,所以 路由 vue-router 就必不可少了八、集成 vue-router
安装 vue-routerpnpm i vue-router --save
复制代码新增对应的路由页面 index.vue 、 login.vue 、 user.vue新增 src/router/index.ts
import {
createRouter as createVueRouter,
createMemoryHistory,
createWebHistory,
Router
}
fromvue-router;
exportconst createRouter = (type: client | server): Router =>
createVueRouter({
history:
type === client ? createWebHistory() : createMemoryHistory(),
routes: [
{
path:
/,
name: index,
meta: {
title: 首页,
keepAlive:
true,
requireAuth: true
},
component: () =>import
(@/pages/index.vue)
},
{
path: /login,
name:
login,
meta: {
title: 登录,
keepAlive: true,
requireAuth:
false
},
component: () =>import(@/pages/login.vue)
},
{
path:
/user,
name: user,
meta: {
title: 用户中心,
keepAlive:
true,
requireAuth: true
},
component: () =>import
(@/pages/user.vue)
}
]
});
复制代码修改入口文件 src/enter-client.tsimport { createApp }
from./main;
import { createRouter } from./router;
const router = createRouter(client);
const { app } = createApp();
app.use(router);
router.isReady().then(
() => {
app.mount(#app, true);
});
复制代码修改入口文件 src/enter-server.ts...
import { createRouter } from
./routerconst router = createRouter(client);
exportconst render = async (
ctx: ParameterizedContext,
manifest: Record<
string, string[]>
): Promise => {
const { app } = createApp();
// 路由注册const
router = createRouter(server);
app.use(router);
await router.push(ctx.path);
await router.isReady();
...
};
...
复制代码
运行 pnpm run build && pnpm run serve 就可正常显示了九、集成 pinia安装pnpm i pinia --save
复制代码新建 src/store/user.tsimport
{ defineStore } frompinia;
exportdefault defineStore(user, {
state: () => {
return {
name:
张三,
age: 20
};
},
actions: {
updateName(name: string) {
this.name = name;
},
updateAge(age: number) {
this.age = age;
}
}
});
复制代码
新建 src/store/index.tsimport { createPinia } frompinia;
import useUserStore from./user;
exportdefault
() => {
const pinia = createPinia();
useUserStore(pinia);
return pinia;
};
复制代码新建 UsePinia.vue
使用,并且在 pages/index.vue 中引入
欢迎使用vite+vue3+ts+pinia+vue-router4
{{ userStore.name }}的年龄: {{ userStore.age }}
点击给{{ userStore.name }}的年龄增加一岁
lang="ts">import { defineComponent } fromvue;
import useUserStore from@/store/user;
exportdefault
defineComponent({
name: UsePinia,
setup() {
const userStore = useUserStore();
const addAge = () => {
userStore.updateAge(++userStore.age);
};
return {
userStore,
addAge
};
}
});
复制代码注入 pinia :修改 src/entry-client.ts...
import createStore from@/store;
const pinia = createStore();
const { app } = createApp();
app.use(router);
app.use(pinia);
// 初始化 pini// 注意:__INITIAL_STATE__需要在 src/types/shims-global.d.ts中定义
if (window.__INITIAL_STATE__) {
pinia.state.value = JSON.parse(window.__INITIAL_STATE__);
}
...
复制代码
修改 src/entry-server.ts...
import createStore from@/store;
exportconst render = () => {
...
// piniaconst pinia = createStore();
app.use(pinia);
const state = JSON.stringify(pinia.state.value);
...
return [renderedHtml, state, preloadLinks];
}
...
复制代码修改 server-dev.js 和 server-prod.js...
const [renderedHtml, state, preloadLinks] = await render(ctx, {});
const html = template
.replace(
, renderedHtml)
.replace(, state);
// server-prod.js
.replace(
, preloadLinks)
...
复制代码给 index.html 添加 标记
window.__INITIAL_STATE__ =
;
复制代码运行 pnpm run dev 就可正常显示了备注:集成 pinia 这块由于注入较为复杂且方法不一,暂时不做详细讲解,如果大家有需要,后面会出详细解析!
十、其它vueuse 的集成:可参考 《手把手教你用 vite+vue3+ts+pinia+vueuse 打造大厂企业级前端项目》CSS 集成:参考如上可使用:原生 css variable 新特性、scss
或者 lessCSS 的 UI 库:参考同上需要注意的是 按需引入当然还有很多需要考量的,比如 压测, 并发 , 负载均衡 等,但是这些不在文章主题范围内,这里就不做详细介绍,感兴趣的可以留言,后面有时间会新开对应的专栏
其中 负载均衡 这块前端同学可使用 pm2, 或者直接丢给运维去搞个 docker项目模板地址传送门最后友情提示:目前 Vite 的 SSR 支持还处于试验阶段,可能会遇到一些未知 bug ,所以在公司的生产环境请谨慎使用,个人项目中可以滥用哟 ~
该系列会是一个持续更新系列,关于整个《Vite 从入门到精通》专栏,我主要会从如下图几个方面讲解,请大家拭目以待吧!!!
靓仔靓女们,都看到这里了,要不点个赞再走呗作者:易师傅链接:https://juejin.cn/post/7086467466703929358
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!
8. 精力有限,不少源码未能详细测试(解密),不能分辨部分源码是病毒还是误报,所以没有进行任何修改,大家使用前请进行甄别
丞旭猿论坛
暂无评论内容