Социальные сети. Фотография Андре Тайссена на Unsplash.
Самый простой способ расширить любой встроенный элемент DOM:
Никаких полифилов не требуется , все современные браузеры просто работают™️
можно без проблем расширить HTML , SVG или любое другое пользовательское пространство имен, например MathML .
элементы можно создавать с нуля или обновлять по требованию для изящной гидратации.
умещается в 313 байт (ядро) или 774 байта с включенными обратными вызовами жизненного цикла пользовательских элементов (ничего нового для изучения)
Этот пример можно протестировать вживую, и он лишь поверхностно показывает, что можно сделать с этим чрезвычайно крошечным, но мощным модулем.
// const createRegistry = require('nonchalance');import createRegistry from 'nonchalance/core';// экспорт по умолчанию дополнительно принимает контекст типа `globalThis` для всех тех сред, которые не имеют собственного DOM./ / Такой реестр может предоставлять полные имена классов плюс ссылку на документ, // который будет использоваться для создания элементов.const {HTML} = createRegistry(); // расширяем любой элемент для создания, или обновление, пользовательские элементы// из реестра, который не имеет ничего общего с глобальным контекстным классом. Пароль расширяет HTML.Input { // унаследует статический тег field = "input" конструктор(...args) {super(...args);this.type = 'пароль'; } // избегаем вредоносных скриптов, легко получаем любой пароль получить значение () {вернуть '********'; } // демонстрационный случай: по-прежнему использовать собственное `value`, когда установлен пароль установить значение (секрет) {super.value = секрет; }}document.body.innerHTML = ` <form> <input type="text" name="user" Placeholder="user"> <input type="password" name="password" Placeholder="password"> <input type="submit"> </form>`;// обновите нужный элемент с помощью нового ClassTypenew Password(document.querySelector('[type="password"]'));// или создайте новый секретный код экземпляра = Object.assign(новый пароль, { имя: 'пройти', значение: Math.random(), заполнитель: 'пароль времени выполнения'});const form = document.querySelector('form');form.insertBefore(secret, form.lastElementChild);
Используя модуль настраиваемых функций, можно обновить элемент любого типа, не столкнувшись с ошибкой Illegal Constructor , которая появляется каждый раз, когда естественный class extends HTMLSomethingElement {}
что вызывает намерение, когда такой класс не определен глобально как запись в Реестр customElements
.
Мало того, что через этот модуль нет ничего общего в глобальном контексте, всякая неуклюжая дополнительная работа по работе любого встроенного расширения совершенно не нужна:
новые или переданные элементы всегда сохраняют корневую цепочку прототипа
никаких дополнительных атрибутов или конфликтующих имен быть не может.
Вдобавок ко всему, поскольку для каждого модуля или проекта может быть создан любой HTML-реестр для совместного использования его компонентами, также возможно передать в такое создание реестра любую поддельную или имитируемую среду, подобную globalThis
, по крайней мере, с полем document
, которое предоставляет createElementNS(namespace, tagName)
и один или несколько классов, которые проект должен протестировать, например HTMLElement
и/или любой другой, необходимый для успеха такого проекта.
Однако, поскольку основной целью этого модуля является DOM , ссылка globalThis
используется как разумная по умолчанию, но это все равно не означает, что что-либо используется совместно с реестрами, созданными с помощью экспорта по умолчанию.
Нет . Принцип работы custom-function
можно резюмировать следующим образом:
# a native <p> protoype chain HTMLParagraphElement -> HTMLElement -> Element -> Node # a <p> passed to new (class CustomP extends HTML.P {}) CustomP -> HTMLParagraphElement -> HTMLElement -> Element -> Node # a <p> passed to class AnotherP extends CustomP {} AnotherP -> CustomP -> HTMLParagraphElement -> HTMLElement -> Element -> Node
В двух словах, создание элемента с помощью new AnotherP
или обновление элемента с помощью new AnotherP(liveParagraph)
просто обновляет цепочку прототипов, не требуя, чтобы элемент когда-либо покидал DOM или менял свою собственную природу, поскольку это сохраняется в цепочке наследования прототипа. .
Резюме: небрежные реестры просто обновляют элементы, не меняя их природу, точно так же, как встроенные встроенные функции расширяют работу под капотом.
Да , либо через экспорт /jsx
, либо через /ref
.
О /jsx См. раздел Что такое экспорт /jsx? раздел.
О /ref См. раздел «Что такое экспорт /ref?» раздел.
Экспорт /ce
автоматически обновляет элементы таким образом, чтобы они были совместимы с методами классов connectedCallback
, disconnectedCallback
и attributeChangedCallback
вместе с их статическим полем observedAttributes
.
Модуль использует доработанную версию уже хорошо работающего модуля пользовательского элемента.
Посмотрите эту живую демонстрацию на Codepen, чтобы иметь представление о том, как это работает.
Нет . Образно говоря, элементы HTML имеют как семантическое значение, так и четко определенную и желаемую полезность, когда-то живую, точно так же, как функция JS навсегда останется функцией JS , даже если Object.setPrototypeOf(() => {}, Number.prototype)
бывает... понимаете или согласны, насколько это неправильно?
Этот модуль не хочет (и, вероятно, не может) защититься от неправильного использования своих функций, поэтому убедитесь, что при обновлении элемента он сохраняет свою собственную цепочку прототипов за сценой, иначе вы в одиночку сражаетесь с DOM . .. что весьма неудобно, если вы спросите меня?
Короче говоря, так же, как customElements.define('my-link', class extends HTMLDivElement {}, {extends: 'a'})
не имеет смысла, этот модуль доверяет своим пользователям, что бессмысленные классы, мы надеемся, будут избегаться.
В настоящее время экспорт по умолчанию /основной для этого модуля указывает на тот же экспорт /core
.
Поскольку этот модуль открывает ящик Пандоры своей простотой и пустым размером кода, и главным образом потому, что он все еще находится за версией 0.
semver, я пытаюсь рассмотреть, что должно быть включено в индекс, и вот некоторые из моих мыслей:
не было бы здорово иметь модуль на базе ESX, который понимает компоненты, определенные таким образом?
разве не было бы здорово иметь прагматическую функцию JSX , которая создает компоненты с помощью этого модуля?
было бы здорово иметь... (ваш заполнитель здесь) ... ?
Да, это было бы здорово, и если я смогу решить, как следует называть экспорт по умолчанию, я готов включить это имя среди других полезных свойств в качестве записи по умолчанию для этого модуля... следите за обновлениями или, пожалуйста, дайте мне мысли и подсказки, как это сделать
А до тех пор, пожалуйста, используйте явный экспорт, чтобы быть уверенным, что будущие обновления не испортят вашу логику, и я извиняюсь, если недавние изменения вызвали у вас проблемы, но я почти уверен, что вы легко сможете понять или понять, что это было к лучшему!
Когда элементы обновляются на расстоянии, возможно, что к ним было прикреплено какое-то свойство, которое не смогло пройти через их средства доступа.
Этот помощник просто гарантирует, что унаследованные свойства будут удалены как ключи собственных элементов, а затем сразу же будут активированы как методы доступа.
импортировать createRegistry из 'nonchalance/ce';импортировать средства доступа из 'nonchalance/accessors';const {HTML} = createRegistry();class WithAccessors расширяет HTML.Div { конструктор(...args) {аксессоры(супер(...args)); } получить значение() {console.log('получить значение', this._value);вернуть это._value; } set value(_value) {this._value = _value;console.log('set value', this._value); }}// собственный элемент div elementconst div = document.createElement('div');div.value = 123;// обновленный новый WithAccessors(div);// повторная проверкаconsole.log(div.value);
Посмотрите это вживую, чтобы проверить больше.
Экспорт /builtin
(248 байт) точно такой же, как /core
за исключением того, что он не использует custom-function
«за сценой», а это означает, что:
невозможно использовать new BuiltIn()
или new BuiltIn(element)
так как это приведет к ошибке, если он еще не зарегистрирован как встроенное расширение customElement
его можно использовать для автоматизации регистрации компонентов, как показано в этой живой демонстрации на CodePen.
Единственное серьезное предостережение относительно этого экспорта заключается в том, что, поскольку он основан на реальных стандартных пользовательских элементах, встроенный полифилл может потребоваться для Safari или WebKit, например:
<!-- скрипт самой верхней страницы только для полифила Safari --><script>self.chrome ||self.netscape ||document.write('<script src="//unpkg.com/@webreflection/custom-elements -builtin"><x2fscript>');</script>
Обратите внимание , что хотя пространства имен HTML
и SVG
по умолчанию разрешены как встроенные расширения, пользовательские элементы не принимают расширения SVG , поэтому при текущем экспорте /builtin
возможны практически только расширения HTML .
Экспорт ./dummy
в основном предназначен для SSR , предоставляя точно такую же утилиту для расширения классов, которые будут содержать только статическое поле tag
.
В сочетании с /tag
можно выполнить 100% SSR с небрежностью и пить воду на расстоянии.
импортировать createRegistry из «https://unpkg.com/nonchalance/dummy»; импортировать createTag из «https://unpkg.com/nonchalance/tag»;const {HTML} = createRegistry();class HelloDiv расширяет HTML.Div { linkedCallback() {console.log('вот и я'); }}// создаем пространство имен, которое можно повторно использовать в гидратконсте nmsp = {HelloDiv}; // создаем тег Transformerconst tag = createTag(nmsp); // вместо этого представляем ответ сервера // Примечание: этот код предназначен только для демонстрации, onlyconsole.log( tag`<!doctype html><script type="module">import createRegistry из 'https://unpkg.com/nonchalance/ce';const {HTML} = createRegistry();const nmsp = {};for (const el of document.querySelectorAll('[data-comp]')) { const {comp} = el.dataset; delete el.dataset.comp; new nmsp[comp; ](el);</script><HelloDiv></HelloDiv>` .присоединиться('') .подрезать() .replace('const nmsp = {};',`const nmsp = { ${[...Object.entries(nmsp)].map( ([key, value]) => `${key}: ${ value}` ).join(',n')} };` ));
Экспорт /jsx
(976 байт) принимает дополнительную опцию createElement
и возвращает функцию jsx
, которую можно использовать как прагму @jsx для преобразования, помимо всего остального, уже работающего по умолчанию в React или Preact , а также классов, расширенных через реестр HTML или SVG. , включая все функции, которые /ce
привносит в эти классы: Пользовательские элементы, такие как жизненный цикл, с некоторыми специями сверху:
классы будут получать в своем конструкторе реквизиты , передаваемые вместе с элементом, позволяя использовать сигналы, другие функции или обрабатывать все, что уже возможно, для обработки компонентами JSX по умолчанию.
когда вызывается конструктор, элемент уже будет заполнен своими дочерними элементами, что позволяет избежать возможных махинаций, известных со стандартными пользовательскими элементами, когда классы определяются/регистрируются до анализа документа.
аналогично расширению /builtin
, это невозможно для new Component(props)
но всегда возможно <Component {...props} />
.
Посмотрите, как это используется на практике с React в прямом эфире на CodePen.
DOM — это DOM , независимо от того, сколько косвенных указаний между ними. Ваш DX может различаться в зависимости от функций фреймворка, но если вам нужен React , есть небольшой, но элегантный и основанный на ref
способ продвигать обычные узлы JSX с помощью nonchalance/core или nonchalance/ce :
ссылка на импорт из 'nonchalance/ref';// указывает, что компонент будет передан как ссылка// Примечание: это всего лишь легкий прокси, который обеспечивает целостность класса// независимо от его использования в wildconst. Component = referenced(class расширяет HTML. Див { конструктор(...args) {super(...args);this.addEventListener('click', console.log); }});ReactDOM.render( <div ref={Component}>нажмите на меня</div>, документ.тело);
Утилита ref
также может использоваться в качестве декоратора, не затрагивая никаких функций обычных небрежных классов. Кроме того, каждый элемент обновляется только один раз, поэтому можно безопасно добавлять прослушиватели или логику в конструктор.
Посмотрите эту демонстрацию в прямом эфире на Codepen, чтобы поиграться с ней.
Экспорт /selector
(796 байт) позволяет определять в реальном времени любой селектор CSS, так что связанный класс автоматически обновляет любой элемент, соответствующий этому селектору.
Обратите внимание, что такой селектор должен быть максимально уникальным, иначе могут случиться сюрпризы. Используйте определенные классы или атрибуты data-
, чтобы лучше описать ваш селектор.
import createRegistry from 'nonchalance/core';import { define } from 'nonchalance/selector';const { HTML } = createRegistry();const Special = define('[data-comp="special"]', класс расширяет HTML. Див { constructor(...args) {super(...args);this.textContent = 'Я особенный!'; }});// будет содержать "Я особенный!" text Once livedocument.body.innerHTML = `<div data-comp="special"></div>`;
Экспорт ./tag
(188 байт) позволяет преобразовывать шаблоны удобным для гидратации способом.
Его можно использовать в качестве промежуточного значения после полнофункциональных литеральных тегов шаблона, и гидратация может произойти, как только эти элементы попадут в DOM .
импортировать createRegistry из 'nonchalance/ce'; импортировать createTag из 'nonchalance/tag';const {HTML} = createRegistry();class HelloDiv расширяет HTML.Div { linkedCallback() {console.log('вот и я'); }}// создаем пространство имен, которое можно повторно использовать в гидратконсте nmsp = {HelloDiv};// создаем тег Transformerconst tag = createTag(nmsp);// быстрый и грязный demodocument.body.innerHTML = tag`<HelloDiv />`.join( '');// пример гидратацииfor (const el of document.querySelectorAll('[data-comp]')) { const {comp} = el.dataset; удалить el.dataset.comp; // обновляем элемент один раз новый nmsp[comp](el);}
Посмотрите это в прямом эфире на CodePen.
Да . Экспорт /core
и /ce
позволяет по умолчанию создавать реестры HTML и SVG :
импортировать createRegistry из 'nonchalance/core';const {HTML, SVG} = createRegistry();class Circle расширяет SVG.Circle { конструктор (опции) {Object .assign(super(), options).setAttribute('fill', 'gold'); } set cx(value) { this.setAttribute('cx', value) } set cy(value) { this.setAttribute('cy', value) } set r(value) { this.setAttribute('r', value) }}document.querySelector('svg').append( новый круг({cx: 100, cy: 100, r: 50}));
Посмотрите это в прямом эфире на codepen.
Также можно передать любое пространство имен в createRegistry(options)
, используя в качестве примера {MathML: "http://www.w3.org/1998/Math/MathML"}
.
Разрешено любое пространство имен, имеющее значение для document.createElementNS
, нет ограничений на то, какие элементы DOM мы можем обновлять.
Раньше у меня был очень длинный ответ на этот вопрос, но суть в том, что этот модуль использует стандарты , предоставленные W3C , WHATWG или ECMAScript , и для его работы везде требуется менее 1 КБ.
Это не полифил, это утилита, которая поможет вам писать компоненты в мире JS и не беспокоиться о том, что они будут конфликтовать, требовать инструментов или быть непереносимыми в любом целевом проекте, который вам нравится/нужен/предпочтителен.
Короче говоря, если вы согласны добавить менее 1 КБ для доставки универсальных компонентов как для внешнего, так и для внутреннего интерфейса, вы попали в правильный модуль?
Нет ничего более раскрепощающего, чем быть нерадивым ребенком, который играет в грязи против всех мыслителей « не делай этого! ».
Этот модуль каким-то образом отражает это чувство через свободу, которую предлагают современные функции JS, показывая элегантную, портативную и сверхлегкую альтернативу постоянно растущей сложности, предлагаемой вместо этого поставщиками браузеров и современными спецификациями, - все необходимое, чтобы заставить разработчиков обойти возможность простого расширения встроенные функции и сохраняют как простоту, так и большую доступность, которой славится Интернет.