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 图片裁剪技巧解读
Nov 15 Javascript
关于全局变量和局部变量的那些事
Jan 11 Javascript
jQuery实现的给图片点赞+1动画效果(附在线演示及demo源码下载)
Dec 31 Javascript
JavaScript实现的选择排序算法实例分析
Apr 14 Javascript
jquery拖动改变div大小
Jul 04 jQuery
vue  自定义组件实现通讯录功能
Sep 30 Javascript
css配合JavaScript实现tab标签切换效果
Oct 11 Javascript
VueJs里利用CryptoJs实现加密及解密的方法示例
Apr 29 Javascript
angula中使用iframe点击后不执行变更检测的问题
May 10 Javascript
Vue切换组件实现返回后不重置数据,保留历史设置操作
Jul 21 Javascript
vue 通过 Prop 向子组件传递数据的实现方法
Oct 30 Javascript
vue 使用饿了么UI仿写teambition的筛选功能
Mar 01 Vue.js
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
PHP 在线翻译函数代码
2009/05/07 PHP
php download.php实现代码 跳转到下载文件(response.redirect)
2009/08/26 PHP
网页游戏开发入门教程二(游戏模式+系统)
2009/11/02 PHP
php读取txt文件组成SQL并插入数据库的代码(原创自Zjmainstay)
2012/07/31 PHP
php读取文件内容的几种方法详解
2013/06/26 PHP
CodeIgniter中使用cookie的三种方式详解
2014/07/18 PHP
php和editplus正则表达式去除空白行
2015/04/17 PHP
php提交post数组参数实例分析
2015/12/17 PHP
对setInterval在火狐和chrome切换标签产生奇怪的效果之探索,与解决方案!
2011/10/29 Javascript
json的前台操作和后台操作实现代码
2012/01/20 Javascript
js形成页面的一种遮罩效果实例代码
2014/01/04 Javascript
网站繁简切换的JS遇到页面卡死的解决方法
2014/03/12 Javascript
Javascript实现禁止输入中文或英文的例子
2014/12/09 Javascript
jQuery中:eq()选择器用法实例
2014/12/29 Javascript
jQuery实现动态生成表格并为行绑定单击变色动作的方法
2017/04/17 jQuery
jquery ui sortable拖拽后保存位置
2017/04/27 jQuery
AngularJS全局警告框实现方法示例
2017/05/18 Javascript
vue 基于element-ui 分页组件封装的实例代码
2018/12/10 Javascript
深入解析微信小程序开发中遇到的几个小问题
2020/07/11 Javascript
javascript实现点击小图显示大图
2020/11/29 Javascript
[05:29]2014DOTA2国际邀请赛 赛后专访:LGDNewbee顺利过关
2014/07/13 DOTA
Python生成随机验证码的两种方法
2015/12/22 Python
Python数据持久化shelve模块用法分析
2018/06/29 Python
基于pandas将类别属性转化为数值属性的方法
2018/07/25 Python
Windows下将Python文件打包成.EXE可执行文件的方法
2018/08/03 Python
python将.ppm格式图片转换成.jpg格式文件的方法
2018/10/27 Python
Anaconda+Pycharm环境下的PyTorch配置方法
2020/03/13 Python
快速创建 HTML5 Canvas 电信网络拓扑图的示例代码
2018/03/21 HTML / CSS
移动端Html5页面生成图片解决方案
2018/08/07 HTML / CSS
详解h5页面在不同ios设备上的问题总结
2019/03/01 HTML / CSS
WINDOWS域的具体实现方式是什么
2014/02/20 面试题
交通安全寄语大全
2014/04/08 职场文书
领导班子遵守党的政治纪律情况对照检查材料
2014/09/26 职场文书
关于颐和园的导游词
2015/01/30 职场文书
总结Python连接CS2000的详细步骤
2021/06/23 Python
golang生成vcf通讯录格式文件详情
2022/03/25 Golang