完整代码地址: https://github.com/dirkhe1051931999/vue-ssr-template demo 地址: https://github.com/dirkhe1051931999/common-demo/tree/master/vue-ssr-demo 服务端渲染、SPA、同构 服务端渲染:后端不仅仅进行数据的获取处理,而且也服务页面生成,最终传给客户端(浏览器)是…

  1. 完整代码地址:https://github.com/dirkhe1051931999/vue-ssr-template

  2. demo 地址:https://github.com/dirkhe1051931999/common-demo/tree/master/vue-ssr-demo

服务端渲染、SPA、同构

  1. 服务端渲染:后端不仅仅进行数据的获取处理,而且也服务页面生成,最终传给客户端(浏览器)是包含数据的 html
  2. SPA(单页应用):即客户端渲染,后端提供 API,前端面向 API 编程,更重要是前后端分离,首屏即可加载完所有资源,但也伴随白屏时间过长和不利于 SEO 问题
  3. 同构:前后端共用一套 js 代码,通过 node 服务器进行首屏渲染,跟现在 B 格较高的前端微服务有千丝万缕的联系

详细的网上说的很多,比如这篇https://zhuanlan.zhihu.com/p/33383814

服务端渲染解决什么问题

  1. SEO
  2. 首屏加载耗时较多,白屏时间长
  3. 导航不可用,ctrl+链接不是一个完整的链接
  4. 一些鉴权问题

服务端渲染什么时候用

  1. 个人博客,购物网站,需要 SEO 蜘蛛爬的页面
  2. 页面交互较少,大部分都是一些偏向展示的页面

一个简单的服务端渲染

  1. 使用 createRenderer() 创建 render 实例
  2. 使用 renderToString(app)将 vue 实例转成 html 字符串 ,返回一个 promise,html 字符串带有 data-sercer-rendered,意思是服务端渲染的元素
const Vue = require("vue");
// 创建渲染器
const renderer = require("vue-server-renderer").createRenderer();
const app = new Vue({
  template: `<div>Hello World</div>`
});
// 输出:<div data-server-rendered="true">Hello World</div>
renderer
  .renderToString(app)
  .then(html => {
    console.log(html);
  })
  .catch(err => {
    console.log(err);
  });

vue-server-renderer 与 koa 用法

  1. 创建 koa koa-router,使用 createRenderer()创建 render 实例
  2. 在路由中间件中,创建 vue 实例
  3. 使用 renderToString(app) 将 vue 实例转成 html 字符串
  4. 将 html 写入 ctx.body 中
  5. 加载路由组件,app.use(router.routes()).use(router.allowMethods())
  6. 启动 koa 服务器
// server.js
const Vue = require("vue");
const Koa = require("koa");
const Router = require("koa-router");
const renderer = require("vue-server-renderer").createRenderer();
// 创建koa koa-router实列
const app = new Koa();
const router = new Router();
// 路由中间件
router.get("*", async (ctx, next) => {
  // 创建vue实列
  const app = new Vue({
    data: {
      url: ctx.url
    },
    template: `<div>访问的URL是:{{url}}</div>`
  });
  try {
    // vue 实列转换成字符串
    const html = await renderer.renderToString(app);
    ctx.status = 200;
    ctx.body = `
      <!DOCTYPE html>
      <html>
        <head><title>vue服务器渲染组件</title></head>
        <body>${html}</body>
      </html>
    `;
  } catch (e) {
    console.log(e);
    ctx.status = 500;
    ctx.body = "服务器错误";
  }
});

// 加载路由组件
app.use(router.routes()).use(router.allowedMethods());

// 启动服务
app.listen(3001, () => {
  console.log(`server started at localhost:3001`);
});

vue-server-renderer 、koa 和 index.template.html

  1. 使用 createRenderer({template:html})读取 index.template.html,作为模板,创建 render 实例,其中模板的中的双括号是占位符,三括号不会进行 html 转译
  2. 创建 koa koa-router 实例
  3. 在路由中间件中,创建 vue 实例
  4. 使用 renderToString(app,context) 将 vue 实例转成 html 字符串,context 就是要用占位符的上下文对象
  5. 将 html 写入 ctx.body 中,
  6. 加载路由组件,app.use(router.routes()).use(router.allowMethods())
  7. 启动 koa 服务器

为每一个路由请求创建一个 根 vue 实例

|---- ssr-demo1
|  |--- src
|  | |--- app.js
|  | |--- index.template.html
|  |--- .babelrc
|  |--- .gitignore
|  |--- server.js
|  |--- package.json
  1. app.js 只负责 createApp,创建一个 vue 实例,避免状态单例,成为引用对象,一处修改,其他引用也跟着改
  2. server.js 中
    1. 使用 createRenderer({template:html})读取 index.template.html,作为模板,创建 render 实例,其中模板的中的双括号是占位符,三括号不会进行 html 转译
    2. 创建 koa koa-router 实例
    3. 在路由中间件中,执行 createApp,进而创建 vue 实例
    4. 使用 renderToString(app,context) 将 vue 实例转成 html 字符串,context 就是要用占位符的上下文对象
    5. 将 html 写入 ctx.body 中,
    6. 加载路由组件,app.use(router.routes()).use(router.allowMethods())
    7. 启动 koa 服务器

