creaton

0

Описание

Расширенные Веб-компоненты

https://github.com/reacton-js

Языки

  • JavaScript90,8%
  • HTML9%
  • SCSS0,2%
год назад
год назад
год назад
год назад
год назад
год назад
год назад
год назад
год назад
год назад
README.md

EN / RU

Расширенные Веб-компоненты


creaton

GitHub | GitFlic | GitVerse | NpmJS | Скачать⤵️


Creaton (сокр. Ctn) – это фреймворк JavaScript для быстрого создания Веб-компонентов. Он поддерживает все методы и свойства, которые предоставляются стандартными Веб-компонентами. Кроме этого, фреймворк содержит ряд дополнительных методов и реализует рендеринг Веб-компонентов на стороне сервера.


Ниже представлен пример создания простого компонента:


  1. Быстрый старт
  2. Состояние компонента
  3. Циклы
  4. Примеси
  5. Статические свойства
  6. Специальные методы
  7. Эмиттер событий
  8. Маршрутизатор
  9. Серверный рендеринг



Быстрый старт


Для создания компонентов применяются классы. Классы могут быть как встроенными в основной скрипт, так и импортированы из внешнего модуля. Создайте новый рабочий каталог, например, с названием app, и скачайте в этот каталог файл ctn.global.js.

Добавьте в каталог файл index.html со следующим содержимым:

Чтобы гарантировать отсутствие конфликтов имён между стандартными и пользовательскими HTML-элементами, имя компонента должно содержать дефис «-», например, "my-element" и "super-button" – это правильные имена, а "myelement" – нет.

В большинстве примеров этого руководства, префикс будет состоять из одной буквы «w-». т.е. компонент Hello будет называться "w-hello".

При определении класса компонента, его префикс и имя должны начинаться с заглавной буквы. WHello – это правильное название класса, а wHello – нет.

Открыв файл index.html в браузере, на экране отобразится созданное в компоненте Hello сообщение:

Привет, Creaton!


Компоненты можно выносить в отдельные модули. В этом случае, файл компонента Hello выглядел бы как показано ниже:

Для работы с внешними компонентами, вам потребуется любой разработочный сервер, такой, например, как lite-server.

Установить данный сервер можно с помощью команды в терминале:

npm install --global lite-server

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

lite-server

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

Ниже показан пример простого однофайлового компонента:

Однофайловый компонент должен присваивать свой класс переменной exports. Эта переменная будет автоматически объявлена во время создания структуры компонента в системе сборки проекта.

В однофайловых компонентах можно использовать инструкцию import, например:


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

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

Ниже показан пример простого встроенного компонента:

Встроенный компонент должен возвращать свой класс, а содержимое его тега <script> можно рассматривать как функцию. Однако, встроенные компоненты не подходят для рендеринга на стороне сервера и, кроме этого, в них нельзя использовать инструкцию import, но допускается использование выражения import(), например:

