vuecli项目构建SSR服务端渲染的实现


Posted in Javascript onOctober 30, 2020

服务端渲染(SSR)

将一个 Vue 组件在服务端渲染成 HTML 字符串并发送到浏览器,最后将这些静态标记“激活”为可交互应用程序的过程就叫服务端渲染(SSR)

服务器渲染的 Vue.js 应用程序也可以被认为是"同构"或"通用",因为应用程序的大部分代码都可以在服务器和客户端上运行

为什么使用 服务端渲染(SSR)

  • 更好的 SEO:传统的 spa 页面数据都是异步加载,搜索引擎爬虫无法抓取,服务端渲染(SSR)使搜索引擎爬虫抓取工具可以直接查看完全渲染的页面,解决 vue 项目的 seo 问题
  • 更快的内容到达时间 (首屏加载更快):请求页面时,服务端将渲染好的页面直接发送给浏览器进行渲染,浏览器只需要解析渲染 HTML,无需等待所有的 JavaScript 都完成下载并执行,才显示服务器渲染的标记

服务端渲染(SSR)缺点

  • 开发条件所限:浏览器特定的代码,只能在某些生命周期钩子函数中使用;一些外部扩展库可能需要特殊处理,才能在服务器渲染应用程序中运行
  • 涉及构建设置和部署的更多要求:与可以部署在任何静态文件服务器上的完全静态单页面应用程序 (SPA) 不同,服务器渲染应用程序,需要处于 Node.js server 运行环境
  • 更多的服务器端负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用 CPU 资源,因此如果你预料在高流量环境下使用,需要准备相应的服务器负载,并采用缓存策略

服务端渲染(SSR)vs 预渲染(Prerendering)

如果你只是想改善少数营销页面(例如 /, /about, /contact 等)的 SEO,那么你可能需要预渲染,无需使用 web 服务器实时动态编译 HTML,而是使用预渲染方式,在构建时简单地生成针对特定路由的静态 HTML 文件,优点是设置预渲染更简单,并可以将你的前端作为一个完全静态的站点
如果你使用 webpack,你可以使用 prerender-spa-plugin (npm地址) 插件轻松地添加预渲染

服务端渲染(SSR)原理

构建流程:所有的文件拥有一个公共入口 app.js,进入服务端入口 entry-server.js 和客户端入口 entry-client.js ,项目完成后通过使用 webpack 打包生成服务端 server bundle(一个供服务端 SSR 使用的 json 文件)和客户端 client bundle(用于浏览器),当请求页面时,服务端将 vue 组件组装成 HTML 字符串发送到浏览器,混入到客户端访问的 HTML 模板中,完成页面渲染

vuecli项目构建SSR服务端渲染的实现

通过 vuecli 创建 vue 项目

vue create vue-ssr-demo

vue-server-renderer

vue-server-renderer 是 SSR 渲染的核心,提供 createRenderer 方法,这个方法的 renderToString 可以把 app 渲染成字符串。createBundleRenderer 方法可以通过预打包应用程序代码创建 bundleRenderer 实例,来渲染 bundle 和 HTML 模板

安装 vue-server-renderer

npm install vue-server-renderer --save

注意:

  • vue-server-renderer 和 vue 必须匹配版本
  • vue-server-renderer 依赖一些 Node.js 原生模块,因此只能在 Node.js 中使用

 避免状态单例

Node.js 服务器是一个长期运行的进程,当我们的代码进入该进程时,它将进行一次取值并留存在内存中,这意味着如果创建一个单例对象,它将在每个传入的请求之间共享,所以我们应该暴露一个可以重复执行的工厂函数,为每个请求创建一个新的根 Vue 实例,如果我们在多个请求之间使用一个共享的实例,很容易导致交叉请求状态污染(同样的规则也适用于 router、store 和 event bus 实例)

创建 路由 router

安装 vue-router

npm install vue-router --save

src 目录下创建 router 文件夹和 index.js
在 components 目录下创建 Home.vue 和 About.vue 页面(根据项目需求自定义创建)

router/index.js:

import Vue from "vue"
import Router from "vue-router"

import Home from "@/components/Home"
import About from "@/components/About"

Vue.use(Router)

//每次用户请求都需要创建一个新的router实例
//创建createRouter工厂函数
export default function createRouter() {
  //创建router实例
  return new Router({
    mode: "history",
    routes: [
      {
        path: "/", 
        name: 'home',
        component: Home
      },
      {
        path: "/about", 
        name: 'about',
        component: About
      }
    ]
  })
}

修改 App.vue

修改 App.vue 页面,进行页面布局(根据项目需求自定义布局)

App.vue:

<template>
 <div id="app">
  <nav>
   <router-link to="/">首页</router-link>
   <router-link to="/about">关于</router-link>
  </nav>
  <router-view></router-view>
 </div>
</template>

