
UnsplashのFederico Bottosによるソーシャルメディアの写真
ツールが含まれている小さなToollessライブラリが含まれています。ライブデモ
このプロジェクトを中心にコミュニティが成長するのを支援するために、専用のディスカッションリポジトリで質問してください♥
VUE 3の「ワンピース」に触発されたUCE-Templateは、コンポーネントをVUEの方法で定義するカスタムビルドイン<template>要素を提供します。
< 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>タグ、および/または1つ以上の<style>定義が含まれる場合があります。
コンポーネントに{{slot.name}}定義が含まれている場合、コンポーネントがアップグレードされる前に、生きているHTMLのノードがライブ後に配置されます。
詳細を理解するには、このライブの例を参照してください。
各「コンポーネント」は、独自の静的、または動的なコンテンツで、またはそれなしでそれ自体を定義する場合があります。
このようなコンテンツは、各カスタム要素を「マウント」(ライブ)し、それぞれのリアクティブ状態の変更ごとにレンダリングするために使用されますが、テンプレートが空のものではない場合のみです。
ここに示すように、すべての動的部品は{{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に関しては、そのポリフィルはこのプロジェクトには含まれていませんが、 Shadow属性を追加してShadow Rootを使用してコンポーネントを定義することができます。
< 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 }}スペースを使用したい場合、属性は引用符である必要があります。
<!-- 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>ではないノードを無視します。これは、コメントがその過程で飲み込まれたり失われたりしないためです。
Custom <table>とCustom <tr>コンポーネントの両方のdbmonster.htmlファイル定義を見ることができます。
コンポーネントは、特定の範囲内で、 1つ以上のスタイルを含むことができます。
<style>は、そのコンテンツをグローバルに適用します。これは、例として、 my-counter + my-counter {...}ケースに対処するのに役立ちます。<style scoped>カスタム要素名が付いたコンテンツを適用します(すなわち、 my-counter span, my-counter button {...} )<style shadow>は、コンポーネントがshadow属性で定義されていると仮定して、シャドウルートの上にそのコンテンツを適用しますIE11が<template>要素の目的と動作を理解していないため、グローバルスタイルがIE11を妨害している場合はIE11に干渉する可能性があることを除いて、ここで考慮すべき特別なことはありません。
定義には1つのスクリプトタグのみを含めることができ、そのようなスクリプトはモジュールのように実質的に処理されます。
IE11は<template>要素と互換性がないため、 typeが指定されていない場合、 IE11はページ上のすべてのスクリプトを正しく評価しようとします。
したがって、このライブラリとは完全に無関係であるため、 type属性は実際には何らかの価値を持つことができますが、そのような値はIE11互換であってはなりません。 module IE11が無視する値の1つにすぎません。
スクリプトには、 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 Reactiveヘルパーは、そのプロパティの1つが変更されるたびに、ビューを自動的に更新することを可能にします。
反応的な変更について詳しく知るために、この中程度の投稿をお読みください。
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 、 Settings JSONを入力し、 Open Settings(JSON)を選択し、 HTMLとしてファイルを強調表示するために次のファイルに以下を追加できます.uce
{
"other-settings" : "..." ,
"files.associations" : {
"*.uce" : "html"
}
}コンポーネントをview/my-component.uceとして定義する場合、これらが現在のページで見つかった場合にのみ、これらを怠lazに、またはそれ以上含めることを決定する場合があります。
このアプローチは、多くのバンドル、依存関係、不必要な膨満感を簡素化し、 uce-templateと小さな(364バイト) uce-roaderをブートストラップとして含めることで実行できます。
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テンプレートライブラリを持ち込むことを担当する場合を除き、まったく同じ怠zyなロードコンポーネントアプローチに従うことができます。
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未満に削減されますが、他のすべてがページのどこかにコンポーネントがある場合にのみ自動的に発生します。
ページには、サードパーティとライブラリの他のカスタム要素が含まれている可能性があるため、 view/${...}.uceを介して可能なカスタム要素をロードしようとすることとは反対の、予想されるコンポーネントの有名なセットを事前に定義することをお勧めします。
以前の怠zyな読み込み手法はすでに正常に機能しますが、コンポーネント名が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 )
) ;
} ) ;
}
} ) ;この手法の利点は、 knownセットをview/*.uceファイルを介して動的に生成できるため、見つかったコンポーネントがUCEテンプレートファミリの一部ではない場合、何も壊れません。
uce-template必然的にFunctionを使用して、テンプレートの部分またはsscript require(...)を評価する必要があります。
NonCe ijeLM8+5uwZ7ZXFmK+H2dwIWdiKJ1A4zhZIsq2Ffqqo=を使用してセキュリティを増やすことをお勧めします。
< 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 >これらの値はすべてのリリースで変更されることに注意してください。最新バージョンがあることを確認してください(このREADMEは最新のものを反映しています)。
UCEの場合と同様に、定義にonEvent(){...}メソッドが含まれている場合、これらはコンポーネントを定義するために使用されます。
ただし、通常、状態はコンポーネント自体から切り離されているため、 WeakMapを使用してコンポーネントをその状態に関連付けることをお勧めします。
ライブデモ
< 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 >この例は、 WeakMapを使用することが推奨であるため、状態とコンポーネントのユースケースをカバーしています。
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 >ただし、同名スロットの順序が必ずしも順番に視覚化されているわけではない場合は、代わりに一連のノードを渡すことが常に可能です。
つまり、補間値は、µHTMLが動作するのと同じように、DOMノード、ある程度の値、またはノードの配列である可能性があります。
ライブデモ
< 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が提供/解決された限り。
単一のバンドルエントリポイントを定義し、各コンポーネントが1つ以上の依存関係が必要であることがわかっている場合、次のことを行うことができます。
import { resolve } from 'uce-template' ;
import moduleA from '3rd-party' ;
const moduleB = { any : 'value' } ;
resolve ( 'module-a' , moduleA ) ;
resolve ( 'module-b' , moduleB ) ;このビルドが単一のWebページのエントリポイントとして着陸すると、すべてのコンポーネントがすべてのベース/デフォルトモジュールに加えて、すべてのリゾルビングをすべてすぐにインポートできます。
ライブデモ(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 > 定義されたコンポーネントが './js/module.js' ./js/module.js import module from './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 >怠zyなロードされたコンポーネントとともに、このアプローチにより、外部vue/comp.uceファイル定義に完全に基づいたコンポーネントを出荷することができます。これらのコンポーネントのいずれかが、ここまたはそこで必要なモジュールを解決できる1つまたは複数の.jsファイルを共有できます(出荷されたコンポーネントごとに依存関係の反対である1つのファイルで共有依存関係)。
スタンドアロンファイルとして、私のカスタム要素サイズは約2.1kですが、それがほとんどすべてのライブラリが共有しているため、 UCEが一緒にバンドルするのは最良の方法のように見え、約7K〜10Kの予算に合うモジュールではわずか1kの追加になります。
一方、ポリフィルは目立たず、ランタイム機能の検出に基づいているため、これは誰も他のポリフィルを持ち込むことを気にする必要があることを意味しますが、クロム、ファイアフォックス、エッジも触れられないため、すべてのカスタム要素がネイティブに動作します。
Safariの場合、またはWebKitベースでは、 IE11と古いMSエッジでは、ビルトイン拡張機能と通常の要素の両方がパッチされている間、カスタム要素のみが構築されています。
それだけです:ポリフィルについて心配しないでください。すべてがここにすでに含まれているからです!
ブラウザをターゲットにしている場合、すでにネイティブカスタム要素V1を提供していることがわかっている場合、すべてのポリフィルを除外し、ロジックのみを含むこのESMバージョンを使用できます。
現在のes.jsバンドルは確かに〜7K Gziptと〜6.5K Brotliであるため、プロジェクトの帯域幅さえも節約できます。
そのような場合、それが唯一のターゲットブラウザである場合、 @webreflection/custom-elements-builtininモジュールをページにuce-templateモジュールが着陸する前に含める必要があります。
< script defer src =" //unpkg.com/@webreflection/custom-elements-builtin " > </ script >
< script defer src =" //unpkg.com/uce-template " > </ script >これにより、通常の拡張機能とビルトインの両方が期待どおりに機能するようになります。
残念ながら、 Shadowdomはポリフィルすることは不可能な仕様の1つですが、良いニュースはUCE-TemplateでShadoddomを必要とすることはめったにないことですが、ブラウザが互換性がある場合は、 Shadowdomを好きなだけ使用できます。
ただし、考慮すべき部分的なポリフィルが少なくとも2つあります。アタッチシャドウは最小限で軽量で、標準に近いものですが、明確に重いですが、両方のポリフィルは、現在のブラウザが必要な場合にのみ挿入する必要があります。
<!-- 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の問題を避け、単に事実上の標準的な代替品に固執することにしました。
また、 Vueは{{...}}も使用していることを考慮する価値があります。他の多くのテンプレートベースのエンジンも使用します。
Functionが必要なのですか?方法/例の「 CSP&Integrity/Nonce 」の部分で説明されているように、少なくとも2つの理由でFunctionを使用する必要があります。
"use strict";ディレクティブとパスを備えたwith(object)ステートメント。<script type="module"> content内の機能require CJSを提供する唯一の方法ですしかし、方程式にFunctionがなかったとしても、 <script>タグを解析して実行してカスタム要素を定義することは、操作が基本的にグローバルなコンテキストでの評価呼びかけであるため、 CSPはとにかく特別なルールが必要だったため、 Functionを使用することとまったく同じと同じでした。
要約として、ブラウザにFunction呼び出しと同じくらい安全な、または安全でないプラクティスでブラウザをトリックする代わりに、代わりにFunctionを使用し、コードサイズを合理的に保ちました。
このプロジェクトは、定義コストを除き、ネイティブのカスタム要素が可能な場合があります。これは、各ユニークなカスタム要素クラスごとに1回限りの操作であるため、長期的には無関係であり、最初のテンプレート解析ロジック内では重要ではないオーバーヘッドがありますが、 UHTMLの繰り返しの実行は、最新の状況を確認できれば、その繰り返しの状況がわかります。
ここで古典的なDbmonsterデモを確認して、それがうまく機能していることを確認できます。
このライブラリには何もブロックされておらず、モジュールは一度にのみ解決され、相対的なパスインポートも解決されます。
ロジックは非常に単純です。モジュール名が解決されておらず、相対的なインポートである場合、非同期リクエストが後で行われ、評価されますが、モジュールが解決されず、資格のある名前である場合、コードが提供されたら解決されます。
これらすべてに加えて、解決を要求するインポートは、UCE-Requireヘルパーによって処理されます。これは、意図的にこのモジュール自体と組み合わされていません。
uce-templateとそれがどのように機能するかについて詳しく理解したい場合は、このページをチェックしてください。