Русский
Русский
English
Статистика
Реклама

Spa

EasyUI действительно easy?

31.05.2021 12:16:05 | Автор: admin

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

Дизайн сегодня один из необходимых компонентов любого продукта, а для сайтов и веб-приложений это самая важная часть. Всё, что находится под капотом, скрыто от глаз клиента. Пользователя не интересует гениальность исполнения движка или уникальность архитектуры: подразумевается само собой, что программа должна работать надежно и безопасно. Современному пользователю необходим стильный графический интерфейс.

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

Вопрос нелёгкий, и решать, в итоге, вам. Я же расскажу о своём лекарстве: библиотеке EasyIU, предназначенной для создания полноценных одностраничных веб-приложений (SPA) и основанной на jQuery, Angular, Vue и React.

Моё знакомство с EasyUI началось около двух лет назад, когда наша команда приступила к разработке софта для системы управления питанием и его интеллектуального распределения между потребителями. Управляющее устройство имело на борту Linux и кроме распределения питания должно было обмениваться данными с различными периферийными устройствами, уметь контролировать их, а также принимать показания от большого количества (до нескольких сотен) датчиков. Правила управления могли изменяться, позволяя пользователю настроить работу всего комплекса для выполнения необходимых задач.

Настройка и мониторинг устройства могли вестись через различные протоколы: ssh, snmp, redfish, BACnet, но основным способом общения с ним был http, то есть всем комплексом можно было управлять через обыкновенный веб-браузер. Это широко используемое решение, и оно не сулило никаких проблем. Однако, дьявол всё же основательно порылся в деталях.