创建 公共入口 app.js

src 目录下创建 公共入口 app.js ,用于创建 vue 实例

app.js:

import Vue from "vue"
import App from "./App.vue"
import createRouter from "./router"

//创建createApp工厂函数
export default function createApp() {
  const router = createRouter()
  //创建vue实例
  const app = new Vue({
    router,
    render: h => h(App),
  })
  return { app, router }
}

创建 服务端入口 entry-server.js

src 目录下创建 服务端入口 entry-server.js ,用于渲染首屏

entry-server.js:

import createApp from "./app"

export default context => {
  return new Promise((resolve, reject) => {
    const { app, router } = createApp()
    //渲染首屏
    router.push(context.url)
    router.onReady(() => {
      resolve(app)
    }, reject)
  })
}

创建 客户端入口 entry-client.js

src 目录下创建 客户端入口 entry-client.js ,用于挂载激活 app

entry-client.js:

import createApp from "./app"

const { app, router } = createApp()
router.onReady(() => {
  //挂载激活app
  app.$mount("#app")
})

创建 页面模板 index.temp.html

public 目录下创建 index.temp.html ,作为渲染 Vue 应用程序时,renderer 生成 HTML 页面包裹容器,来包裹生成的 HTML 标记
<!--vue-ssr-outlet--> 注释将是应用程序 HTML 标记注入的地方

index.temp.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>vue ssr</title>
</head>
<body>
  <!--vue-ssr-outlet-->
</body>
</html>

创建 Node.js 服务器

服务端渲染(SSR)需要使用 Node.js 服务器,这里使用 express 框架搭建
安装 express

npm install express --save

根目录下创建 server.js 文件,用于搭建 Node.js 服务器

server.js:

//nodejs服务器
const express = require("express")
const Vue = require("vue")
const fs = require("fs")

//创建express实例
const app = express()
//创建渲染器
const { createBundleRenderer } = require("vue-server-renderer")
const serverBundle = require("./dist/server/vue-ssr-server-bundle.json")
const clientManifest = require("./dist/client/vue-ssr-client-manifest.json")
const renderer = createBundleRenderer(serverBundle, {
  runInNewContext: false,
  template: fs.readFileSync("./public/index.temp.html", "utf-8"), //页面模板
  clientManifest
})

//中间件处理静态文件请求
app.use(express.static("./dist/client", {index: false}))

//将路由的处理交给vue
app.get("*", async (req, res) => {
  try {
    const context = {
      url: req.url,
      title: ""
    }
    const html = await renderer.renderToString(context)
    res.send(html)
  }catch {
    res.status(500).send("服务器内部错误!")
  }
})

app.listen(9999, () => {
  console.log("服务器渲染成功!")
})

webpack 打包配置

根目录下创建 vue 配置文件 vue.config.js 进行 webpack 配置,该配置会覆盖 vue-cli 中 webpack 的默认配置

vue.config.js:

const VueSSRServerPlugin = require("vue-server-renderer/server-plugin")
const VueSSRClientPlugin = require("vue-server-renderer/client-plugin")

//环境变量,决定入口是客户端还是服务端
const TARGRT_NODE = process.env.WEBPACK_TARGET === "node"
const target = TARGRT_NODE ? "server" : "client"

module.exports = {
  css: {
    extract: false
  },
  outputDir: "./dist/" + target,
  configureWebpack: () => ({
    //将 entry 指向应用程序的 server entry 文件
    entry: `./src/entry-${target}.js`,
    //对 bundle renderer 提供 source map 支持
    devtool: "source-map",
    //这允许 webpack 以 Node 适用方式(Node-appropriate fashion)处理动态导入(dynamic import)
    //并且还会在编译 Vue 组件时,告知 `vue-loader` 输送面向服务器代码(server-oriented code)
    target: TARGRT_NODE ? "node" : "web",
    node: TARGRT_NODE ? undefined : false,
    output: {
      //此处告知 server bundle 使用 Node 风格导出模块(Node-style exports)
      libraryTarget: TARGRT_NODE ? "commonjs2" : undefined
    },
    optimization: { splitChunks: TARGRT_NODE ? false : undefined },
    //将服务器的整个输出构建为单个 JOSN 文件的插件
    //服务端默认文件名为 vue-ssr-server-bundle.json
    plugins: [TARGRT_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]
  })
}

打包脚本配置

cross-env 插件:运行跨平台设置和使用环境变量的脚本
安装 cross-env 插件

npm install cross-env --save

package.json 文件中定义项目运行打包脚本

package.json:

{
	......
	"scripts": {
		"server": "node server",
	  "build:client": "vue-cli-service build",
	  "build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build --mode server",
	  "build": "npm run build:server && npm run build:client"
	},
	......
}

终端执行命令打包项目:

npm run build

打包完成后,终端执行命令启动 Node.js 服务器

