Используем Vesp в MODX

Привет, друзья, давно не виделись!

Прошло уже больше года, как я перестал активно работать с MODX и погрузился в работу с более современными технологиями: Vue, Eloquent и т.д.

Со временем написал и потихоньку развиваю свой фреймворк Vesp, на котором делаю все текущие проекты. Тем не менее, текущий работодатель компания Pixmill не собирается прощаться с MODX, поэтому и мне переодически приходится с ним работать.

В понедельник поступила задача написать функционал для отправки поздравительных открыток постояльцам пансионата: родственник заходит на сайт, грузит фотографию, пишет текст послания, проверяет и сохраняет. А сотрудники заведения печатают открытку и дарят её пожилому человеку.

Что нужно для выполнения?
— Форма на сайте
— Механизам предпросмотра перед отправкой
— Сохранение открыток от юзеров
— Просмотр и управление отправленными открытками в админке

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

В любом случае ясно, что прямо в админке на сайте такое программировать не стоит — лучше написать это как дополнение, взяв за основу modExtra. Да вот беда, там всё на xPDO и ExtJS, которые я совершенно забыл и вспоминать нет желания. Что же делать?

Правильно, нужно перенести свои наработки из Vesp в MODX!

Готовый результат


Сразу показываю, что получилось.


А дальше буду долго рассказывать, что в этом интересного. Вот, кстати, ссылка на исходный код, который Pixmill разрешили открыть всем желающим.

Сборка пакета


Как обычно, у нас есть бэкенд и фронтенд.

С ExtJS мне связываться совершенно не хотелось, поэтому я решил собирать фронт как привык — через Webpack. Со времени разработки App, конечно, воды много утекло, поэтому нужны некоторые пояснения.

В директории _frontend я приготовил сразу 2 фронтенд приложения (и еще 1 бонусное): для админки и публичной части сайта. Они собираются и минифицируются каждое в свой файл, при этом загруженные стили выносятся в отдельные CSS файлы, чтобы их можно было подключать по желанию. Есть 2 точки входа: mgr.js и web.js, из них уже подключается всё нужное.

В целом, обычный webpack.config.js, из интересного разве что возможность запуска dev-сервера для отладки прямо в MODX. При сборке пакета весь фронт собирается в готовые файлы и складывается в директорию assets, которая уже заворачивается в пакет.

Серверная часть лежит в core и работает через Composer. Тут без изысков, просто упаковываем всё, что есть.

Итого исходники, 1 пункт меню, резолвер с запуском миграций, 1 сниппет и 2 чанка (+1 бонусный).

Бэкенд


В целом, PHP код полностью самостоятелен и на modX не завязан почти никак. Все модели и контроллеры наследуются от vesp/core, таблицы создаются миграциями Phinx. Такой подход даёт мне возможность легко переносить код в MODX дополнения и обратно.

В корне компонента есть файл bootstrap.php, который нужно подключать для работы с приложением — он автоматически находит вышестоящий MODX_CORE_PATH, загружает класс modX, если он еще не был загружен и выставляет необходимые переменные окружения для Vesp.

Вот, посмотрите заметку про vesp/core — именно он используется в качестве бэкенда.

Через bootstrap.php функционирует и единственный коннектор, который принимает все запросы от сайта и админки. Не удивляйтесь, что в нём нет проверки прав доступа, она расположена в контроллерах, где это нужно.

Запросы отправляются напрямую на файл, в виде connector.php?q=/end/point/id¶m=value, то есть без использования каких-либо плагинов OnPageNotFound. Но внутри коннектора запрос превращается в более привычный для FastRoute таким способом:
$key = $modx->getOption('request_param_alias', null, 'q');
$_SERVER['REQUEST_URI'] = $_REQUEST[$key];
$_SERVER['QUERY_STRING'] = html_entity_decode($_SERVER['QUERY_STRING']);
$app->run();
Так что все маршруты и контроллеры работают как будто они вообще не в MODX.

Контроллеры есть двух видов: Web для юзеров сайта без проверки прав, и Admin, которые просто требуют контекст mgr. Для этого админским контроллерам нужно использовать специальный трейт.

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