使用 vue-router 路由的实现及代码分割

|----- ssr-demo2
|  |--- build
|  | |--- webpack.base.config.js              # webpack 基本配置
|  | |--- webpack.client.config.js            # 客户端打包配置
|  | |--- webpack.server.config.js            # 服务器端打包配置
|  |--- src
|  | |--- assets
|  | |--- views
|  | | |--- home.vue
|  | | |--- item.vue
|  | |--- app.js                            # 创建每一个实列文件
|  | |--- App.vue
|  | |--- entry-client.js                   # 挂载客户端应用程序
|  | |--- entry-server.js                   # 挂载服务器端应用程序
|  | |--- index.template.html
|  | |--- router.js                         # 所有的路由
|  |--- .babelrc
|  |--- .gitignore
|  |--- server.js
|  |--- package.json
  1. app.vue 作为根 vue 实例
  2. app.js 只负责 createApp,创建一个 vue 实例,并且往 vue 实例中传入路由实例,避免状态单例,成为引用对象,一处修改,其他引用也跟着改
  3. entry-client.js 只负责挂载客户端应用程序,调用 createApp return 的 app,在 router.onReady 时执行 app.\$mount()挂载 dom
  4. entry-server.js 负责挂载服务端应用程序,返回一个 promise,并且传入 app 和 router,使用router.push(context.url)设置 router 的位置,因为路由可能是异步路由,服务器需要再数据渲染完准备,所以返回一个 promise,并且的router.onReady中使用router.getMatchedComponents获取路由匹配的所有组件后再 resolve createApp 的 app,
  5. router.js 跟 vue spa 一样,简单的创建路由实例
  6. webpack.base.config.js,的 entry 就是entry-client.js,设置了一些 rules
  7. webpack.client.config.js merge webpack.base.config.js,设置了 style 的 rules,压缩 css 的插件,最终执行npm run build:client生成vue-ssr-client-manifest.json作为客户端渲染的 json 插件
  8. webpack.client.server.js merge webpack.base.config.js ,entry 是entry-server.js,服务端也要编译 css,设置了一些 style 的 rules,最终执行npm run build:server生成vue-ssr-server-bundle.json作为服务端渲染的 json 插件
  9. server.js
    1. 使用 createBundleRenderer读取 index.template.html,作为模板,传入 vue-ssr-server-bundle.json 和 vue-ssr-client-manifest.json`,创建 render 实例,其中模板的中的双括号是占位符,三括号不会进行 html 转译

      createRenderer() 和 createBundleRenderer(), 那么 createRenderer()是以 vue 组件为入口的,而 createBundleRenderer() 以打包后的 JS 文件或 json 文件为入口的

    2. 创建 koa koa-router 实例
    3. 在路由中间件中,执行 createApp,进而创建 vue 实例
    4. 使用 renderToString(context) 将 vue 实例转成 html 字符串,context 就是要用占位符的上下文对象,context 会作为打包的 entry-server.js 的 context 传入,context 带上 koa 的路由,然后传入 router 实例,作为客户端的路由
    5. 将 html 写入 ctx.body 中,
    6. 可设置一个静态资源服务器
    7. 加载路由组件,app.use(router.routes()).use(router.allowMethods())
    8. 启动 koa 服务器

开发环境的热更新

  1. server.js 中的加入 dev.config.js 支持热更新
    1. 线上环境就是上面的打包方式
    2. 生产环境需要动态监听文件发生改变的配置文件
  2. 动态监听文件 dev.config.js
    1. 原理就是使用 memory-fs 将 webpack 写入内存,不是写入磁盘,使用 chokidar 监听文件变化,配置 koa 热替换插件,进行热替换,然后手动刷新 html,实现实时监听文件,实时打包

vuex 和 ssr

  1. 如果页面有异步请求,再服务器渲染之前,需要提前返回异步数据,在挂在 dom 之前可以通过 vue.$options 的 asyncData 应该获取到异步数据,asyncData 在组件实例化前用,所以此时不能使用 vue 的上下文,当前 this 是 window
  2. 在 entry-server.js 中,当 router.onReady 的时候,需要提前在 router.getMatchedComponents 获取匹配组件,如果匹配到组件由 asyncData,就执行他, 并且传入已经已经实例化后的 store 和 router.currentRoute,再把 store 的 state 放入window.__INITIAL_STATE__,提前注入 html 中
  3. 在 entry.client.js 中如果window.__INITIAL_STATE__存在,需替换 store 的根状态,确保服务端渲染是的数据和客户端获取的数据一致

暂无评论