Направление оптимизации: руководство по предварительному рендерингу одностраничного приложения с несколькими маршрутами.

внешний интерфейс Vue.js
Направление оптимизации: руководство по предварительному рендерингу одностраничного приложения с несколькими маршрутами.

предисловие

Появление технологии Ajax позволяет нашим веб-приложениям отображать содержимое разных страниц без обновления, что является одностраничным приложением. В одностраничном приложении часто имеется только один HTML-файл, а затем соответствующий сценарий маршрутизации сопоставляется в соответствии с полученным URL-адресом для динамического отображения содержимого страницы. Оптимизируя взаимодействие с пользователем, одностраничные приложения также приносят нам много проблем, таких как недружественный SEO, долгое время видимости в верхней части страницы и т. д. Технологии рендеринга на стороне сервера (SSR) и предварительного рендеринга (Prerender) созданы для решения этих проблем.

Прочитав эту статью, вы сможете понятьчто такое предварительный рендеринг, предварительный рендеринг и рендеринг на стороне сервераСходства и различияи предварительно отрендеренный вИспользование в проектах Vue.js.

Рендеринг на стороне сервера и предварительный рендеринг

некоторые концепции

  1. рендеринг на стороне клиента: пользователь получает доступ к URL-адресу, запрашивает html-файл, и внешний интерфейс динамически отображает содержимое страницы в соответствии с маршрутом. Ключевая ссылка длинная и есть определенное время белого экрана;
  2. рендеринг на стороне сервера: когда пользователь получает доступ к URL-адресу, сервер запрашивает необходимые данные в соответствии с путем доступа, объединяет их в строку html и возвращает во внешний интерфейс. Когда внешний интерфейс получает html, уже есть какой-то контент;
  3. предварительный рендеринг: на этапе сборки создаются HTML-файлы, соответствующие предварительно отрисованным путям (примечание: каждый маршрут, требующий предварительной отрисовки, имеет соответствующий HTML-код). Созданный html-файл уже имеет некоторый контент.

На следующем рисунке кратко показан поток запросов рендеринга на стороне клиента, рендеринга на стороне сервера и предварительного рендеринга.

Пример в этой статье создан с использованием vue-cli, нажмитездесьСм. примеры.distdirectory — это каталог упаковки с включенным предварительным рендерингом,dist2Каталог является каталогом упаковки для обычного рендеринга на стороне клиента. Сравнив файлы в каталоге, вы можете получить начальное представление о предварительном рендеринге. Если вы до сих пор не знаете, что такое пререндеринг, вы можете сначала прочитать всю статью.

точки соприкосновения

Для одностраничных приложений проблемы, решаемые рендерингом на стороне сервера и предварительным рендерингом:

  1. SEO: содержимое веб-сайта одностраничного приложения динамически отображается в соответствии с текущим путем.Часто в html-файле нет содержимого, и веб-краулер не будет ждать выполнения скрипта страницы перед сканированием;
  2. Слабое сетевое окружение: Когда пользователи посещают ваш сайт в слабом окружении, вы хотите как можно быстрее доставлять им контент. еще до загрузки и анализа js-скрипта;
  3. браузер с низкой версией: браузер пользователя может не поддерживать используемую вами функцию js, предварительный рендеринг или рендеринг на стороне сервера позволяют пользователю, по крайней мере, видеть содержимое в верхней части страницы, а не пустую веб-страницу.

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

неуместная сцена

В каких сценариях не подходит пререндеринг:

  1. Персонализированный контент: Для страниц с маршрутом /my-profile предварительный рендеринг работать не будет. Потому что содержимое страницы выглядит по-разному в зависимости от того, кто ее просматривает;
  2. часто меняющийся контент: если вы выполняете предварительный рендеринг таблицы лидеров игры, она будет обновляться новыми записями игроков. Предварительная рендеринг приведет к неправильному отображению вашей страницы до тех пор, пока скрипт не будет загружен и заменен новыми данными. Это плохой пользовательский опыт;
  3. Тысячи маршрутов: Не рекомендуется выполнять предварительную визуализацию большого количества маршрутов, так как это серьезно замедлит процесс сборки.

Prerender SPA Plugin

prerender-spa-plugin— это плагин веб-пакета для предварительного рендеринга статического HTML-контента в одностраничных приложениях. Таким образом, этот плагин ограничивает, что ваш SPA должен быть создан с помощью веб-пакета, он не зависит от фреймворка и может использоваться для предварительного рендеринга независимо от того, используете ли вы React или Vue или даже без фреймворка.Пример этой статьиНа основе Vue.js 2.0 + vue-router.

