vue ssr服务端渲染(小白解惑)


Posted in Javascript onNovember 10, 2019

>初学ssr入坑

初学vue服务端渲染疑惑非常多,我们大部分前端都是半路出家,上手都是前后端分离,对服务端并不了解,不说java、php语言了,连node服务都还没搞明白,理解服务端渲染还是有些困难的;

网上有非常多的vue服务渲染的入门案例,但看了很久,很多,还是一头雾水,搞不明白这些文件和关键字的联系和意思:

  • server.js
  • entrt-client.js
  • server-js
  • built-server-bundle.js
  • vue-ssr-server-bundle.json
  • vue-ssrclientmanifest.json
  • createBundleRenderer
  • clientManifest

这篇内容会按照 基础服务端渲染--vue实例渲染--加入vueRouter--加入vueX的顺序入坑,后续应该还有--开发模式--seo优化--部分渲染,这里先不挖那么多坑了;

>基础服务端渲染

顾名思义,得启个服务:(建个新项目,不要用vue-cli)

vue ssr服务端渲染(小白解惑)

//server.js
const express = require('express');
const chalk = require('chalk');//加个chalk就是console好看点。。

const server = express();

server.get('*', (req, res) => {
res.set('content-type', "text/html");
res.end(`
<!DOCTYPE html>
<html lang="en">
 <head><title>Hello</title></head>
 <body>你好</body>
</html>
`)
})

server.listen(8080,function(){
let ip = getIPAdress();
console.log(`服务器开在:http://${chalk.green(ip)}:${chalk.yellow(8080)}`)
})

function getIPAdress(){//node下的os模块可以拿到启动该文件的服务端的部分信息
var interfaces = require('os').networkInterfaces();
for (var devName in interfaces) {
 var iface = interfaces[devName];
 for (var i = 0; i < iface.length; i++) {
  var alias = iface[i];
  if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
   return alias.address;
  }
 }
}
}

启动 node server.js

vue ssr服务端渲染(小白解惑)

再看页面 正常,这就是最基础的服务端渲染

vue ssr服务端渲染(小白解惑)

其实就是一个get请求,返回一个字符串,浏览器默认展示返回结果;

然而对于这个字符串的解析还不明确,什么意思,比如:

vue ssr服务端渲染(小白解惑)

去掉这句话,页面就成了这样,原因不深究,自己百度

vue ssr服务端渲染(小白解惑)

>加入vue实例

跳过官网说的built-server-bundle.js应用,意思就是不用管这个文件了,只是一个过渡文件,项目中也不会用到。直接使用createBundleRenderer方法,直接用vue-ssr-server-bundle.json;

看下现在的目录结构:

vue ssr服务端渲染(小白解惑)

新增了5个文件;有关客户端的配置entry-client.js不是必须的,这里先不管;

app.js是用来创建vue实例的;

entry-server.js是用来创建生成vue-ssr-server-bundle.json(需要用到app.js)所需的配置配件;是给webpack.server.config.js用的;

webpack.server.config.js是用来生成vue-ssr-server-bundle.json的;

vue-ssr-server-bundle.json是给server.js中的createBundleRenderer用的。

//app.js 
import Vue from 'vue'
import Vue from './App.vue'//这里一定要写上.vue,不然会匹配到app.js,require不区分大小写0.0
export default createApp=function(){
return new Vue({
 render:h => h(App)
})
}

一个createApp生成一个vue实例;

//App.vue
<template>
<div id='app'>
 这是个app
</div>
</template>
<script>
export default {}
</script>

还没用到<router-view>

//weback-base.config.js
const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin')

module.exports = {
output:{
 path:path.resolve(__dirname,'./dist'),
 filename:'build.js',
},
module: {
 rules: [
  {
   test:/\.js$/,
   use: {
    loader: 'babel-loader',
    options: {
     presets: ['@babel/preset-env']
    }
   },
   exclude:[/node_modules/,/assets/]
  },
  {
   test:/\.vue$/,
   use:['vue-loader']
  }
 ]
},
resolve: {
 alias:{
  '@':path.resolve(__dirname,'../')
 },
 extensions:['.js','.vue','.json']
},
plugins:[
 new VueLoaderPlugin()
]
}

