根据官网例子一步步实现vueSSR(详细)

根据 官网例子一步步实现vue ssr
标题对应官网标题

基本用法

  1. 新建一个文件夹 ssr
    cd ssr
    npm init -y
    npm install vue vue-server-renderer express --save 或者yarn add vue vue-server-renderer express --save

  2. 新建文件server.js
    server.js

// 第 1 步:创建一个 Vue 实例
const Vue = require('vue')
const app = new Vue({
  template: `<div>Hello World</div>`
})
// 第 2 步:创建一个 renderer
const renderer = require('vue-server-renderer').createRenderer()
// 第 3 步:将 Vue 实例渲染为 HTML
renderer.renderToString(app, (err, html) => {
  if (err) throw err
  console.log(html)
})
// 在 2.5.0+,如果没有传入回调函数,则会返回 Promise:
renderer.renderToString(app).then(html => {
  console.log(html)
}).catch(err => {
  console.error(err)
})

运行命令node server.js,控制台会输出两行<div data-server-rendered="true">Hello World</div>

  1. 修改server.js
const Vue = require('vue')
const server = require('express')()
const renderer = require('vue-server-renderer').createRenderer()

server.get('*', (req, res) => {
  const app = new Vue({
    data: {
      url: req.url
    },
    template: `<div>访问的 URL 是: {{ url }}</div>`
  })

  renderer.renderToString(app, (err, html) => {
    if (err) {
      res.status(500).end('Internal Server Error')
      return
    }
    res.end(`
      <!DOCTYPE html>
      <html lang="en">
        <head>
        <meta charset="utf-8">
        <title>vue ssr</title>
        </head>
        <body>${html}</body>
      </html>
    `)
  })
})

server.listen(8080,()=>{
    console.log('已监听 localhost:8080')
})

运行命令node server.js,控制台会输出已监听 localhost:8080
浏览器打开 localhost:8080
在这里插入图片描述

  1. 使用页面模版,新建文件 index.template.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <title>vue ssr</title>
</head>

<body>
    <!--vue-ssr-outlet-->
</body>

</html>

修改server.js,并运行,结果不变.
在这里插入图片描述

模版插值

修改 index.template.html 和 server.js ,结果不变

<!DOCTYPE html>
<html lang="en">

<head>
    <!-- 使用三花括号(triple-mustache)进行 HTML 不转义插值(non-HTML-escaped interpolation) -->
    {{{ meta }}}
    <!-- 使用双花括号(double-mustache)进行 HTML 转义插值(HTML-escaped interpolation) -->
    <title>{{ title }}</title>
</head>

<body>
    <!--vue-ssr-outlet-->
</body>

</html>
const context = {
    title: 'vue ssr',
    meta: `
    <meta charset="utf-8">
    `
  }
renderer.renderToString(app,context, (err, html) => {
   ...
   res.end(html)
 })

源码结构

将server.js 拆分为app.js 和 server.js
app.js

const Vue = require('vue')

module.exports = function createApp (context) {
  return new Vue({
    data: {
      url: context.url
    },
    template: `<div>访问的 URL 是: {{ url }}</div>`
  })
}

server.js
在这里插入图片描述
运行node server.js,结果不变.

简单来说就是 server.js 将vue代码注入到模版html中 ,并返回
这里是基本用法,源码在官网Vue SSR 指南中都有注解,先将前面弄懂在看下面。

使用 webpack 的源码结构(vue-cli3)

另起一个项目,使用vue-cli3脚手架搭建,根据自己需求选择
在这里插入图片描述
在这里插入图片描述
项目结构( +是新增文件,*是修改文件 )

.
├── README.md
├── babel.config.js
├── node_modules
│   └──...
├── public
│   ├── favicon.ico
│   └── index.html
├── src
│   ├── App.vue 
│   ├── assets
│   ├── components
│   ├── entry-client.js  +
│   ├── entry-server.js  +
│   ├── main.js *
│   ├── router.js
│   ├── store.js
│   └── views
├── package.json  *
├── vue.config.js +
└── yarn.lock

这是一个vue-cli3搭建完成后的前端项目

暂时先不考虑路由的问题

  1. main.js
    main.js 是我们应用程序的「通用 entry」。在纯客户端应用程序中,我们将在此文件中创建根 Vue 实例,并直接挂载到 DOM。但是,对于服务器端渲染(SSR),责任转移到纯客户端 entry 文件。main.js 简单地使用 export 导出一个 createApp 函数:
import Vue from 'vue'
import App from './App.vue'