Далее будет изпостроить проектговори, потом смотриПредварительный рендеринг не настроенКак и раньше, настройте предварительный рендеринг, чтобы построить, сравнитьразница до и после.

построить проект

Сначала сгенерируйте проект и установите зависимости.

vue init webpack vue-prerender-demo
cd vue-prerender-demo && npm install

Мы не обращаем внимания на процесс разработки компонентов, вы можете просматривать исходный код образца для получения подробной информации. Разработка завершена просмотр следующим образом.

конфигурация маршрутизации

Это страница новостного приложения, включающая две последние и самые популярные страницы списка и страницу статьи. Конфигурация маршрутизации следующая.

new Router({ 
  mode: 'history',
  routes: [
    {
      path: '/',
      component: Home,
      children: [
        {
          path: 'new',
          alias: '/',
          component: () => import('@/components/New')
        },
        {
          path: 'hot',
          component: () => import('@/components/Hot')
        }
      ]
    },
    {
      path: '/article/:id',
      component: Article
    }
  ]
})

Предварительно обработанные одностраничные маршруты приложений должны использовать режим History вместо режима Hash. Причина проста, хэш не будет доставлен на сервер, а информация о маршрутизации будет утеряна. vue-router включить справочник по режиму историиздесь.

Режим истории требует поддержки фоновой конфигурации, проще всего настроить директиву try_files через nginx.

location / {
  try_files $uri $uri/ /index.html;
}

Перед пре-рендерингом не настроен

Выполнить сборку после завершения конфигурацииnpm run build, согласно конфигурации nginx, dist/index.html теперь возвращается независимо от того, к какому маршруту осуществляется доступ.

доступ/маршрутизация.

Видно, что в сети Fast 3G видимое время первого экрана составляет 4,34 с, а страницу можно просмотреть как минимум после загрузки файла ниже.

  1. html
  2. app.css- стиль
  3. manifest.js - webpack manifest
  4. vendor.js- сторонняя библиотека
  5. app.js- Бизнес-логика
  6. 0.js- Файл подпакета маршрутизации

Файл поставщика содержит указанную стороннюю библиотеку и имеет большой размер. Загрузка большого количества файлов увеличивает время белого экрана. Таким образом, наиболее эффективным решением для оптимизации является уменьшение количества файлов зависимостей, расположенных выше сгиба. Начните настройку предварительного рендеринга здесь.

Конфигурация перед рендерингом

Установите prerender-spa-plugin, время установки немного больше, потому что это зависит от phantomjs, наберитесь терпения.

npm install prerender-spa-plugin --save-dev

Мы выполняем предварительный рендеринг только в производственной среде, модифицируем build/webpack.prod.conf.js и добавляем следующий код там, где настроен плагин.

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

{
  // ...
  plugins: [
    // ...
    new PrerenderSpaPlugin(
      // 输出目录的绝对路径
      path.join(__dirname, '../dist'),
      // 预渲染的路由
      [ '/new', '/hot' ]
    )
  ]
}

Для создания экземпляра PrerenderSpaPlugin требуется как минимум два параметра. Первый параметр — это выходной каталог одностраничного приложения, а второй параметр указывает маршрут предварительного рендеринга. Здесь выполняются два маршрута./newа также/hot. выполнить сборкуnpm run build.

Предварительно обработанные эффекты

доступ/newмаршрутизация.

Также в сети Fast 3G время видимости первого экрана сокращается до 2,30 с. На самом деле, пока файлы html и app.css загружены, содержимое страницы видно.

dist
│  index.html
│  
├─hot
│      index.html
│      
├─new
│      index.html
│      
└─static

Сравнив каталог завершения сборки, вы можете обнаружить, что в предварительно обработанном каталоге есть еще два файла.new/index.html, hot/index.html.

Проверитьnew/index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>vue-prerender-demo</title>
  <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
  <link href="/static/css/app.23611ac69a9fa48640e3bad8ceeab7bf.css" rel="stylesheet">
  <script type="text/javascript" charset="utf-8" async="" src="/static/js/0.41194d76e86bbf547b16.js"></script>
</head>
<body>
  <div id="app">
    <div>
      <div class="mu-appbar mu-paper-1">
        <div class="left">
          <i class="mu-icon material-icons">home</i>
        </div>
        <div class="mu-appbar-title">
          <span>新闻</span>
        </div>
        <div class="right"></div>
      </div>
      ...
    </div>
  </div>
  <script type="text/javascript" src="/static/js/manifest.4410c20c250c68dac5bc.js"></script>
  <script type="text/javascript" src="/static/js/vendor.d55f477df6e96ccceb5c.js"></script>
  <script type="text/javascript" src="/static/js/app.f199467bd568ee8a197a.js"></script>
