creaton
Языки
- JavaScript90,8%
- HTML9%
- SCSS0,2%
Расширенные Веб-компоненты

GitHub | GitFlic | GitVerse | NpmJS | Скачать⤵️
Creaton (сокр. Ctn) – это фреймворк JavaScript для быстрого создания Веб-компонентов. Он поддерживает все методы и свойства, которые предоставляются стандартными Веб-компонентами. Кроме этого, фреймворк содержит ряд дополнительных методов и реализует рендеринг Веб-компонентов на стороне сервера.
Ниже представлен пример создания простого компонента:
- Быстрый старт
- Состояние компонента
- Циклы
- Примеси
- Статические свойства
- Специальные методы
- Эмиттер событий
- Маршрутизатор
- Серверный рендеринг
Быстрый старт
Для создания компонентов применяются классы. Классы могут быть как встроенными в основной скрипт, так и импортированы из внешнего модуля. Создайте новый рабочий каталог, например, с названием 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