$Mol: 4 Года Спустя



$mol: 4 года спустя

Здравствуйте, меня зовут Дмитрий Карловский и я.

люблю плевать против ветра.

Вытрись и сплюнь еще раз.

Это мое хобби.

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

Часто бывает, что основная трудность даже не в том, чтобы найти решение, а в том, чтобы объяснить другим, что проблема вообще существует. Знаю, я уже всех запутал, но сегодня мне хотелось поговорить о фреймворке, который я разработал 4 года назад, какой путь он прошел, где находится сейчас и где прокладывает новые пути.

Мы пройдемся по нашим конкурентам, крупным игрокам и даже мне самому.

Так что никто не уйдет обиженным.

Статья, как обычно, длинная.

Мужаться.

Как это все началось

$mol: 4 года спустя

Конец 2015 г.

  • AngularJS 1.4 — самый популярный фреймворк до поддержка компонентов оставалось еще несколько месяцев.

  • Angular 2 альфа — еще пытаются поддерживать JS, вот-вот появится первая бета-версия .

  • React 0.14 — его популярность все еще набирает обороты.

  • Polymer 1.2.3 — рендерит 1 компонент в секунду на мобильных устройствах.

  • VueJS 1.0 — о нем еще мало кто слышал.

Мы в компании пытаемся заниматься разработкой мобильных приложений на заказ.

Но это медленно и дорого, так как вам придется одновременно разрабатывать 4 одинаковых приложения для разных платформ (iOS, Android, Windows, WEB).

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

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

В этих условиях возникает идея создания собственного фреймворка, отвечающего следующим требованиям:

  1. Быстрый запуск приложения даже на мобильном телефоне.

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

  2. Быстрая разработка для всех платформ и размеров экранов одновременно.

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

  3. Высокая степень повторного использования кода.

    Чтобы можно было перемещаться между приложениями не просто кнопками и полями ввода, а целыми разделами.

  4. Высокая степень настраиваемости.

    Чтобы вы могли быстро и легко настроить любой аспект работы компонента под приложение.

Звучит как утопия? Конечно.

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

$mol: 4 года спустя

Слева находится список демонстрационных компонентов.

В середине, по сути, идет демонстрация компонента в определенном состоянии.

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

А справа дерево состояний со всеми зависимостями между ними.

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

Да, аналог сборника рассказов у нас уже был, когда он еще не был мейнстримом.

Да ведь даже компонентный подход тогда был в новинку — все составлялось на страницах.

Обратите внимание, что данное приложение с 13 демо весит всего 24кб.

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

SAPUI5 .

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

И это даст нам существенное конкурентное преимущество в борьбе за тендеры.

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

На этот раз все полностью на машинописном языке.

Принципы дизайна

$mol: 4 года спустя



Искренность

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

Мол, а что делать, если пользователь не успевает никуда кликнуть.

Будет время, особенно на медленном соединении.

Мы не выпендриваемся: если пользователь видит интерфейс, то он уже может с ним взаимодействовать.

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

Если возникает ошибка, мы показываем пользователю место, где она произошла, а не стыдливое «ой, что-то пошло не так».

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

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

Пессимистичный — тормозит, ожидая ответа сервера на каждый чих.

Мы за реалистов — пользователь видит, что есть запрос или произошла ошибка.



Никакого скрытого интереса

Фреймворки часто подкупают новичков простотой создания простого приложения.

Но эта простота часто оказывается обманчивой.

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

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

Поэтому все, что можно, автоматизировано.

И принятые соглашения только способствуют этому.



Кросс-платформенный

Не формальный типа «скрипт не крашится, но потом делаешь сам», а настоящий.

Серверный рендеринг никогда не был проблемой для $mol. Компоненты адекватно адаптируются как к большим (эффективно заполняющим все пространство), так и к маленьким экранам (без рендеринга того, что не помещается на экране).



Прагматизм

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

Вы никогда не сделаете что-то лучше, пока не выйдете из зоны комфорта.

Поэтому мы всегда задаемся вопросом: «Что будет лучше для конечной целиЭ» И часто оказывается, что лучше делать что-то совершенно не так, как вы привыкли.

Что случилось

$mol: 4 года спустя

Конец 2016 г.

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

Находясь в больнице, я пишу огромную статью "$mol: реактивный микромодульный UI-фреймворк" пытаясь раскрыть как можно больше идей, содержащихся в рамках, но я даже не затронул половину из них.

