Технология PWA в 1С - для чего и как использовать

Главный принцип нашей работы - постоянное развитие. Мы верим, что годные результаты может показать только тот, кто В платформе 1С версии 8.3.18 появилась поддержка технологии PWA (Progressive Web Apps - прогрессивные веб-приложения). В описании изменений объявили возможность вывода пиктограммы запуска приложения веб-клиента на рабочий стол и запуска веб-клиента в отдельном окне. На странице описания архитектуры платформы упоминается, что данная технология “позволяет создавать веб-приложения, которые выглядят как нативные приложения и работают почти так же быстро, как нативные приложения”.
И тут я задался вопросом. Неужели веб-клиент в новой платформе сможет работать так же быстро, как и тонкий клиент?
Сама технология PWA была создана в далёком 2007 году, но широкую известность получила только в 2015. На сегодняшний день PWA приложения умеют:
- отправлять push-уведомления;
- работать в оффлайн-режиме и активно использовать кэширование;
- устанавливать ярлык на рабочий стол или стартовое меню.
Как мы видим, никакой магии в PWA нет. Единственное, за счёт чего PWA может увеличить скорость работы веб-клиента, - это агрессивное кэширование. Я решил попробовать проверить эту теорию.
Для начала надо разобраться, как устроена архитектура прогрессивного веб-приложения. В документации MDN web docs указано, что веб-приложение может называться PWA в том случае, если оно соответствует таким критериям:
- работает по протоколу HTTPS
- содержит файл манифеста
- подключает один или несколько сервис-воркеров
По поводу первого первого пункта у меня вопросов нет. Если адрес публикации информационной базы начинается с https:// - критерий пройден. Манифест представляет собой файл формата JSON с набором атрибутов, описывающих свойства устанавливаемого приложения, такие как наименование, пиктограммы, версия и многие другие. Для указания ссылки на манифест используется конструкция вида
<link rel="manifest" href="/manifest.json">
в коде html страницы.
Если открыть код страницы приложения веб-клиента, то можно найти ссылку на манифест.
<link id="id-manifest" rel="manifest" href="manifest.json?sysver=8.3.18.1128" crossorigin="use-credentials"/>
Сам манифест в платформе 8.3.18 имеет вид:
{
"name": "Бухгалтерия предприятия, редакция 3.0",
"short_name": "Бухгалтерия предприятия, редакция 3.0",
"icons": [
{
"src": "e1csys/mngsrv/_PWA_L16.png",
"sizes": "16x16",
"type": "image/png"
},
...
{
"src": "e1csys/mngsrv/_PWA_L512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": "./",
"display": "standalone",
"background_color": "#FFFFFF",
"theme_color": "#FFFFFF"
}
Как мы видим, в нем указано наименование приложения, набор иконок с разрешением от 16 до 512 пикселей, стартовый адрес, режим запуска и цвета фона и темы.
Концепция последнего критерия может показаться немного непонятной, так как связана непосредственно с особенностями разработки веб-приложений и не имеет аналогов в экосистеме 1С. Сервис-воркер - это JavaScript-файл, который может контролировать веб-страницу/сайт, с которым он ассоциируется, перехватывать и модифицировать запросы навигации и ресурсов, очень гибко кешировать ресурсы, для того чтобы предоставить полный контроль над тем, как приложение ведет себя в определенных ситуациях (например, когда сеть не доступна).
Чтобы просмотреть зарегистрированные сервис-воркеры в Chrome, я иду в “Инструменты разработчика” на вкладке “Application - Service Workers”. Веб-клиент платформы 8.3.18 регистрирует единственный сервис воркер scripts/mod_sw_sw.js:
use strict';
(this || self).g = {
"goog.editor.defines.USE_CONTENTEDITABLE_IN_FIREFOX_3": !0
};
var a;
a = self;
a.addEventListener("install", function() {
a.skipWaiting()
});
self.addEventListener("acti vate", function() {
a.clients.claim()
});
self.addEventListener("fetch", function() {});
В данном сервис-воркере все обработчики не выполняют никаких действий, которые могут повысить производительность веб-клиента. Таким образом, никакого увеличения производительности веб-клиента за счёт применения технологии PWA платформа 1С 8.3.18 не несет. Возможности модификации сервис-воркера средствами платформы не предусмотрено. Единственное, на что можно повлиять, - это манифест, который меняется через правку vrd файла или через параметры запуска.
Тогда я решил попробовать написать расширение, позволяющее использовать PWA на предыдущих версиях платформы. Наше расширение будет содержать в себе http-сервис для вывода компонентов PWA, таких как манифест, пиктограммы, сервис-воркер и страница установки PWA приложения. Также я добавлю ссылку на страницу установки в командный интерфейс. Я не могу модифицировать html-код веб-клиента, поэтому ссылку на манифест размещаю на отдельной странице установки PWA приложения. На этой же странице зарегистрирую сервис-воркер.
Готовлю 3 общих макета: pwa_144 с пиктограммой разрешением 144х144 пикселя (можно добавить и остальные разрешения, но это не обязательно), pwa_css с css-стилями страницы установки (я использовал готовый w3c.css) а также sw_js с сервис-воркером (в первой итерации я возьму за основу сервис-воркер из платформы 8.3.18). Далее создадаю http-сервис PWA_Installer с корневым адресом “pwa” и добавляю в него шаблоны url, обрабатывающие GET-запросы наших общих макетов:
Функция CSSGET(Запрос)
Ответ = Новый HTTPСервисОтвет(200);
ТекстCSS = ПолучитьОбщийМакет("pwa_css").ПолучитьТекст();
Ответ.УстановитьТелоИзСтроки(ТекстCSS);
Ответ.Заголовки.Вставить("content-type", "text/css");
Возврат Ответ;
КонецФункции
Функция IconGET(Запрос)
Ответ = Новый HTTPСервисОтвет(200);
ДД = ПолучитьОбщийМакет("PWA_144");
Ответ.УстановитьТелоИзДвоичныхДанных(ДД);
Ответ.Заголовки.Вставить("content-type", "image/png");
Возврат Ответ;
КонецФункции
Функция SWGET(Запрос)
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("content-type", "application/javascript; charset=utf-8");
АдресИБ = ОбщегоНазначения.АдресПубликацииИнформационнойБазыВИнтернете();
СтруктураURI = ОбщегоНазначенияКлиентСервер.СтруктураURI(АдресИБ);
ПутьНаСервере = СтруктураURI.ПутьНаСервере;
Если НЕ СтрНачинаетсяС(ПутьНаСервере, "/") Тогда
ПутьНаСервере = "/" + ПутьНаСервере;
КонецЕсли;
Ответ.Заголовки.Вставить("service-worker-allowed", ПутьНаСервере);
Ответ.УстановитьТелоИзСтроки(ПолучитьОбщийМакет("sw_js").ПолучитьТекст());
Возврат Ответ;
КонецФункции
Для каждого общего макета устанавливается соответствующий заголовок content-type. Для пиктограммы - “image/png”, для css - “text/css”, для сервис-воркера - "application/javascript; charset=utf-8". Это позволяет браузеру корректно обработать полученные данные. Также для сервис-воркера устанавливается заголовок "service-worker-allowed", указывающий, для какого именно url разрешена работа данного воркера. Без этого заголовка сервис-воркер не будет работать на главной странице приложения, а только на странице http-сервиса.
Файл манифеста я буду формировать “на лету”:
Функция ManifestGET(Запрос)
Ответ = Новый HTTPСервисОтвет(200);
Структура = Новый Структура;
Структура.Вставить("name", Метаданные.Синоним);
Структура.Вставить("short_name", Метаданные.Имя);
Структура.Вставить("start_url", "./../../"); // относительно hs/pwa/index.html
Структура.Вставить("display", "standalone");
Структура.Вставить("background_color", "#FFFFFF");
Структура.Вставить("theme_color", "#FFFFFF");
МассивИконок = Новый Массив;
МассивИконок.Добавить(Новый Структура("src, sizes, type", "PWA_144.png", "144x144", "image/png"));
Структура.Вставить("icons", МассивИконок);
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, Структура);
Ответ.Заголовки.Вставить("content-type", "application/json; charset=UTF-8");
Ответ.УстановитьТелоИзСтроки(ЗаписьJSON.Закрыть());
Возврат Ответ;
КонецФункции
Страницу установки PWA приложения я хочу захардкодить:
Функция IndexGET(Запрос)
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("content-type", "text/html; charset=utf-8");
СтрокаШаблона = "<!DOCTYPE html>
|<html>
|<script>if('serviceWorker' in navigator) { navigator.serviceWorker.register('sw.js', {scope: '%SCOPE%'}); }</script>
|<title>PWA</title>
|<meta name=""viewport"" content=""width=device-width, initial-scale=1"">
|<link rel=""stylesheet"" href=""w3c.css"">
|<link rel=""manifest"" href=""manifest.json"" crossorigin=""use-credentials"">
|<body>
|<div class=""w3-container"">
| <h2>PWA</h2>
| <a href=""%АдресИБ%"">%АдресИБ%</a>
|</div>
|</body>
|</html>";
АдресИБ = ОбщегоНазначения.АдресПубликацииИнформационнойБазыВИнтернете();
СтруктураURI = ОбщегоНазначенияКлиентСервер.СтруктураURI(АдресИБ);
ПутьНаСервере = СтруктураURI.ПутьНаСервере;
Если НЕ СтрНачинаетсяС(ПутьНаСервере, "/") Тогда
ПутьНаСервере = "/" + ПутьНаСервере;
КонецЕсли;
СтрокаШаблона = СтрЗаменить(СтрокаШаблона, "%SCOPE%", ПутьНаСервере);
СтрокаШаблона = СтрЗаменить(СтрокаШаблона, "%АдресИБ%", АдресИБ);
Ответ.УстановитьТелоИзСтроки(СтрокаШаблона);
Возврат Ответ;
КонецФункции
При регистрации сервис-воркера я также указываю ему область видимости (SCOPE) равную странице приложения веб-клиента.
Последним штрихом добавляю ссылку на страницу установки, переопределив в расширении функцию “ИнтернетПоддержкаИСервисы_ПриСозданииНаСервере” общего модуля “ИнтеграцияПодсистемБИП”:
&После("ИнтернетПоддержкаИСервисы_ПриСозданииНаСервере")
Процедура PWA_ИнтернетПоддержкаИСервисы_ПриСозданииНаСервере(Форма)
АдресИБ = ОбщегоНазначения.АдресПубликацииИнформационнойБазыВИнтернете();
Если НЕ ПустаяСтрока(АдресИБ) Тогда
Декорация = Форма.Элементы.Добавить(
"PWA",
Тип("ДекорацияФормы"));
Декорация.Заголовок = Новый ФорматированнаяСтрока(
"Установить базу как прогрессивное веб приложение (progressive web app, PWA)",,,,
АдресИБ + "/hs/pwa/index.html"
);
КонецЕсли;
КонецПроцедуры
Применяю расширение, открываю в веб-клиенте “Интернет поддержку и сервисы” и нажимаю на ссылку “Установить как PWA…” или просто перехожу по адресу http-сервиса hs/pwa/index.html. На открывшейся странице вижу заветную кнопку “Установить”:

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

Можно пойти немного дальше и добавить в сервис-воркер, например, поддержку оффлайн-режима. Конечно, я не буду делать полноценную работу без сети, но могу вывести культурное сообщение о временной недоступности сервиса. Добавляю еще один шаблон url в наш http-сервис:
Функция OfflineGET(Запрос)
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("content-type", "text/html; charset=utf-8");
СтрокаШаблона = "<!DOCTYPE html>
|<html>
|<title>PWA</title>
|<meta name=""viewport"" content=""width=device-width, initial-scale=1"">
|<link rel=""stylesheet"" href=""w3c.css"">
|<body>
|<div class=""w3-container"">
| <h2>Сервис временно недоступен. Подробности по телефону +7-123-456-78-90</h2>
|</div>
|</body>
|</html>";
Ответ.УстановитьТелоИзСтроки(СтрокаШаблона);
Возврат Ответ;
КонецФункции
Также модифицирую код сервис-воркера:
'use strict';
(this || self).g = {
"goog.editor.defines.USE_CONTENTEDITABLE_IN_FIREFOX_3": !0
};
const ASSETS = [
"offline.html",
"w3c.css"
];
var a;
a = self;
let cache_name = "pwa_1c";
a.addEventListener("install", event => {
event.waitUntil(
caches
.open(cache_name)
.then(cache => {
return cache.addAll(ASSETS);
})
.catch(err => console.log(err))
);
});
self.addEventListener("activate", function() {
a.clients.claim()
});
self.addEventListener("fetch", event => {
event.respondWith(
fetch(event.request).catch(err =>
caches.open(cache_name).then(cache => cache.match("offline.html"))
)
);
});
При установке (событие install) сервис-воркер поместит в кэш страницу с сообщением о недоступности, а при ошибке получения данных из интернета он вернёт кэшированную страницу, и пользователи увидят сообщение “Сервис временно недоступен. Подробности по телефону +7-123-456-78-90”.
А тем кто дочитал - лови бонус! Я слепил и поделился) Пример расширения