
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更多信息以及它如何工作,請簽出此頁面。