npm run server

服务器启动后,浏览器打开 localhost:9999 即可访问 SSR 项目
查看网页源代码发现根元素上添加了一个特殊的属性:data-server-rendered,该属性让客户端 Vue 知道这部分 HTML 是由 Vue 在服务端渲染的,并且应该以激活模式进行挂载

<div id="app" data-server-rendered="true">......</div>

vuecli项目构建SSR服务端渲染的实现

项目目录:

vuecli项目构建SSR服务端渲染的实现

打包后 dist 目录:

vuecli项目构建SSR服务端渲染的实现

到此这篇关于vuecli项目构建SSR服务端渲染的实现的文章就介绍到这了,更多相关vuecli构建SSR服务端渲染内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
用js来定义浏览器中一个左右浮动元素相对于页面主体宽度的位置的函数
Jan 21 Javascript
Js与下拉列表处理问题解决
Feb 13 Javascript
在JavaScript中正确引用bind方法的应用
May 11 Javascript
基于javascript实现九九乘法表
Mar 27 Javascript
用jQuery的AJax实现异步访问、异步加载
Nov 02 Javascript
weUI应用之JS常用信息提示弹层的封装
Nov 21 Javascript
jQuery获取table下某一行某一列的值实现代码
Apr 07 jQuery
layui实现文件或图片上传记录
Aug 28 Javascript
vue将单页面改造成多页面应用的方法
Nov 25 Javascript
微信JS-SDK updateAppMessageShareData安卓不能自定义分享详解
Mar 29 Javascript
Vue混入mixins滚动触底的方法
Nov 22 Javascript
原生js实现随机点名
Jul 05 Javascript
Javascript文本框脚本实现方法解析
Oct 30 #Javascript
vue使用keep-alive实现组件切换时保存原组件数据方法
Oct 30 #Javascript
vue内置组件keep-alive事件动态缓存实例
Oct 30 #Javascript
Javascript表单序列化原理及实现代码详解
Oct 30 #Javascript
解决Vue-cli无法编译es6的问题
Oct 30 #Javascript
Vue2.0 ES6语法降级ES5的操作
Oct 30 #Javascript
Vue自定义表单内容检查rules实例
Oct 30 #Javascript
You might like
[FAQ]PHP中的一些常识:类篇
2006/10/09 PHP
PHP中利用substr_replace将指定两位置之间的字符替换为*号
2011/01/27 PHP
PHP中break及continue两个流程控制指令区别分析
2011/04/18 PHP
PHP连接SQLServer2005的实现方法(附ntwdblib.dll下载)
2012/07/02 PHP
完美解决PHP中的Cannot modify header information 问题
2013/08/12 PHP
php删除文本文件中重复行的方法
2015/04/28 PHP
PHP+JQuery+Ajax实现分页方法详解
2016/08/06 PHP
PHP使用phpunit进行单元测试示例
2019/09/23 PHP
PHP获取php,mysql,apche的版本信息及更多服务器信息
2021/03/09 PHP
Jquery中LigerUi的弹出编辑框(实现方法)
2013/07/09 Javascript
JS实现Enter键跳转及控件获得焦点
2013/08/12 Javascript
js 窗口抖动示例
2013/09/04 Javascript
基于JQuery的列表拖动排序实现代码
2013/10/01 Javascript
AngularJS HTML编译器介绍
2014/12/06 Javascript
AngularJS 最常用的功能汇总
2016/02/17 Javascript
Cpage.js给组件绑定事件的实现代码
2017/08/31 Javascript
python字符串连接方法分析
2016/04/12 Python
简单谈谈Python中的闭包
2016/11/30 Python
windows上安装Anaconda和python的教程详解
2017/03/28 Python
python版DDOS攻击脚本
2019/06/12 Python
pandas 层次化索引的实现方法
2019/07/06 Python
python flask 如何修改默认端口号的方法步骤
2019/07/12 Python
用python生成与调用cntk模型代码演示方法
2019/08/26 Python
pytorch方法测试——激活函数(ReLU)详解
2020/01/15 Python
K近邻法(KNN)相关知识总结以及如何用python实现
2021/01/28 Python
中国宠物用品商城:E宠商城
2016/08/27 全球购物
工厂保洁员岗位职责
2013/12/04 职场文书
银行实习的自我鉴定
2013/12/10 职场文书
软件项目实施计划书
2014/05/02 职场文书
安全目标管理责任书
2014/07/25 职场文书
工程索赔意向书
2014/08/30 职场文书
县政府领导班子“四风”方面突出问题整改措施
2014/09/23 职场文书
三严三实对照检查材料思想汇报
2014/09/28 职场文书
学习党的群众路线对照检查材料
2014/09/29 职场文书
小学语文新课改心得体会
2016/01/22 职场文书
创业计划书之孕婴生活馆
2019/11/11 职场文书