瑞士使用带有Shadow dom的PREAXCT采用功能性方法来进行Web组件,用于样式封装,互操作性的自定义元素以及用于通用性的服务器端渲染。
纱线: yarn add switzerland
NPM : npm install switzerland
CDN : https://cdn.jsdelivr.net/npm/switzerland@latest/dist/index.client.js
瑞士(Switzerland)可选地始于服务器端渲染,从而通过声明的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元素进行补充。然后,我们可以进一步迈出一步,并允许使用<x-countries list="Japan,Croatia,Singapore">在DOM节点上作为HTML属性通过。
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登录器变化。
const node = document . querySelector ( "x-countries" ) ;
node . attributes . values = ` ${ node . attributes . values } ,Ukraine,Maldives` ; 瑞士不需要编译,除了可选的打字稿和JSX转移,因为它在服务器上使用了浏览器和节点16+的本机ES模块。当使用命名导入在服务器上渲染时,它通过使用node_modules来实现这一目标,并且在浏览器中,它使用导入地图来解决提供增强式缓存的CDN URL的命名导入。我们为服务器提供了一个实用程序,可以根据其依赖项自动为您的应用程序生成导入地图。
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软件包将依赖项解析到jspm.io URL默认情况下 - 但是,您还可以通过provider选项来更改提供商。
配置了导入地图后,在浏览器中渲染瑞士组件时,它将使用这些CDN URL,并阻止通过Bundler包装依赖项的任何需求。您可以纯粹专注于使用tsc将Typescript和JSX转换为本机ES模块的简单任务 - 尽管如果您想缩小,则可能需要添加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应该已经熟悉了。为了易于使用,我们可以重新脱离挂钩功能,但您也可以直接从预见中使用它们。
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文档。尽管瑞士为StyleSheet和Variables提供了node实用程序,但我们可以通过使用常规link节点将样式表附加到组件上,但后者将自定义变量应用于组件树,允许CSS访问CSS访问这些JavaScript变量。我们使用use.path钩子解决媒体 - 相对于我们的组件,CSS文档,图像等。
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" );
}由于瑞士允许默认情况下使用服务器端渲染,因此提供了用于use.loader数据的Loader Utility Hook - 尽管您可以选择使用任何其他第三方提取实用程序或简单的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函数提供了独特的ID,该ID应该标识单个请求以防止重复并允许对客户端进行对帐。使用第三位置的依赖项参数,我们可以在参数更改时重新启动loader客户端。在我们的案例中,我们可能不想重新挑选任何更改,但是如果通过给定列表提取,我们可能会期望当前提供的国家 /地区作为依赖关系。
提供环境上下文需要服务器端上的某些用户配置 - render函数采用可选的第二个参数,该参数允许我们在Web-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 ) ;
} ) ;我们使用这些选项使用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 >
</ >
) ;
} ) ; 您还可以使用x-hello:button create函数中的按钮语法扩展本机HTML元素 - 它将创建一个x-hello自定义元素,该X自定义元素从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 > ;
} ) ;