Его встретили, мягко говоря, прохладно.

Кому-то не понравился синтаксис «шаблонов», хотя шаблонами они никогда не были.

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

В течение следующего года мы увеличили функциональность.

Иногда удавалось переманить разработчиков из других проектов.

Но самым полезным оказался Артур, которого мы наняли специально для разработки фреймворка.

Это человек, которого, вероятно, уволили с того места, где он работал.

Причина тому — идеализм и очень выразительная речь.

Несмотря на свои 20 лет, он прекрасно разбирался во фронтенде, лучше многих «старших».

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

Но чем больше я погружал его в проблему, тем чаще он приходил к тем же выводам, что и я.

И вскоре ненависть сменилась искренней любовью.

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

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

Но результат был почти таким же, как исходный синтаксис.

Это связано с тем, что он наиболее подходит для решения возложенных на него задач.

И чтобы это выглядело как-то более привычно - потерять все свои преимущества .

я активен писал статьи об идеях, содержащихся в $mol, а также выступал на конференциях .

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

Это оказался тот самый Артур.

Это маленький мир.

В ИТ много планктона, выполняющего задачи от забора до обеда.

Но мало кто способен на большее, чем просто повторять за всеми одно и то же.

И таких кошек нужно уметь пасти.

Дайте им интересное задание, и они свернут ради вас горы.

Но окружающая бездарность попытается задавить таких людей.

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

Полное дно

$mol: 4 года спустя

Конец 2017 года.

У компании финансовые трудности, люди бегут. Я предлагаю директору кого-то уволить, чтобы поднять зарплаты ключевым разработчикам, но нет. Я снова попадаю в больницу и тут мне предлагают вакансию с зарплатой в два раза выше, от которой я не могу отказаться.

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

Больше некому руководить.

Так наш отдел практически исчез.

А вскоре компанию поглотила другая.

Итак, $mol отправился в свободное плавание.

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

Вокруг $mol сформировалось небольшое сообщество людей, недовольных текущим состоянием мира фронтенда.

Вкратце работа над фреймворком проходила так:

$mol: 4 года спустя

Друзей нет, девушек тоже.

Всю свою жизнь я чувствовал себя одиноким.

И тогда я решаю начать свою социализацию.

(Знаете, как круто общаться в 33 года? Спойлер: всё зря).

Вот и в свободное время я куда-то пропадал.

А на основной работе я ремонтировал Изогнутый дизайн Angular : я приделал к нему гибкие темы с поддержкой браузеров без css-переменных, создал лучший виртуальный скролл (и на тот момент единственный работающий, потому что с новыми версиями Angular компоненты быстро ломались), переписал mobx-angular с поддержкой API приостановки (скоро это будет API представлено также для React , но спустя 2 года так и не выпустили)… Всё это закрытые разработки, которые никогда не попадут в открытый код, к сожалению.

Но не волнуйтесь, все эти функции доступны в $mol и в более удобной форме.

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

Все это не принесло особого удовольствия.

Простое на первый взгляд приложение, которое можно было реализовать за $mol за несколько дней и даже в более качественном виде, мы его пилили на Angular несколько месяцев и результат получился так себе - огромная бандла, медленная работа, медленные тесты, просто тонны кода и, конечно, много кастомных компонентов, так как существующие либо неправильно настроены, либо вообще не работают. Все это было очень удручающе и последней каплей стал момент, когда пара инициативных ребят протолкнула Scrum НгРкс (Это такой редукс на потоках).

Я устал, но сделал пулл-реквест на замену mobx на это чудо и ушел.

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

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

Ну а в свободное время мы Сергей мы сократили $моль.

Например, недавно он героически провел рефакторинг самая быстрая библиотека для рисования графиков - $мол_граф.

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

Это уже было в Симпсонах

$mol: 4 года спустя

То, что совсем недавно появилось в популярных фреймворках, что изначально было в $mol:

Автоматическое отслеживание зависимостей



$mol: 4 года спустя

Это вещь, позволяющая написать простой и лаконичный код, вычисляющий одни состояния на основе других.

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

Это обеспечивает автоматическую динамическую реструктуризацию потоков данных для минимизации ненужных вычислений.

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

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

Но даже там всё равно прикручено к визуальной составляющей.

В третьей версии вроде обещают выделить этот механизм в отдельную абстракцию, наподобие МобХ , что позволит вам использовать полную реактивность во всем приложении, а не только в компонентах.

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

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