class TestModx extends VespController
{
    protected modX $modx;

    public function __construct(Eloquent $eloquent, modX $modx)
    {
        parent::__construct($eloquent);
        $this->modx = $modx;
    }
}
Ну а модели как обычно наследуются от Eloquent и работают с БД через своё собственное подключение, никак не связанное с xPDO. Настройки для этого подключения, впрочем, берутся из MODX автоматически.

Так что мне не нужно писать дремучую xml схему, что-то по ней генерировать и обновлять таблицы собственным скриптом. Я могу использовать миграции, внешние ключи InnoDB, прописывать связи прямо в моделях и вообще, работать с гораздо более продвинутой и безопасной Eloquent ORM.

Из минусов — отсутствие поддержки моих моделей в других дополнениях, типа pdoTools, но для меня это совсем не критично. Любой желающий может просто подключить bootstrap.php компонента в своём скрипте и использовать модели напрямую.

Фронтенд приложение для админки


В отличие от PHP, целиком использовать готовую JS библиотеку @vesp/frontend здесь не получится. Она работает на NuxtJS, который рассчитан на самостоятельную сборку целого сайта. Он имеет для этого всё необходимое, и никак не предназначен вызываться где-то на HTML странице, наоборот, он сам эти HTML страницы и генерирует.

Так, хоть я и не мог использовать всю библиотеку, я всё же мог импортировать из неё всё нужное, благодаря ES6, поэтому в готовый билд попадёт только нужное.

Смотрим точку входа приложения админки mgr.js — там импортируется VueJS, FontAwesome, нужные компоненты Bootstrap-Vue и некоторые компоненты Vesp.

Еще добавляются Axios для запросов в бэкенд и VueRouter, который позволяет разделить нам приложение на страницы с собственными прямыми адресами. Структура исходников повторяет таковую из NuxtJs, так что и здесь я могу легко портировать код из своих проектов в MODX и обратно.

С лексиконами пришлось повозиться. Обычно все тексты в Vesp я пишу через лексиконы, так как проекты в основном рассчитаны на международную аудиторию, используя Vue-I18n, но здесь я решил его не подключать и дописать обёртки над JS функцией _. Да-да, у MODX действительно есть функция с именем «нижнее подчёркивание» для вывода лексиконов, и я её использую в функциях с теми же названиями, что и в i18n. То есть, просто подменил функции, чтобы компоненты не заметили разницы.

Дальше пришла пора оформления, которое у админки завязано на ExtJS. У меня же используется Bootstrap-Vue, так что стили нужны, очевидно, тоже из Bootstrap. Чтобы ничего не сломать, стили импортируются только в блок с компонентом, через это и модальные окна должны физически располагаться внутри этого элемента, а не цепляться к body, как обычно.

Для регистрации всей этого радости используется обычный контроллер MODX, который выдаёт пустой блок с id="postcard-app-root" и выдаёт для него скрипты и стили. Стили, кстати, можно и не подключать, если вы хотите загрузить что-то своё — настройка для этого есть.

Таким образом мы и получаем в админке полностью независимое Vue приложение, с маршрутами через #/page/subpage, лексиконами, реактивностью и лёгким отзывчивым интерфейсом на Bootstrap. Никакой вёрстки таблицами!





Фронтенд приложение для сайта


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

Поэтому HTML код формы отправки открытки не собран в JS, а поставляется отдельно в 2х видах чанков: обычном и расширенном. Обычный чанк содержит только HTML, а расширенный еще и VueJs компонент, который эту форму оживит при загрузке.
Чанк выводятся сниппетом PostCard и он имеет настройку &extended=`0|1` для указания версии JS билда, который вам нужен.

Если выводить стандартный `tpl.PostCard.form`, то &extended=`0`, то есть JS билд будет содержать Vue компонент в себе, и вы ничего в нём не поменяете, только оформление формы в чанке.

