服务器端呈现(SSR)是全栈Web应用程序的设计概念,它为浏览器提供呈现的页面。我们的想法是,当用户等待下载和运行脚本时,可以显示该页面。

如果你没有为你的应用程序使用Node.js服务器那么你运气不好; 只有Javascript服务器才能呈现Javascript应用。

但是,对于某些用例,SSR的替代方案可能足够好,甚至更好。在本文中,我将解释一个使用Vue.js和Laravel来“伪造”服务器端渲染的方法。

预渲染

预渲染(PR)尝试使用无头浏览器呈现应用程序并将输出捕获到HTML文件,然后将其提供给浏览器,从而尝试获得与SSR相同的结果。这与SSR的区别在于它是提前完成的,而不是即时完成的。

限制:用户特定的内容

某些页面(如您网站的首页)可能包含一般内容,即所有用户都会查看相同内容的内容。但是其他页面(如管理页面)将包含特定于用户的内容,例如用户的姓名和出生日期。

PR的限制是它不能用于包含此类内容的页面。正如我刚才所说,预渲染模板只进行一次,无法自定义。SSR没有此限制。

伪造服务器端渲染

我对Vue和Laravel的假SSR方法是预呈现页面,但用Laravel Blade令牌替换任何特定于用户的内容。提供页面时,Laravel的view帮助程序将使用特定于用户的内容替换令牌。

因此,在预呈现之前,您的页面将具有以下内容:

<div id="app"></div>

预渲染后你会得到这个:

<div id="app">
    <div>
        Hello {{ $name }}, your birthday is {{ $birthday }}
    </div>
</div>

当页面由Laravel提供时,您的浏览器会收到以下内容,这正是它从SSR收到的内容:

<div id="app" server-rendered="true">
    <div>
        Hello Anthony, your birthday is 25th October.
    </div>
</div>

使用这种方法,我们可以获得SSR的所有好处,但可以使用像Laravel这样的非Node后端来完成。

它是如何完成的

我已经设置了这个repo,有一个演示供你参考,但下面我将介绍让它工作的主要步骤。

1. Vue.js app

任何特定于用户的内容都需要位于数据属性中。我们将使用Vuex商店使这更容易:

const store = new Vuex.Store({
  state: {
    // These are the user-specific content properties
    name: null,
    birthday: null
  }
});

new Vue({
  el: '#app',
  store
});

在预呈现应用程序时,我们希望将用户特定数据设置为包含Laravel Blade令牌的字符串。为此,我们将replaceState在创建商店后使用Vuex 方法,但在安装应用程序之前(我们将很快设置全局值window.__SERVER__)。

if (window.__SERVER__) {
  store.replaceState({
    name: '{{ $name }}',
    birthday: '{{ $birthday }}'
  });
}

客户端补水

当Vue应用程序安装时,我们希望它接管页面。它需要实际的初始存储状态来执行此操作,所以让我们现在提供它而不是使用AJAX。为此,我们将初始状态放在JSON编码的字符串中,我们将在下一步中创建它。现在,让我们通过修改以上内容来创建逻辑:

if (window.__SERVER__) {
  store.replaceState({
    name: '{{ $name }}',
    birthday: '{{ $birthday }}'
  });
} else {
  store.replaceState(JSON.parse(window.__INITIAL_STATE__));
}

2.刀片模板

让我们设置一个Blade模板,包括:

  • 我们的Vue应用程序的mount元素
  • 用于设置上一步中讨论的全局变量的内联脚本
  • 我们的Webpack构建脚本
<div id="app"></div>
<script>window.__SERVER__=true</script>
<script>window.__INITIAL_STATE__='{!! json_encode($initial_state) !!}'</script>
<script src="/js/app.js"></script>

$initial_state当页面被提供时,Laravel将设置值。

3. Webpack配置

我们将使用Webpack prerender-spa-plugin进行预渲染。我在这里写了一篇关于它是如何工作的更详细的文章,但这里的概念简要说明:

  1. 使用将模板的副本放在Webpack构建输出中html-webpack-plugin
  2. prerender-spa-plugin将引导PhantomJS,运行我们的应用程序,并与预渲染标记覆盖模板副本。
  3. Laravel将使用此预渲染模板作为视图。
if (isProduction) {
  var HtmlWebpackPlugin = require('html-webpack-plugin');

  module.exports.plugins.push(
    new HtmlWebpackPlugin({
      template: Mix.Paths.root('resources/views/index.blade.php'),
      inject: false
    })
  );

  var PrerenderSpaPlugin = require('prerender-spa-plugin');

  module.exports.plugins.push(
    new PrerenderSpaPlugin(
      Mix.output().path,
      [ '/' ]
    )
  ); 
}

4.构建后脚本

如果您现在运行Webpack,您将拥有index.blade.phpWebpack构建文件夹,它将包含:

<div id="app">
    <div>
        Hello {{ $name }}, your birthday is {{ $birthday }}
    </div>
</div>
<script>window.__SERVER__=true</script>
<script>window.__INITIAL_STATE__='{!! json_encode($initial_state) !!}'</script>
<script src="/js/app.js"></script>

在使用此功能之前,我们还需要执行一些其他任务:

  1. 将属性添加server-rendered="true"到mount元素。这让Vue知道我们已经渲染了页面,它将尝试无缝接管。在replaceNPM模块可以做这个工作。
  2. 更改window.__SERVER__=true为,window.__SERVER__=false以便当应用程序在浏览器中运行时,它会以初始状态加载商店。
  3. 将此文件移动到您的路由可以使用它的位置。让我们resources/views/rendered为此创建一个目录。(也可以像添加.gitignoreWebpack一样添加它。)

我们将创建一个bash脚本render.sh来完成所有这些:

#!/usr/bin/env bash
npm run production &&
mkdir -p resources/views/rendered
./node_modules/.bin/replace "<div id=\"app\">" "<div id=\"app\" server-rendered=\"true\">" public/index.html
./node_modules/.bin/replace "<script>window.__SERVER__=true</script>" "<script>window.__SERVER__=false</script>" public/index.html &&
mv public/index.html resources/views/rendered/index.blade.php

现在我们可以随时渲染或重新渲染我们的模板:

$ source ./render.sh

路线

最后一步是让我们的路由web.php服务于预渲染模板,并使用view帮助程序用特定于用户的数据替换标记:

Route::get('/', function () {
    $initial_state = [
        'name' => 'Anthony',
        'birthday' => '25th October'
    ];
    $initial_state['initial_state'] = $initial_state;
    return view('rendered.index', $initial_state);
});

该数组$initial_state包含用户特定的数据,但在真实应用程序中,您可能首先检查用户是否已获得授权并从数据库中获取数据。

伪造SSR方法的性能优势

在前端应用程序中显示具有特定于用户的内容的页面的常规方法,例如在使用Vue.js构建应用程序中解释的:从身份验证到调用API,需要在浏览器和服务器之间来回切换在它实际显示任何东西之前:

  1. 浏览器请求页面
  2. 提供空页面,但尚未显示任何内容
  3. 浏览器请求脚本
  4. 脚本现在运行,向服务器发出AJAX请求以获取特定于用户的内容
  5. 内容被返回,所以现在页面终于具有显示内容所需的内容

使用这种方法,我们不仅可以更早地显示某些内容,还可以消除不必要的HTTP请求:

  1. 浏览器请求页面
  2. 提供完整页面,以便浏览器可以立即显示它
  3. 浏览器请求脚本
  4. 脚本现在运行,具有无缝接管页面的所有必要内容。

当然,这也是真正的SSR具有的优势,不同之处在于这种方法使得它可以像Laravel这样的非Node.js服务器实现!