Независимо от типа компонента, при использовании в HTML-разметке символа обратная кавычка «`», он должен быть экранирован символом обратного слэша «\», как показано ниже:

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


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

Теперь откройте консоль браузера и последовательно введите команды:

hello.$state.message = 'Веб-компоненты' hello.$state.color = 'green' hello.$update()

Цвет и содержимое заголовка изменятся:

Привет, Веб-компоненты!



Состояние компонента


Каждый компонент может содержать изменяющиеся данные, которые называются состоянием. Состояние можно определить в конструкторе класса компонента:

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


Методы компонента не являются состоянием. Они предназначены для выполнения действий с состоянием компонента и хранятся в прототипе объекта состояния:


Для доступа к объекту состояния, применяется специальное свойство $state. С помощью этого свойства, можно получить или присвоить новое значение состоянию, как показано ниже:

hello.$state.message = 'Веб-компоненты'

Для обновления содержимого компонента на основе нового состояния, применяется специальный метод $update(), например:

hello.$update()

Когда содержимое компонента обновляется, то его старый DOM не удаляется. Вместо этого, создаётся временный виртуальный DOM, на основе возвращаемого содержимого статического метода template() и обновлённого объекта состояния. Это означает, что обработчики, назначенные элементам внутри компонента, сохраняются, поскольку старый элемент не заменяется новым элементом.

В примере ниже, обработчик элемента <h1> будет работать и после обновления компонента. Поскольку обновление изменит только старое значение его атрибута и текстового содержимого:



Циклы


Для вывода циклов, применяются методы map() и join() или метод reduce().

При использовании метода map(), необходимо в конце добавлять метод join() с аргументом пустой строки, чтобы удалить запятые между элементами массива:

При использовании метода reduce(), добавлять в конце метод join() не нужно:


Можно воспользоваться специальной теговой функцией $tag, которая автоматически добавляет метод join() всем массивам и может вызывать методы объекта состояния, когда они указаны в HTML-разметке без скобок "()", например:

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


Для вывода объектов, используется метод Object.keys(), как показано ниже:

или цикл for-in, например:



Примеси


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

В примере ниже, метод printName() из примеси используют компоненты Hello и Goodbye:



Статические свойства


name – это статическое свойство используется, например, когда функции Ctn передаётся анонимный класс, как показано ниже:


mode – это статическое свойство отвечает за добавление компоненту Теневого DOM. Оно может содержать два значения: "open" или "closed". В последнем случае, когда компонент является закрытым, то невозможно получить доступ из консоли к свойствам его объекта состояния, методам выборки элементов и обновления содержимого.

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

Только компоненты имеющие Теневой DOM, могут содержать локальные стили.


extends – это статическое свойство отвечает за создание модифицированных компонентов, т.е. таких, которые встраиваются в стандартные HTML-элементы, например:

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


serializable – это статическое свойство отвечает за сериализацию Теневого DOM компонента с помощью метода getHTML(). По умолчанию имеет значение "false".


template() – этот статический метод возвращает будущее HTML-содержимое компонента:


startConnect() – этот статический метод выполняется в самом начале подключения компонента к документу, до формирования HTML-содержимого компонента и вызова статического метода connected(), но после создания объекта состояния компонента.

В нём можно инициализировать свойства объекта состояния имеющимися значениями:

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

Это единственный статический метод, который можеть быть асинхронным.


connected() – этот статический метод выполняется в самом конце подключения компонента к документу, после формирования HTML-содержимого компонента и вызова статического метода startConnect().

В нём можно добавлять обработчики событий внутренним элементам компонента:

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


disconnected() – этот статический метод выполняется при удалении компонента из документа.

adopted() – этот статический метод выполняется при перемещении компонента в новый документ.

changed() – этот статический метод выполняется при изменении одного из отслеживаемых атрибутов.

attributes – этот статический массив содержит имена отслеживаемых атрибутов, например:


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

В примере ниже, свойства id не существует в объекте состояния компонента. Поэтому оно запрашивается из самого компонента:



Специальные методы


Все специальные методы и свойства начинаются с символа доллара «$», за которым следует название метода или свойства.

$update() – этот специальный метод выполняется для обновления содержимого компонента, после изменения его состояния:

hello.$state.message = 'Веб-компоненты' hello.$update()

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


$() – этот специальный метод выбирает элемент из содержимого компонента по указаному селектору, например, для добавления элементу обработчика события:

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


$$() – этот специальный метод выбирает все элементы из содержимого компонента по указаному селектору, например, для добавления элементам обработчиков событий при переборе их в цикле:

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


$entities() – этот специальный метод обезвреживает строку, содержащую HTML-содержимое полученное из недостоверных источников. По умолчанию, экранируется символ амперсанда «&», символы меньше «<» и больше «>», двойные «"» и одинарные кавычки «'», например:

Кроме вышеперечисленных символов, можно экранировать любые символы, передав во втором и последующих аргументах массив вида: [регулярное выражение, строка замены], например:

Этот метод доступен в качестве свойства функции Ctn, как показано ниже:

или именованного импорта, когда используется модульная версия фреймворка:


$tag() – этот специальный метод является теговой функцией, которая автоматически добавляет метод join() с аргументом пустой строки всем массивам, для удаления запятых между элементами, и может вызывать методы объекта состояния, когда они указаны в HTML-разметке без скобок "()", например:


Специальные методы: $event(), $router() и $render(), будут рассмотрены в следующих разделах. Как и для метода $entities(), для них так же имеются свои именованные импорты:

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


$state – это специальное свойство ссылается на прокси объекта состояния компонента. Это означает, что если необходимое свойство не обнаруживается в объекте состояния, то поиск происходит в самом компоненте.

В примере ниже, свойства id не существует в объекте состояния компонента. Поэтому оно запрашивается из самого компонента:


$host – это специальное свойство ссылается на элемент, который подключает компонент к документу, т.е. на элемент компонента. Это может пригодиться, если свойства с одинаковыми именами имеются и объекте состояния и в компоненте.

Прокси объекта состояния изначально ищет свойство в самом объекте состояния, это значит, что для получения одноимённого свойства из элемента компонента, необходимо использовать специальное свойство $host, как показано ниже:


$shadow – это специальное свойство ссылается на Теневой DOM компонента:

hello.$shadow

Для закрытых компонентов и компонентов без Теневого DOM, это свойство возвращает значение "null".


$data – это специальное свойство ссылается на объект dataset компонента, который используется для доступа к пользовательским атрибутам, например:



Эмиттер событий


Чтобы компоненты могли взаимодействовать между собой и обмениваться друг с другом данными, применяются пользовательские события. Для создания пользовательских событий, используется специальный метод $event(), который доступен в качестве свойства функции Ctn.

Если метод вызывается как конструктор, то он возвращает новый объект эмиттера, который будет генерировать и отслеживать пользовательские события, например:

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

Когда метод $event() вызывается как обычная функция, то в первом аргументе он получает эмиттер, во втором передаётся название пользовательского события, а в третьем аргументе можно передавать любые данные:

Эти данные затем будут доступны в обработчике пользовательского события, в качестве свойства detail объекта события Event, как показано ниже:

В системе сборки webpack, эмиттер можно экспортировать из отдельного модуля, например, из файла Events.js:

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


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

Для отслеживания пользовательских событий, эмиттеру назначаются соответствующие обработчики в компоненте Colors. В последнем обработчике, через свойство detail объекта события Event, свойству состояния присваивается новый массив:



Маршрутизатор


В основе маршрутизатора лежат пользовательские события. Для создания маршрутных событий, используется специальный метод $router(), который доступен в качестве свойства функции Ctn.

Если метод вызывается как конструктор, то он возвращает новый объект эмиттера с переопределённым методом addEventListener(), который будет генерировать и отслеживать маршрутные события, например:

Когда метод $router() вызывается как обычная функция, то в первом аргументе он получает эмиттер, во втором передаётся название маршрутного события, а в третьем аргументе можно передавать любые данные:

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


Передаваемые в последнем аргументе метода $router() пользовательские данные, будут доступны в обработчике маршрутного события, в качестве свойства detail объекта события Event, как показано ниже:

Начальный слэш «/» в названии маршрутного события не является обязательным:

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


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

Чтобы не приходилось дважды использовать символ обратного слэша в обычной строке для экранирования специальных символов регулярных выражений, можно воспользоваться теговой функцией raw() встроенного объекта String, заключив название маршрутного события в шаблонную строку, например:

или так:


Кроме свойства detail, объект события Event имеет дополнительное свойство params, для получения параметров маршрутов, как показано ниже:

Этот обработчик будет выполняться для всех ссылок вида:

тогда catId будет иметь значение 5, а prodId значение 7.

Для поддержки параметров запросов, объект события Event имеет дополнительное свойство search, которое является короткой ссылкой на свойство searchParams встроенного класса URL, например:

Этот обработчик будет выполняться для всех ссылок вида:

тогда catId будет иметь значение 5, а prodId значение 7.

Последнее дополнительное свойство объекта события Event называется url, оно является объектом встроенного класса URL и помогает разобрать запрос на составляющие:


Ниже показан пример создания простого маршрутизатора для трёх компонентов страниц:

Для обработки маршрутов этих страниц, эмиттеру маршрутизатора назначается обработчик с необязательным параметром маршрута в компоненте Content:

Для того, чтобы этот обработчик срабатывал сразу при открытии приложения и подключал соответствующий маршруту компонент страницы, в конце статического метода connected(), инициируется событие для адреса текущего маршрута из свойства href объекта location:

Остальные компоненты страниц загружаются при клике по соответствующей им ссылке в компоненте Menu:

Чтобы при открытии приложения не создавался компонент страницы с неопределённым названием, используется условная проверка на значение свойства page объекта состояния компонента Content:

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

Если бы подключаемый компонент содержал слоты, в которое передавалось бы какое-нибудь HTML-содержимое, то необходимо было бы использовать открывающий и закрывающий теги элемента компонента:

Когда статический метод template() ничего не возвращает, то создаётся компонент с пустым HTML-содержимым.

Передавать HTML-содержимое в слоты можно только для компонентов имеющих Теневой DOM. Это означает, что при обновлении компонента, HTML-содержимое передаваемое с компоненты без Теневого DOM, просто игнорируется и никакие изменения в него не вносятся.

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

В отличие от HTML-содержимого, атрибуты элемента любого компонента обновляются всегда.



Серверный рендеринг


SSR (Server Side Rendering) – это методика разработки, при которой содержимое веб-страницы отрисовывается на сервере, а не в браузере клиента. Для рендеринга содержимого веб-страниц используется метод render(), который доступен в качестве свойства функции Ctn. Этот метод работает как на стороне сервера, так и в браузере клиента.

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

Также этот метод доступен в качестве именованного импорта, при использовании модульной версии фреймворка:

Метод возвращает промис, который разрешается после того, когда HTML-содержимое всех используемых компонентов для текущего маршрута приложения будет доступно:

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


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

В примере ниже, содержимое документа выводится начиная с элемента BODY:

По умолчанию, метод выводит очищенное HTML-содержимое документа, т.е. такое, в которых удалены теги: STYLE, SCRIPT и TEMPLATE. Чтобы метод выводил полное HTML-содержимое, необходимо передать ему объект с параметром clean и значением "false", как показано ниже:

Во всех примерах выше, передаваемое в слот содержимое выводилось без самих тегов SLOT. Чтобы передаваемое содержимое выводилось внутри этих тегов, т.е. в полном соответствии со структурой расположения данного содержимого в компоненте, методу необходимо передать объект с параметром slots и значением "true", например:

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


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

npm i

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

npm start

а для финальной сборки, со всеми минимизациями приложения в режиме продакшен, команда:

npm run build

Это обычный проект с использованием менеджера задач Gulp и сборщика модулей Webpack. Код сервера находится в файле app.js, а сам сервер написан с помощью фреймворка Express.

Файл сервера представляет собой типичное приложение на фреймворке Express:


Чтобы метод render() мог работать на сервере, используется jsdom – это реализация Веб-стандартов на JavaScript.

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

Если в заголовке запроса будет присутствовать любое из этих названий, то сервер будет отдавать отрендеренное HTML-содержимое:

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


Рендеринг осуществляется с помощью функции _$CtnRender_() глобального объекта window, как показано ниже:

Эта функция назначается объекту в файле index.js, который является главным файлом всего приложения:


Рендеринг не поддерживает динамические импорты, вместо них необходимо использовать обычные инструкции импорта и экспорта модулей. Кроме этого, рендеринг не поддерживает глобальный метод fetch(). Вместо него необходимо использовать встроенный объект XMLHttpRequest.

Объект XMLHttpRequest можно обернуть в функцию и затем, вызывать эту функцию вместо того, чтобы каждый раз писать код запроса этого объекта вручную, как показано в файле helpers.js, например:


После установки всех зависимостей приложения из файла package.json, для запуска сервера в обычном режиме, необходимо открыть консоль из каталога /server, и ввести в ней следующую команду:

node app

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

node app test