// 导出一个工厂函数,用于创建新的
// 应用程序、router 和 store 实例
export function createApp () {
  const app = new Vue({
    // 根实例简单的渲染应用程序组件。
    render: h => h(App)
  })
  return { app }
}
  1. entry-client.js:
    客户端 entry 只需创建应用程序,并且将其挂载到 DOM 中:
    功能相当于未改动之前的main.js
import { createApp } from './main'

// 客户端特定引导逻辑……

const { app } = createApp()

// 这里假定 App.vue 模板中根元素具有 `id="app"`
app.$mount('#app')
  1. entry-server.js:
    服务器 entry 使用 default export 导出函数,并在每次渲染中重复调用此函数。此时,除了创建和返回应用程序实例之外,它不会做太多事情 - 但是稍后我们将在此执行服务器端路由匹配 (server-side route matching) 和数据预取逻辑 (data pre-fetching logic)。
import { createApp } from './main'

export default context => {
  const { app } = createApp()
  return app
}

在这里插入图片描述

过程简述
entry-server.js构建后成为 vue-ssr-server-bundle.json文件(vue代码)
server.js将 vue-ssr-server-bundle.json (vue代码)注入到模版文件中,并输出
暂时未用到 entry-client.js

构建配置

安装 cross-env
安装 webpack-node-externals
安装 vue-server-renderer
yarn add cross-env webpack-node-externals vue-server-renderer --save-dev

vue.config.js(文件配置参考官网 Vue SSR 指南 构建配置,官网里面有注解,这里就不加注解了)

调整 webpack 配置最简单的方式就是在 vue.config.js 中的 configureWebpack 选项提供一个对象,
该对象将会被 webpack-merge 合并入最终的 webpack 配置。

const nodeExternals = require('webpack-node-externals')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
module.exports = {
    configureWebpack: () => {
        if (process.env.WEBPACK_TARGET === 'node') {
            return {
                entry: './src/entry-server.js',
                target: 'node',
                devtool: 'source-map',
                output: {
                    libraryTarget: 'commonjs2'
                },
                externals: nodeExternals({
                    whitelist: /\.css$/
                }),
                plugins: [
                    new VueSSRServerPlugin()
                ]
            }
        } else {
            return {
                entry: './src/entry-client.js',
                plugins: [
                    new VueSSRClientPlugin()
                ]
            }
        }

    },
}

修改package.json

"scripts": {
    ...
    "build:client": "vue-cli-service build",
    "build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build --mode server",
    "build:win": "npm run build:server && move dist\\vue-ssr-server-bundle.json bundle && npm run build:client && move bundle dist\\vue-ssr-server-bundle.json",
    "build:mac": "npm run build:server && mv dist/vue-ssr-server-bundle.json bundle && npm run build:client && mv bundle dist/vue-ssr-server-bundle.json"
  },

根据自己系统运行 yarn run build:win 或者yarn run build:mac,会在根目录下生成dist文件夹

.
├── css
│   └── ...
├── favicon.ico
├── index.html
├── js
│   └── ....
├── vue-ssr-client-manifest.json
└── vue-ssr-server-bundle.json

服务端

在当前项目内 或者 新建一个文件夹 ,放置文件内容如下

.
├── ...
├── dist
├── index.template.html
└── server.js

安装express
yarn add express --save-dev

index.template.html 代码在上面代码构建
server.js(根据上面的js略作修改,使用了createBundleRenderer,详情看Bundle Renderer 指引)

const serverBundle = require('./dist/vue-ssr-server-bundle.json')
const server = require('express')()
const renderer = require('vue-server-renderer').createBundleRenderer(serverBundle,{
    template: require('fs').readFileSync('./index.template.html', 'utf-8')
})

server.get('*', (req, res) => {
  const context = {
    title: 'ssr demo',
    meta: `
    <meta charset="utf-8">
    `
  }
  renderer.renderToString(context, (err, html) => {
    if (err) {
      res.status(500).end('Internal Server Error')
      return
    }
    res.end(html)
  })
})

server.listen(8080,()=>{
    console.log('已监听 localhost:8080')
})

运行node server.js,并打开浏览器(因为还未用到router,所以home|about不可跳转路由)
在这里插入图片描述

路由和代码分割

路由和代码分割

  1. router.js
    类似于 createApp,我们也需要给每个请求一个新的 router 实例,所以文件导出一个 createRouter 函数
import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