А вот `tpl.PostCard.form-extended` поставляет вам как форму, так и нескомпилированный Vue компонент, который с ней работает. И тогда сниппету PostCard нужно указать &extended=`1`, чтобы он загрузил вам облегчённый JS билд, только с нужными зависимостями для работы.

Таким образом, обладая некоторыми знаениями, вы получаете полный контроль над формой отправки открытки и можете серьёзно там всё поменять.

Учитывая, что это всё выводится в публичной части сайта, я постарался импортировать меньше зависимостей, чтобы не только облегчить конечные билды, но и не пугать людей не знакомых с Bootstrap-Vue непонятными <b-row>, <b-form> и другими непривычными тегами.

А что там про PDF?


Ах, да! Совсем забыл рассказать, что компонент тащит с собой dompdf через composer, чтобы выводить сохранённые открытки в PDF как на фронте, так и в админке. Это обеспечивает одинаковый внешний вид при предпросмотре и печати.

Сама генерация PDF находится в модели PostCard, а это значит, что для работы функции данные должны быть записаны в БД. Как же тогда организовать временный предпросмотр?

А очень просто. При каждой загрузке формы на фронте в ней генерируется случайный uuid, который отправляется на сервер с каждым запросом. Если такого uuid еще нет в БД, то создаётся новая запись, если есть — она обновляется. То есть, при каждом предпросмотре в БД заносятся временные данные, и отличает их от постоянных только наличие uuid.

Таким образом, записи с uuid — это записи от предпросмотра, черновики. Стоит юзеру обновить страницу и отправить форму еще раз — будет уже новый uuid, а старый останется в БД и будет удалён через сутки. В админке, понятное дело, такие записи не выводятся.

Более того, маршрут для запросов обязан включать в себя этот uuid, иначе контроллер даже не запустится. То есть, перебрать открытки по id без авторизации не получится.

Чтобы черновик стал отправленной открыткой, юзер должен нажать кнопку «отправить», и тогда uuid будет удалён. Вот такой механизм, который при желании можно чуть доработать до постоянного хранения черновиков в других дополнениях (просто не менять uuid при каждой загрузке).

Заключение


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

Более того, это даёт мне возможность довольно-таки легко перетаскивать наработки из других проектов в MODX и обратно, с минимальным изменением кода. Я уж молчу про подключение собственных компонентов Vue, на разработку которых ушло много времени.

Буду рад вашим комментариям!
Василий Наумкин
16 января 2022, 14:54
modx.pro
1 112
+20

Комментарии: 32

