Как правильно передать конфигурацию из бэкенда на фронтенд

Часто для реализации какого-либо конфигурируемого функционала на фронтенде необходимо передать параметры конфигурации из бэкенда на фронтенд. Для этого используется множество не оптимальных подходов.

Простой подход

Как это обычно делают. Подключают вот такой вот js-скрипт в head блок:

<html>
  <head>
    <!-- блокировка рендеринга страницы на время выполнения скрипта -->
    <script>
      const configuration = <?php echo json_encode($configuration) ?>;
    </script>

    <!-- блокировка рендеринга страницы на время выполнения скрипта -->
    <script>
      //какое-то использование конфигурации в head
      window.console.log(configuration);
    </script>

    <!-- блокировка рендеринга страницы на время загрузки и выполнения скрипта -->
    <script src="/app-configuration-initialization.js"></script>
  <body>
    <!-- блокировка рендеринга страницы на время выполнения скрипта -->
    <script>
      //какое-то использование конфигурации в body
      window.console.log(configuration);
   </script>
  </body>
</html>

Дальше глобальную константу configuration можно будет использовать как в подключаемых js-файлах так и в js-коде в теле самой страницы, на которой мы подключили данный блок.

Казалось бы все хорошо. Удобно конфигурировать и удобно использовать. Но есть в таком подходе ряд проблем с производительностью.

Браузер выполняет отрисовку страницы и выполнение скриптов в одном потоке. То есть, встретив в теле страницы js-код, браузер переключается на его выполнение и только после этого продолжает отрисовку страницы. Данный эффект легко заметить на медленных устройствах — контент отрисовывается частями от скрипта к скрипту.

Часто используемый подход

Что же можно улучшить в данной ситуации?
Во-первых, подключить внешний js-скрипт без блокировки рендеринга страницы.
Во-вторых, опустить конфигурацию и весь js-код ее использующий как можно ниже.

<html>
  <head>
    <!-- загрузка скрипта теперь не блокирует рендеринг страницы -->
    <!-- скрипт будет выполнен после инициализации всей страницы -->
    <!-- не страница ждет скрипт, а скрипт страницу -->
    <script defer src="/app-configuration-initialization.js"></script>
  <body>
    <!-- блокировка рендеринга страницы на время выполнения скрипта --> 
    <script> 
      const configuration = <?php echo json_encode($configuration) ?>;
    </script> 

    <!-- блокировка рендеринга страницы на время выполнения скрипта --> 
    <script> 
      //какое-то использование конфигурации в head 
      window.console.log(configuration); 
    </script>
    <!-- блокировка рендеринга страницы на время выполнения скрипта -->
    <script>
      //какое-то использование конфигурации в body
      window.console.log(configuration);
   </script>
  </body>
</html>

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

Каждому свое место

Но не все так хорошо как кажется. Из-за того, что js-код присутствует в коде страницы, не происходит событие onLoad, до которого желательно никакие скрипты не выполнять. js-код нужно писать в js-файлах и подключать как внешний ресурс.

Итак, выносим весь js-код, который можем во внешние скрипты:

<html>
  <head>
    <!-- загрузка скрипта теперь не блокирует рендеринг страницы -->
    <!-- скрипт будет выполнен после инициализации всей страницы -->
    <!-- не страница ждет скрипт, а скрипт страницу -->
    <!-- в этом же скрипте теперь находится весь код использующий конфигурацию -->
    <script defer src="/app-configuration-initialization.js"></script>
  <body>
    <!-- блокировка рендеринга страницы на время выполнения скрипта --> 
    <script> 
      const configuration = <?php echo json_encode($configuration) ?>;
    </script> 
  </body>
</html>

Стало на много лучше, js-код инициализации конфигурации все еще блокирует окончание закгрузки страницы.

Отложить на потом не так уж и плохо

Как оказалось, использовать json-объект для инициализации конфигурации очень дорого  и лучше использовать для этого json-строку, которую можно будет распарсить при инициализации нашего внешнего js-скрипта, используя нативный JSON.parse() метод.

Подробнее и с примерами можно посмотреть в видео:

Совет от Google конечно интересный и хороший, но всегда же можно сделать лучше.

Есть у тега скрипт возможность задать его тип. И есть такой тип как «application/json». Его особенность в том, что браузер не парсит его содержимое во время рендеринга страницы и попросту пропускает такие теги как вроде бы их и нет вовсе. Но при этом мы можем получить содержимое этих тегов как текст в нашем js-скрипте и распарсить уже известным нам методом, предложенным в видео.

В итоге у нас получается вот такой код:

<html>
  <head>
    <!-- загрузка скрипта теперь не блокирует рендеринг страницы -->
    <!-- скрипт будет выполнен после инициализации всей страницы -->
    <!-- не страница ждет скрипт, а скрипт страницу -->
    <!-- в этом же скрипте теперь находится весь код использующий конфигурацию -->
    <!-- в этом же скрипте теперь происходит инициализация конфигурации-->
    <script defer src="/app-configuration-initialization.js"></script>
  <body>
    <!-- скрипт не блокирует рендеринг страницы и не выполняется --> 
    <script type="application/json" id="app-configuration-json"> 
      <?php echo json_encode($configuration) ?>;
    </script> 
  </body>
</html>

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

Итоги

Используя описанный подход. мы экономим трафик (внешние скрипты кешируются, а код страницы стал на много меньше), не блокируем рендеринг страницы, не блокируем onLoad событие, экономим ресурсы процессора т.к. используем JSON.parse вместо json-объекта конфигурации, улучшаем организационную структуру и читабельность кода.

Повторим принципы, которые были реализованы данным подходом:

  • внешние ресурсы нужно подключать асинхронно, чтобы не блокировать рендеринг страницы;
  • использовать js-скрипты в теле страницы нельзя, чтобы не блокировать рендеринг страницы;
  • передавать конфигурацию лучше всего через script тег с типом «application/json» и парсить ее при инициализации с помощью JSON.parse, чтобы не блокировать рендеринг страницы и onLoad событие.

Как по мне, не так уж и сложно реализовать правильное взаимодействие бекенда и фронтенда, если понимать какой подход для этого правильно использовать.