Suiza adopta un enfoque funcional para los componentes web que usan Preact con Shadow DOM para encapsulación de estilo, elementos personalizados para la interoperabilidad y la representación del lado del servidor para la universalidad.
hilo : yarn add switzerland
NPM : npm install switzerland
CDN : https://cdn.jsdelivr.net/npm/switzerland@latest/dist/index.client.js
Suiza opcionalmente comienza con la representación del lado del servidor con hidratación en el cliente gracias a Declarative Shadow DOM, con nuestros componentes muy familiares debido a nuestro uso de PREACT.
import { create } from "switzerland" ;
export default create ( "x-countries" , ( ) => {
return (
< ul >
< li > Japan </ li >
< li > Croatia </ li >
< li > Singapore </ li >
</ ul >
) ;
} ) ; Una vez que hayamos definido nuestro componente x-countries , podemos representarlo en el servidor e hidratarlo en el cliente como un elemento DOM estándar <x-countries /> . Luego podemos dar un paso más allá y permitir que nuestros países sean aprobados como un atributo HTML en el nodo DOM utilizando <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 >
) ;
} ) ; El uso de nuestro componente desde dentro de un entorno de nodo requiere que usemos la función render asíncrono exportado; Podemos especificar un segundo parámetro opcional a la función, sin embargo, nuestro componente actualmente no realiza la recuperación de datos o la inclusión de medios y, por lo tanto, es innecesario.
import { render } from "switzerland" ;
app . get ( "/" , async ( _ , response ) => {
const html = await render ( < Countries list = "Japan,Croatia,Singapore" /> ) ;
response . send ( html ) ;
} ) ;Como nuestros componentes son módulos autónomos, cualquier cambio en sus atributos iniciará un reiniciado del árbol del componente, independientemente de si esos atributos cambian desde dentro de otro componente o a través de accesorios DOM de vainilla.
const node = document . querySelector ( "x-countries" ) ;
node . attributes . values = ` ${ node . attributes . values } ,Ukraine,Maldives` ; Suiza no necesita ser compilada, excepto por la transposición opcional de TypeScript y JSX porque utiliza módulos ES nativos en el navegador y el nodo 16+ en el servidor. Logra esto utilizando node_modules al renderizar en el servidor utilizando importaciones con nombre, y en el navegador utiliza mapas de importación para resolver las importaciones nombradas a URL CDN que ofrece almacenamiento en caché mejorado. Proporcionamos una utilidad para que el servidor genere automáticamente los mapas de importación para su aplicación en función de sus dependencias.
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>
` ) ;
} ) ; Debe dar a la función imports la ruta base de sus componentes de Suiza. Luego atravesará los archivos con ts-morph que proporciona un árbol de sintaxis abstracto (AST) de su código y nos permitirá elegir las dependencias externas; Luego coincide iterativamente en cada una de esas dependencias que encuentra con las versiones instaladas por su administrador de paquetes elegido. Usamos el paquete @jspm/generator para resolver las dependencias a las URL de jspm.io de forma predeterminada, sin embargo, también puede aprobar la opción provider para cambiar el proveedor.
Una vez que tenga el mapa de importación configurado, al hacer que los componentes de Suiza en el navegador usen esas URL CDN y evitarán cualquier necesidad de empacar dependencias a través de un Bundler. Puede concentrarse exclusivamente en la tarea simple de transpionar TypeScript y JSX en módulos ES nativos que no usan más que tsc , aunque si desea minificar, es posible que necesite agregar 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
}
}Dado que usamos PREACT para representar los componentes de Suiza, la API ya debería ser familiar. Para facilitar el uso, reexportamos las funciones de gancho de Preact, pero también puede usarlas directamente 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 >
) ;
} ) ; Los estilos dentro de un límite de sombra permiten la encapsulación, lo que significa que podemos usar documentos CSS regulares alcanzados en el árbol de nuestro componente. Podemos adjuntar nuestras hojas de estilo a nuestro componente utilizando un nodo link normal, aunque Suiza proporciona una utilidad node para StyleSheet y Variables : este último aplica variables personalizadas a su árbol de componentes que permite que CSS acceda a esas variables JavaScript. Utilizamos el use.path Gancho para resolver los medios - documentos CSS, imágenes, etc. - en relación con nuestro componente.
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" />
</ >
) ;
} ) ;Entonces podemos estar bastante flojos al aplicar esos estilos a nuestro componente sabiendo que el límite de la sombra evitará que los estilos se filtren: usamos una variable CSS para aplicar un color de fondo condicional con una alternativa.
: host {
box - shadow : 0 0 5px # e8c5b0;
}
ul {
background - col or : var( --background-color , "#E39AC7" );
} Dado que Suiza permite la representación del lado del servidor de use.loader predeterminada. Se proporciona un gancho de utilidad de carga para obtener datos, aunque puede optar por usar cualquier otra utilidad de recuperación de terceros o un useEffect simple y eso también está bien. El uso de loader Hook permite obtener el lado del servidor de datos y luego evitar una recolección del cliente; Logramos esto al representar nuestros componentes dos veces en la función render asíncrono que cubrimos anteriormente y luego, incluidos los datos serializados en el árbol.
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 >
) ;
} ) ; Proporcionamos una identificación única a la función loader que debe identificar la solicitud individual para evitar duplicados y permitir la reconciliación del cliente. Con el argumento de dependencias en tercera posición, podemos volver a invocar el lado del cliente loader cada vez que cambia un parámetro; En nuestro caso, probablemente no queremos volver a buscar en los cambios, pero si se obtiene una lista determinada, podríamos esperar que la lista actual de países se proporcione como dependencias.
Proporcionar el contexto del entorno requiere alguna configuración del usuario en el lado del servidor: la función render toma un segundo parámetro opcional que nos permite especificar tanto el directorio raíz en el servidor web como opcionalmente el dominio en el que estamos ejecutando el servidor.
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 ) ;
} ) ; Utilizamos estas opciones para resolver los medios utilizando el gancho use.path con import.meta.url en relación con el componente: en el servidor necesitamos conocer el directorio raíz para lograrlo. Sin embargo, en el lado del cliente es un poco más simple ya que conocemos la raíz en función de la ruta de cada componente. Del mismo modo con la opción path donde especificamos el dominio en el que se está ejecutando el servidor web; Utilizamos esto para proporcionar rutas absolutas a los medios para que los componentes puedan utilizarse en aplicaciones de terceros, sin embargo, dado que es opcional, usamos la root mencionada para especificar una ruta relativa que está perfectamente bien cuando solo estamos usando nuestros componentes en nuestro propio servidor web.
Usando el gancho use.env podemos acceder a estos parámetros definidos, así como a algunos elementos adicionales.
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 >
</ >
) ;
} ) ; También puede extender los elementos HTML nativos utilizando la sintaxis x-hello:button en la función create : creará un elemento personalizado x-hello que se extiende desde el constructor button que le permite agregar su propio giro a él.
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 > ;
} ) ;