Веб-страница должна была отражать состав комплекса, его внутренние и внешние компоненты и их содержимое в виде дерева разнотипных объектов. Показания датчиков должны были выводиться в виде таблицы (как в Excel'е, безмятежно улыбался заказчик), причём часть данных (показания, состояния датчиков и т.п.) требовалось непрерывно обновлять, а часть могла изменяться пользователем непосредственно в таблице. Для настройки комплекса было необходимо использовать многоуровневое меню и большое количество модальных диалоговых окон.

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

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

Итак, что же такое такое EasyUI?

Как я уже упоминал выше, EasyIU представляет собой набор компонентов пользовательского интерфейса, основанных на jQuery, Angular, Vue и React. Мы использовали библиотеку, базирующуюся на jQuery.

С самого начала мне понравилась возможность создания макета приложения без программирования на javascript. EasyUI для jQuery имеет встроенный парсер, который расширяет HTML-разметку и ассоциирует её с библиотечным кодом. Для этого в классе HTML-элемента достаточно указать наименование компонента, который необходимо применить.

Например, эта разметка создаст на странице приложения пять зон: шапку и подвал высотой 100 пикселей, и разделённую на три колонки среднюю часть. Левая и правая колонки имеют ширину 100 пикселей, а центральная часть имеет серый фон и занимает всё оставшееся место. Кроме этого, высоту шапки и подвала, ширину левой и правой колонок можно изменять мышью во время работы приложения. Для этого EasyUI создаст на странице приложения специальные разделители.

<body class="easyui-layout">  <div data-options="region:'north',title:'North Title',split:true"       style="height:100px;"></div>  <div data-options="region:'south',title:'South Title',split:true"       style="height:100px;"></div>  <div data-options="region:'east',title:'East',split:true"       style="width:100px;"></div>  <div data-options="region:'west',title:'West',split:true"       style="width:100px;"></div>  <div data-options="region:'center',title:'center title'"       style="padding:5px;background:#eee;"></div></body>

Конечно, EasyUI позволяет сделать то же самое при помощи javascript:

$('body').layout({fit: true}).layout('add', {  region: 'north', title: 'North Title', split: true, height: 100}).layout('add', {  region: 'south', title: 'South Title', split: true, height: 100}).layout('add', {  region: 'east', title: 'East Title', split: true, width: 100}).layout('add', {  region: 'west', title: 'West Title', split: true, width: 100}).layout('add', {  region: 'center', title: 'сenter Title', split: true, widht:100,  style: {padding: 5, background: '#eee'}});

В результате EasyUI создаст вот такую страницу:

Пока всё easy, не так ли? Мы можем создать предварительный макет без написания javascript кода, что очень удобно на начальных этапах проектирования приложения.

А что ещё она умеет?

Для беглой оценки возможностей EasyUI, достаточно посмотреть на её конструктор тем:

Здесь показаны не все возможности EasyUI, но для первого впечатления вполне достаточно и этого: разметка (layout), панели (panel), многоуровневое меню (menu, menubutton), вкладки (tab), аккордеоны (accordion), календарь (calendar), таблица (datagrid), набор конфигурационных параметров (propertygrid), список (datalist), дерево (tree), диалоги (dialog), формы (form) и их элементы (validatebox, textbox, passwordbox, maskedbox, combobox, tagbox, numberbox, datetimebox, spinner, slider, filebox, checkbox, radiobutton) и этот перечень далеко не полон. При более глубоком погружении в возможности библиотеки выясняется, что эти компоненты можно расширять и создавать на их основе новые. На сайте проекта есть раздел Extention, на котором представлены некоторые расширения, например, всем известная лента (Ribbon):

Для демонстрации всех компонентов, реализованных EasyUI, на странице проекта есть специальный раздел с демонстрацией их работы.

И снова о дизайне

EasyUI поддерживает изменение стиля оформления пользователем из набора готовых тем прямо во время выполнения приложения. На сайте проекта предлагается собственная коллекция стилей. К сожалению, решение из такого набора не всегда может удовлетворить заказчика. Это, скорее, набор стилей для начала работы, который впоследствии потребует корректировки. Для этого на сайте проекта имеется конструктор тем. Разумеется, всегда можно настроить тему приложения в соответствующей таблице стилей, которые находятся в каталоге themes проекта. Для нашего проекта поддержка возможности изменения темы приложения не требовалась, однако, таблицы стилей были существенно переработаны, чтобы UI соответствовал корпоративному стилю заказчика.

Создание диалога при помощи EasyUI

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

Код для создания диалога настроек HTTP
(function($) {   $.fn.httpConfDlg = function(icon) {     var title = _("HTTP Configuration"), me;     var succ = _(       "HTTP properties have been changed. " +       "You need to re-connect your browser " +       "according to the new properties."     );     var errcode = "System returned error code %1."     var errset = _(       "Can't set HTTP configuration. " + errcode     );     var errget = _(       "Can't get HTTP configuration. " + errcode     );     var allowed = $.SMR_PRIVILEGE.CHECK(       $.SMR_PRIVILEGE.CHANGE_NETWORK_CONFIGURATION     );     var buttons = [];     if (allowed) {       buttons.push({         okButton: true,         handler: function() {           var ho = $(this.parentElement).api({             fn: $.WAPI.FN_SET_HTTP_PROPERTIES,             param: {               httpPort: parseInt($('#httpPort').textbox('getValue')),               httpsPort: parseInt($('#httpsPort').textbox('getValue')),               forceHttps: $.HpiBool($('#forceHttp')[0].checked)             },             before: function() {               $('body').css('cursor', 'wait');             },             done: function() {               $('body').css('cursor', 'default');               me.dialog('close');             },             error: function(err) {               if (err.RC == $.WAPI.RC_BAD_RESPONSE) {                 $.messager.alert(                   title,                   $.fstr(errset, err.IC),                   'error'                 );                 return false;               } else if (err.RC == 1003) {                 ho.api('drop');                 $.messager.alert(title, succ, 'info', function() {                   $('#sinfo').session('logout');                 });                 return false;               }               return true;             }           });         }       });     }     buttons.push({cancelButton: true});     return this.each(function() {       document.body.appendChild(this);       me = $(this).append(         '<div id="httpSetting" style="padding: 10px 30px">' +         $.fitem('httpPort', _("HTTP port")) +         $.fitem('httpsPort', _("HTTPS port")) +         $.fcheck('forceHttp', _("Force HTTPS for Web Access")) +         '</div>'       );       $('#httpPort').textbox({         type: 'text', width: 60, disabled: !allowed       });       $('#httpsPort').textbox({         type: 'text', width: 60, disabled: !allowed       });       if (!allowed) $('#forceHttp').attr('disabled', 'disabled');         me.mdialog({           title: title,           iconCls: icon,           width: 320,           height: 180,           modal: true,           buttons: buttons,           onOpen: function() {             var ho = $(this).api({               fn: $.WAPI.FN_GET_HTTP_PROPERTIES,               receive: function(res) {                 $('#httpPort').textbox('setValue', res.httpPort);                 $('#httpsPort').textbox('setValue', res.httpsPort);                 if (res.forceHttps == 1) {                   $('#forceHttp').attr('checked', 'checked')                 } else {                   $('#forceHttp').removeAttr('checked')}               },               error: function(err) {                 if (err.RC == $.WAPI.RC_BAD_RESPONSE) {                   $.messager.alert(                     _("HTTP"),                     $.fstr(                       errget,                       err.IC                     ),                   'error'                 );                 me.dialog('close');                 return false;               }               me.dialog('close');               return true;             }           });         }       });     });   }; })(jQuery); 

Поскольку компоненты EasyUI реализованы в виде коллекций jQuery (в нашем случае это $('div').httpConfDlg(http_icon)), инициализация диалога производится через метод this.each().

В начале активируются кнопки диалога: OK и Cancel. Это можно сделать непосредственно при инициализации диалога, но кнопка OK создается только для обеспечения привилегированного доступа. Таким образом, для пользователя, не имеющего достаточных прав для изменения параметров HTTP протокола, диалог будет отображать только кнопку Cancel (Конечно, EasyUI допускает установку и снятие запрета нажатия на кнопки во время инициализации диалога, а также во время его работы кнопка при запрете использования изменяет стиль и не реагирует на нажатия. Однако, для сохранения общего стиля, неиспользуемые кнопки в диалогах нами не отображаются). Обработчик кнопки Cancel по умолчанию закрывает окно диалога без дополнительных действий. У кнопки OK есть обработчик, который выполняет AJAX-запрос. В качестве параметра запросу передаётся JSON структура, содержащая номер функции для бэкенда, набор параметров для самой функции и обработчики результатов выполнения (callback).

Затем родительский элемент, переданный через параметр this, заполняется контентом: двумя полями для указания номеров портов и одного флажка, устанавливающего принудительное использование защищённого протокола. Далее поля активируются как EasyUI textbox компоненты. Если пользователь не имеет привилегий для их изменения, текстовые поля и флажок будут недоступны для изменения.

И наконец, производится активация самого диалога: устанавливаются его размеры, заголовок, иконка, модальный режим, активируются кнопки диалога и их обработчики (указанные явно или по умолчанию). После открытия диалога запускается функция, выполняющая AJAX запрос для получения текущих параметров HTTP. Если при выполнении запроса происходит ошибка, генерируется сообщение об ошибке и диалог закрывается. Если запрос выполнен успешно, текстовые поля и флажок устанавливаются в соответствии с полученными результатами запроса.

При нажатии на кнопку OK вызывается функция-обработчик, которая считывает текущие значения текстовых полей и состояние флажка, формирует соответствующий AJAX-запрос и выполняет его. При любом результате выполнения этого запроса производится закрытие окна диалога (параметр done). В случае возникновения ошибки, в зависимости от её типа, выдаётся сообщение об ошибке. В нашем случае, даже при успешном выполнении запроса происходит ошибка, поскольку изменение настроек протокола приводит к разрыву соединения. Для его восстановления производится закрытие сеанса и вызов процедуры новой регистрации.

Несколько, пояснений к коду.

  • Вызов $.fitem('httpPort', _("HTTP port")) создаёт набор связанных HTML элементов, реализующих типовое для нашего приложения поле ввода с идентификатором httpPort и меткой (label) HTTP port. Функция _() обеспечивает использование языка, указанного пользователем в настройках. Последующий вызов компонента EasyUI $('#httpPort').textbox({type: 'text', width: 60, disabled: !allowed}); регистрирует поле ввода как EasyUI textbox. Вызов $('#httpPort').textbox('setValue', res.httpPort); устанавливает значение для текстового поля в соответствие результату AJAX запроса. И наконец, parseInt($('#httpPort').textbox('getValue')) в обработчике OK-кнопки возвращает текущее значение текстового поля.

  • Компонент mdialog() является нашим собственным расширением от базового компонента EasyUI dialog() для автоматического закрытия диалога при наступлении определённых событий, а также для создания типовых кнопок с обработкой нажатия по умолчанию. В данном случае это кнопка Cancel, которая создаётся короткой инструкцией buttons.push({cancelButton: true});

  • Функция $.messager вызывает окно предупреждения, которое также является компонентом EasyUI, производным от компонента Dialog.

В итоге диалог выглядит так:

EasyUI диалог для настройки HTTPEasyUI диалог для настройки HTTP

Ложка дёгтя

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

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

И всё-таки, почему EasyUI?

Работа с большими массивами данных, представленными в виде таблиц и деревьев это та фишка, которая определила выбор EasyUI.

Для нас критически важно было отображать большой и постоянно обновляемый набор данных как в виде иерархической структуры, так и в виде таблицы. И EasyUI предоставила нам такую возможность. Для этого в составе библиотеки есть специальные компоненты: tree и datagrid.

EasyUI tree отображает иерархические данные в виде древовидной структуры. Узлы дерева можно наполнять различной информацией и извлекать её по событиям, разворачивать, сворачивать, перетаскивать, изменять стиль оформления по содержимому или другим условиям.

Компонент datagrid отображает данные в табличном формате и предлагает богатую поддержку для выбора, сортировки и группировки данных. Слияние ячеек, многоколоночные заголовки, замороженные столбцы и нижние колонтитулы вот лишь некоторые из его особенностей. Кроме этого, для datagrid есть специальные расширения: datagrid-scrollview, которое манипулирует полным набором данных, сохраняя в DOM-е лишь отображаемую их часть (а это существенно повышает скорость работы приложения), и datagrid-filter позволяющее накладывать фильтры на используемый набор данных.

И tree, и datagrid поддерживают асинхронную загрузку и редактирование данных непосредственно по месту положения элемента, имеют динамически настраиваемое оформление и свободно подключаемые иконки для элементов компонента, а также возможность отображения данных в виде чекбоксов.

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

В завершение хочу ещё раз сказать, что библиотека EasyUI обеспечивает все потребности пользовательского интерфейса, оставаясь при этом лёгкой в использовании. Я думаю, эта библиотека может быть интересна разработчику веб-приложений, ориентированных на управление различными устройствами. В этой нише её использование, на мой взгляд, может быть предпочтительнее Bootstrap-а. По крайней мере, это хорошая альтернатива панадолу.

Подробнее..

Про Shadow DOM

06.09.2020 20:21:10 | Автор: admin

Всем привет!

Продолжаю свой цикл публикаций о группе стандартов Web Components. Моя цель - сформировать реалистичные ожидания от данного набора технологий, а также, вместе с вами, прийти к более четкому пониманию того, где их не стоит применять, и где, напротив, ничего лучше еще не придумано. На этот раз, предлагаю подробнее остановится на Shadow DOM.

Начнем с азов, чтобы те из нас, кто пока не сталкивался с предметом обсуждения, не потеряли интерес к основной части статьи. Итак, Shadow DOM - это часть современного DOM API, которая позволяет создавать изолированные участки документа, со своей собственной внутренней разметкой и своими собственными стилями. Если вы откроете, с помощью инструментов разработчика в браузере, структуру документа, содержащего, к примеру, стандартный HTML-элемент audio - вы увидите в его составе область, обозначенную как #shadow-root и какую-то разметку внутри. Это и есть Shadow DOM элемента audio. Как видите, мы можем столкнуться с "теневыми" участками документов в приложениях и на страницах сайтов, даже если при их создании никак не использовались веб-компоненты или основанные на них библиотеки. Это утверждение справедливо для всех стандартных браузерных UI-примитивов, таких как кнопки, селекты, инпуты и т. д. Хорошая новость в том, что теперь у нас есть возможность создавать собственные универсальные элементы, подобные встроенным браузерным. И Shadow DOM, в данном случае, это ответ на вопрос "как?".

Какие основные вопросы решает Shadow DOM?

  1. Инкапсуляция. Внутри Shadow DOM создается отдельный "поддокумент", к которому можно применять свои стили, экранированные от воздействий внешней среды (вам не нужно писать многоэтажные имена классов, чтобы обезопасить ваш элемент или внешний документ от "протечек") и где создается свой контекст для методов DOM API, где, к примеру, с помощью селекторов можно получить только те элементы которые находятся внутри и остаются почти невидимыми снаружи (при этом, допустимо использование одинаковых ID у элементов в разных контекстах, без опасности все поломать).

  2. Композиция. Shadow DOM дает вам контроль над "размещением" непосредственных потомков сложного DOM-элемента в нужных местах его внутренней разметки. В этом случае, Shadow DOM выступает в роли некоего шаблона, в котором можно размещать содержимое в местах, обозначенных специальным тегом - slot. Это дает возможность, к примеру, создавать элементы-лейауты и повторно использовать их.

У тех, кто уже имел дело с библиотеками, основанными на веб-компонентах (такими как LitElement), могло сформироваться ложное впечатление о том, что Shadow DOM - это неотъемлемая часть любого веб-компонента. Это не так. Вы можете создавать компоненты с помощью стандарта Custom Elements и НЕ использовать Shadow DOM при этом, и напротив, создавать теневой DOM у обычных элементов, таких как старый добрый div. Теневой DOM открывает довольно интересные и нетривиальные возможности, например, когда вы динамически добавляете CSS-свойства к элементу через интерфейс "element.style", у вас нет возможности определить псевдоклассы и псеводоэлементы, а также, использовать media-запросы или создавать ключи анимации. Это большой недостаток модели работы со стилями через JavaScript в современных браузерах (работа в этом направлении ведется, но это отдельная сложная тема). Все меняет Shadow DOM:

let myElement = document.createElement('div');myElement.attachShadow({  mode: 'open',});myElement.shadowRoot.innerHTML = /*html*/ `<style>:host {  padding: 10px;}:host(:hover) {  color: red;}</style><slot></slot>`;

Теперь у нашего div есть реакция на наведение мыши при том, что мы не создавали для этого никаких классов и не вносили никаких изменений во внешние стили. Shadow DOM дает нам доступ к своему элементу-контейнеру через селектор :host, и используя этот селектор, мы можем создавать любые сложные стили для элемента в JS. Прошу принять во внимание, что код приведенный выше, написан исключительно для демонстрации самого принципа, в бою все может выглядеть немного иначе.

Когда стоит применять Shadow DOM?

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

Модные микрофронтенды также являются интересной областью для применения возможностей Shadow DOM.

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

Вялотекущий рефакторинг сложной системы тоже можно проводить через создание "островков безопасности".

Какие могут возникнуть сложности?

Следует понимать, что Shadow DOM в отдельности, НЕ решает вопрос контроля жизненного цикла ваших компонентов и инициализации компонентов во внешней среде (помните, для этого есть Custom Elements).

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

В случае, если на сайте используется CSP (Content Security Policy) - вы будете ограничены в выборе способов добавления стилей для элементов внутри теневого DOM. Любая попытка парсить стили из строки вызовет ошибку. Не будет работать ни innerHTML, ни insertRule, ничто иное в этом роде. Самое простое и быстрое решение, но, на мой взгляд, наименее красивое - CSP-флаг unsafe-inline. Если вы создаете виджет для интеграции его на сторонний сайт, рекомендовать пользователям использование небезопасных настроек - это не комильфо. Для браузеров на основе Chromium, выходом может быть использование adoptedStylesheets. Более универсальными решениями будет создание динамических стилей через element.style (что, как писалось выше, имеет свои ограничения), либо добавление в Shadow DOM внешнего файла стилей:

let myElement = document.createElement('div');myElement.attachShadow({  mode: 'open',});myElement.shadowRoot.innerHTML = /*html*/ `<link rel="stylesheet" href="styles.css"><slot></slot>`;

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

Ну и, поскольку данный вопрос все еще всплывает в обсуждениях, затрону и его: если среди требований к вашему проекту имеется поддержка IE - теневой DOM не ваш выбор. Радует, что все большее число разработчиков отказывается от поддержки некробраузеров, надеюсь скоро не нужно будет вообще об этом вспоминать.

Вывод

Shadow DOM - мощная и гибкая технология. Ее использование может существенно облегчить решение многих задач и открывает простор для творчества в решении задач нетипичных. Но не ждите от нее волшебного ответа на все свои вопросы и полного отсутствия сложностей.

Подробнее..

Минимальное PWA

17.03.2021 00:15:17 | Автор: admin

Какие характеристики должны быть у web-приложения, чтобы соответствовать критерию "прогрессивное"? Понятно, что, как и обычные web-приложения, прогрессивные строятся на базе "большой тройки" web-технологий - HTML/CSS/JS. Но что именно делает web-приложения прогрессивными?

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

Принципы отбора характеристик

Я возьму пустой HTML-файл и буду постепенно добавлять к нему артефакты, превращающие его в PWA, пока мой смартфон не решит установить это приложение, а Chrome не перестанет выкидывать предупреждения. Chrome выбран по причине того, что основным "движителем" технологии PWA на данный момент является Google.

HTTPS

Первое, что нужно настроить на сервере - это шифрование трафика. Тут всё делается так же, как и для обычных web-приложений. Лично я использую Apache httpd, а сертификаты для шифрования генерирую через Let's Encrypt.

App Shell

Оболочка приложения - это минимальный набор файлов, позволяющий приложению запускаться в offline-режиме. Я попробую весь необходимый код (HTML/CSS/JS) по максимуму уместить в одном файле - index.html.

<!DOCTYPE html><html lang="en"><head>    <title>PWA</title>    <style>        BODY {            background-color: #FB9902;            color: #342309;            font-size: xx-large;            margin: 0;        }        DIV {            align-content: center;            display: grid;            height: 100vh;            justify-content: center;        }    </style></head><body><div>App Shell</div></body></html>

Manifest

Манифест является непосредственным маркером того, что данная страница является частью прогрессивного web-приложения. Подключается манифест в заголовке HTML-страницы:

<link rel="manifest" href="demo.pwa.json">

Специальных правил для наименования файла с манифестом я не видел, тип содержимого маркируется через значение manifestатрибута rel.

Пустой манифест генерирует такие сообщения об ошибках в Chrome:

Вот минимальное содержимое, которое не вызывает появления в Chrome предупреждений:

{  "start_url": "./",  "name": "PWA",  "display": "standalone",  "icons": [    {      "src": "./icon.png",      "sizes": "144x144",      "type": "image/png"    }  ]}

Icon

Как видно из предупреждений Chrome'а, иконка должна быть размера 144x144 минимум и в формате PNG, SVG или WebP. Примерно такую я и сделал:

В общем случае может быть множество иконок различных размеров.

Service Worker

После добавления минимального манифеста и иконки в Chrome остаётся только одно предупреждение - относительно service worker'а.

Service worker подключается через скрипты в оболочке приложения (index.html):

<script>    if ("serviceWorker" in navigator) {        self.addEventListener("load", async () => {            const container = navigator.serviceWorker;            if (container.controller === null) {                const reg = await container.register("sw.js");            }        });    }</script>

Минимальное содержимое файла sw.js:

'use strict';

вызывает вот такое предупреждение в Chrome: Page does not work offline

Stackoverflow сообщает, что предупреждение связано с отсутствием обработчика на событие fetch. Добавляем пустой обработчик:

'use strict';function hndlEventFetch(evt) {}self.addEventListener('fetch', hndlEventFetch);

Теперь предупреждение изменилось на:

Site cannot be installed: Page does not work offline. Starting in Chrome 93, the installability criteria is changing, and this site will not be installable. See https://goo.gle/improved-pwa-offline-detection for more information.

Моя текущая версия Chrome: Version 89.0.4389.72 (Official Build) (64-bit)

Тем не менее, смартфон предлагает установить приложение при заходе на страницу:

То есть, на данный момент service worker может быть номинальным, но в ближайшем будущем (в августе 2021-го) этого будет недостаточно.

Кэширование

Чтобы наше приложение оставалось PWA и после августа 2021-го, нам нужно добавить кэширование файлов, входящих в оболочку приложения. Вот полный код service worker'а:

'use strict';const CACHE_STATIC = 'static-cache-v1';function hndlEventInstall(evt) {    /**     * @returns {Promise<void>}     */    async function cacheStaticFiles() {        const files = [            './',            './demo.pwa.json',            './icon.png',            './index.html',            './sw.js',        ];        const cacheStat = await caches.open(CACHE_STATIC);        await Promise.all(            files.map(function (url) {                return cacheStat.add(url).catch(function (reason) {                    console.log(`'${url}' failed: ${String(reason)}`);                });            })        );    }    //  wait until all static files will be cached    evt.waitUntil(cacheStaticFiles());}function hndlEventFetch(evt) {    async function getFromCache() {        const cache = await self.caches.open(CACHE_STATIC);        const cachedResponse = await cache.match(evt.request);        if (cachedResponse) {            return cachedResponse;        }        // wait until resource will be fetched from server and stored in cache        const resp = await fetch(evt.request);        await cache.put(evt.request, resp.clone());        return resp;    }    evt.respondWith(getFromCache());}self.addEventListener('install', hndlEventInstall);self.addEventListener('fetch', hndlEventFetch);

С таким service worker'ом наше приложение будет считаться прогрессивным и в Chrome 93+. Вот какие файлы легли в кэш:

Ошибка, которая высветилась в консоли - отсутствие файла favicon.ico:

GET https://bwl.local.teqfw.com/favicon.ico 404

Но это уже особенности работы браузера, а не PWA.

Резюме

Чтобы web-приложение считалось прогрессивным (в том числе и после августа 2021-го) оно должно удовлетворять следующим условиям:

  1. Использовать шифрование (HTTPS).

  2. Оболочка приложения (загружает манифест приложения).

  3. Манифест (загружает иконку и service worker).

  4. Иконка.

  5. Service worker.

  6. Кэширование файлов оболочки приложения.

При соблюдении этих условий PWA устанавливается на смартфоне:

Послесловие

На написание этой статьи меня натолкнуло сообщение в Chrome о том, что с версии 93 web-приложения без кэширования оболочки приложения более не будут считаться прогрессивными и им будет отказано в установке. Я вышел на PWA через такое замечательное приложение, как Vue Storefront. Разработчики провели гигантскую работу и привязали к Magento 2 фронт, созданный с использованием современных технологий (положа руку на сердце, оригинальный фронт Magento 2 очень сильно отстал от современных тенденций web-разработки).

Когда я разбирался с тем, как устроен Vue Storefront, я обратил внимание, что приложение написано из расчёта, что соединение с интернетом будет всегда. Что и понятно, e-коммерция без интернета перестаёт быть таковой. И хотя браузеры предоставляют определённые возможности для того, чтобы PWA были максимально похожи на нативные мобильные приложения, современные PWA не спешат их использовать. Всё-таки, они во-первых - web, а прогрессивные - лишь во-вторых. Можно написать web-приложение, можно написать serverless приложение (за исключением App Shell, манифеста и service worker'а, разумеется - их всё равно придётся тянуть с сервера). Но написать приложение, которое бы работало как web-приложение при наличии интернет-соединения, и работало в автономном режиме в его отсутствие - в разы сложнее каждого из этих вариантов.

Именно поэтому разработчики Vue Storefront не стали заморачиваться с автономной функциональностью, ограничившись кэшированием статики и некоторых данных. Именно поэтому для Vue Storefront вылетает сообщение о том, что в Chrome 93+ их приложение более не будет считаться прогрессивным (демо, откройте консоль). Кстати, для собственной PWA-разработки самой Magento ситуация аналогичная.

Другими словами Google ужесточает критерии PWA, сдвигая фокус от web'а в сторону мобильных платформ. Конечно, можно и дальше называть обычные web-приложения прогрессивными, даже если они на 100% предполагают использование на десктопах и в условиях стабильного интернет-соединения. Но общая тенденция говорит о том, что место для PWA - мобильные устройства:

In December 2020, Firefox for desktop abandoned implementation of PWAs (specifically, removed the prototype "site-specific browser" configuration that had been available as an experimental feature). A Firefox architect noted: "The signal I hope we are sending is that PWA support is not coming to desktop Firefox anytime soon." Mozilla still plans to support PWAs on Android.

Разработчики Firefox не собираются поддерживать PWA в десктопной версии своего браузера.

Нельзя сказать, что PWA являются чисто Google'овской технологией:

всё-таки поддержка PWA-технологии различными браузерами на различных платформах достаточно объемлющая. Но дрейф в сторону "мобилизации" технологии я игнорировать не могу.

На мой взгляд PWA - это "нишевое" решение, для мобильных устройств. Да, оно сделано с использованием web-технологий (HTML/CSS/JS) и крутится внутри браузера, но к разработке PWA нужно подходить скорее с точки зрения нативных мобильных приложений, чем с точки зрения web-приложений (сайтов или SPA).

Другими словами, если вам нужно именно web-приложение, то вам не нужно PWA. А вот если вам нужно разработать именно мобильное приложение, то PWA может быть приемлемым вариантом.

Подробнее..

Из песочницы Как скрестить Excel c интерактивным веб-приложением

07.10.2020 16:09:34 | Автор: admin
Не секрет, что Excel довольно мощный инструмент для работы с числовыми табличными данными. Однако средства, которые предоставляет Microsoft для интеграции с ним, далеки от идеала. В частности, сложно интегрировать в Excel современные пользовательские интерфейсы. Нам нужно было дать пользователям Excel возможность работать с довольно насыщенным и функциональным интерфейсом. Мы пошли несколько другим путем, который в итоге показал хороший результат. В этой статье я расскажу, как можно организовать интерактивное взаимодействие Excel c веб-приложением на Angular и расшить Excel практически любым функционалом, который реализуем в современном веб-приложении.



Итак, меня зовут Михаил и я CTO в Exerica. Одна из проблем которые мы решаем облегчение работы финансовых аналитиков с числовыми данными. Обычно они работают как с исходными документами финансовой и статистической отчетности, так и каким-либо инструментом для создания и поддержания аналитических моделей. Так сложилось, что 99% аналитиков работают в Microsoft Excel и делают там довольно сложные вещи. Поэтому перевести их с Excel на другие решения не эффективно и практически невозможно. Объективно, облачные сервисы электронных таблиц до функционала Excel пока не дотягивают. Но в современном мире инструменты должны быть удобны и соответствовать ожиданиям пользователей: открываться по клику мышки, иметь удобный поиск. А реализация в виде разных несвязанных приложений будет довольно далека от ожданий пользователя.

То с чем работает аналитик выглядит примерно так:



Основные данные тут это числовые финансовые показатели, например, доход за 1 квартал 2020 года. Для простоты буду называть их дальше просто числами. Как видно, почти никакой связи между числами в документе и в аналитической модели нет, все только в голове у аналитика. И работа с заполнением и поддержанием модели занимает часы поиска и перебивания чисел из документа в таблицы, а потом поиск ошибок ввода. Вместе этого мы хотели бы предложить пользователю привычные инструменты: перетаскивание, вставка через клипборд и так далее, а также быстрый просмотр исходных данных.

Что у нас уже было


К моменту, когда мы начали реализацию интерактивного взаимодействия с Excel в виде, изложенном в этой статье, у нас уже была база данных на MongoDB, бэкэнд в виде REST API на .NET Core, фронтовое SPA на Angular и некоторые другие сервисы. Мы к этому моменту уже пробовали разные варианты интеграции в приложения электронных таблиц, в том числе и в Excel, и все они не пошли дальше MVP, но это тема отдельной статьи.



Связываем данные


В Excel существует два распространенных инструмента, с помощью которых можно решить задачу связывания данных в таблице с данными в системе: RTD (RealTimeData) и UDF (User-Defined Functions). Чистый RTD менее удобен для пользователя в плане синтаксиса и ограничивает гибкость решения. С помощью UDF можно создать кастомную функцию, которая будет работать привычным для Excel-пользователя образом. Ее можно использовать в других функциях, она понимает ссылки типа A1 или R1C1 и вообще ведет себя как надо. При этом никто не мешает использовать механизм RTD для обновления значения функции (что мы и сделали). UDF мы разрабатывали в виде Excel addin с использованием привычного нам C# и .NET Framework. Для ускорения разработки мы использовали библиотеку Excel DNA.

Кроме UDF наш addin реализует ribbon (панель инструментов) с настройками и некоторыми полезными функциями по работе с данными.

Добавляем интерактивность


Для передачи данных в Excel и налаживания интерактива мы разработали отдельный сервис, который предоставляет подключение по Websocket при помощи библиотеки SignalR и фактически является брокером для сообщений о событиях, которыми должны обмениваться фронтовые части системы в реальном времени. Он у нас называется Notification Service.



Вставляем данные в Excel


В нашем SPA мы подсвечиваем все числа, которые обнаружила система. Пользователь может выделять их, навигировать по ним и т.п. Для вставки данных мы реализовали 3 механизма, чтобы закрыть различные варианты использования:

  • Перетаскивание (drag-and-drop)
  • Автоматическая вставка по клику в SPA
  • Копирование и вставка через клипборд

Когда пользователь инициирует dragndrop некоторого числа из SPA, для перетаскивания формируется ссылка с идентификатором этого числа из нашей системы (.../unifiedId/005F5549CDD04F8000010405FF06009EB57C0D985CD001). При вставке в Excel наш addin перехватывает событие вставки и парсит регэкспом вставляемый текст. При обнаружении валидной ссылки на лету подменяет ее на соответствующую формулу =ExrcP(...).

При клике на числе в SPA через Notification Service отправляется сообщение в addin, содержащее все необходимые данные для вставки формулы. Далее формула просто вставляется в текущую выделенную ячейку.

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

  • Пользователь выделяет область с числами в SPA
  • Массив выделенных чисел передается на Notification Service
  • Notification Service передает его в addin
  • Addin формирует OpenXML и вставляет его в клипборд
  • Пользователь может вставить данные из клипборда в любое место любой Excel-таблицы.



Несмотря на то, что данные проделывают довольно долгий путь, благодаря SignalR и RTD происходит это довольно быстро и абстрагированно от пользователя.

Распространяем данные


После того, как пользователь выбрал начальные данные для своей модели, их надо распространить все периоды (года, полугодия и кварталы), которые представляют интерес. Для этих целей одним из параметров нашей UDF является дата (период) данного числа (вспоминаем: доход за 1 квартал 2020 года). В Excel существует нативный механизм распространения формул, который позволяет заполнить ячейки той же формулой с учетом ссылок, заданных в параметрах. То есть вместо конкретной даты в формулу вставлена ссылка на нее, а далее пользователь распространяет ее на другие периоды, при этом в таблицу автоматически загружаются те же числа из других периодов.

А что это там за число?


Теперь у пользователя есть модель на несколько сотен строк и несколько десятков столбцов. И у него может возникнуть вопрос, что же там за число в ячейке L123? Чтобы получить ответ, у нас ему достаточно кликнуть на эту ячейку и в нашем SPA откроется тот самый отчет, на той самой странице, где записано кликнутое число, а число в отчете будет выделено. Вот так:



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

В качестве заключения


Вот такая, на мой взгляд, нестандартная реализация взаимодействия между Excel и веб-приложением получилась довольно удобной для пользователя. Благодаря использованию Excel порог входа для пользователей из целевой аудитории довольно низкий. При этом мы получаем также всю мощь Excel по работе с числовыми данными. Сами данные всегда остаются связанными с источником, а также связанными во времени. Для целевых клиентов нет необходимости встраивать в бизнес-процесс работы с данными абсолютно новые внешние системы. Наше решение встраивается как дополнительный инструмент внутри Excel, что является стандартом де-факто для провайдеров финансовых данных.

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

Перевод Рендеринг в веб

22.03.2021 20:04:15 | Автор: admin

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


о переводе

Переведя почти всю статью я внезапно обнаружил, что в том же источнике она присутствует и на русском языке (сразу не обнаружил потому что в выпадающем списке переводов нет русского языка, перевод нашелся через Google - https://developers.google.com/web/updates/2019/02/rendering-on-the-web?hl=ru). Однако, при ближайшем рассмотрении перевод "рендеринг сервера" вместо "статический рендеринг" сразу дал понять что перевод был сделан машиной.
Непосредственно перед публикацией своего перевода здесь, я ещё раз зашел на тот русский перевод и ... обнаружил что его кто-то уже существенно улучшил :))) Однако, на мой пристальный взгляд, он всё равно не лишён недостатков.
В итоге, я всё же решил опубликовать свой вариант перевода, так как, на мой взгляд, статья в любом случае достойна того чтобы привлечь к ней внимание, а какой перевод прочесть, в итоге каждый выберет сам.
Спасибо!

Наше понимание в этой области основано на нашей работе с Chrome, и контактировании с большими сайтами в течение последних нескольких лет. В общем, мы хотим вдохновить разработчиков рассмотреть использование серверного рендеринга или статического рендеринга с полноценной регидратацией.

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

Терминология

Рендеринг

  • SSR: Server-Side Rendering - рендеринг в HTML клиентского или универсального приложения на сервере.

  • CSR: Client-Side Rendering - рендеринг приложения в браузере, обычно используя DOM

  • Rehydration (регидратация): "загрузка" JavaScript отображениий на клиенте таким образом, чтобы они повторно использовали отрендеренное на сервере DOM-дерево и данные HTML-а

  • Prerendering (пре-рендеринг): выполнение клиентского приложения во время сборки для захвата его начального состояния в виде статического HTML.

Performance

  • TTFB:Time to First Byte - время между нажатием на ссылку и временем прихода первого бита контента

  • FP:First Paint - время когда первый пиксель становится виден пользователю

  • FCP:First Contentful Paint - время до показа пользователю запрошенного контента (тела статьи и т.п.)

  • TTI:Time To Interactive - время до момента когда страница становится интерактивной (начинают работать события и т.д.)

Server Rendering (Серверный рендеринг)

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

Серверный рендеринг обычно даёт быстрый First Paint (FP) и First Contentful Paint (FCP). Выполнение логики страницы и её рендеринг на сервере позволяют избежать отправки большого количества JavaScript клиенту, что помогает достичь быстрого Time to Interactive (TTI). Это имеет смысл потому, что при серверном рендеринге вы на самом деле просто посылаете текст и ссылки в браузер пользователя. Такой подход может хорошо работать для широкого спектра устройств и сетевых условий и открывает интересные возможности для оптимизации браузера, например можно выполнять разбор потоковых (streaming) документов.

При серверном рендеринге пользователи вряд ли будут вынуждены ждать, пока CPU-зависимый JavaScript будет выполнен, прежде чем они смогут использовать ваш сайт. Даже когда стороннего JS не избежать, использование серверного рендеринга для уменьшения собственных JS costs (JS затрат) может дать вам больше "budget" (бюжета) для остального. Однако, есть один основной недостаток такого подхода: генерация страниц на сервере занимает время, что часто может привести к замедлению Time to First Byte (TTFB).

Достаточно ли серверного рендеринга для вашего приложения, во многом зависит от того, какое приложение вы строите. Существует давняя дискуссия о правильности применения серверного рендеринга вместо клиентского рендеринга, но важно помнить, что вы можете использовать серверный рендеринг для одних страниц, а для других нет. Некоторые сайты с успехом переняли гибридный рендеринг.Netflix делает серверный рендеринг своих относительно статических страниц, в то время как делает prefetching JS для страниц с тяжелым взаимодействием, давая этим более тяжелым отрендеренным на клиенте страницам больше шансов на быструю загрузку.

Многие современные фреймворки, библиотеки и архитектуры позволяют отрисовывать одно и то же приложение как на клиенте, так и на сервере. Эти инструменты могут быть использованы для Server Rendering, однако важно отметить, что архитектуры, где рендеринг происходит как на сервере, так и на клиенте, являются собственным классом решений с очень различными характеристиками производительности и компромисами. React пользователи могут использовать для серверного рендеринга renderToString() или решения, построенные на нем, такие как Next.js. Пользователи Vue могут ознакомиться с руководством по серверному рендерингу Vue или познакомиться с Nuxt. В Angular есть Universal. Однако большинство популярных решений используют ту или иную форму гидратации (hydration), поэтому перед выбором инструмента следует ознакомиться с используемыми подходами.

Static Rendering (Статический рендеринг)

Статический рендеринг происходит во время сборки и даёт быстрый FP, FCP и TTI - это если предопложить, что количество клиентского JS невелико. В отличие от серверного рендеринга, ему также удаётся достичь стабильно быстрого TTFB, так как HTML для страницы не нужно генерировать "на лету". Как правило, статический рендеринг означает создание отдельного HTML-файла для каждого URL заранее. С HTML генерируемым заранее, статический рендеринг может быть развернут на нескольких CDN, чтобы воспользоваться преимуществами edge-кеширования.

Решения для статического рендеринга бывают разных форм и размеров. Такие инструменты как Gatsby разработаны для того, чтобы разработчики чувствовали, что их приложение отрисовывается динамически, а не генерируется на этапе сборки. Другие, такие как Jekyll и Metalsmith, принимают их статическую природу, предоставляя подход более заточенный на шаблоны.

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

React пользователи могут быть знакомы с Gatsby, Next.js static export или Navi - все они дают удобство для использующих эти компоненты. Однако, важно понимать разницу между статическим рендерингом и пре-рендингом: статический рендеринг страниц интерактивен без необходимости выполнения большого количества клиентского JS, в то время как пре-рендеринг улучшает FP (First Paint) или FCP (First Contentful Paint) одностраничного приложения (SPA), которое должно быть загружено на клиенте для того, чтобы страницы были по-настоящему интерактивными.

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

Другой полезный тест - замедление работы сети с помощью Chrome DevTools и наблюдение за тем, сколько JavaScript было загружено до того, как страница стала интерактивной. Пре-рендеринг обычно требует больше JavaScript для получения интерактивности, а так же этот JavaScript имеет тенденцию быть более сложным, чем Progressive Enhancement подход, используемый при статическом рендеринге.

Серверный рендеринг против статического

Серверный рендеринг не является серебряной пулей - его динамическая природа может сопровождаться значительными накладными расходами. Многие решения для серверного рендеринга don't flush early, могут задерживать TTFB или задваивать отправленные данные (например, в inlane стэйте, используемом JS на клиенте). В React, renderToString() может быть медленным, так как он синхронный и однопоточный. Получение "правильного" рендеринга сервера может включать в себя поиск или создание решения для компонентного кеширования, управление потреблением памяти, применение memoization техник, и многие другие вопросы. Как правило, вы обрабатываете/пересобираете одно и то же приложение несколько раз - один раз на клиенте и один раз на сервере. То, что серверный рендеринг может заставить что-то появиться раньше, не означает, что у вас вдруг стало меньше работы.

Серверный рендеринг генерирует HTML по требованию для каждого URL, но это может быть медленнее, чем просто обслуживание статически отрендереного контента. Если вы готовы сделать дополнительные усилия, то серверный рендеринг + [HTML кеширование] (https://freecontent.manning.com/caching-in-react/) может значительно сократить время серверного рендеринга. Положительной стороной серверного рендеринга является возможность получать более "живые" данные и отвечать на более полный набор запросов, чем это возможно при статическом рендеринге. Страницы, требующие персонализации, являются хорошим примером типа запроса, который плохо работает со статическим рендерингом.

Серверный рендеринг также может представлять интересные решения при построении PWA. Лучше ли использовать full-page service worker кеширование, или просто рендерить на сервере отдельные фрагменты контента?

Client-Side Rendering (CSR)

Рендеринг на стороне клиента (CSR) означает рендеринг страниц непосредственно в браузере с использованием JavaScript. Вся логика, сбор данных, шаблонирование и маршрутизация обрабатываются на клиенте, а не на сервере.

Клиентский рендеринг может быть сложным в части получения и быстроты на мобильных устройствах. Он может достигать производительности чистого сервер-рендеринга, если делать минимальную работу, сохраняя компактным JavaScript бюджет и доставляя объёмы в как можно меньшем количестве RTTs. Критические скрипты и данные могут быть доставлены быстрее с помощью HTTP/2 Server Push или <link rel=preload>, что заставит парсер работать на вас быстрее. Такие шаблоны, как PRPL, стоит рассмотреть, чтобы первоначальная и последующая навигация чувствовалась быстрыми.

Основным недостатком Client-Side Rendering является то, что количество требуемого JavaScript имеет тенденцию расти по мере роста приложения. Это становится особенно трудным с добавлением новых JavaScript-библиотек, полифилов и стороннего кода, которые конкурируют за вычислительную мощность и часто должны быть обработаны, прежде чем содержимое страницы может быть визуализировано. Опыт построения CSR, опирающийся на большие пакеты JavaScript, должен учитывать агрессивное разделение кода, и чувствовать себя уверенным в работе с ленивой загрузкой JavaScript - "обслуживать только то, что вам нужно и когда вам это нужно". Для случаев с небольшой интерактивностью или вообще без нее, серверный рендеринг может представлять собой более масштабируемое решение этих проблем.

Для тех, кто создает одностраничное приложение, определение основных частей пользовательского интерфейса, разделяемого большинством страниц, означает возможность применить технику Application Shell caching. В сочетании с service workers это может драматически повысить воспринимаемую производительность при повторных визитах.

Комбинация серверного рендеринга и клиентского через регидратацию

Часто называемый Universal Rendering или просто "SSR", этот подход пытается сгладить компромиссы клиентского и серверного редеринга, делая и то, и другое. Навигационные запросы, такие как полная загрузка страницы или перезагрузка, обрабатываются сервером, который рендерит приложение в HTML, затем JavaScript и данные, используемые для рендеринга, встраиваются в результирующий документ. При тщательной реализации, это даёт быстрый FCP (First Contentful Paint) такой же, как Server Rendering, а далее "усиливает это" путем рендеринга опять же на клиенте с помощью техники, называемой (re)hydration ((ре)гидратация). Это новое решение, но оно может иметь некоторые существенные недостатки в производительности.

Основной недостаток SSR с регидратацией (rehydration) заключается в том, что она может оказать значительное негативное влияние на TTI (Time To Interactive), даже если она улучшает FP (First Paint). SSR-страницы часто выглядят обманчиво полностью загруженными и интерактивными, но на самом деле не могут реагировать на ввод, пока не будет выполнен JS на стороне клиента и не будут прикреплены обработчики событий. Это может занять секунды или даже минуты на мобильном устройстве.

Возможно, вы сами это испытавали - в течение некоторого времени после того, как страница выглядит так, как будто она загружена, нажатие на нее или касание ничего не даёт. Это быстро расстраивает...Почему ничего не происходит? Почему я не могу скроллить?

= Проблема регидратации: Одно приложение по цене двух

Проблемы с регидратацией часто могут быть хуже, чем задержка интерактивности из-за JS. Для того, чтобы JavaScript на стороне клиента мог точно "определить" ("pick up") то место, где остановился сервер, без необходимости повторно запрашивать все данные, использованные сервером для рендеринга этого HTML, текущие SSR решения обычно сериализуют ответ из зависимых данных UI в документ в виде тегов script. Полученный HTML-документ содержит высокий уровень дублирования:

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

Показатели производительности, собранные с реальных веб-сайтов, использующих SSR регидратацию, указывают на то, что их использование должно приводить в уныние. В конце концов, причина сводится к Пользовательскому Опыту: очень легко оставить пользователей в "жуткой долине".

Но всё же надежда на SSR с регидратацией есть. В краткосрочной перспективе, только использование SSR для высоко кешируемого содержимого может уменьшить задержку TTFB (Time to First Byte), давая результаты, схожие с пре-рендерингом. Регидратация инкрементальная, прогрессивная или частичная, может быть ключом к тому, чтобы сделать эту технику более жизнеспособной в будущем.

Потоковый серверный рендеринг и прогрессивная регидратация

Серверный рендеринг за последние несколько лет претерпел ряд изменений.

Потоковый серверный рендеринг (Streaming server rendering) позволяет посылать HTML в чанках, которые браузер может прогрессивно рендерить по мере получения. Это может обеспечить быстрый FP (First Paint) и FCP (First Contentful Paint), так как разметка поступает к пользователям быстрее. В React, потоковость, будучи асинхронной в renderToNodeStream() - по сравнению с синхронным renderToString - означает, что backpressure обрабатывается хорошо.

Прогрессивная регидратация также заслуживает внимания, и кое-что в React было исследовано. При таком подходе отдельные части приложения, возвращаемого с сервера, "загружаются" постепенно, вместо текущего общепринятого подхода когда инициализируется сразу всё приложение. Это может помочь уменьшить количество JavaScript, необходимого для того, чтобы сделать страницы интерактивными, так как обновление на клиентской стороне низкоприоритетных частей страницы может быть отложено, чтобы предотвратить блокировку основного потока. Это также может помочь избежать одной из наиболее распространенных ловушек SSR Rehydration, когда отрендеренный на сервере DOM разрушается, а затем сразу же восстанавливается - чаще всего потому, что начальный синхронный рендеринг на стороне клиента требует данных, которые были не совсем готовы, возможно, ожидая завершения Promise.

= Частичная регидратация

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

= Трисоморфный рендеринг (Trisomorphic Rendering)

Если service workers, являются подходящим вариантом для вас, то "трисоморфный" рендеринг также может быть вам интересен. Это метод, при котором вы можете использовать потоковый серверный рендеринг для начальных/не-JS навигаций, а затем попросить ваш service worker взять на себя рендеринг HTML для навигации после того как он будет смонтирован. Это может поддерживать кешированные компоненты и шаблоны в актуальном состоянии и позволяет использовать навигацию в стиле SPA для рендеринга новых UI-частей в той же сессии. Такой подход лучше всего работает, когда вы можете поделиться одним и тем же шаблоном и кодом маршрутизации между сервером, клиентской страницей и service worker.

SEO соображения

Команды часто учитывают влияние SEO при выборе стратегии для рендеринга в вебе. Серверный рендеринг часто выбирается для обеспечения поисковым роботам возможности лёгкого "полного поиска". Поисковые роботы могут понимать JavaScript, но часто существуют ограничения, о которых стоит знать в части того как они рендерят. Рендеринг на стороне клиента может работать, но часто не без дополнительного тестирования и трудной работы. В последнее время динамический рендеринг также стал вариантом, заслуживающим внимания, если ваша архитектура в значительной степени ориентирована на клиентский JavaScript.

В случае сомнений, инструмент Mobile Friendly Test бесценен для проверки, что выбранный вами подход делает то, что бы вы хотели. Он показывает визуальный предварительный просмотр того, как какую-либо страницу видет поисковый робот Google, сериализованный HTML контент, найденный (после выполнения JavaScript), и любые ошибки, обнаруженные во время рендеринга.

Заключение...

При принятии решения о подходе к рендерингу, измеряйте и понимайте, каковы ваши "узкие места". Подумайте, может ли статический рендеринг или серверный рендеринг дать вам хотя бы 90% возможностей. Совершенно нормально обычно отправлять HTML с минимальным количеством JS, чтобы получить интерактивный опыт. Вот удобная инфографика, показывающая спектр возможностей в разрезе сервер-клиент:

Благодарности

Спасибо всем этим людям за отзывы и вдохновение:
Jeffrey Posnick, Houssein Djirdeh, Shubhie Panicker, Chris Harrelson, and Sebastian Markbge

Подробнее..

Из песочницы Как разместить статический сайт с помощью Yandex.Cloud Object Storage

20.07.2020 12:12:02 | Автор: admin
Привет, Хабр!

В этой статье, я расскажу как легко и просто разместить статический сайт с помощью технологий Яндекса, а именно Object Storage.


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


Эта статья будет полезна, если вы


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

О себе


Недавно, я разрабатывал SaaS сервис, подобие маркетплейса, где люди находят спортивных тренеров для персональных тренировок. Использовал стек Amazon Web Services (далее AWS). Но чем глубже погружался в проект тем больше нюансов узнавал о разных процессах организации стартапа.


Я столкнулся с следующими проблемами:


  • AWS потреблял много денег. Поработав 3 года в Enterprise компаниях, я привык к таким радостям, как Docker, Kubernetes, CI/CD, blue green deployment, и, как начинающий программист-стартапер, захотел реализовать тоже самое. В итоге пришел к тому, что ежемесячно AWS потреблял по 300-400 баксов. Самым дорогим оказался Kubernetes, около 100 баксов, при минималке с одним кластером и одной нодой.
    P.S. На старте не нужно так делать.
  • Далее, задумавшись о юридической стороне, я узнал про закон 152-ФЗ, в котором говорилось примерно следующее: "Персональные данные граждан РФ должны храниться на территории РФ", иначе штрафы, чего мне не хотелось. Я решил заняться этими вопросами, пока "сверху" мне не прилетело :).

Вдохновленный статьей о мигрировании инфраструктуры из Amazon Web Services в Яндекс.Облако, я решил изучить стек Яндекса подробнее.


Для меня ключевыми особенностями Яндекс.Облака было следующее:



Я изучал других конкурентов этого сервиса, но на тот момент Яндекс выигрывал.


О себе рассказал, можно и перейти к делу.


Шаг 0. Подготовим сайт


Для начала нам понадобится сайт, который мы хотим разместить в интернете. Так как я Angular разработчик, я сделаю простой шаблон SPA приложения, который далее размещу в интернете.


P.S. Кто разбирается в Angular или знает про его документацию https://angular.io/guide/setup-local, переходите к Шагу 1.


Установим Angular-CLI чтобы создавать SPA-сайты на Ангуляре:


npm install -g @angular/cli

Создадим Angular приложение с помощью следующей команды:


ng new angular-habr-object-storage

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


cd angular-habr-object-storageng serve --open

Статическое SPA-приложение на Angular


Приложение создано, но пока не готово к размещению на хостинге. Соберем приложение в небольшой билд (Production), чтобы убрать все лишнее и оставить только необходимые файлы.
В ангуляре это можно сделать следующей командой:


ng build --prod

В результате этой команды в корне приложения появилась папочка dist с нашим сайтом.


Работает. Теперь переходим к хостингу.



Шаг 1.


Переходим на сайт https://console.cloud.yandex.ru/ и жмем на кнопку "Подключиться".


Примечание:


  • Для пользования сервисом Яндекса может понадобится почта Яндекса (но это не точно)
  • Для некоторых функций придется положить деньги на счет в личном кабинете (минимум 500 рублей).

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


Интерфейс личного кабинета Yandex.Cloud


Далее слева в меню нужно найти сервис "Object Storage", который мы как раз будем использовать для хостинга сайта.


Коротко по терминам:


  • Object Storage это хранилище файлов, совместимое с аналогичной технологией Амазона AWS S3, у которого также есть свой API для управления хранилищем из кода и его также как и AWS S3 можно использовать для размещения статического сайта.
  • В Object Storage мы создаем "бакеты" (bucket / Корзина), которые являются отдельными хранилищами наших файлов.

Интерфейс сервиса Yandex.Cloud Object Storage


Создадим один из них. Для этого в консоли сервиса жмем на кнопку "Создать бакет".


Интрфейс создания бакета в Yandex.Cloud


В форме создания бакета есть следующие поля, пробежимся по ним:


  • Имя бакета. Для простоты, назовем так же как и ангуляр проект angular-habr-object-storage
  • Макс. размер. Ставим столько, сколько у нас весит сайт, так как сайт хранится не бесплатно и за каждый выделенный гигабайт, мы будем платить Яндексу копеечку.
  • Доступ для чтения объектов. Ставим "Публичный", так как пользователь должен получать каждый файл нашего статического сайта, чтобы на нем правильно отрисовывалась верстка, отрабатывали скрипты и тд.
  • Доступ к списку объектов и Доступ на чтение настроек. Оставляем "Ограниченный". Это нужно для того, чтобы использовать бакет как внутреннее хранилище файлов для приложений.
  • Класс хранилища. Оставляем "Стандартный". Это означает, что наш сайт часто будут посещать, а значит и часто скачивать файлы, составляющие сайт. Плюс пункт влияет на производительность и оплату (вставить ссылку).

Жмем "Создать бакет" и бакет создан.


Yandex.Cloud Бакет создан


Теперь нужно загрузить наш сайт в бакет. Самый простой способ открыть рядом папочку dist нашего сайта и ручками перетащить прямо на страницу. Это удобнее, чем жать на кнопку "Загрузить объекты", потому что в таком случае папки не переносятся и их придется создавать ручками в правильной последовательности.


Загрузили в бакет наш сайт


Итак, сайт загружен в хранилище, тем можем предоставить пользователям возможность обращаться к хранилищу, как к сайту.
Для этого слева в меню жмем на вкладку "Веб-сайт".


Настройка бакета под сайт


На странице настройки бакета как сайта, выбираем таб "Хостинг". Здесь указываем главную страницу сайта, обычно это index.html. Если у вас SPA приложение, то вероятно все ошибки обрабатываются также на главной странице, поэтому укажем на странице ошибки также index.html.


Мы сразу видим, по какой ссылке будет доступен наш сайт. Жмем сохранить.


Через минут 5, перейдя по ссылке мы видим что теперь наш сайт доступен всем желающим.


Хостинг Angular приложения с помощью Yandex.Cloud Object Storage


Спасибо всем кто дочитал до конца! Это моя первая статья, планирую дальше описать другие сервисы Яндекса и их интеграцию с frontend и backend технологиями.


Напишите в комментариях насколько интересно вам узнать про другие сервисы Яндекса или про использование Angular в современной разработке.

Подробнее..

Категории

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

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru