Éléments personnalisés
Les composants Svelte peuvent aussi être compilés en éléments personnalisés (aussi connus sous le
nom de composants web, ou web components), en utilisant l’option de compilateur customElement: true
. Il est recommandé de définir un nom de balise pour le composant, et ce en utilisant
l’élément <svelte:options>
.
<svelte:options customElement="mon-element" />
<script>
let { name = 'tout le monde' } = $props();
</script>
<h1>Coucou {name} !</h1>
<slot />
Vous pouvez oubliez le nom de balise pour tous les composants internes que vous ne souhaitez pas
exposer, et les utiliser comme des composants normaux. Les composants consommateurs d’un composant
non nommé peuvent toujours le nommer ultérieurement si nécessaire, en utilisant la propriété
statique element
qui contient le constructeur de l’élément personnalisé et qui est disponible
lorsque l’option de compilateur customElement
vaut true
.
import type MonElement = SvelteComponent<Record<string, any>, any, any>
const MonElement: LegacyComponentType
MonElement from './MonElement.svelte';
var customElements: CustomElementRegistry
Defines a new custom element, mapping the given name to the given constructor as an autonomous custom element.
customElements.CustomElementRegistry.define(name: string, constructor: CustomElementConstructor, options?: ElementDefinitionOptions): void
define('mon-element', const MonElement: LegacyComponentType
MonElement.element);
Une fois qu’un élément personnalisé a été défini, il peut être utilisé comme un élément DOM classique :
module document
var document: Document
document.Document.body: HTMLElement
Specifies the beginning and end of the document body.
body.InnerHTML.innerHTML: string
innerHTML = `
<my-element>
<p>Ceci est du contenu slotté</p>
</my-element>
`;
Toutes les props sont exposées comme propriétés de l’élément DOM (tout étant des attributs de lecture/ecriture, lorsque possible).
const module el
const el: Element | null
el = var document: Document
document.ParentNode.querySelector<Element>(selectors: string): Element | null (+4 overloads)
Returns the first element that is a descendant of node that matches selectors.
querySelector('mon-element');
// récupère la valeur courante de la prop `name`
var console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream.
- A global
console
instance configured to write to process.stdout
and
process.stderr
. The global console
can be used without calling require('console')
.
Warning: The global console object’s methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stdout
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()
).
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format()
for more information.
log(module el
const el: Element | null
el.name);
// définit une nouvelle valeur, qui va ainsi modifier le shadow DOM
module el
const el: Element | null
el.name = 'les gens';
Notez que vous avez besoin de lister toutes les propriétés explicitement, en effet écrire let props = $props()
sans déclarer props
dans les options de composant signifie que
Svelte ne peut pas connaître les props à exposer comme propriétés de l’élément DOM.
Cycle de vie du composant
Les éléments personnalisés sont créés à partir de composants Svelte avec une approche d’"emballage”. Cela signifie que le composant Svelte interne n’a pas conscience d’être un élément personnalisé. L’élément personnalisé parent — celui qui “emballe” — a la responsabilité de gérer correctement son cycle de vie.
Lorsqu’un élément personnalisé est créé, le composant Svelte qu’il emballe n’est pas créé tout de
suite. Il ne sera créé que lors du premier tick suivant l’exécution de connectedCallback
. Les
propriétés assignées à l’élément personnalisé avant qu’il ne soit ajouté au DOM sont temporairement
sauvegardées puis définies lors de la création du composant, pour que leurs valeurs ne soient pas
perdues. Ceci ne fonctionne toutefois pas si vous essayez d’exécuter des fonctions exportées depuis
l’élément personnalisé, elles ne seront disponibles qu’après le montage de l’élément. Si vous avez
besoin d’exécuter ce genre de fonctions avant la création du composant, vous pouvez contourner le
problème en utilisant l’option extend
.
Lorsqu’un élément personnalisé écrit en Svelte est créé ou mis à jour, le shadow DOM reflètera la valeur lors du prochain tick, et non immédiatement. Ainsi, les mises à jour peuvent être cumulées, et les modifications de DOM — qui détachent l’élément temporairement (mais de manière synchrone) du DOM — ne découlenet pas sur le démontage du composant interne.
Le composant Svelte interne est détruit lors du premier tick suivant l’exécution de
disconnectedCallback
.
Options de composant
Depuis Svelte 4, lorque vous construisez un élément personnalisé, vous pouvez ajuster certains
aspects en définissant un objet customElement
dans <svelte:options>
. Cet objet peut contenir les
propriétés suivantes :
tag: string
: une propriététag
optionnelle pour le nom de l’élément personnalisé. Si défini, un élément personnalisé portant ce nom sera défini avec le registrecustomElements
du document lors de l’import de ce composantshadow
: une propriété optionnelle qui peut être définie à"none"
pour renoncer à la création de la racine du Shadow DOM. Noter que les styles ne sont alors plus encapsulés, et vous ne pouvez plus utiliser de slotsprops
: une propriété optionnelle pour modifier certains détails et comportements des propriétés de votre composant. Elle offre les paramètres suivants :attribute: string
: Pour mettre à jour une prop d’élément personnalisé, vous avez deux alternatives : soit définir la propriété sur la référence de l’élément personnalisé comme illustré plus haut, ou utiliser un attribut HTML. Dans le deuxième cas, le nom par défaut de l’attribut est le nom de la propriété en minuscules. Vous pouvez modifier cela en assignantattribute: "<desired name>"
reflect: boolean
: Par défaut, les mises à jour de props ne se reflètent pas sur le DOM. Pour activer ce comportement, utilisezreflect: true
type: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object'
: Lorsque la valeur d’un attribut est convertie en valeur de prop et reflétée sur le DOM, la valeur de la prop est supposée être uneString
par défaut. Ce n’est pas toujours le cas. Par exemple, pour un nombre, utiliseztype: "Number"
Vous n’avez pas besoin de lister toutes les propriétés, celles qui ne seront pas listées utiliseront les paramètres par défaut.
extend
: une propriété optionnelle qui attend une fonction en argument. Cette fonction prend en entrée la classe de l’élément personnalisée générée par Svelte et suppose que vous renvoyiez une classe d’élément personnalisé. Ceci est utile lorsque vous avez des constraintes spécifiques sur le cycle de vie de votre élément personnalisé ou souhaitez améliorer la classe pour, par exemple, utiliserElementInternals
pour une meilleure intégration des formulaires HTML.
<svelte:options
customElement={{
tag: 'custom-element',
shadow: 'none',
props: {
name: { reflect: true, type: 'Number', attribute: 'element-index' }
},
extend: (customElementConstructor) => {
// Étend la classe pour lui permettre de participer dans les formulaires HTML
return class extends customElementConstructor {
static formAssociated = true;
constructor() {
super();
this.attachedInternals = this.attachInternals();
}
// Ajoutez la fonction ici, pas plus bas dans le composant, de sorte
// qu'elle soit toujours disponible, pas uniquement lorsque le composant
// Svelte interne est monté
randomIndex() {
this.elementIndex = Math.random();
}
};
}
}}
/>
<script>
let { elementIndex, attachedInternals } = $props();
// ...
function check() {
attachedInternals.checkValidity();
}
</script>
...
Mises en garde et limitations
Les éléments personnalisés sont un moyen utile pour empaqueter des composants et les rendre consommables dans une application n’utilisant pas Svelte, pusiqu’ils fonctionnent tout aussi bien en JS vanille qu’avec la plupart des frameworks. Il y a en revanche quelques différences importantes qu’ils important de connaître :
- Les styles sont encapsulés, plutôt que simplement scopés (sauf si vous avez défini l’option
shadow: "none"
). Ceci implique que les styles non liés au composant (comme ceux que vous pourriez définir dans un fichierglobal.css
) ne s’appliqueront pas à l’élément personnalisé. Cela vaut également pour les styles ayant le modificateur:global(...)
- Au lieu d’être extrait dans un fichier
.css
distinct, les styles sont inlinés dans le composant en tant que chaîne de caractères JavaScript - Les éléments personnalisés ne sont en général pas adaptés pour effectuer du rendu côté serveur, puisque le shadow DOM est invisible tant que les scripts JavaScript n’ont pas été chargés
- Avec Svelte, le contenu slotté est rendu de manière “paresseuse” (lazily). Dans le DOM
classique, il est rendu immédiatement (eagerly). En d’autres termes, le contenu slotté sera créé
même si l’élément
<slot>
du composant est dans un bloc{#if ...}
. De même, inclure un<slot>
dans un bloc{#each ...}
ne va pas conduire à ce que le contenu slotté soit rendu plusieurs fois - La directive
let:
dépréciée n’a pas d’effet, puisque les éléments personnalisés n’ont aucun moyen de passer des données au composant parent remplissant le slot - Des polyfills sont nécessaires pour assurer le support de navigateurs plus anciens
- Vous pouvez utiliser la fonctionnalité Svelte de contexte entre des composants Svelte standards au
sein d’un élément personnalisé, mais vous ne pouvez pas vous servir d’un même contexte pour
différents éléments personnalisés. Autrement dit, vous ne pouvez pas utiliser
setContext
sur un élément personnalisé parent et lire ce contexte avecgetContext
dans un élément personnalisé enfant - Ne déclarez pas de propriétés ou d’attributs commençant pas
on
, puisque leur usage sera interprété comme un gestionnaire d’évènement. Autrement dit, Svelte traite<custom-element oneworld={true}></custom-element>
commecustomElement.addEventListener('eworld', true)
(et non commecustomElement.oneworld = true
)
Modifier cette page sur Github