Статьи

Подделка рендеринга на стороне сервера с помощью Vue.js и Laravel

Рендеринг на стороне сервера (SSR) — это концепция дизайна для полнофункциональных веб-приложений, которая предоставляет отображаемую страницу в браузер. Идея состоит в том, что страница может отображаться, пока пользователь ожидает загрузки и запуска скриптов.

Если вы не используете сервер Node.js для своего приложения, то вам не повезло; только сервер JavaScript может отображать приложение JavaScript.

Однако есть альтернативы SSR, которые могут быть достаточно хорошими или даже лучше для некоторых случаев использования. В этой статье я собираюсь объяснить метод, который я использую для «подделки» серверного рендеринга с использованием Vue.js и Laravel.

Pre-Rendering

Предварительный рендеринг (PR) пытается достичь того же результата, что и SSR, используя браузер без монитора для рендеринга приложения и захвата вывода в файл HTML, который затем передается в браузер. Разница между этим и SSR заключается в том, что это делается заранее, а не на лету.

Ограничение: пользовательский контент

Некоторые страницы, такие как главная страница вашего сайта, вероятно, будут содержать общий контент, то есть контент, который все пользователи будут просматривать одинаково. Но другие страницы, такие как страницы администратора, будут содержать пользовательский контент, например, имя пользователя и дату рождения.

Ограничением PR является то, что его нельзя использовать для страниц, содержащих такой контент. Как я только что сказал, предварительно созданные шаблоны создаются только один раз и не могут быть изменены. У ССР нет этого ограничения.

Подделка рендеринга на стороне сервера

Мой фальшивый метод SSR для Vue и Laravel — предварительно отрендерить страницу, но заменить любой пользовательский контент токенами Laravel Blade. Когда страница обслуживается, viewпомощник Laravel заменит токены пользовательским контентом.

Поэтому перед предварительным рендерингом ваша страница будет иметь следующее:

<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, но это может быть сделано с не-Node-сервером, таким как Laravel.

Как это сделано

Я настроил этот репозиторий с демо-версией, на которую вы можете сослаться, но ниже я опишу основные шаги, чтобы заставить это работать.

1. Приложение Vue.js

Любой пользовательский контент должен быть в свойстве данных. Мы будем использовать магазин 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__вскоре мы установим значение global ).

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.
  • Встроенные скрипты для установки глобальных переменных, обсуждаемых на предыдущем шаге.
  • Наш скрипт сборки 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 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.phpпапке сборки Webpack, и она содержала бы:

<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"к элементу монтирования. Это позволяет Vue знать, что мы уже отрисовали страницу, и она попытается осуществить плавное поглощение. Модуль replaceNPM может сделать эту работу.
  2. Изменение window.__SERVER__=trueк window.__SERVER__=falseтак что , когда приложение работает в браузере он загружает магазин с начальным состоянием.
  3. Переместите этот файл куда-нибудь, чтобы ваш маршрут мог его использовать. Давайте создадим resources/views/renderedдля этого каталог (возможно, было бы неплохо добавить его так .gitignoreже, как для сборки Webpack).

Мы создадим 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

5. Маршрут

Последний шаг — подключить наш маршрут 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содержит пользовательские данные, хотя в реальном приложении вы, вероятно, сначала проверите, что пользователь авторизован, и получите данные из базы данных.

Преимущество в производительности подхода Fake SSR

Например, обычный подход к отображению страницы с пользовательским контентом в приложении внешнего интерфейса, описанный в разделе «Создание приложения с помощью Vue.js: от аутентификации до вызова API» . Это требует некоторой перемотки между браузером и сервером, прежде чем он сможет что-либо отобразить:

  1. Браузер запрашивает страницу.
  2. Пустая страница обслуживается и ничего не отображается.
  3. Браузер запрашивает скрипт.
  4. Скрипт теперь запускается, выполняет AJAX-запрос к серверу для получения пользовательского контента.
  5. Контент возвращается, так что теперь на странице наконец есть то, что нужно для отображения чего-либо.

При таком подходе мы можем не только отобразить что-то намного раньше, но и устранить ненужный HTTP-запрос:

  1. Браузер запрашивает страницу.
  2. Полная страница предоставляется, поэтому браузер может отобразить ее сразу.
  3. Браузер запрашивает скрипт.
  4. Скрипт теперь запускается, имеет весь необходимый контент для плавного перехода на страницу.

Это, конечно, то преимущество, которое имеет и реальная SSR, разница в том, что этот подход делает его достижимым с сервером, отличным от Node.js, таким как Laravel!

Ограничения

  • Это довольно хрупкая и сложная установка. Честно говоря, установка SSR тоже не прогулка в парке.
  • Сборка вашего веб-пакета займет больше времени.
  • Если ваши данные подвергаются манипуляциям с помощью JavaScript перед их отображением, вам придется заново создавать эти манипуляции и на стороне сервера, на другом языке. Это будет отстой.