La Suisse adopte une approche fonctionnelle des composants Web en utilisant Preact avec Shadow Dom pour l'encapsulation de style, des éléments personnalisés pour l'interopérabilité et le rendu côté serveur pour l'universalité.
Fil : yarn add switzerland
NPM : npm install switzerland
CDN : https://cdn.jsdelivr.net/npm/switzerland@latest/dist/index.client.js
La Suisse commence éventuellement par le rendu côté serveur avec l'hydratation sur le client grâce au Decarative Shadow Dom - avec nos composants qui semblent très familiers en raison de notre utilisation de Preact.
import { create } from "switzerland" ;
export default create ( "x-countries" , ( ) => {
return (
< ul >
< li > Japan </ li >
< li > Croatia </ li >
< li > Singapore </ li >
</ ul >
) ;
} ) ; Une fois que nous avons défini notre composant x-countries nous sommes en mesure de le rendre sur le serveur et de l'hydrater sur le client en tant qu'élément standard <x-countries /> DOM. Nous pouvons ensuite aller plus loin et permettre à nos pays d'être passés en tant qu'attribut HTML sur le nœud Dom en utilisant <x-countries list="Japan,Croatia,Singapore"> .
import { create , type , use } from "switzerland" ;
type Attrs = {
countries : string [ ] ;
} ;
export default create < Attrs > ( "x-countries" , ( ) => {
const attrs = use . attrs ( {
countries : type . Array ( type . String ) ,
} ) ;
return (
< ul >
{ attrs . countries . map ( ( country ) => (
< li key = { country } > { country } </ li >
) ) }
</ ul >
) ;
} ) ; L'utilisation de notre composant à partir d'un environnement de nœud nous oblige à utiliser la fonction render asynchrone exportée; Nous pouvons spécifier un deuxième paramètre facultatif à la fonction, mais notre composant n'effectue actuellement pas de la récupération de données ou de l'inclusion de support et n'est donc pas nécessaire.
import { render } from "switzerland" ;
app . get ( "/" , async ( _ , response ) => {
const html = await render ( < Countries list = "Japan,Croatia,Singapore" /> ) ;
response . send ( html ) ;
} ) ;Étant donné que nos composants sont des modules autonomes, toute modification de leurs attributs initiera un rendement de l'arborescence du composant - que ces attributs changent de l'intérieur d'un autre composant ou par le biais d'accessoires vanille Dom.
const node = document . querySelector ( "x-countries" ) ;
node . attributes . values = ` ${ node . attributes . values } ,Ukraine,Maldives` ; La Suisse n'a pas besoin d'être compilée, sauf pour le transplage de TypeScript et JSX en option car il utilise des modules ES natifs dans le navigateur et le nœud 16+ sur le serveur. Il y parvient en utilisant node_modules lors du rendu sur le serveur à l'aide d'importations nommées, et dans le navigateur, il utilise des cartes d'importation pour résoudre les imports nommées vers des URL CDN qui offre une mise en cache améliorée. Nous fournissons un utilitaire au serveur pour générer automatiquement les cartes d'importation pour votre application en fonction de ses dépendances.
import fs from "node:fs" ;
import { imports , render } from "switzerland" ;
app . get ( "/" , async ( _ , response ) => {
const html = await render ( < Countries list = "Japan,Croatia,Singapore" /> ) ;
const importMap = await imports ( { path : path . resolve ( "../app/src" ) } ) ;
response . send ( `
<head>
<script type="importmap">
${ importMap }
</script>
</head>
<body>
${ html }
</body>
` ) ;
} ) ; Vous devez donner à la fonction les imports le chemin de base de vos composants Suisse. Il traversera ensuite les fichiers à l'aide de ts-morph qui fournit une arborescence de syntaxe abstraite (AST) de votre code et nous permet de choisir les dépendances externes; Il corresponde ensuite itérativement à chacune de ces dépendances qu'il trouve aux versions installées par le gestionnaire de packages choisi. Nous utilisons le package @jspm/generator pour résoudre les URL des dépendances sur jspm.io par défaut - mais vous pouvez également transmettre l'option provider pour modifier le fournisseur.
Une fois que la carte d'importation est configurée, lors de la création de composants Suisse dans le navigateur, il utilisera ces URL CDN et évitera tout besoin d'emballer les dépendances via un bundler. Vous pouvez vous concentrer uniquement sur la tâche simple de transbriler TypeScript et JSX dans des modules ES natifs en utilisant rien de plus que tsc - bien que si vous souhaitez minimer, vous devrez peut-être ajouter Terser.
{
"include" : [ " src " ],
"compilerOptions" : {
"rootDir" : " ./src " ,
"outDir" : " ./dist " ,
"module" : " esnext " ,
"moduleResolution" : " nodenext " ,
"esModuleInterop" : true ,
"target" : " esnext " ,
"strict" : true ,
"jsx" : " react-jsx " ,
"jsxImportSource" : " preact " ,
"declaration" : true
}
}Étant donné que nous utilisons Preact pour rendre les composants de la Suisse, l'API devrait déjà être familière. Pour plus de facilité d'utilisation, nous réexportons les fonctions de crochet de Preact, mais vous pouvez également les utiliser directement à partir de Preact.
import { create , use } from "switzerland" ;
export default create ( "x-countries" , ( ) => {
const [ countries , setCountries ] = use . state ( [
"Japan" ,
"Croatia" ,
"Singapore" ,
] ) ;
return (
< ul >
{ countries . map ( ( country ) => (
< li key = { country } > { country } </ li >
) ) }
</ ul >
) ;
} ) ; Les styles dans une frontière d'ombre permettent l'encapsulation, ce qui signifie que nous pouvons utiliser des documents CSS réguliers étendus dans l'arbre de notre composant. Nous pouvons attacher nos feuilles de style à notre composant en utilisant un nœud link régulier, bien que la Suisse fournit un utilitaire de node pour StyleSheet et Variables - ce dernier applique des variables personnalisées à votre arbre de composant permettant à CSS d'accéder à ces variables JavaScript. Nous utilisons le hook use.path pour résoudre les médias - documents CSS, images, etc ... - par rapport à notre composant.
import { create , node , use } from "switzerland" ;
export default create ( "x-countries" , ( ) => {
const path = use . path ( import . meta . url ) ;
const [ countries , setCountries ] = use . state ( [
"Japan" ,
"Croatia" ,
"Singapore" ,
] ) ;
return (
< >
< ul >
{ countries . map ( ( country ) => (
< li key = { country } > { country } </ li >
) ) }
</ ul >
< node . Variables
backgroundColour = { countries . length === 0 ? "#8ECCD4" : "#FBDEA3" }
/>
< node . StyleSheet href = { path ( "./styles/default.css" ) } />
< node . StyleSheet
href = { path ( "./styles/mobile.css" ) }
media = "(max-width: 768px)"
/>
< node . StyleSheet href = { path ( "./styles/print.css" ) } media = "print" />
</ >
) ;
} ) ;Nous pouvons alors être assez lâches lors de l'application de ces styles à notre composant sachant que la limite de l'ombre empêchera les styles de fuir - nous utilisons une variable CSS pour appliquer une couleur d'arrière-plan conditionnelle avec une secours.
: host {
box - shadow : 0 0 5px # e8c5b0;
}
ul {
background - col or : var( --background-color , "#E39AC7" );
} Étant donné que la Suisse permet le rendu côté serveur par défaut, un crochet utilitaire de use.loader est fourni pour récupérer les données - bien que vous puissiez choisir d'utiliser tout autre utilitaire de récupération tiers ou un useEffect simple et c'est également très bien. L'utilisation de loader permet de récupérer le côté du serveur de données, puis de prévenir une repection sur le client; Nous y parvenons en rendant nos composants deux fois dans la fonction render asynchrone que nous avons couverte plus tôt, puis en incluant les données sérialisées dans l'arbre.
import { create , use } from "switzerland" ;
export default create ( "x-countries" , ( ) => {
const { data , loading , error } = use . loader (
"x-countries" ,
( ) =>
fetch ( "https://www.example.org/countries" ) . then ( ( response ) =>
response . json ( )
) ,
null
) ;
return loading ? (
< p > Loading… </ p >
) : (
< ul >
{ data . map ( ( country ) => (
< li key = { country } > { country } </ li >
) ) }
</ ul >
) ;
} ) ; Nous fournissons un ID unique à la fonction loader qui devrait identifier la demande individuelle pour éviter les doublons et permettre la réconciliation sur le client. Avec l'argument des dépendances en troisième position, nous pouvons ré-invoquer le côté du loader chaque fois qu'un paramètre change; Dans notre cas, nous ne voulons probablement pas redéfinir les changements, rien ne change, mais si vous êtes en train de récupérer par une liste donnée, nous pourrions nous attendre à ce que la liste actuelle des pays soit fournie en tant que dépendances.
La fourniture du contexte environnemental nécessite une configuration utilisateur côté serveur - la fonction render prend un deuxième paramètre facultatif qui nous permet de spécifier à la fois le répertoire racine sur le serveur Web et éventuellement le domaine sur lequel nous exécutons le serveur.
import App from "./App" ;
import { preload , render } from "switzerland" ;
const vendor = path . resolve ( ".." ) ;
const options = {
path : process . env [ "DOMAIN" ]
? `https:// ${ process . env [ "DOMAIN" ] } /client`
: "http://localhost:3000/client" ,
root : vendor ,
} ;
app . get ( "/" , async ( _ , response ) => {
const html = await render (
< Countries list = "Japan,Croatia,Singapore" / > ,
options
) ;
response . send ( html ) ;
} ) ; Nous utilisons ces options pour résoudre les médias à l'aide du crochet use.path avec import.meta.url par rapport au composant - sur le serveur, nous devons connaître le répertoire racine afin d'y parvenir. Sur le côté client, c'est légèrement plus simple car nous connaissons la racine en fonction du chemin de chaque composants. De même avec l'option path où nous spécifions le domaine sur lequel le serveur Web fonctionne; Nous l'utilisons pour fournir des chemins absolus vers les médias afin que les composants puissent être utilisés dans des applications tierces, mais comme il est facultatif, nous utilisons la root susmentionnée pour spécifier un chemin relatif qui est parfaitement bien lorsque nous n'utilisons nos composants que sur notre propre serveur Web.
En utilisant le crochet use.env nous pouvons accéder à ces paramètres définis ainsi que quelques éléments supplémentaires.
import { create , use } from "switzerland" ;
export default create ( "x-countries" , ( ) => {
const { path , root , node , isServer , isClient } = use . env ( ) ;
return (
< >
{ node && < h1 > Hey { node . nodeName } ! </ h1 > }
< p > Server: { isServer } </ p >
< p > Client: { isClient } </ p >
< ul >
< li > Japan </ li >
< li > Croatia </ li >
< li > Singapore </ li >
</ ul >
</ >
) ;
} ) ; Vous pouvez également étendre les éléments HTML natifs à l'aide de la syntaxe x-hello:button dans la fonction create - il créera un élément personnalisé x-hello qui s'étend du constructeur button vous permettant d'y ajouter votre propre torsion.
import { create , use } from "switzerland" ;
export default create ( "x-hello:button" , ( ) => {
const handleClick = use . callback ( ( ) : void => console . log ( "Hello!" ) , [ ] ) ;
return < button onClick = { handleClick } > Say Hello! </ button > ;
} ) ;