$mol изначально был разработан для автоматического отслеживания зависимостей.

И, как и все в $mol, механизм реактивных состояний выделен в отдельный небольшой модуль, ортогональный остальному функционалу.

$mol_mem .



Абстракция от асинхронности



$mol: 4 года спустя

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

В React это называется API ожидания и все еще является экспериментальной функцией.

В третьей версии Vue обещают нечто подобное .

MobX, как ни странно, они боятся адаптировать это для саспенса .

$mol полностью основан на идее саспенса.

В нем вы вряд ли найдете какие-либо промисы, обратные вызовы или async/await. Это связано с тем, что любое реактивное свойство по умолчанию поддерживает возможность приостановить вычисления.

Это, конечно, накладывает требование писать идемпотентный код, но его лаконичность и скорость все компенсируют. Только посмотрите, как легко загрузить данные с сервера благодаря саспенсу:

  
  
  
  
  
   

@ $mol_mem user_name() : string { return $mol_fetch.json( '/profile' ).

name }



Машинопись



$mol: 4 года спустя

Сейчас этого персонажа уже не нужно никому знакомить, но 4 года назад все было иначе — нам нужно было доказать, что за TS — будущее.

Один из первых фреймворков, написанных на TS, Angular 2, изначально даже пытался поддерживать возможность работы с ним как из TS, так и из JS. Но довольно быстро его пользователи поняли, что писать на JS вообще и Angular в частности — это больно.

Vue 3 сейчас переписывается на TS. React и большинство библиотек в его экосистеме написаны на JS, несмотря на попытки MicroSoft встроить JSX в TypeScript. Да, конечно, наборы для них есть, но пишутся они сторонними людьми с опозданием, да еще и пяткой левой ноги.

А API JS-библиотек зачастую таковы, что для них сложно написать адекватную типизацию.

$mol изначально был написан на TS, чтобы максимально использовать его возможности.

Для этого не нужно писать типизацию.

Декларативные описания компонентов переводятся в TS, что практически бесплатно обеспечивает их статическую типизацию.

А с недавних пор стили стали еще и статически типизированными, но об этом позже.

Angular лишь относительно недавно применил аналогичный подход к шаблонам.

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

Например, хотелось бы лучшей интеграции описаний view.tree в TS, чтобы подсказки, рефакторинги и ошибки компилятора работали в исходных исходниках, а не в сгенерированных.



Нулевая конфигурация



$mol: 4 года спустя

В те далекие времена каркасы поставлялись самостоятельно.

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

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

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

Шутка ли, есть даже специалисты, специализирующиеся на настройке WebPack. $mol изначально создавался так, чтобы начать новый проект было проще простого — я создал директорию, в ней был файл с исходным кодом приложения и всё.

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

Однако между проектами могут существовать зависимости.

И все это на уровне исходного кода, без изнурительных «подняли версию, опубликовали в репозитории пакетов, подняли зависимости в зависимых пакетах и так по кругу».

Да, своя «лерна» у нас уже была за год до ее появления.

Эта вещь называется МАМ и вообще говоря, к $mol это не привязано.

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

Короче говоря, МАМ предоставляет всю необходимую инфраструктуру для разработки качественных приложений, без написания кучи однотипных конфигов.

У Parcel похожая идеология: вы подаете html и он все делает сам.

Правда, он очень ограничен своей идеологией быть не более чем необязательным оптимизатором.

Например, он может компилировать машинописный текст только без проверки типа .

Какая польза от такой машинописи - не знаю.

MAM гораздо более гибок.

Легко поддерживать любой тип исходного кода.

А при необходимости вы всегда можете его форкнуть и настроить по своему вкусу.

Это похоже на выброс в общий CLI для популярных фреймворков.

Единственное отличие состоит в том, что вам придется полностью поддерживать извлеченные конфигурации самостоятельно.

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



Перетрясли дерево зависимостей



$mol: 4 года спустя

В отличие от PHP, в JS не было реализовано автоматическое разрешение зависимостей.

Поэтому программисту приходится явно импортировать зависимости.

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

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

Связки начали расти как на дрожжах.

И чтобы справиться с этой проблемой, придумали Tree Shaking, который пытается вырезать все ненужное, но это не всегда удается, так как код нужно писать особым образом.

Поэтому сейчас такие фреймворки, как Angular и Vue, постепенно переписываются, чтобы бандлы можно было хорошо тришейкать.

Что ж, React по-прежнему остается большим и неделимым куском кода.

