Швейцария использует функциональный подход к веб-компонентам, используя Preact с Shadow Dom для инкапсуляции стиля, пользовательские элементы для совместимости и рендеринг на стороне сервера для универсальности.
пряжа : yarn add switzerland
NPM : npm install switzerland
CDN : https://cdn.jsdelivr.net/npm/switzerland@latest/dist/index.client.js
Швейцария необязательно начинается с рендеринга на стороне сервера с гидратацией клиента благодаря декларативному Shadow Dom-с нашими компонентами выглядят очень знакомыми из-за нашего использования Preact.
import { create } from "switzerland" ;
export default create ( "x-countries" , ( ) => {
return (
< ul >
< li > Japan </ li >
< li > Croatia </ li >
< li > Singapore </ li >
</ ul >
) ;
} ) ; Как только мы определили наш компонент x-countries мы сможем как отображать его на сервере, так и увлажнять его на клиенте в качестве стандартного элемента <x-countries /> dom. Затем мы можем сделать еще один шаг и позволить нашим странам быть принятым в качестве атрибута HTML на узле DOM с использованием <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 >
) ;
} ) ; Использование нашего компонента из среды узла требует от нас использовать экспортируемую асинхронную функцию render ; Мы можем указать необязательный второй параметр для функции, однако наш компонент в настоящее время не выполняет извлечение данных или включение медиа и, таким образом, не нужно.
import { render } from "switzerland" ;
app . get ( "/" , async ( _ , response ) => {
const html = await render ( < Countries list = "Japan,Croatia,Singapore" /> ) ;
response . send ( html ) ;
} ) ;Поскольку наши компоненты являются автономными модулями, любые изменения в их атрибутах будут инициировать повторный рендеринг дерева компонента-независимо от того, изменяются ли эти атрибуты изнутри другого компонента или через Vanilla Dom Accessors.
const node = document . querySelector ( "x-countries" ) ;
node . attributes . values = ` ${ node . attributes . values } ,Ukraine,Maldives` ; Швейцария не должна быть скомпилирована, за исключением дополнительной TypeScript и JSX Transpiling, поскольку она использует нативные модули ES в браузере и узле 16+ на сервере. Он достигает этого, используя node_modules при рендеринге на сервере с использованием названных импортов, а в браузере он использует карты импорта для разрешения тех названных импорт в URL -адреса CDN, которые предлагают расширенное кэширование. Мы предоставляем утилиту для сервера автоматически генерировать карты импорта для вашего приложения на основе его зависимостей.
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>
` ) ;
} ) ; Вы должны дать функцию imports базовый путь ваших компонентов Швейцарии. Затем он будет пересекать файлы, используя ts-morph , который предоставляет абстрактное синтаксисное дерево (AST) вашего кода и позволяет нам выбрать внешние зависимости; Затем он итеративно соответствует каждой из тех зависимостей, которые он находит с версиями, установленными выбранным вами диспетчера пакетов. Мы используем пакет @jspm/generator для разрешения зависимостей от URL -адресов jspm.io по умолчанию - однако вы также можете передать опцию provider , чтобы изменить поставщика.
После того, как у вас настроена импортная карта, при отмене компонентов Швейцарии в браузере она будет использовать эти URL -адреса CDN и предотвратить любую необходимость упаковки зависимостей через бундлер. Вы можете сосредоточиться исключительно на простой задаче Transpling TypeScript и JSX в нативные модули ES, используя только tsc - хотя, если вы хотите минифить, вам может потребоваться добавить 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
}
}Поскольку мы используем Preact для отображения компонентов Швейцарии, API уже должен быть знаком. Для простоты использования мы реэкспортируем функции крючка Preact, но вы также можете использовать их непосредственно из 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 >
) ;
} ) ; Стили в пределах теневой границы позволяют инкапсулировать, что означает, что мы можем использовать обычные документы CSS, оцениваемые до дерева нашего компонента. Мы можем прикрепить наши таблицы стилей к нашему компоненту, используя обычный узел link , хотя Швейцария предоставляет утилиту node для StyleSheet и Variables - последняя применяет пользовательские переменные к дереву вашего компонента, позволяя CSS получить доступ к этим переменным JavaScript. Мы используем крючок use.path
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" />
</ >
) ;
} ) ;Затем мы можем быть довольно свободными при применении этих стилей к нашему компоненту, зная, что граница тени предотвратит вытекание стилей - мы используем переменную CSS для применения условного фонового цвета с запасением.
: host {
box - shadow : 0 0 5px # e8c5b0;
}
ul {
background - col or : var( --background-color , "#E39AC7" );
} Поскольку Switzerland допускает рендеринг на стороне сервера по умолчанию, для извлечения данных предоставляется утилита use.loader ,-хотя вы можете использовать любую другую стороннюю утилиту выбора или простой useEffect , и это тоже хорошо. Использование loader Hook позволяет получить сторону сервера данных, а затем предотвращать повторную сбору на клиенте; Мы достигаем этого, дважды отменяя наши компоненты в асинхронной render функции, которую мы рассмотрели ранее, а затем включали сериализованные данные в дереве.
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 >
) ;
} ) ; Мы предоставляем уникальный идентификатор функции loader , которая должна идентифицировать отдельный запрос, чтобы предотвратить дубликаты и обеспечить согласование клиента. С аргументом зависимостей в третьей позиции мы можем повторно внести нагрузку на клиент- loader всякий раз, когда изменяется параметр; В нашем случае мы, вероятно, не хотим переоценить ничего, что ничего не меняется, но если извлекать в данное список, мы могли бы ожидать, что текущий список стран будет предоставлена в качестве зависимостей.
Предоставление контекста среды требует некоторой конфигурации пользователя на стороне сервера-функция render принимает дополнительный второй параметр, который позволяет нам указать как корневой каталог на веб-сервере, так и , необязательно, домен, на котором мы запускаем сервер.
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 ) ;
} ) ; Мы используем эти параметры для разрешения носителя с помощью use.path Hook с import.meta.url относительно компонента - на сервере нам нужно знать корневый каталог, чтобы достичь этого. На стороне клиента, однако, это немного проще, поскольку мы знаем корень, основанный на пути каждого компонентов. Аналогично с опцией path , где мы указываем домен, на котором работает веб-сервер; Мы используем это для предоставления абсолютных путей для носителя, чтобы компоненты могли быть использованы в сторонних приложениях, однако, поскольку они необязательно, мы используем вышеупомянутый root , чтобы указать относительный путь, который вполне хорош, когда мы используем наши компоненты на нашем собственном веб-сервере.
Используя крючок use.env мы можем получить доступ к этим определенным параметрам, а также несколько дополнительных элементов.
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 >
</ >
) ;
} ) ; Вы также можете расширить собственные элементы HTML, используя синтаксис x-hello:button в функции create -он создаст пользовательский элемент x-hello , который простирается от конструктора button позволяя вам добавить свой собственный поворот в него.
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 > ;
} ) ;