有关webpack配置不???/p>

//webpack.server.config.js用来生成vue-ssr-server-bundle.json
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.js')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')

module.exports = merge(baseConfig, {
 entry: './entry-server.js',

 // 这允许 webpack 以 Node 适用方式(Node-appropriate fashion)处理动态导入(dynamic import),
 // 并且还会在编译 Vue 组件时,
 // 告知 `vue-loader` 输送面向服务器代码(server-oriented code)。
 target: 'node',

 // 对 bundle renderer 提供 source map 支持
 devtool: 'source-map',

 // 此处告知 server bundle 使用 Node 风格导出模块(Node-style exports)
 output: {
 libraryTarget: 'commonjs2'
 },


 // 这是将服务器的整个输出
 // 构建为单个 JSON 文件的插件。
 // 默认文件名为 `vue-ssr-server-bundle.json`
 plugins: [
 new VueSSRServerPlugin()
 ]
})

这个配置哪都能找到,重点是VueSSRServerPlugin这个插件,生成vue-ssr-server-bundle.json全靠它,去掉的话生成的是built-server-bundle.js;关于merge插件,libraryTarget,target配置问题自己百度webpack去0.0;

//entry-server.js
import { createApp } from './src/app'

export default context => {
 return createApp()
}

固定写法,返回一个函数供createBundleRenderer使用;

生成vue-ssr-server-bundle.json

到目前为止安装的插件有:

vue ssr服务端渲染(小白解惑)

自己手动一个一个装就行了。

生成vue-ssr-server-bundle.json,使用webpack命令

vue ssr服务端渲染(小白解惑)

一切都手动,熟悉webpack;

vue ssr服务端渲染(小白解惑)

修改server.js

const express = require('express');
const chalk = require('chalk');

const server = express();
const serverBundle = require('./dist/vue-ssr-server-bundle.json')//**新增**//
const renderer = require('vue-server-renderer').createBundleRenderer(serverBundle,{
 runInNewContext: false, // 看名字也知道是生成某个新的Context对象,默认是true,改成false理解为某种缓存机制,提高服务器效率
 template: require('fs').readFileSync('./index.html', 'utf-8'),
 })//**新增**//
server.get('*', (req, res) => {
 //res.set('content-type', "text/html");
 //res.end(`
 //<!DOCTYPE html>
 //<html lang="en">
 // <head><title>Hello</title></head>
 // <body >
 // <div style='color:red'>你好</div>
 // </body>
 // </html>
 //改成下面这样
 const context = {//这里的参数现在还没用,但这个对象还是得用,要做renderToString的参数
 url:req.url
 }
 renderer.renderToString(context, (err, html) => {
  if (err) {
  res.status(500).end('Internal Server Error')
  return
  } else {
  res.end(html)
  }
 })
 `)
 })

server.listen(8080,function(){
 let ip = getIPAdress();
 console.log(`服务器开在:http://${chalk.green(ip)}:${chalk.yellow(8080)}`)
})

function getIPAdress(){//node下的os模块可以拿到启动该文件的服务端的部分信息,细节自己去node上面查
 var interfaces = require('os').networkInterfaces();
 for (var devName in interfaces) {
  var iface = interfaces[devName];
  for (var i = 0; i < iface.length; i++) {
   var alias = iface[i];
   if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
    return alias.address;
   }
  }
 }
}

试一蛤:node server.js

vue ssr服务端渲染(小白解惑)

正常,箭头指的地方官网有解释。别忘了inde.html中加入一行注释:

vue ssr服务端渲染(小白解惑)

后续修改title,meta头部都是通过类似的注释方式,原理就是正则匹配替换字符串-。-;

>加入路由vue-router

新增几个文件

