Swiss mengambil pendekatan fungsional untuk komponen web menggunakan preact dengan shadow dom untuk enkapsulasi gaya, elemen khusus untuk interoperabilitas dan rendering sisi server untuk universalitas.
Benang : yarn add switzerland
NPM : npm install switzerland
CDN : https://cdn.jsdelivr.net/npm/switzerland@latest/dist/index.client.js
Swiss opsional dimulai dengan rendering sisi server dengan hidrasi pada klien berkat Declarative Shadow Dom-dengan komponen kami terlihat sangat akrab karena penggunaan preact kami.
import { create } from "switzerland" ;
export default create ( "x-countries" , ( ) => {
return (
< ul >
< li > Japan </ li >
< li > Croatia </ li >
< li > Singapore </ li >
</ ul >
) ;
} ) ; Setelah kami mendefinisikan komponen x-countries kami, kami dapat membuatnya di server dan melembabkannya pada klien sebagai elemen standar <x-countries /> DOM standar. Kita kemudian dapat mengambil langkah lebih jauh dan membiarkan negara kita disahkan sebagai atribut HTML pada simpul DOM menggunakan <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 >
) ;
} ) ; Menggunakan komponen kami dari dalam lingkungan simpul mengharuskan kami untuk menggunakan fungsi render asinkron yang diekspor; Kami dapat menentukan parameter kedua opsional ke fungsi, namun komponen kami saat ini tidak melakukan pengambilan data atau inklusi media dan karenanya tidak perlu.
import { render } from "switzerland" ;
app . get ( "/" , async ( _ , response ) => {
const html = await render ( < Countries list = "Japan,Croatia,Singapore" /> ) ;
response . send ( html ) ;
} ) ;Karena komponen kami adalah modul mandiri, setiap perubahan atributnya akan memulai render ulang pohon komponen-terlepas dari apakah atribut tersebut berubah dari dalam komponen lain atau melalui aksesor vanilla dom.
const node = document . querySelector ( "x-countries" ) ;
node . attributes . values = ` ${ node . attributes . values } ,Ukraine,Maldives` ; Swiss tidak perlu dikompilasi kecuali untuk naskah opsional dan transpiling JSX karena menggunakan modul ES asli di browser dan node 16+ di server. Ini mencapai ini dengan menggunakan node_modules saat rendering di server menggunakan impor bernama, dan di browser ia menggunakan peta impor untuk menyelesaikan impor bernama tersebut ke URL CDN yang menawarkan caching yang disempurnakan. Kami menyediakan utilitas untuk server untuk secara otomatis menghasilkan peta impor untuk aplikasi Anda berdasarkan ketergantungannya.
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>
` ) ;
} ) ; Anda perlu memberikan fungsi imports jalur dasar komponen Swiss Anda. Ini kemudian akan melintasi file menggunakan ts-morph yang menyediakan pohon sintaks abstrak (AST) kode Anda dan memungkinkan kami untuk memilih dependensi eksternal; Kemudian secara iteratif cocok dengan masing -masing dependensi yang ditemukan pada versi yang diinstal oleh manajer paket pilihan Anda. Kami menggunakan paket @jspm/generator untuk menyelesaikan dependensi ke jspm.io URL secara default - namun Anda juga dapat melewati opsi provider untuk mengubah penyedia.
Setelah Anda memiliki peta impor yang dikonfigurasi, ketika merender komponen Swiss di browser itu akan menggunakan URL CDN dan mencegah kebutuhan untuk mengemas dependensi melalui bundler. Anda dapat fokus murni pada tugas sederhana mentranspiling naskah dan JSX ke modul ES asli menggunakan tidak lebih dari tsc - meskipun jika Anda ingin meminifkan, Anda mungkin perlu menambahkan 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
}
}Karena kami menggunakan preact untuk membuat komponen Swiss API seharusnya sudah familiar. Untuk kemudahan penggunaan, kami mengekspor kembali fungsi-fungsi kait Preact tetapi Anda juga dapat menggunakannya langsung dari 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 >
) ;
} ) ; Gaya dalam batas bayangan memungkinkan untuk enkapsulasi yang berarti kita dapat menggunakan dokumen CSS biasa yang dicakup ke pohon komponen kita. Kami dapat melampirkan stylesheet kami ke komponen kami dengan menggunakan node link biasa, meskipun Swiss menyediakan utilitas node untuk StyleSheet dan Variables - yang terakhir menerapkan variabel khusus untuk pohon komponen Anda yang memungkinkan CSS untuk mengakses variabel javascript tersebut. Kami menggunakan hook use.path untuk menyelesaikan media - dokumen CSS, gambar, dll ... - relatif terhadap komponen kami.
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" />
</ >
) ;
} ) ;Kita kemudian bisa cukup longgar saat menerapkan gaya -gaya itu ke komponen kami mengetahui bahwa batas bayangan akan mencegah gaya bocor - kami menggunakan variabel CSS untuk menerapkan warna latar belakang bersyarat dengan fallback.
: host {
box - shadow : 0 0 5px # e8c5b0;
}
ul {
background - col or : var( --background-color , "#E39AC7" );
} Karena Swiss memungkinkan untuk rendering sisi server secara default a use.loader utility hook disediakan untuk mengambil data-meskipun Anda dapat memilih untuk menggunakan utilitas pengambilan pihak ketiga lainnya atau useEffect sederhana dan itu juga baik-baik saja. Menggunakan loader Hook memungkinkan untuk mengambil sisi data server dan kemudian mencegah pengambilan ulang pada klien; Kami mencapai ini dengan merender komponen kami dua kali dalam fungsi render asinkron yang kami bahas sebelumnya dan kemudian memasukkan data serial di pohon.
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 >
) ;
} ) ; Kami memberikan ID unik untuk fungsi loader yang harus mengidentifikasi permintaan individu untuk mencegah duplikat dan untuk memungkinkan rekonsiliasi pada klien. Dengan argumen dependensi di posisi ketiga kita dapat melakukan investasi ulang sisi klien loader setiap kali parameter berubah; Dalam kasus kami, kami mungkin tidak ingin membuat kembali apa pun karena tidak ada perubahan tetapi jika diambil dengan daftar yang diberikan, kami mungkin mengharapkan daftar negara saat ini disediakan sebagai dependensi.
Memberikan konteks lingkungan memerlukan beberapa konfigurasi pengguna di sisi server-fungsi render mengambil parameter kedua opsional yang memungkinkan kami untuk menentukan kedua direktori root pada server web dan secara opsional domain yang kami jalankan server.
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 ) ;
} ) ; Kami menggunakan opsi -opsi ini untuk menyelesaikan media menggunakan Hook use.path dengan import.meta.url relatif terhadap komponen - di server kami perlu mengetahui direktori root untuk mencapai ini. Di sisi klien namun sedikit lebih sederhana karena kita tahu root berdasarkan jalur masing-masing komponen. Demikian juga dengan opsi path di mana kami menentukan domain yang dijalankan oleh server web; Kami menggunakan ini untuk memberikan jalur absolut ke media sehingga komponen dapat digunakan dalam aplikasi pihak ketiga, namun karena itu opsional kami menggunakan root yang disebutkan di atas untuk menentukan jalur relatif yang baik-baik saja ketika kami hanya menggunakan komponen kami pada server web kami sendiri.
Menggunakan hook use.env kita dapat mengakses parameter yang ditentukan ini serta beberapa item tambahan.
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 >
</ >
) ;
} ) ; Anda juga dapat memperluas elemen HTML asli menggunakan sintaks x-hello:button di fungsi create -itu akan membuat elemen kustom x-hello yang memanjang dari konstruktor button yang memungkinkan Anda untuk menambahkan twist Anda sendiri ke dalamnya.
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 > ;
} ) ;