</body>
</html>

По сравнению с index.html в new/index.html<div id="app"></div>имеет содержание и<head></head>Есть больше файлов js для текущего субподряда по маршрутизации. Остальное то же самое, что и index.html. Несмотря на то, что существует несколько html-файлов, при переходе с /new на другие маршруты он все равно переходит в пределах одной страницы, и новых html-запросов не будет.

Согласно настроенным выше правилам nginx, возвращаемые файлы, соответствующие маршрутам:

/ -> index.html
/new -> new/index.html
/hot -> hot/index.html
/article/:id -> index.html

в,/newа также/hothtml, возвращаемый маршрутом, содержит содержимое соответствующего маршрута для достижения предварительного рендеринга. Маршрут без предварительного рендеринга такой же, как и исходный, по-прежнему посещайте /index.html, запрашивайте скрипт и выполняйте рендеринг динамически.

Предварительно отрендеренные достигают аналогичного эффекта от рендеринга на стороне сервиса. Разница в том, что предварительно отрендеренные происходятво время сборки, рендеринг на стороне сервера происходит на сервереПри обработке запроса.

Принцип prerender-spa-плагина

Так как же prerender-spa-plugin упаковывает исполняемый HTML-код в файл? Принцип очень прост, то есть в конце этапа построения вебпака запускаем фантомы локально, обращаемся к предварительно отрендеренному маршруту, выводим отрендеренную в фантомках страницу в html файл и создаем директорию, соответствующую маршруту.

Посмотреть исходный код prerender-spa-pluginprerender-spa-plugin/lib/phantom-page-render.js.

// 打开页面
page.open(url, function (status) {
  ...
  // 没有设置捕获钩子时,在脚本执行完捕获
  if (
    !options.captureAfterDocumentEvent &&
    !options.captureAfterElementExists &&
    !options.captureAfterTime
  ) {
    // 拼接 html
    var html = page.evaluate(function () {
      var doctype = new window.XMLSerializer().serializeToString(document.doctype)
      var outerHTML = document.documentElement.outerHTML
      return doctype + outerHTML
    })
    returnResult(html) // 捕获输出
  }
  ...
})

Лучшие практики

Указать крючок

По умолчанию html будет захвачен и выведен после выполнения скрипта. Вы также можете указать некоторые хуки, html будет перехватываться в определенное время.

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

{
  // ...
  plugins: [
    // ...
    new PrerenderSpaPlugin(
      path.join(__dirname, '../dist'),
      [ '/new', '/hot' ],
      {
        // 监听到自定事件时捕获
        // document.dispatchEvent(new Event('custom-post-render-event'))
        captureAfterDocumentEvent: 'custom-post-render-event',

        // 查询到指定元素时捕获
        captureAfterElementExists: '#content',

        // 定时捕获
        captureAfterTime: 5000
      }
    )
  ]
}

Предварительно обработанный экран скелета

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

<template>
  <div>
    <new-list v-if="news.length > 0"></new-list>
    <new-list-skeleton></new-list-skeleton>
  </div>
</template>

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

{
  mounted () {
    document.dispatchEvent(new Event('sketelon-render-event'))
    fetchNews()
  }
}

При доступе к странице пользователь сначала видит предварительно обработанный экран-скелет (левое изображение), ждет загрузки js, а затем извлекает данные для отображения правильного содержимого.

Полный путь к прокси

Если вы настроите ссылку на справочный ресурс как полный путь с именем домена.

// config/index.js

module.exports = {
  build: {
    ...
    assetsPublicPath: '//www.example.com/'
  },
  ...
}

Затем вам нужно проксировать доменное имя на локальное при сборке, иначе prerender-spa-plugin захватит онлайн-код.

127.0.0.1 www.example.com

Предварительно отрисовать корневой маршрут

Обычно, динамические маршруты, такие как / пользователи /: идентификатор не настраивают предварительно отображаемые, потому что вы не можете перечислить все идентификаторы пользователя. При доступе к динамическому маршруту сервер вернется в корневой маршрут / HTML, поэтому корневые маршруты не подходят для предварительной рендеринга. Но корневой маршрут часто является домашней страницей сайта и является маршрутированием максимального количества посетителей. Эта проблема может быть решена некоторыми nginx.

location = / {
  try_files /home/index.html /index.html;
}

location / {
  try_files $uri $uri/ /index.html;
}

Когда пользователь посещает маршрут /, он фактически посещает /home/index.html и использует /home, настроенный в маршрутизаторе, в качестве домашней страницы. /index.html можно использовать как другие ответы, не соответствующие маршруту.

Эпилог

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

Пролистайте и обратите внимание на внешний публичный аккаунт Xunlei.