vue ssr服务端渲染(小白解惑)

需要修改的文件有:

App.vue//加个router-view就行

vue ssr服务端渲染(小白解惑)

//app.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
export function createApp(){
 const app = new Vue({
  router,
  render:h => h(App)
 })
 return {app,router}
}

把app实例和router都抛出去,给entry-server.js用

// entry-server.js
import { createApp } from './src/app'

export default context => {
 //这里用promise的原因有很多,其中有一个就是下面这个onReady方法是异步的。createBundleRenderer支持promise
 return new Promise((resolve, reject) => {
 const { app, router } = createApp()

 router.push(context.url)

 router.onReady(() => {//onReady方法还有getMatchedComponents方法还是需要了解一下
  const matchedComponents = router.getMatchedComponents()
  if (!matchedComponents.length) {
  return reject({ code: 404 })
  }
  resolve(app)
 }, reject)
 })
}

最后看一下router.js

//router.js
 import Vue from 'vue'
 import VueRouter from 'vue-router'
//页面要先声明后使用,不要问为什么
import home from './pages/home'
import store from './pages/store'

Vue.use(VueRouter)
export default new VueRouter({
 mode: 'history',
 routes:[
  {path:'/',name:'home',component:home},
  {path:'/store',name:'store',component:store},
 ]
})

再看一下两个页面的代码;

//store.vue 
 <template>
 <div>this is store</div>
 </template>
 <script>
   export default {}
 </script>

改的差不多了,试一哈:

重新打个包webpack --config webpack.server.js

启动node server

vue ssr服务端渲染(小白解惑)

>entry-client.js是干啥的

到目前为止还没用到entry-client.js叫客户端配置,不着急使用,先做个测试,写点逻辑试试:
修改下store.vue

//store.vue
<template>
<div @click='run'>{{msg}}</div>
</template>
<script>
 export default {
  data(){
   msg:'this is store'
  },
  created(){
   this.msg = 'this is created'
  },
  mounted(){
   this.msg = 'this is mounted'
  },
  methods: {
   run(){
    alert('this is methods')
   }
  }
 }
</script>

看这个样子页面最终展示的结果应该是this is mounted,然而结果是这样的:

vue ssr服务端渲染(小白解惑)

很好解释,服务端对于钩子函数的理解也是很正确的,created会在页面返回之前执行,而mounted是在vue实例成型之后执行,就是页面渲染后,这个是要在客户端才会执行,可是为什么页面出来了没有执行mounted,而且run的点击事件没有生效;

看看页面:

vue ssr服务端渲染(小白解惑)

一个js文件都没加载,怎么执行逻辑,就是个静态页面0.0;

这时候entry-client.js就出场了

vue ssr服务端渲染(小白解惑)

新增两个文件

//entry-client.js 
import { createApp } from './src/app.js';

const { app } = createApp();

app.$mount('#app');

基本配置;

//webpack.client.config.js

const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.config.js')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')

module.exports = merge(baseConfig, {
 entry: './entry-client.js',
 optimization:{
 runtimeChunk:true
 },
 plugins: [
 // 此插件在输出目录中
 // 生成 `vue-ssr-client-manifest.json`。
 new VueSSRClientPlugin(),
 ]
})

这个地方重点除了VueSSRClientPlugin生成vue-ssr-client-manifest.json外,optimization是webpack4产物,用来分离生成共公chunk,配置还算复杂,可以看下这里webpack4 optimization总结

修改下server.js