export function createRouter() {
  return new Router({
    mode: 'history',
    routes: [
      { path: '/', component: () => import('./views/Home.vue') },
      { path: '/about', component: () => import('./views/About.vue') }
    ]
  })
}
  1. main.js
    在这里插入图片描述

  2. entry-server.js

import { createApp } from './main'

export default context => {
  // 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise,
    // 以便服务器能够等待所有的内容在渲染前,
    // 就已经准备就绪。
  return new Promise((resolve, reject) => {
    const { app, router } = createApp()

    // 设置服务器端 router 的位置
    router.push(context.url)

    // 等到 router 将可能的异步组件和钩子函数解析完
    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents()
      // 匹配不到的路由,执行 reject 函数,并返回 404
      if (!matchedComponents.length) {
        return reject({ code: 404 })
      }

      // Promise 应该 resolve 应用程序实例,以便它可以渲染
      resolve(app)
    }, reject)
  })
}
  1. entry-client.js
    在这里插入图片描述

  2. 重新构建,yarn run build:win 或者yarn run build:mac,生成新的dist

  3. index.template.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>{{ title }}</title>
  </head>
  <body>
    <!--vue-ssr-outlet-->
  </body>
</html>
  1. 修改server.js

在这里插入图片描述

url 会传给 entry-server.js

在这里插入图片描述

此时点击home|about,页面均是从服务端发送过来的

图片未加载,引入静态文件.

  1. app.use(express.static(path.resolve(__dirname, './dist')))(全部引入,不可取)
const express = require('express')
const app = express()
const { createBundleRenderer } = require('vue-server-renderer')
const path = require("path");


const template = require('fs').readFileSync('./index.template.html', 'utf-8')
const serverBundle = require('./dist/vue-ssr-server-bundle.json')

const renderer = createBundleRenderer(serverBundle, {
  template,
})
//引入静态文件  否则运行报错
app.use(express.static(path.resolve(__dirname, './dist')))

app.get('*', (req, res) => {
  const context = {
    title: 'ssr demo====',
    url: req.url
  }
  renderer.renderToString(context, (err, html) => {
    if (err) {
      if (err.code === 404) {
        res.status(404).end('Page not found')
      } else {
        res.status(500).end('Internal Server Error')
      }
    } else {
      res.end(html)
    }
  })

})
app.listen(8080, () => {
  console.log('已监听 localhost:8080')
})

运行node server.js
在这里插入图片描述
这里你会发现,你在服务端设置模版了(index.template.html),并设置了title为ssr demo====,但是他的显示不是这样,他所显示的是public文件夹下的index.html(若是删掉他,它会通过webpack配置自动生成一个index.html ,title为Vue App)
点击about标签,你会发现并没有发送请求.点击浏览器的刷新按钮,他才发送请求.
在这里插入图片描述

这里需要注意的是 静态文件的引入 app.use(express.static(path.resolve(__dirname, './dist'))) 所以连dist中的index.html也一并引入了,此index.html使用了main.js ,所以点击about走的是路由而不是服务端渲染.

测试
在entry-client.js 加入测试

router.onReady(() => {
 console.log('entry-client.js');
  app.$mount('#app')
})

重新打包,并运行,浏览器控制台会显示 entry-client.js

  • 要么将dist/index.html 删除
  • 或者将app.use(express.static(path.resolve(__dirname, './dist')))替换成
app.use('/js', express.static(path.resolve(__dirname, './dist/js')))
app.use('/img', express.static(path.resolve(__dirname, './dist/img')))
app.use('/css', express.static(path.resolve(__dirname, './dist/css')))

也就是说不引入 dist/index.html
在这里插入图片描述
这回浏览器的标题变成了ssr demo====,点击about,也是从服务请求的.

按需服务端渲染

之前的是所有的请求都是服务端渲染app.get('*' (req, res)

改成只/服务端渲染

app.get('/' (req, res) => {
...
})

这时,除/其他路由页面 会显示Cannot GET /about,
要想要about也展示,须使用entry-client.js打包生成的vue-ssr-client-manifest.json

const clientManifest = require("./dist/vue-ssr-client-manifest.json");

const renderer = createBundleRenderer(serverBundle, {
  runInNewContext: false, // 推荐
  template,
  clientManifest
})

这时,页面还是有问题,Internal Server Error
须修改vue.config.js

module.exports = {
	...
    css: {
        extract: false
      },
}

打包后,再次运行
现在已经达到目的.
也可以写成数据形式.

app.get(['/','/about'], (req, res) => {
...
})

注:有的地方没有粘贴代码,而是使用的图片,主要是突出变化

未完待续…

  • 6
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值