Статьи

Предварительная визуализация приложения Vue.js (с узлом или Laravel)

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

Рендеринг на стороне сервера

Одним из недостатков JavaScript-приложений является то, что браузер получает практически пустую страницу с сервера. DOM не может быть построен, пока JavaScript не будет загружен и запущен.

Это означает, что пользователь должен ждать немного дольше, чтобы что-то увидеть. Это также может повлиять на SEO, если сканеры не смогут быстро увидеть содержимое страницы.

Рендеринг на стороне сервера (SSR) преодолевает эту проблему путем рендеринга приложения на сервере, так что клиент получает полный контент DOM, когда страница загружается еще до запуска JavaScript.

Так что вместо браузера, получающего это с сервера:

<head> ... </head>
<body>
<div id="app">
  <!--This is empty, Javascript will populate it later-->
</app>
</body>

С помощью SSR он получает страницу с полным содержимым:

<head> ... </head>
<body>
<div id="app">
  <div class="container">
    <h1>Your Server-Side Rendered App</h1>
    <div class="component-1">
      <p>Hello World</p>
      <!--etc etc. This was all rendered on the server-->
</app>
</body>

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

  • Ваше приложение должно быть исполняемым на сервере, поэтому вам нужно будет разработать универсальный код, то есть оно будет работать как в браузере, так и на сервере Node.
  • Ваше приложение будет запускаться при каждом запросе к серверу, добавляя дополнительную нагрузку и замедляя ответы. Кэширование может частично облегчить это.
  • Вы можете сделать SSR только с Node.js. Если ваш основной сервер — Laravel, Django и т. Д., Вам нужно будет запустить Node-сервер вместе с основным сервером, чтобы позаботиться о SSR.

Pre-Rendering

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

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

Предварительный рендеринг обычно выполняется с помощью безголового браузера, такого как PhantomJS, и может быть включен в процесс сборки с помощью Webpack, Gulp и т. Д.

Pre-Rendering Pros

  • Никакой дополнительной нагрузки на сервер, поэтому быстрее и дешевле, чем SSR.
  • Более простая производственная настройка и более простой код приложения, поэтому менее подверженный ошибкам.
  • Не требует производственного сервера Node.js.

Минусы перед рендерингом

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

Сравнительная таблица

Пример предварительной визуализации Vue.js

Давайте сделаем простой пример предварительной визуализации приложения Vue.js. Я приведу пример один раз в среде Node.js и один раз в среде Laravel.

В этих примерах мы будем использовать Webpack с командой prerender-spa-pluginдля выполнения предварительного рендеринга.

Vue и Node

Шаг 1: Установка проекта

Мы будем использовать vue- cli с простым шаблоном webpack .

$ vue init webpack-simple vue-node-pr-test
$ cd vue-node-pr-test
$ npm install

Нам понадобятся три дополнительных модуля, пояснения к которым необходимо выполнить.

$ npm install --save-dev http-server html-webpack-plugin prerender-spa-plugin

Шаг 2. Включите index.html в сборку Webpack

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

Используйте html-webpack-pluginв нашем файле webpack.config.js, чтобы включить файл в сборку Webpack:

var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports.plugins.push(
  new HtmlWebpackPlugin({
    template: './index.html',
    inject: false
  }),
);

Теперь мы изменим наш Webpack, publicPathтак как index.html теперь будет в той же папке, что и другие статические ресурсы:

output: {
  path: path.resolve(__dirname, './dist'),
  filename: 'build.js',
  publicPath: '/', // was originally 'dist'
},

И нам также нужно будет изменить <script src="/dist/build.js"></script>наш index.html в <script src="/build.js"></script>связи с измененным путем.

Шаг 3. Тестирование производственной сборки Webpack

Теперь, когда мы строим:

$ npm run build

Наша distпапка должна выглядеть так:

- dist
-- build.js
-- index.html
-- logo.png

И если мы проверим dist / index.html, это будет выглядеть так:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>vue-node-pr-test</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="text/javascript" src="/build.js"></script>
  </body>
</html>

Теперь мы можем использовать http-serverи обслуживать приложение из папки dist . По умолчанию он будет работать на localhost: 8080 :

$ ./node_modules/.bin/http-server ./dist

Шаг 4: Приложение предварительной визуализации

Теперь, когда наш файл index.html находится в сборке Webpack, мы можем обновить его с помощью предварительно отрендеренного HTML.

Во-первых, нам нужно добавить prerender-spa-pluginв наш конфиг веб-пакета. Удостоверьтесь, что это прибывает после html-webpack-plugin.

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

module.exports.plugins.push(
  new PrerenderSpaPlugin(
    path.join(__dirname, './dist'),
    [ '/' ]
  )
);

Первый аргумент PrerenderSpaPlugin— это расположение нашего файла index.html , второй — список маршрутов в приложении. Для каждого добавляемого файла вы получите отдельный выходной файл! В этом примере у нас только один маршрут.

Теперь мы строим снова:

$ npm run build

Наша сборка займет больше времени, чем раньше, потому что плагин предварительной визуализации делает свое дело:

  1. Он создает экземпляр Phantom JS и запускает приложение.
  2. Делает снимок DOM.
  3. Вывод снимка в файл HTML в нашей папке сборки.

Он повторяет этот процесс для каждого маршрута, поэтому создание приложения может занять много времени, если у вас много страниц.

После сборки наш dist / index.html должен теперь включать весь предварительно обработанный HTML:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>prerender-test</title>
  <style type="text/css">#app {
    font-family: Avenir, Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-align: center;
    color: #2c3e50;
    margin-top: 60px
  }

  h1, h2 {
    font-weight: 400
  }

  ul {
    list-style-type: none;
    padding: 0
  }

  li {
    display: inline-block;
    margin: 0 10px
  }

  a {
    color: #42b983
  }</style>
</head>
<body>
<div id="app"><img src="/logo.png?82b9c7a5a3f405032b1db71a25f67021">
  <h1></h1>
  <h2>Essential Links</h2>
  <ul>
    <li><a href="https://vuejs.org" target="_blank">Core Docs</a></li>
    <li><a href="https://forum.vuejs.org" target="_blank">Forum</a></li>
    <li><a href="https://gitter.im/vuejs/vue" target="_blank">Gitter Chat</a></li>
    <li><a href="https://twitter.com/vuejs" target="_blank">Twitter</a></li>
  </ul>
  <h2>Ecosystem</h2>
  <ul>
    <li><a href="http://router.vuejs.org/" target="_blank">vue-router</a></li>
    <li><a href="http://vuex.vuejs.org/" target="_blank">vuex</a></li>
    <li><a href="http://vue-loader.vuejs.org/" target="_blank">vue-loader</a></li>
    <li><a href="https://github.com/vuejs/awesome-vue" target="_blank">awesome-vue</a></li>
  </ul>
</div>
<script type="text/javascript" src="/build.js"></script>

</body>
</html>

Vue и Laravel

If you skipped the Vue and Node example, I recommend you go back and read it first as it includes a more thorough explanation of any common steps.

Step 1: Project Installation

Firstly we’ll set up a new Laravel project.

$ laravel new vue-laravel-pr-test
$ cd vue-laravel-pr-test
$ npm install

We’ll also add two more NPM modules we’re going to need:

$ npm install --save-dev html-webpack-plugin prerender-spa-plugin

Step 2: Serve a Plain HTML File

By default, Laravel serves a Blade template file at the root URL. To keep the example simple, we’ll replace it with the following plain HTML file that we’ll create at resources/views/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Laravel</title>
    <link rel="stylesheet" href="/css/app.css">
<body>
<div id="app">
  <example></example>
</div>
<script type="text/javascript" src="/js/app.js"></script>
</body>
</html>

Now we need to serve that file instead of the Blade template at the root route. Change routes/web.php to this:

Route::get('/', function () {
  return File::get(public_path() . '/index.html');
});

This is actually pointing to our build folder which we’ll generate shortly.

Step 3: Add the HTML File to the Build

Like in the Node example, we want to include our index.html in the Webpack build so we can overwrite it later with the pre-rendered HTML.

We’ll need to do some Webpack configuration. I’m using Laravel 5.4 in this example, which uses Laravel Mix. Mix doesn’t give you a local webpack configuration file because it uses its own default file, so let’s make one by copying from the laravel-mix module:

$ cp ./node_modules/laravel-mix/setup/webpack.config.js .

We’ll also need to make our NPM production script point to this config file, so edit package.json and change the production script to this:

cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=webpack.config.js

Now we add html-webpack-plugin to our webpack.config.js file. Add this to the bottom of the file above the Mix Finalizing section:

var HtmlWebpackPlugin = require('html-webpack-plugin');

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

Step 4: Test the Weback Production Build

Let’s now build for production and serve:

$ npm run production
$ php artisan serve

You’ll probably get an error in the browser when you run the app, though, because we never set a value for window.Laravel.csrfToken. For this simple example, it’s quicker just to comment it out, so change resources/assets/js/bootstap.js like this:

window.axios.defaults.headers.common = {
  'X-Requested-With': 'XMLHttpRequest'
  // 'X-CSRF-TOKEN': window.Laravel.csrfToken;
};

Step 5: Pre-Render App

We now need to use prerender-spa-plugin in our webpack config to perform the pre-rendering. Make sure it comes after html-webpack-plugin.

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

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

Now we can do a production build:

$ npm run production

If you check the build folder, dist/index.html should now look like the following, complete with pre-rendered HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Laravel</title>
    <link rel="stylesheet" href="/css/app.css">
</head>
<body>
<div id="app">
    <div class="container">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <div class="panel panel-default">
                    <div class="panel-heading">Example Component</div>
                    <div class="panel-body">
                        I'm an example component!
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
<script src="/js/app.js"></script>
</body>
</html>