Photo des réseaux sociaux par André Taissin sur Unsplash
Le moyen le plus simple d'augmenter n'importe quel élément intégré du DOM :
Aucun polyfills nécessaire , tous les navigateurs modernes fonctionnent tout simplement™️
il est possible d'étendre HTML , SVG ou tout autre espace de noms personnalisé, tel que MathML , sans problème
les éléments peuvent être créés à partir de zéro ou mis à niveau à la demande pour une hydratation gracieuse
s'intègre dans 313 octets (noyau) ou 774 octets avec les rappels du cycle de vie des éléments personnalisés inclus (rien de nouveau à apprendre)
Cet exemple peut être testé en direct et il ne fait qu'effleurer la surface de ce qu'il est possible de faire avec ce module extrêmement petit, mais puissant.
// const createRegistry = require('nonchalance');import createRegistry from 'nonchalance/core';// l'exportation par défaut accepte éventuellement un contexte de type `globalThis` // pour tous les environnements qui n'ont pas de DOM natif./ / Un tel registre peut exposer des noms de classes qualifiés ainsi qu'une référence de `document` // qui sera utilisée pour créer des éléments.const {HTML} = createRegistry();// étendre n'importe quel élément pour créer ou mettre à niveau des éléments personnalisés elements// d'un registre qui ne partage rien avec la classe de contexte globale. Le mot de passe étend HTML.Input { // héritera d'un champ de balise statique = "input" constructeur(...args) {super(...args);this.type = 'mot de passe'; } // évite les scripts malveillants en récupérant facilement n'importe quel mot de passe obtenir la valeur() {return '********'; } // Cas de démonstration : utilisez toujours la `valeur` native lorsque le mot de passe est défini définir la valeur (secret) {super.value = secret ; }}document.body.innerHTML = ` <form> <input type="text" name="user" placeholder="user"> <input type="password" name="password" placeholder="password"> <input type="submit"> </form>`;// mettez à niveau l'élément souhaité via un nouveau ClassTypenew Password(document.querySelector('[type="password"]'));// ou créez un nouveau secret d'instanceconst = Object.assign (nouveau mot de passe, { nom : 'passer', valeur : Math.random(), espace réservé : 'mot de passe d'exécution'});const form = document.querySelector('form');form.insertBefore(secret, form.lastElementChild);
En utilisant le module de fonction personnalisée, il est possible de mettre à niveau n'importe quel type d'élément sans jamais être confronté à l'erreur Illegal Constructor qui apparaît chaque fois qu'une class extends HTMLSomethingElement {}
, lorsqu'une telle classe n'est pas définie globalement en tant qu'entrée dans le Registre customElements
.
Non seulement il n'y a rien de partagé globalement via ce module sur le contexte global, mais tout travail supplémentaire gênant pour avoir une extension intégrée est totalement inutile :
les éléments nouveaux ou passés préservent toujours leur chaîne racine prototype
aucun attribut supplémentaire ni aucun nom conflictuel ne peut se produire
De plus, étant donné que n'importe quel registre HTML peut être créé pour chaque module ou projet afin de le partager entre ses composants, il est également possible de transmettre à une telle création de registre tout environnement faux ou simulé de type globalThis
, avec au moins un champ document
qui expose un createElementNS(namespace, tagName)
et une ou plusieurs classes que le projet est censé tester, telles que HTMLElement
et/ou tout autre élément nécessaire à la réussite d'un tel projet.
Cependant, puisque la cible principale de ce module est le DOM , la référence globalThis
est utilisée comme valeur par défaut raisonnable, mais cela ne signifie toujours pas que quoi que ce soit soit partagé autour des registres créés via l'exportation par défaut.
Non . Le fonctionnement custom-function
peut être résumé ainsi :
# 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
En quelques mots, créer un élément via new AnotherP
ou mettre à niveau un élément via new AnotherP(liveParagraph)
met simplement à jour la chaîne de prototypes, sans exiger que l'élément quitte le DOM ou change sa nature native, car celle-ci est préservée tout au long de la chaîne d'héritage prototypique. .
Résumé : les registres de nonchalance mettent simplement à niveau les éléments sans changer leur nature, exactement de la même manière que le module natif étend le travail sous le capot.
Oui , soit via l'export /jsx
, soit via celui /ref
.
À propos de /jsx Veuillez consulter Qu'est-ce que l'exportation /jsx ? section.
À propos de /ref Veuillez consulter Qu'est-ce que l'exportation /ref ? section.
L'exportation /ce
met automatiquement à niveau les éléments d'une manière compatible avec les connectedCallback
, disconnectedCallback
attributeChangedCallback
des classes, ainsi que leur champ statique observedAttributes
.
Le module utilise une version affinée du module as-custom-element qui fonctionne déjà bien.
Regardez cette démo en direct sur codepen pour avoir une idée de comment cela fonctionne.
Non . Métaphoriquement parlant, les éléments HTML ont à la fois une signification sémantique et une utilité bien définie et souhaitée une fois en ligne, de la même manière qu'une fonction JS sera, pour toujours, une fonction JS , même si Object.setPrototypeOf(() => {}, Number.prototype)
se produit... pouvez-vous voir, ou être d'accord, à quel point est-ce faux ?
Ce module ne veut pas (et ne peut probablement pas non plus) se prémunir contre une mauvaise utilisation de ses fonctionnalités, alors assurez-vous que chaque fois qu'un élément est mis à niveau, il préserve sa chaîne de prototypes native en coulisse, ou vous êtes seul à lutter contre le DOM . .. ce qui est assez gênant, à mon avis ?
En bref, de la même manière que customElements.define('my-link', class extends HTMLDivElement {}, {extends: 'a'})
n'a aucun sens, ce module est convaincu que les classes non-sens de ses utilisateurs seront, espérons-le, évitées.
Actuellement, l'exportation par défaut/principale pour ce module pointe vers la même exportation /core
.
Parce que ce module ouvre une boîte de Pandore avec sa simplicité et la taille du code vaporware, et surtout parce qu'il est toujours derrière une version 0.
semver, j'essaie de réfléchir à ce qui devrait être inclus dans l'index, et voici quelques-unes de mes réflexions :
ne serait-il pas cool d'avoir un module basé sur ESX qui comprend les composants définis de cette façon ?
ne serait-il pas cool d'avoir une fonction pragma JSX qui crée des composants via ce module ?
ne serait-ce pas cool d'avoir ... (votre espace réservé ici) ... ?
Oui, ce serait cool, et si je peux me décider sur la façon dont l'exportation par défaut doit être nommée, je suis prêt à apporter ce nom, entre autres, comme entrée par défaut pour ce module... restez à l'écoute ou s'il vous plaît donnez-moi réflexions et conseils sur la façon de procéder
D'ici là, veuillez utiliser les exportations explicites pour être sûr que les futures mises à jour ne perturberont pas votre logique, et je m'excuse si les modifications récentes vous ont causé des problèmes, mais je suis presque sûr que vous pouvez facilement comprendre ou comprendre que c'était pour de bon !
Lorsque des éléments sont améliorés à distance, il est possible qu'ils aient des propriétés attachées qui n'ont pas eu la chance de passer par leurs accesseurs.
Cet assistant garantit simplement que les propriétés héritées sont supprimées en tant que clés d'élément propres pour ensuite être déclenchées en tant qu'accesseurs juste après.
importer createRegistry depuis 'nonchalance/ce'; importer des accesseurs depuis 'nonchalance/accessors'; const {HTML} = createRegistry();class WithAccessors extends HTML.Div { constructeur(...args) {accesseurs(super(...args)); } get value() {console.log('get value', this._value);return this._value; } set value(_value) {this._value = _value;console.log('set value', this._value); }}// elementconst div natif div = document.createElement('div');div.value = 123;// Upgradednew WithAccessors(div);// re-checkconsole.log(div.value);
Voyez-le en direct pour en tester davantage.
L'exportation /builtin
(248 octets) est exactement comme /core
sauf qu'elle n'utilise pas custom-function
en arrière-plan, ce qui signifie que :
il n'est pas possible d'utiliser new BuiltIn()
ou new BuiltIn(element)
car cela générerait une erreur, à moins qu'il ne soit déjà enregistré en tant qu'extension intégrée customElement
il peut être utilisé pour automatiser l'enregistrement des composants, comme le montre cette démo en direct sur CodePen
Le seul inconvénient majeur concernant cette exportation est que, parce qu'elle est basée sur de véritables éléments personnalisés standard, le polyfill intégré peut être nécessaire pour Safari ou WebKit, par exemple :
<!-- script le plus haut de la page pour Safari uniquement polyfill --><script>self.chrome ||self.netscape ||document.write('<script src="//unpkg.com/@webreflection/custom-elements -builtin"><x2fscript>');</script>
Veuillez noter que même si les espaces de noms HTML
et SVG
sont autorisés par défaut en tant qu'extensions intégrées, les éléments personnalisés n'acceptent pas les extensions SVG , de sorte que pratiquement seules les extensions HTML sont possibles avec l'exportation /builtin
actuelle.
L'exportation ./dummy
est principalement destinée à SSR , fournissant exactement le même utilitaire pour étendre les classes qui ne porteront qu'un champ tag
statique.
Combiné avec le /tag
il est possible de faire du 100% SSR avec nonchalance et de s'hydrater à distance.
importer createRegistry depuis 'https://unpkg.com/nonchalance/dummy'; importer createTag depuis 'https://unpkg.com/nonchalance/tag';const {HTML} = createRegistry();class HelloDiv étend HTML.Div { connectedCallback() {console.log('me voici'); }}// créez un espace de noms réutilisable pour hydrateconst nmsp = {HelloDiv};// créez une balise transformerconst tag = createTag(nmsp);// imaginez une réponse du serveur à la place// remarque : ce code est destiné à la démonstration onlyconsole.log( tag`<!doctype html><script type="module">import createRegistry from 'https://unpkg.com/nonchalance/ce';const {HTML} = createRegistry();const nmsp = {};for (const el of document.querySelectorAll('[data-comp]')) { const {comp} = el.dataset; delete new nmsp[comp] (el);}</script><HelloDiv></HelloDiv>` .rejoindre('') .garniture() .replace('const nmsp = {};',`const nmsp = { ${[...Object.entries(nmsp)].map( ([clé, valeur]) => `${clé} : ${ valeur}` ).join(',n')} };` ));
L'export /jsx
(976 octets) accepte une option createElement
supplémentaire et renvoie une fonction jsx
qui peut être utilisée comme pragma @jsx pour transformer, entre tout ce qui fonctionne déjà par défaut dans React ou Preact , également les classes étendues via le registre HTML ou SVG , y compris toutes les fonctionnalités que /ce
apporte à ces classes : Éléments personnalisés comme le cycle de vie avec un peu de piquant en plus :
les classes recevront dans leur constructeur les accessoires transmis le long de l'élément, activant les signaux, d'autres fonctions ou gérant tout ce qui est déjà possible pour être géré par les composants JSX par défaut.
lorsque le constructeur est appelé, l'élément serait déjà rempli avec ses enfants, évitant ainsi les possibles manigances connues avec les éléments personnalisés standard lorsque les classes sont définies/enregistrées avant l'analyse du document.
similaire à /builtin
extend cependant, il n'est pas possible de new Component(props)
mais il est toujours possible de <Component {...props} />
.
Voyez-le utilisé en pratique avec React live sur CodePen.
Le DOM est le DOM , quel que soit le nombre d'indirections intermédiaires. Votre DX peut varier en fonction des fonctionnalités du framework, mais si React est ce que vous recherchez, il existe un moyen petit mais élégant et basé sur ref
de promouvoir des nœuds JSX classiques avec nonchalance/core ou nonchalance/ce :
import référencé depuis 'nonchalance/ref';// indique que le composant sera passé comme référence// Remarque : il s'agit simplement d'un proxy léger qui accorde l'intégrité de la classe// quelle que soit son utilisation dans le composant wildconst = référencé (la classe étend HTML. Division { constructor(...args) {super(...args);this.addEventListener('click', console.log); }});ReactDOM.render( <div ref={Component}>cliquez moi</div>, document.body);
L'utilitaire ref
pourrait également être utilisé comme décorateur et sans affecter aucune fonctionnalité des classes de nonchalance régulières. De plus, chaque élément n'est mis à niveau qu'une seule fois afin qu'il soit possible d'ajouter en toute sécurité des écouteurs ou une logique dans le constructeur.
Regardez cette démo en direct sur codepen pour jouer avec.
L'exportation /selector
(796 octets) permet la définition en direct de n'importe quel sélecteur CSS afin que la classe associée mette automatiquement à niveau tout élément correspondant à ce sélecteur.
Veuillez noter que ce sélecteur doit être aussi unique que possible, sinon des surprises pourraient survenir. Utilisez des classes ou des attributs data-
spécifiques pour décrire au mieux votre sélecteur.
import createRegistry from 'nonchalance/core';import { definition } from 'nonchalance/selector';const { HTML } = createRegistry();const Special = definition('[data-comp="special"]', la classe étend le HTML. Division { constructor(...args) {super(...args);this.textContent = 'Je suis spécial !'; }});// contiendra "Je suis spécial!" texte une fois livedocument.body.innerHTML = `<div data-comp="special"></div>` ;
L'export ./tag
(188 octets) permet la transformation des modèles de manière conviviale.
Il peut être utilisé comme valeur intermédiaire derrière des balises littérales de modèle complètes et l'hydratation peut se produire une fois que ces éléments atterrissent sur le DOM .
importer createRegistry depuis 'nonchalance/ce'; importer createTag depuis 'nonchalance/tag';const {HTML} = createRegistry();class HelloDiv étend HTML.Div { connectedCallback() {console.log('me voici'); }}// créer un espace de noms réutilisable pour hydrateconst nmsp = {HelloDiv};// créer un tag transformerconst tag = createTag(nmsp);// demodocument.body.innerHTML = tag`<HelloDiv />`.join( '');// exemple d'hydratation pour (const el de document.querySelectorAll('[data-comp]')) { const {comp} = el.dataset ; supprimer el.dataset.comp ; // met à jour l'élément une fois nouveau nmsp[comp](el);}
Voyez-le en direct sur CodePen.
Oui . Les exports /core
et /ce
permettent de créer, par défaut, des registres HTML et SVG :
importer createRegistry depuis 'nonchalance/core'; const {HTML, SVG} = createRegistry();class Circle extends SVG.Circle { constructor(options) {Object .assign(super(), options) .setAttribute('fill', 'gold'); } set cx(valeur) { this.setAttribute('cx', valeur) } set cy(valeur) { this.setAttribute('cy', valeur) } set r(value) { this.setAttribute('r', value) }}document.querySelector('svg').append( nouveau Cercle({cx : 100, cy : 100, r : 50}));
A voir en direct sur codepen.
Il est également possible de transmettre n'importe quel espace de noms au createRegistry(options)
, en utilisant {MathML: "http://www.w3.org/1998/Math/MathML"}
comme exemple.
Tout espace de noms ayant une signification pour document.createElementNS
est autorisé, il n'y a aucune limitation quant au type d'éléments DOM que nous pouvons mettre à niveau.
J'ai déjà eu une très longue réponse à cette question, mais le résumé est que ce module utilise les standards fournis par W3C , WHATWG ou ECMAScript , et qu'il nécessite moins de 1 Ko pour fonctionner partout.
Il ne s'agit pas de polyfill, c'est un utilitaire pour vous aider à écrire des composants dans le monde JS sans craindre qu'ils n'entrent en conflit, nécessitent des outils ou ne soient portables sur n'importe quel projet cible que vous aimez/besoin/préférez.
En bref, si vous êtes prêt à ajouter moins de 1 Ko pour fournir des composants universels pour le monde Front End et Back End, vous avez frappé le bon module ?
Il n'y a rien de plus libérateur que d'être un enfant insouciant qui joue dans la boue contre tous les penseurs du « ne fais pas ça ! ».
Ce module représente en quelque sorte ce sentiment à travers la liberté qu'offrent les fonctionnalités JS modernes, montrant une alternative élégante, portable et ultra légère à la complexité toujours croissante offerte par les fournisseurs de navigateurs et les spécifications modernes, toutes nécessaires pour forcer les développeurs à contourner la possibilité d'étendre simplement intégrés et préservent à la fois la simplicité et la grande accessibilité qui font la renommée du Web.