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 相关文章推荐
JSON.stringify转换JSON时日期时间不准确的解决方法
Aug 08 Javascript
setTimeout()递归调用不加引号出错的解决方法
Sep 05 Javascript
深入理解js函数的作用域与this指向
May 28 Javascript
canvas学习之API整理笔记(一)
Dec 29 Javascript
vue.js开发实现全局调用的MessageBox组件实例代码
Nov 22 Javascript
javascript Function函数理解与实战
Dec 01 Javascript
微信小程序项目实践之验证码倒计时功能
Jul 18 Javascript
详解JavaScript中的坐标和距离
May 27 Javascript
JS+html5实现异步上传图片显示上传文件进度条功能示例
Nov 09 Javascript
HTML+JS实现“代码雨”效果源码(黑客帝国文字下落效果)
Mar 17 Javascript
JS字符串补全方法padStart()和padEnd()
May 27 Javascript
在vue中实现嵌套页面(iframe)
Jul 30 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
php加密解密实用类分享
2014/01/07 PHP
php 生成Tab键或逗号分隔的CSV
2016/09/24 PHP
JS基础之undefined与null的区别分析
2011/08/08 Javascript
jquery与prototype框架的详细对比
2013/11/21 Javascript
JavaScript函数作用域链分析
2015/02/13 Javascript
jquery实现可自动收缩的TAB网页选项卡代码
2015/09/06 Javascript
Angular 表单控件示例代码
2017/06/26 Javascript
Canvas放置反弹效果随机图形(实例)
2017/08/17 Javascript
vue实现全选和反选功能
2017/08/31 Javascript
VUE中的无限循环代码解析
2017/09/22 Javascript
Scala解析Json字符串的实例详解
2017/10/11 Javascript
Vue 3.x+axios跨域方案的踩坑指南
2019/07/04 Javascript
微信小程序-API接口安全详解
2019/07/16 Javascript
[02:27]《DAC最前线》之附加赛征程
2015/01/29 DOTA
python求crc32值的方法
2014/10/05 Python
Python实现批量压缩图片
2018/01/25 Python
python 爬虫一键爬取 淘宝天猫宝贝页面主图颜色图和详情图的教程
2018/05/22 Python
python使用Matplotlib改变坐标轴的默认位置
2019/10/18 Python
详解python破解zip文件密码的方法
2020/01/13 Python
python中Django文件上传方法详解
2020/08/05 Python
维多利亚的秘密官方旗舰店:VICTORIA’S SECRET
2018/04/02 全球购物
配置管理计划的主要内容有哪些
2014/06/20 面试题
文秘专业大学生求职信
2013/11/10 职场文书
护士思想汇报
2014/01/12 职场文书
幼儿园教学管理制度
2014/02/04 职场文书
硕士生工作推荐信
2014/03/07 职场文书
yy婚礼司仪主持词
2014/03/14 职场文书
经济贸易专业自荐信
2014/06/11 职场文书
婚前协议书标准版
2014/10/19 职场文书
计算机实训报告范文
2014/11/05 职场文书
护士实习自荐信
2015/03/06 职场文书
家长反馈意见及建议
2015/06/03 职场文书
安全知识竞赛主持词
2015/06/30 职场文书
四群教育工作总结
2015/08/10 职场文书
html实现弹窗的实例
2021/06/09 HTML / CSS
Mysql数据库按时间点恢复实战记录
2021/06/30 MySQL