
Federico Bottos在Unsplash上拍摄的社交媒体照片
一个带有工具的微小图书馆。现场演示
请在专门讨论的存储库中提出问题,以帮助围绕该项目的社区成长♥
受VUE 3“一件”的启发, UCE-Template提供了一个自定义的内置<template>元素,可以以vue的方式定义组件。
< template is =" uce-template " >
< style scoped >
span { color: green }
</ style >
< the-green >
The < span > {{thing}} </ span > is green
</ the-green >
< script type =" module " >
export default {
setup ( ) {
return { thing : 'world' }
}
}
</ script >
</ template >将此库添加到方程式中,并看到它引导所有定义的组件。
<template lazy>组件,仅在现场时解决其定义<custom-element shadow>组件,以及可选的阴影<style shadow>样式@uce模块,以创建反应性UI等resolve(name, module)导出的实用程序预先定义虽然建议在全球安装CLI ,但由于一些不舒适的依赖性,它仍然是npx命令:
# check all options and usage
npx uce-template --help
# works with files
npx uce-template my-component.html
# works with stdin
cat my-component.html | uce-template就是这样,但是当然,我们应该确保生产的布局仍然可以按预期工作?
任何扩展uce-template模板都必须在其中至少包含一个自定义元素,无论是常规或内置的扩展:
<!-- register regular-element -->
< template is =" uce-template " >
< regular-element >
regular
</ regular-element >
</ template >
<!-- register builtin-element as div -->
< template is =" uce-template " >
< div is =" builtin-element " >
builtin
</ div >
</ template >任何模板都可能包含一个<script>标签和/或一个或多个<style>定义。
如果一个组件包含{{slot.name}}定义,则来自Live HTML的节点(在升级组件升级之前)将放置在现场后。
请参阅这个现场示例以了解更多。
每个“组件”可能通过或不使用其自己的静态或动态内容来定义自己。
此类内容将在“安装”(live)和每个反应性状态更改后渲染每个自定义元素,但前提是模板不是一个空模板。
所有动态零件必须包裹在{{dynamic}}卷曲括号中,如下所示:
< my-counter >
< button onclick = {{dec}} > - </ button >
< span > {{state.count}} </ span >
< button onclick = {{inc}} > + </ button >
</ my-counter > state , dec和inc参考将通过脚本节点(如果有)传递。
每当渲染组件时,都会调用其更新回调,以将元素本身作为上下文提供。
< button is =" my-button " >
I am a {{this.tagName}}
</ button >关于Shadowdom ,该项目不包括其多填充,但可以通过添加阴影属性来通过其阴影根来定义组件:
< my-counter shadow >
<!-- this content will be in the shadowRoot -->
< button onclick = {{dec}} > - </ button >
< span > {{state.count}} </ span >
< button onclick = {{inc}} > + </ button >
</ my-counter >默认情况下, shadow属性是open ,但也可以指定为shadow=closed 。
关于{{JS}} ,如果属性,并且您想在周围使用{{ JS }}空格,则属性必须在引号中,否则HTML模板以意外的方式破坏了布局。
<!-- OK -->
< my-counter >
< button onClick = {{dec}} > - </ button >
</ my-counter >
<!-- OK -->
< my-counter >
< button onClick =" {{ dec }} " > - </ button >
</ my-counter >
<!-- IT BREAKS!!! -->
< my-counter >
< button onClick = {{ dec }} > - </ button >
</ my-counter ><!--{{interpolation}}-->案例由于这里的所有内容主要基于标准的HTML行为,因此在某些情况下应将插值作为评论包装。
经验法则是,如果您看不到布局,或者读取一些错误的模板错误,则可能会被模板元素吞噬您的插值。
这主要发生在表,选择和其他仅接受特定类型的子节点但不接受文本的元素。
<!-- ? this won't work as expected -->
< table is =" my-table " >
< tbody > {{rows}} </ tbody >
</ table >
<!-- ? this works ? -->
< table is =" my-table " >
< tbody > <!--{{rows}}--> </ tbody >
</ table >在第一种情况下, <tbody>将忽略除评论以外的任何没有<tr>节点,因为在此过程中,评论不会吞噬或丢失。
您可以查看自定义<table>和custom <tr>组件的dbmonster.html文件定义。
组件可以在特定范围内具有一种或多种样式:
<style>将在全球应用其内容,可用于解决my-counter + my-counter {...}案例,例如<style scoped>将应用其内容以自定义元素名称(即my-counter span, my-counter button {...} ))shadow属性定义的,则<style shadow>将其内容应用于影子根部在这里没有什么特别的考虑,除了全球样式可能会干扰IE11 ,如果太令人讨厌,因为IE11再次不了解<template>元素>元素的目的和行为。
一个定义只能包含一个脚本标签,并且该脚本实际上将像模块一样处理。
由于IE11与<template>元素不兼容,因此,如果未指定type , IE11将尝试评估页面上的所有脚本。
因此, type属性确实可以具有任何值,因为它与此库完全无关,但是该值不得兼容IE11,而module只是IE11忽略的一个值。
该脚本可能包含default export ,甚至包含一个module.exports = ... ,该导出可能具有setup(element) { ... }方法,该方法返回组件的动态部分期望:
< script type =" module " >
import { reactive } from '@uce' ;
export default {
setup ( element ) {
const state = reactive ( { count : 0 } ) ;
const inc = ( ) => { state . count ++ } ;
const dec = ( ) => { state . count -- } ;
return { state , inc , dec } ;
}
} ;
</ script > @uce反应助手可以在其属性之一更改时自动更新视图。
要了解有关反应性更改的更多信息,请阅读此中等文章。
setup属性如果找到<script type="module" setup> ,则脚本的内容以元素本身作为上下文调用。
现场演示
< x-clock > </ x-clock >
< template is =" uce-template " >
< x-clock > {{time}} </ x-clock >
< script type =" module " setup >
let id = 0 ;
export default {
get time ( ) {
return ( new Date ) . toISOString ( ) ;
}
} ;
this . connected = e => id = setInterval ( this . render , 1000 / 30 ) ;
this . disconnected = e => clearInterval ( id ) ;
</ script >
</ template >此快捷方式非常方便,对于不需要设置观察到的组件,但可能需要设置道具,并且对于后一种情况, setup属性应包含props 。
< script type =" module " setup =" props " >
// props are defined as key => defaultValue pairs
export const props = {
name : this . name || 'anonymous' ,
age : + this . age || 0
} ;
</ script > 本节的目标是通过UCE-Template展示基本到复杂的示例,其中一些示例可能会使用.uce扩展程序将组件限制在其自己的文件中。
.uce文件为html如果使用VS代码,则可以CTRL+Shift+P ,键入设置JSON ,选择“打开设置”(JSON) ,然后将以下内容添加到该文件中以突出显示为HTML .uce
{
"other-settings" : "..." ,
"files.associations" : {
"*.uce" : "html"
}
}如果我们将组件定义为view/my-component.uce我们不妨决定只有在当前页面中找到这些组件,或者更好地包括这些组件。
这种方法简化了很多束,依赖关系,不必要的膨胀,可以通过仅包括uce-template和Tiny (364个字节) UCE-Loader作为Bootstrap来完成,最终定义了跨组件使用的额外依赖项。
import { parse , resolve } from 'uce-template' ;
import loader from 'uce-loader' ;
// optional components dependencies
import something from 'cool' ;
resolve ( 'cool' , something ) ;
// bootstrap the loader
loader ( {
on ( component ) {
// ignore uce-template itself
if ( component !== 'uce-template' )
fetch ( `view/ ${ component } .uce` )
. then ( body => body . text ( ) )
. then ( definition => {
document . body . appendChild (
parse ( definition )
) ;
} ) ;
}
} ) ;同样的技术可以直接在任何HTML页面上使用,也可以编写一些可能与IE11兼容的代码。
<!doctype html >
< html >
< head >
< script defer src =" //unpkg.com/uce-template " > </ script >
< script defer src =" //unpkg.com/uce-loader " > </ script >
< script defer >
addEventListener (
'DOMContentLoaded' ,
function ( ) {
uceLoader ( {
Template : customElements . get ( 'uce-template' ) ,
on : function ( name ) {
if ( name !== 'uce-template' ) {
var xhr = new XMLHttpRequest ;
var Template = this . Template ;
xhr . open ( 'get' , name + '.uce' , true ) ;
xhr . send ( null ) ;
xhr . onload = function ( ) {
document . body . appendChild (
Template . from ( xhr . responseText )
) ;
} ;
}
}
} ) ;
} ,
{ once : true }
) ;
</ script >
</ head >
< body >
< my-component >
< p slot =" content " >
Some content to show in < code > my-component </ code >
</ p >
</ my-component >
</ body >
</ html >uce-template如果我们的大多数页面根本不使用组件,则在每个页面顶部添加7k+ JS可能是不希望的。
但是,我们可以遵循相同的懒惰加载组件方法,除非我们的加载程序还可以在找到UCE-Template库(当找到UCE-Template本身或任何其他组件时)。
import loader from 'uce-loader' ;
loader ( {
on ( component ) {
// first component found, load uce-template
if ( ! this . q ) {
this . q = [ component ] ;
const script = document . createElement ( 'script' ) ;
script . src = '//unpkg.com/uce-template' ;
document . body . appendChild ( script ) . onload = ( ) => {
// get the uce-template class to use its .from(...)
this . Template = customElements . get ( 'uce-template' ) ;
// load all queued components
for ( var q = this . q . splice ( 0 ) , i = 0 ; i < q . length ; i ++ )
this . on ( q [ i ] ) ;
} ;
}
// when uce-template is loaded
else if ( this . Template ) {
// ignore loading uce-template itself
if ( component !== 'uce-template' ) {
// load the component on demand
fetch ( `view/ ${ component } .uce` )
. then ( body => body . text ( ) )
. then ( definition => {
document . body . appendChild (
this . Template . from ( definition )
) ;
} ) ;
}
}
// if uce-template is not loaded yet
// add the component to the queue
else
this . q . push ( component ) ;
}
} ) ;使用此技术,我们的每页JS有效载荷现在将减少到高于代码后的0.5K小于0.5k,而捆绑和缩小,而其他所有内容只有在页面中的某个地方有组件时才会自动发生。
由于该页面可能包含来自第三方和库中的其他自定义元素,因此预先定义一组众所周知的预期组件可能是一个好主意,这与尝试通过view/${...}.uce请求加载任何可能的自定义元素的相反。
以前的懒惰加载技术已经可以正常工作了,但是我们可以使用一个集合,而不是检查组件名称不是uce-template ,而是可以使用一个集合:
loader ( {
known : new Set ( [ 'some-comp' , 'some-other' ] ) ,
on ( component ) {
if ( this . known . has ( component ) )
fetch ( `view/ ${ component } .uce` )
. then ( body => body . text ( ) )
. then ( definition => {
document . body . appendChild (
parse ( definition )
) ;
} ) ;
}
} ) ;该技术的优点是,可以通过view/*.uce文件列表动态生成known集合,这样,如果发现的组件不属于UCE-Template家族,则不会破裂。
uce-template地需要使用Function来评估模板部分或标记内的要求(...) 。
建议使用NONCE ijeLM8+5uwZ7ZXFmK+H2dwIWdiKJ1A4zhZIsq2Ffqqo=或完整性属性,提高安全性,仅通过我们自己域中的CSP脚本来信任。
< meta http-equiv =" Content-Security-Policy " content =" script-src 'self' 'unsafe-eval' " >
< script defer src =" /js/uce-template.js "
integrity =" sha256-ijeLM8+5uwZ7ZXFmK+H2dwIWdiKJ1A4zhZIsq2Ffqqo= "
crossorigin =" anonymous " >
</ script >请注意,这些值在每个版本上都会发生变化,因此请确保您有最新版本(此读数反映了最新版本)。
就像UCE一样,如果定义包含onEvent(){...}方法,则将使用这些定义来定义组件。
但是,由于状态通常与组件本身分离,因此使用弱图将任何组件与其状态联系起来是一个好主意,而且...不用担心, IE11也对弱映射也受到了本地支持!
现场演示
< button is =" my-btn " >
Clicked {{times}} times!
</ button >
< script type =" module " >
const states = new WeakMap ;
export default {
setup ( element ) {
const state = { times : 0 } ;
states . set ( element , state ) ;
return state ;
} ,
onClick ( ) {
states . get ( this ) . times ++ ;
// update the current view if the
// state is not reactive
this . render ( ) ;
}
} ;
</ script >请注意,此示例涵盖了任何状态与组件用例,因为使用弱映射是建议。
如果props对象是定义的,并且由于Props *自动更改视图后,我们可能不需要弱图就可以关联组件的状态。
现场演示
< button is =" my-btn " > </ button >
< template is =" uce-template " >
< button is =" my-btn " >
Clicked {{this.times}} times!
</ button >
< script type =" module " >
export default {
props : { times : 0 } ,
onClick ( ) {
this . times ++ ;
}
} ;
</ script >
</ template >使用道具的优点是,可以通过属性来定义初始状态,或者通过直接设置通过html实用程序渲染时,以便将带有times="3"按钮呈现为例如,将显示显示3次!马上。
< button is =" my-btn " times =" 3 " > </ button >import {ref} from '@uce'简化了ref="name"属性的节点的检索。
< element-details >
< span ref =" name " > </ span >
< span ref =" description " > </ span >
</ element-details >
< template is =" uce-template " >
< element-details > </ element-details >
< script type =" module " setup >
import { ref } from '@uce' ;
const { name , description } = ref ( this ) ;
name . textContent = 'element name' ;
description . textContent = 'element description' ;
</ script >
</ template >import {slot} from '@uce'简化了插槽的检索,返回通过同名分组的元素数组。
如本示例所示,可以用来将单个插槽放置在插值中,或者将多个插槽放置在同一节点中。
现场演示
< filter-list >
Loading filter ...
< ul >
< li slot =" list " > some </ li >
< li slot =" list " > searchable </ li >
< li slot =" list " > text </ li >
</ ul >
</ filter-list >
< template is =" uce-template " >
< filter-list >
< div >
< input placeholder = filter oninput = {{filter}} >
</ div >
< ul >
{{list}}
</ ul >
</ filter-list >
< script type =" module " >
import { slot } from '@uce' ;
export default {
setup ( element ) {
const list = slot ( element ) . list || [ ] ;
return {
list ,
filter ( { currentTarget : { value } } ) {
for ( const li of list )
li . style . display =
li . textContent . includes ( value ) ? null : 'none' ;
}
} ;
}
} ;
</ script >
</ template >但是,如果不一定会顺序可视化相同的插槽顺序,则可以始终通过一系列节点。
也就是说,任何插值值都可以是DOM节点,某些值或一系列节点,相同的方式µHTML可以正常工作。
现场演示
< howto-tabs >
< p > Loading tabs ... </ p >
< howto-tab role =" heading " slot =" tab " > Tab 1 </ howto-tab >
< howto-panel role =" region " slot =" panel " > Content 1 </ howto-panel >
< howto-tab role =" heading " slot =" tab " > Tab 2 </ howto-tab >
< howto-panel role =" region " slot =" panel " > Content 2 </ howto-panel >
</ howto-tabs >
< template is =" uce-template " >
< howto-tabs >
{{tabs}}
</ howto-tabs >
< script type =" module " >
import { slot } from '@uce' ;
export default {
setup ( element ) {
const { tab , panel } = slot ( element ) ;
const tabs = tab . reduce (
( tabs , tab , i ) => tabs . concat ( tab , panel [ i ] ) ,
[ ]
) ;
return { tabs } ;
}
} ;
</ script >
</ template >UCE-Template提供的模块系统非常简单且完全扩展,因此每个组件都可以import any from 'thing';只要已经通过图书馆提供/解决了thing 。
如果我们要定义一个捆绑点入口点,并且我们知道每个组件都需要一个或多个依赖项,我们可以执行以下操作:
import { resolve } from 'uce-template' ;
import moduleA from '3rd-party' ;
const moduleB = { any : 'value' } ;
resolve ( 'module-a' , moduleA ) ;
resolve ( 'module-b' , moduleB ) ;一旦将其构建为单个网页输入点,所有组件就可以立即导入所有基本/默认模块,以及所有预处理的模块。
实时演示(请参阅HTML和JS面板 +控制台)
< my-comp > </ my-comp >
< script type =" module " >
import moduleA from 'module-a' ;
import moduleB from 'module-a' ;
export default {
setup ( ) {
console . log ( moduleA , moduleB ) ;
}
}
</ script > 如果定义的组件从外部文件中导入某些内容,例如import module from './js/module.js' ,则会懒惰地解决此类导入,以及尚不清楚的任何其他模块,这意味着./js/module.js文件可以包含类似的东西:
// a file used to bootstrap uce-template component
// dependencies can always use the uce-template class
const { resolve } = customElements . get ( 'uce-template' ) ;
// resolve one to many modules
resolve ( 'quite-big-module' , { ... } ) ;然后,组件脚本可以导入此文件,并在之后立即访问其导出的模块。
现场演示
< script type =" module " >
import './js/module.js' ;
import quiteBigModule from 'quite-big-module' ;
export default {
setup ( ) {
console . log ( quiteBigModule ) ;
}
}
</ script >与懒惰的加载组件一起,这种方法可以完全基于外部vue/comp.uce文件定义,这些组件都可以共享一个或多个能够在此处或那里解决所需的任何模块(一个文件中的共享.js项,如每个发货组件的相反))。
作为独立文件,我的自定义元素尺寸约为2.1k ,但是由于UCE几乎共享它也可以使用,因此将其捆绑在一起看起来像是最好的选择,因此,对于适合大约7k至10k预算的模块,仅需额外1k 。
另一方面,由于多填充不是令人难以置信的,并且基于运行时特征检测,这意味着没有人会心于带来任何其他聚填充物,但也不会镀铬, firefox和Edge ,都不会被触及,以便每个自定义元素都将以来呈现,要么是内置的或常规的。
在Safari情况或基于WebKit的情况下,仅提供自定义元素,而在IE11和旧的MS Edge中,内置的扩展和常规元素均已修补。
就是这样:不必担心任何多填充,因为这里已经包含在这里!
如果您要针对浏览器,您知道已经提供了本机自定义元素V1,则可以使用此ESM版本,该版本不包括所有polyfills,并且仅包括逻辑。
当前的es.js捆绑包确实是〜7k Gzzed和〜6.5K brotli,因此可以在项目中节省额外的带宽。
好吧,在这种情况下,如果这是唯一的目标浏览器,则必须在页面上的uce-template模块降落之前包括 @web fellefter/custom-elements-builtin模块。
< script defer src =" //unpkg.com/@webreflection/custom-elements-builtin " > </ script >
< script defer src =" //unpkg.com/uce-template " > </ script >这将确保常规和内置的扩展将按预期工作。
不幸的是, Shadowdom是不可能多填充的规格之一,但是好消息是,您很少在UCE-Template中需要Shadowdom ,但是如果您的浏览器兼容,则可以随心所欲地使用Shadowdom 。
但是,至少有两个可能的部分多填充需要考虑:sactershadow,简约且轻巧,而遮阳,它更接近标准,但要重重,尽管两个多填充物都可以并且应该仅在当前的浏览器需要的情况下才能注入,因此只能将此代码粘贴在您的HTML Page上,因此将这些代码粘贴在The Html Page上。
<!-- this must be done before uce-template -->
< script >
if ( ! document . documentElement . attachShadow )
document . write ( '<script src="//unpkg.com/attachshadow"><x2fscript>' ) ;
</ script >
< script defer src =" //unpkg.com/uce-template " > </ script >正如每个现代浏览器都会有document.documentElement.attachShadow那样, document.write才会在IE11中发生,而不会妥协或惩罚,移动和现代桌面浏览器。
ps <x2fscript>不是错字,不需要打破布局应关闭脚本标签
{{...}}而不是${...} ?尽管我希望拥有${...}插值边界,但如果DOM中的元素包含${...}作为属性,则IE11会破裂。
因为{{...}}是一种良好的选择,所以我决定避免猴子捕捉可能的IE11问题,而只是坚持使用deacto标准替代方案。
也值得考虑的是, Vue也使用{{...}} ,许多其他基于模板的引擎也是如此。
Function ?正如如何/示例的“ CSP&Integrity/Nonce ”一部分中所述,有必要使用Function至少两个原因:
"use strict";指令并通过with(object)语句,需要了解插值而无需从刮擦创建整个JS引擎require之类<script type="module">唯一方法但是,即使方程式中没有Function ,解析和执行<script>标签来定义自定义元素的使用也与使用Function完全相同,因为CSP无论如何都需要特殊规则,因为该操作基本上是全局上下文中的评估调用。
总而言之,我不是用像Function调用一样安全或不安全的实践欺骗浏览器,而是简单地使用了Function ,而是使代码大小合理。
除了定义成本外,该项目是表现最好的 - 作为本机自定义元素,这是每个独特的自定义元素类别的一次性操作,因此从长远来看,这是无关紧要的,并且在初始模板解析逻辑中有一个微不足道的高架,但是它的重复执行速度很快,如果您可以检查UHTML ,并且可以检查最新状态,您会发现它的最新状态,您会发现一个非常快的类型。
您可以在此处查看经典的DBMonster演示,并查看其性能很好。
该库中的任何内容都没有阻止,并且仅解决一次模块,甚至相对路径导入。
逻辑非常简单:如果模块名称尚未解决,并且是相对导入的,则稍后将进行异步请求和评估,而如果未解决模块,并且它是合格的名称,则只有在某些代码提供后才解决。
所有这些,加上需要解决的进口,由UCE重新辅助人员处理,故意与该模块本身并不与其他项目一起使用,并且可以启发和使用其他项目。
如果您想了解有关uce-template更多信息以及它如何工作,请签出此页面。