//server.js

 const express = require('express');
 const chalk = require('chalk');

 const server = express();

 const serverBundle = require('./dist/vue-ssr-server-bundle.json')
 const clientManifest = require('./dist/vue-ssr-client-manifest.json')//新增
 const renderer = require('vue-server-renderer').createBundleRenderer(serverBundle,{
  runInNewContext: false, // 推荐
  template: require('fs').readFileSync('./index.html', 'utf-8'),
  clientManifest // //新增
  })
 server.get('*', (req, res) => {
  res.set('content-type', "text/html");
  const context = {
   url:req.url
   }

   renderer.renderToString(context, (err, html) => {
    if (err) {
    res.status(500).end('Internal Server Error')
    return
    } else {
    res.end(html)
    }
   })

  })

 server.listen(8080,function(){
  let ip = getIPAdress();
  console.log(`服务器开在:http://${chalk.green(ip)}:${chalk.yellow(8080)}`)
 })

 function getIPAdress(){//node下的os模块可以拿到启动该文件的服务端的部分信息,细节自己去node上面查
  var interfaces = require('os').networkInterfaces();
  for (var devName in interfaces) {
   var iface = interfaces[devName];
   for (var i = 0; i < iface.length; i++) {
    var alias = iface[i];
    if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
     return alias.address;
    }
   }
  }
 }

打包下:webpack --config webpack.client.config.js

vue ssr服务端渲染(小白解惑)

node server 一下,看看页面

vue ssr服务端渲染(小白解惑)

js有了,可是为什么还不行,不能点0.0;

看看。奥报错了

vue ssr服务端渲染(小白解惑)

读取不到静态文件;

修改server.js加个静态文件托管:

vue ssr服务端渲染(小白解惑)

再看看

vue ssr服务端渲染(小白解惑)

事件也有了,页面没变化,console一下,发现值其实已经变了,只是失去了响应式;这就是为什么要用vuex的缘故;

>加入vuex

开始想在页面中用this.$set方法,然而行不通,而且不可能给每个值都重新写一个这个方法;

vue ssr服务端渲染(小白解惑)

加个sotre.js

// store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

 export default new Vuex.Store({
 state: {
  msg: ''
 },
 actions: {
  setMsg ({ commit }, val) {
   commit('setMsg', val)
  }
 },
 mutations: {
  setMsg (state, val) {
  Vue.set(state, 'msg', val)//关键
  }
 }
 })

很基础的逻辑,关键在Vue.set这个方法,重新增加了响应式;
修改下app.js

//app.js
 import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'//加个store就行了
export function createApp(){
 const app = new Vue({
  router,
  store,
  render:h => h(App)
 })
 return {app,router}
}

store.vue改成这样

<template>
 <div @click='run'>{{msg}}</div>
</template>
<script>
 export default {
  data(){},
  created(){
   this.$store.dispatch('setMsg','this is created')
  },
  computed:{
   msg(){
    return this.$store.state.msg;
   }
  },
  mounted(){
   this.$store.dispatch('setMsg','this is mounted')
  },
  methods: {
   run(){
    alert('this is methods')
   }
  }
 }
</script>

重新打个包,想一下,修改页面的话只需要重新打包client,如果修改了app.js两个就要都重新打包了;

node server 一下

vue ssr服务端渲染(小白解惑)

这回总算完成了;

>总结

服务端渲染东西还是挺多的,涉及领域也非常广,比如vue,webpack,node,它们的生态圈都大的可怕,需要学习东西非常多,
坑又多,又大,又深,后面还有很多问题要解决:

异步数据加载;//html返回前先渲染一部分接口拿到的数据
怎么做seo优化;//做服务端渲染的重要原因,处理异步数据加载问题也是为了这个
缓存怎么加;
开发环境搭建;//你并不希望每改一行代码就重新手动打个包,重启下服务吧0.0
还有怎么实现部分页面ssr;//一个项目不可能所有页面都服务端渲染,太耗性能,服务器压力大呀;

还有很多疑惑:

