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 相关文章推荐
cssQuery()的下载与使用方法
Jan 12 Javascript
JavaScript调用Activex控件的事件的实现方法
Apr 11 Javascript
基于jquery的不规则矩形的排列实现代码
Apr 16 Javascript
Javascript在IE和FireFox中的不同表现简析
Dec 03 Javascript
关于JavaScript对象的动态选择及遍历对象
Mar 10 Javascript
JS实现无限级网页折叠菜单(类似树形菜单)效果代码
Sep 17 Javascript
javascript弹性运动效果简单实现方法
Jan 08 Javascript
基于javascript制作微博发布栏效果
Apr 04 Javascript
JS加载iFrame出现空白问题的解决办法
May 13 Javascript
详解Vue.js——60分钟组件快速入门(上篇)
Dec 05 Javascript
一个例子轻松学会Vue.js
Jan 02 Javascript
封装Vue Element的table表格组件的示例详解
Aug 19 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模板引擎Smarty自定义变量调解器用法
2016/04/11 PHP
php利用ffmpeg提取视频中音频与视频画面的方法详解
2017/06/07 PHP
Mac下快速搭建PHP开发环境步骤详解
2019/05/05 PHP
js禁止小键盘输入数字功能代码
2011/08/01 Javascript
javascript结合ajax读取txt文件内容
2014/12/05 Javascript
js实现的tab标签切换效果代码分享
2015/08/25 Javascript
微信js-sdk分享功能接口常用逻辑封装示例
2016/10/13 Javascript
详解vue.js组件化开发实践
2016/12/14 Javascript
微信小程序 navbar实例详解
2017/05/11 Javascript
angularJS实现不同视图同步刷新详解
2018/10/09 Javascript
jquery实现的分页显示功能示例
2019/08/23 jQuery
electron+vue实现div contenteditable截图功能
2020/01/07 Javascript
javascript解析json格式的数据方法详解
2020/08/07 Javascript
用python代码做configure文件
2014/07/20 Python
浅析Python中else语句块的使用技巧
2016/06/16 Python
Django在win10下的安装并创建工程
2017/11/20 Python
python机器学习之贝叶斯分类
2018/03/26 Python
python实现多线程网页下载器
2018/04/15 Python
Python实现删除排序数组中重复项的两种方法示例
2019/01/31 Python
Python将json文件写入ES数据库的方法
2019/04/10 Python
python爬虫神器Pyppeteer入门及使用
2019/07/13 Python
Selenium执行完毕未关闭chromedriver/geckodriver进程的解决办法(java版+python版)
2020/12/07 Python
pandas数据分组groupby()和统计函数agg()的使用
2021/03/04 Python
CSS3 实现的缩略图悬停效果
2020/12/09 HTML / CSS
Omio中国:全欧洲低价大巴、火车和航班搜索和比价
2018/08/09 全球购物
ETO男装官方网店:ETO Jeans
2019/02/28 全球购物
DNA基因检测和分析:23andMe
2019/05/01 全球购物
教师自荐信
2013/12/10 职场文书
人事主管岗位职责
2014/01/30 职场文书
岗位职责风险防控
2014/02/18 职场文书
2014年最新学校运动会广播稿
2014/09/17 职场文书
终止劳动合同协议书
2014/10/05 职场文书
合作协议书范本
2014/10/25 职场文书
巾帼标兵事迹材料
2014/12/26 职场文书
建议书格式
2015/02/04 职场文书
资金申请报告范文
2015/05/14 职场文书