Если присмотреться, то проблема не в том, как вырезать лишнее, а в том, что лишнее вообще включено в сборку.

Именно поэтому в архитектуре МАМ нет импорта, экспорта, реэкспорта и прочей дребедени.

Ассемблер смотрит не на импорт, а на фактическое использование.

И если она обнаруживается, то включает зависимость в связку.

Включена вся зависимость, но это не имеет большого значения, поскольку все модули в $mol очень маленькие, и каждый из них выполняет только одну функцию.

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

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

Первая ласточка — Svelte, который компилирует своих помощников по мере необходимости.

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



Инверсия контроля



$mol: 4 года спустя

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

Например, вы можете заменить один класс другим.

Или один сервис в другой.

Это все равно плохо.

Из популярных фреймворков только Angular поддерживает IoC посредством внедрения зависимостей.

Но реализация DI там довольно сложная, многословная, медленная и ее просто невыносимо отлаживать.

В $mol IoC реализован довольно элегантно через Ambient Context — это предельно простая вещь, не требующая написания большого количества кода.

Например, посмотрите, как легко работать с контекстами:

namespace $ { export function $my_hello( this : $mol_ambient_context ) { this.console.log( this.$my_name ) } export let $my_name = 'Anonymous' }



namespace $ { $mol_ambient({}).

$my_hello() // logs 'Anonymous' let custom_context = $mol_ambient({ $my_name : 'Jin' , }) custom_context.$my_hello() // logs 'Jin' }



Толерантность к ошибкам



$mol: 4 года спустя

В React 16 появилась новая функция — Границы ошибок .

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

Раньше в любой непредвиденной ситуации ваше приложение превращалось в тыкву, отображающую только белый экран.

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

И по сей день белая тыква на белом фоне является поведением по умолчанию, пока вы вручную не напишете, чтобы отловить ошибки и отобразить заглушку.

Аналогичная ситуация и с другими фреймворками.

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

Остальные по-прежнему показывают белый экран.

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

Надо ли говорить, что такого никогда не происходило в $mol? Если при отрисовке компонента произошла ошибка, именно этот компонент помечается как упавший.

Это происходит автоматически и не требует каких-либо особых движений.

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

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

В других фреймворках вам все равно придется расставлять «вертушки» самостоятельно.

Назад в будущее

$mol: 4 года спустя

Прошло 4 года, а $mol все еще не в курсе.

Он из будущего.

Давайте посмотрим, что еще не появилось в популярных фреймворках, что уже есть в $mol.

Компонентный фокус



$mol: 4 года спустя

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

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

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

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

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

В $mol нет html-верстки — есть только компоненты, которые можно настраивать и комбинировать, собирая любой интерфейс.

Ближе всего на данный момент к $mol находится React Native, где уже нет никаких html-элементов, а есть только базовые компоненты вроде View, Text и т. д. Однако синтаксическая имитация HTML по-прежнему остаётся карго-культом.

Просто сравните два фрагмента кода на TSX и на обычном TS:

function MyApp( { leftColor = 'red', rightColor = 'blue', message = 'Hello World!', } : { leftColor? : string, rightColor? : string, message? : string, } ) { return ( <View> <View style={{ backgroundColor: leftColor }} /> <View style={{ backgroundColor: rightColor }} /> <Text>{message}</Text> </View> ) }



function MyApp( { leftColor = 'red', rightColor = 'blue', message = 'Hello World!', } : { leftColor? : string, rightColor? : string, message? : string, } ) { return ( View({ kids: [ View({ style: { backgroundColor: leftColor } }), View({ style: { backgroundColor: rightColor } }), Text({ kids: [ message ] }), ] }) ) }

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

Следующая волна фреймворков больше не будет нести на шее бремя HTML, а интерфейсы будут собираться либо на TS, либо, как в $mol, на специальных DSL, заточенных для этой цели, таких как view.tree:

$my_app $mol_view sub / <= Left $mol_view style * backgroundColor <= left_color \blue <= Right $mol_view style * backgroundColor <= right_color \red <= Message $mol_view sub / <= message \Hello World!



Настраиваемость



$mol: 4 года спустя

Теги: #Разработка веб-сайтов #JavaScript #typescript #Сборка систем #рендеринг #di #IoC #кастомизация #Обработка ошибок #$mol #компоненты #компоненты #MAM #нулевая конфигурация #тряска дерева #$mol_view #$mol_style #$mol_mem # $mol_atom #лень
Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.