Николай Савин
16 января 2022, 15:27
+1
Здоров отец! Давно не виделись, всегда рад почитать!
    PG
    PG
    16 января 2022, 15:39
    +1
    Спасибо за статью!
    Но вот большинство ссылок на исходники недоступны.
    Aleksandr Huz
    16 января 2022, 18:41
    +3
    Привет, Василий!

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

    На сколько я понял, это статья для тех кто не хочет связываться с ExtJS но очень нужно сделать на MODX, таких я думаю, что очень мало здесь. Но все же спасибо за статью!

    Да вот беда, там всё на xPDO и ExtJS, которые я совершенно забыл и вспоминать нет желания. Что же делать?
    Отдать задачу разработчикам, которые работают с MODX. Компонент легкий, здесь любой автор должен был его осилить. Так сделал бы я, если не хотел бы возвращаться в прошлое, к старым технологиям. Тем более, что заказчик не против открытого кода, это мог бы быть бесплатным хорошим компонентом.
      Василий Наумкин
      17 января 2022, 08:21
      +5
      Меня смущает один момент, если такие компоненты начнут плодить, то это будет ужас!
      По моим подсчётам, в modstore.pro добавлено 76 новых дополнений за последние 2 года — по 38 в год. Вряд ли кто-то сейчас завалит новинками все репозитории.

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

      Хотя как минимум MoreGallery и ContentBlocks не используют ExtJS, являясь при этом очень популярными платными дополнениями.

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

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

      Вряд ли кто-то бы отказался от качественно переписанного интерфейса miniShop2 и Tickets на VueJS, с нормальной адаптивностью.

      На сколько я понял, это статья для тех кто не хочет связываться с ExtJS но очень нужно сделать на MODX, таких я думаю, что очень мало здесь.
      Скорее о том, как притащить современные технологии. Это для тех, кто не хочет застрять в 2015 году, и я надеюсь, тут таких много.

      Отдать задачу разработчикам, которые работают с MODX.
      Много ты знаешь свободных разработчиков, которые решают подобные задачи через написание дополнения, вместо вызова FormIt с костылями?
      Да и потом, я конечно мог бы всё это сделать на xPDO и ExtJS, но зачем? Какой в этом интерес, в чём развитие? Где веселье-то, в конце концов?

      А так — есть новая заметка на сайте, и заготовка для нового типа компонентов MODX. Может еще и дискуссию в комментариях нагенерируем.
        Aleksandr Huz
        17 января 2022, 11:32
        +1
        Вряд ли кто-то сейчас завалит новинками все репозитории.
        Возможно так и есть.

        Хотя как минимум MoreGallery и ContentBlocks не используют ExtJS, являясь при этом очень популярными платными дополнениями.
        По скринам я вижу, что ContentBlocks использует ExtJS для всех настроек. Но в самом ресурсе используется что-то другое, но это очень похоже на ExtJS.

        Это ж от разработчика зависит. В моей заметке есть и видео, и картинки — тебе кажется это монстром Франкенштейна?
        Согласен, что все зависит от разработчика. Я не говорил, что твоя работа похожа на Франкенштейна, я говорю, что если будет много компонентов с разными стилями, то вот это будет ужасно. Ты сделал так, другой разработчик сделал по другому, и уже хаос.

        При желании можно написать свои, идентичные родным админочным.
        Да, вот это было бы решение, использовать единые стили для всех компонентов на vuejs.

        Вряд ли кто-то бы отказался от качественно переписанного интерфейса miniShop2 и Tickets на VueJS, с нормальной адаптивностью.
        Здесь проблема финансирования. Сейчас код поддерживается почти бесплатно, и врятли все разработчики минишопа знают vue и получиться, что переписать то можно, но кто поддерживать будет? Я вот вижу только один выход — сделать эти компоненты платными, но это уже другая тема.

        Скорее о том, как притащить современные технологии. Это для тех, кто не хочет застрять в 2015 году, и я надеюсь, тут таких много.
        Ну это как в москвиче сделать коробку автомат, лучше уже поменять машину. Хотя любители тюнинга и не такое делают, но тут уже каждому свое.

        Много ты знаешь свободных разработчиков, которые решают подобные задачи через написание дополнения, вместо вызова FormIt с костылями?
        Если нужен компонент, то так и писать, что нужен компонент. Лично я мог бы сделать))

        Где веселье-то, в конце концов?
        Надеюсь вам было интересно читать про изобретение моего очередного велосипеда.
        Да, это было интересно. Для меня это было даже познавательно. Но я рассматриваю, это как эксперимент, что можно сделать как-то по другому.
      Александр Мельник
      17 января 2022, 08:54
      0
      родственник заходит на сайт, грузит фотографию, пишет текст послания, проверяет и сохраняет. А сотрудники заведения печатают открытку и дарят её пожилому человеку.
      Мне кажется это так ужасно. Вместо того чтобы приехать, поздравить, побыть рядом, человек загружает картинку и текст, которую распечатают и подарят совершенно чужие люди. Уж лучше никаких поздравлений, чем такие.
        Stanislavsky
        17 января 2022, 10:53
        0
        На дворе 2022 год, а люди все так и юзают бутстрап
          Василий Наумкин
          17 января 2022, 11:35
          +1
          А что с ним не так?

          Про Tailwind, если что, я в курсе.
            Stanislavsky
            17 января 2022, 12:23
            0
            Главное — говнокод. Во vue есть более элегантные решения. Раз ты знаешь про Tailwind, то есть на основе него сделанный uikit.Почему бы нет?
              Алексей Соин
              17 января 2022, 12:37
              +1
              Главное — говнокод
              Это с какого момента bootstrap стал говнокодом?))))
                Stanislavsky
                17 января 2022, 14:09
                -1
                Хорошо, вот кнопка

                1. Куча комментов, которые разъясняют что к чему. В хорошей реализации комменты считаются излишним элементом
                2. Кнопка проверяет на то, что она ссылка. Кнопка должна быть кнопкой, а ссылка ссылкой (КЭП).
                3. Строка 42. Понятно, что тут стиль рендер функций, но зачем, а главное нах...? Абсолютно бессмысленно, когда можно писать в файлах с расширением vue, где можно из коробки обрабатывать такие вещи.
                  Алексей Соин
                  17 января 2022, 14:21
                  +5
                  Говнокодом назвал бутстрап, а в качестве примера приводишь бутстрапВью)) и первая же причина это наличие комментариев) Ок, я думаю на этом и закончим =)
                    Stanislavsky
                    17 января 2022, 15:53
                    0
                    Ну, как бы Василий использует bootstrap-vue, хз с чего ты решил, что я о чистом бутстрапе. А что тебя не устраивает в первом пункте? Хорошие практики написания кода тебе о чем-то говорят? О том, что без комментариев должно быть понятно о смысле функции или переменной?
                    Сам чего-то докопался до первого пункта и слился.
                      Алексей Соин
                      17 января 2022, 16:04
                      0
                      Какая разница, что использует Василий, твоё сообщение:

                      На дворе 2022 год, а люди все так и юзают бутстрап
                      Я спросил именно про бутстрап, а не про то, что ты оказывается имел ввиду. Если реально про бутстрапВью это всё говорил, я и написал, что вопросов нет. А про то, что хорошая практика это код без комментов — я не согласен, я считаю, что комменты нужны там, где они могут помочь, про комментарии в реализации bootstrap-vue я не вникал, но раз эта либа существует и ей пользуются, значит она упрощает и решает проблемы, а кол-во комментариев и подход к реализации это уже дело третье.
                      Stanislavsky
                      17 января 2022, 16:03
                      0
                      Держи github.com/twbs/bootstrap/blob/main/js/src/base-component.js#L64
                      Что за название getOrCreateInstance? Получить или создать. Должно быть что-то одно.

                      Много ответственности
                      github.com/twbs/bootstrap/blob/main/js/src/modal.js#L93

                      If if if if
                      github.com/twbs/bootstrap/blob/main/js/src/tab.js#L52
                        Алексей Соин
                        17 января 2022, 16:07
                        0
                        Хорошо бутстрап отказался от jQuery, а то ещё бы и он попал в этот список))))
                          Stanislavsky
                          17 января 2022, 16:48
                          0
                          Ну там есть обратная совместимость
                            Алексей Соин
                            17 января 2022, 16:57
                            +1
                            Вот что меня реально в бутстрап 5 бесит, это какого хрена спустя столько лет margin-left стал не ml-, а ms- ???))) Ух как меня первое время триггерило)))
                              Stanislavsky
                              17 января 2022, 17:02
                              0
                              Жиза ) Это прям бесящая тема
                              Евгений Webinmd
                              17 января 2022, 19:20
                              0
                              margin start / margin end
                              Алексей Соин
                              17 января 2022, 20:09
                              0
                              д это то понятно))) прост сам факт «апгрейда», особой ценности же такие изменения не несут) д и если даже само свойство называется margin-left, какой смысл делать его сокращением от margin start…
                              Василий Наумкин
                              18 января 2022, 05:58
                              0
                              getbootstrap.com/docs/5.1/migration/#rtl
                              Horizontal direction specific variables, utilities, and mixins have all been renamed to use logical properties like those found in flexbox layouts—e.g., start and end in lieu of left and right.
                        Артем
                        18 января 2022, 12:11
                        +1
                        Абсолютно бессмысленно, когда можно писать в файлах с расширением vue
                        Это далеко не главная проблема бутстрап-вью. Достаточно того факта, что все исходники написаны на миксинах, где один миксин погоняется другим. Наличие миксинов — это уже говнокод по умолчанию, поэтому тут даже нет смысла рыться в их исходниках и выискивать что-то еще.
                      Василий Наумкин
                      17 января 2022, 16:26
                      +2
                      1. Это исходники, которые потом компилируются без комментариев. На качество кода наличие или отсутствие комментариев ни влияет вообще никак. Читать исходники с комментариями приятнее чем без них.

                      2. И если я укажу у кнопки определённый параметр, она отрендерится именно ссылкой, но с классом btn. Будет выглядеть как кнопка и работать как ссылка. Непонятно, что тебя смущает в таком функционале.



                      3. Я .vue файлы в этом репозитории вообще только в примерах нашёл. Может всё на JS и TS, чтобы код было удобнее расширять?

                      Мне тоже еще далеко не всё в этой либе понятно, но вот расширение компонентов я использую:
                        Stanislavsky
                        17 января 2022, 17:01
                        0
                        1. Вопрос не в оптимизации, а в читабельности кода. Тем более представь, что ты поменял функционал какого-то метода и тебе нужно менять еще и комментарий.
                        2. Смущает то, что нарушен принцип единой ответственности. Логичней же подключать кнопку, как компонент содержащий кнопку без лишних проверок, а не кнопку, которая может быть ссылкой и выполнять функцию ссылки.
                        3. Ну, расширять в перспективе не очень удобно, ИМХО и миграция будет больно проходить на Vue 3. На скрине обычный spread оператор. Ничего не мешает свой конфиг с пропсами замутить и так же сделать.
                    Василий Наумкин
                    17 января 2022, 16:13
                    +1
                    Ну хотя-бы потому, что первый коммит VueTailwind на Github был 20 марта 2019, в этот момент я уже использовал BootstrapVue 2.0 в своём проекте.

                    Tailwind тупо новый, и я не вижу причин срочно на него переезжать из-за того, что BootstrapVue слишком хорошо документирован =)

                    В любом случае, это всё вкусовщина, заметка про другое.
                      Stanislavsky
                      17 января 2022, 16:51
                      0
                      Да, но двигаться все равно придётся куда-либо. Взять тот же самый webpack, он на больших проектах жуть как долго разворачивается локально. Поэтому есть смысл юзать, что-то пошустрее типа vite.
                      iWatchYouFromAfar
                      18 января 2022, 17:24
                      0
                      А в TS есть дтски исходников, где все ушито комментариями и типизацией — тоже говнокод? Комментарии в библиотеке, комментарии в проекте, комментарии в декларативных файлах — все это разные вещи и применяются в абсолютно разных случаях.

                      Я тоже не в восторге от бутстрапа (и не только вьюшного), но в основном из-за награмождения DOM всякими col и row. На работе мы используем vuetify, для админки своих и клиентских проектов я использую tailwind + vite, что позволяет мне молниеносно компилить проект. И т.к. я пишу условную админку (или crm), то на всякие col, row и комментарии в исходниках — мне пофиг.

                      На фронтенд же в проектах я использую собственную библиотеку которая построена на css grid и css vars.

                      Не вижу фундаментальных причине не использовать BootstrapVue в проектах, он знаком многим и хорошо адаптирован ко вьюхе.

                      Смущает то, что нарушен принцип единой ответственности. Логичней же подключать кнопку, как компонент содержащий кнопку без лишних проверок, а не кнопку, которая может быть ссылкой и выполнять функцию ссылки.
                      Не согласен. У меня в проекте например зашито 5 видов кнопки со своими классами модификаторами. Мне придется делать 5 видов компонентов кнопки и еще 5 таких же компонентов только ссылок?
                  Stanislavsky
                  17 января 2022, 10:59
                  0
                  А в целом @vesp-frontend для кого?
                    Василий Наумкин
                    17 января 2022, 11:38
                    +1
                    Ты какой ответ ожидаешь увидеть?

                    Для тех, кто зачем-то 50 раз в раз в неделю скачивает эту библиотеку с npmjs.org
                      Stanislavsky
                      17 января 2022, 12:17
                      0
                      Ну, наверное, для каких пользователей. Статистика на npmjs ничего не говорит )
                    Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
                    32