比如为什么会失去响应式,webpack到底该怎么配置。。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript使用eval或者new Function进行语法检查
Oct 16 Javascript
JavaScript高级程序设计阅读笔记(六) ECMAScript中的运算符(二)
Feb 27 Javascript
JavaScript正则表达式之multiline属性的应用
Jun 16 Javascript
全面解析Bootstrap中form、navbar的使用方法
May 30 Javascript
jquery中的常见问题及快速解决方法小结
Jun 14 Javascript
mint-ui的search组件在键盘显示搜索按钮的实现方法
Oct 27 Javascript
JavaScript使用闭包模仿块级作用域操作示例
Jan 21 Javascript
JavaScript JMap类定义与使用方法示例
Jan 22 Javascript
在vue中使用G2图表的示例代码
Mar 19 Javascript
jquery+php后台实现省市区联动功能示例
May 23 jQuery
vue 实现通过vuex 存储值 在不同界面使用
Nov 11 Javascript
原生js拖拽实现图形伸缩效果
Feb 10 Javascript
node后端服务保活的实现
Nov 10 #Javascript
vue动态循环出的多个select出现过的变为disabled(实例代码)
Nov 10 #Javascript
vue父子组件的通信方法(实例详解)
Nov 10 #Javascript
分享Angular http interceptors 拦截器使用(推荐)
Nov 10 #Javascript
vue-父子组件和ref实例详解
Nov 10 #Javascript
vue $set 给数据赋值的实例
Nov 09 #Javascript
Vue 数组和对象更新,但是页面没有刷新的解决方式
Nov 09 #Javascript
You might like
PHILIPS AE3805收音机的分析打磨
2021/03/02 无线电
析构函数与php的垃圾回收机制详解
2013/10/28 PHP
php有道翻译api调用方法实例
2014/12/22 PHP
PHP正则+Snoopy抓取框架实现的抓取淘宝店信誉功能实例
2017/05/17 PHP
Gambit vs CL BO3 第一场 2.13
2021/03/10 DOTA
javascript 函数参数限制说明
2010/11/19 Javascript
NodeJS学习笔记之Http模块
2015/01/13 NodeJs
js行号显示的文本框实现效果(兼容多种浏览器 )
2015/10/23 Javascript
Javascript之Number对象介绍
2016/06/07 Javascript
jQuery动态添加与删除tr行实例代码
2016/10/18 Javascript
bootstrap中使用google prettify让代码高亮的方法
2016/10/21 Javascript
JS正则RegExp.test()使用注意事项(不具有重复性)
2016/12/28 Javascript
Javascript实现信息滚动效果
2017/05/18 Javascript
jquery Ajax实现Select动态添加数据
2017/06/08 jQuery
详解Vue-Cli 异步加载数据的一些注意点
2017/08/12 Javascript
详解在Node.js中发起HTTP请求的5种方法
2019/01/10 Javascript
JS实现获取当前所在周的周六、周日示例分析
2019/05/11 Javascript
Bootstrap 时间日历插件bootstrap-datetimepicker配置与应用小结
2019/05/28 Javascript
如何使用three.js 制作一个三维的推箱子游戏
2020/07/29 Javascript
在Python中操作字典之fromkeys()方法的使用
2015/05/21 Python
python 获取网页编码方式实现代码
2017/03/11 Python
Python通过matplotlib绘制动画简单实例
2017/12/13 Python
Python基于socket实现简单的即时通讯功能示例
2018/01/16 Python
Python多线程threading和multiprocessing模块实例解析
2018/01/29 Python
Django中针对基于类的视图添加csrf_exempt实例代码
2018/02/11 Python
python实现windows下文件备份脚本
2018/05/27 Python
使用Keras加载含有自定义层或函数的模型操作
2020/06/10 Python
keras实现基于孪生网络的图片相似度计算方式
2020/06/11 Python
Python2及Python3如何实现兼容切换
2020/09/01 Python
python批量检查两个对应的txt文件的行数是否一致的实例代码
2020/10/31 Python
Dillard’s百货官网:Dillards.com
2018/05/26 全球购物
《莫高窟》教学反思
2014/02/25 职场文书
2014年国庆节演讲稿
2014/09/19 职场文书
廉政文化进校园广播稿
2014/10/20 职场文书
职称评定个人总结
2015/03/05 职场文书
2015年中学总务处工作总结
2015/07/22 职场文书