
Фото в социальных сетях Федерико Боттос на Unsplash
Крошечная библиотека Toolless с инструментами. Живая демонстрация
Пожалуйста, задайте вопросы в посвященном репозитории дискуссий, чтобы помочь сообществу вокруг этого проекта расти ♥
Вдохновленный Vue 3 « One Piece », 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 , для создания реактивных пользовательских интерфейсов и многого другого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}} определения, узлы из живого 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 , его полифилл не включен в этот проект, но можно определить компонент через его корень Shadow , добавив атрибут Shadow :
< 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}}--> caseПоскольку все здесь в основном основано на стандартном поведении 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> , кроме комментариев , потому что комментарии не проглатываются и не теряются в процессе.
Вы можете увидеть определение файла dbmonster.html как для пользовательского <table> , так и для пользовательского компонента <tr> .
Компонент может иметь в нем один или несколько стилей, в пределах определенной области :
<style> будет применять свой контент по всему миру, полезный для решения my-counter + my-counter {...} случаев, в качестве примера<style scoped> будет применять свой контент, префиксированный с помощью пользовательского имени элемента (то есть my-counter span, my-counter button {...} )<style shadow> будет применять свой контент на вершине теневого уровня , предполагая, что компонент определяется с атрибутом 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) и добавить следующее в такой файл, чтобы выделить файлы .uce как html :
{
"other-settings" : "..." ,
"files.associations" : {
"*.uce" : "html"
}
} Если мы определим компоненты как view/my-component.uce мы могли бы также принять решение включить их лениво или лучше, только когда они найдены на текущей странице.
Этот подход упрощает много пучков, зависимостей, ненужных раздуточных средств, и это можно сделать, включив в качестве начальной загрузки только uce-template и крошечный (364 байт) UCE-загрузчик, в конечном итоге определяя дополнительные зависимости, используемые в компонентах.
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 , либо при обнаружении само -типлята или любого другого компонента.
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,5 тыс. Однажды выше кода будет связана с комплексом и минимизировано, в то время как все остальное произойдет автоматически, только если есть компоненты где -то на странице.
Поскольку страница может содержать другие пользовательские элементы от сторонних и библиотек, было бы неплохо предоставить известный набор ожидаемых компонентов, как противоположность попытке загрузить любые возможные пользовательские элементы через 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 )
) ;
} ) ;
}
} ) ; Преимущество этого метода состоит в том, что known набор может быть динамически сгенерирован через список файлов view/*.uce , так что ничто не сломается, если найденный компонент не является частью семейства UCE-Template .
uce-template неизбежно необходимо использовать Function для оценки либо шаблонов, либо upt- reft (...) .
Рекомендуется повысить безопасность, используя либо 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 >Обратите внимание, что эти значения изменяются в каждом выпуске, поэтому, пожалуйста, убедитесь, что у вас есть последняя версия (этот Readme отражает последние).
Как и для 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 определяется, и, поскольку реквизит * обновляет представление автоматически после изменения, нам может не понадобиться слабая карта , чтобы связать состояние компонента.
Живая демонстрация
< 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' helper упрощает поиск слотов по имени, возвращая массив элементов, сгруппированных с помощью того же имени.
Это можно использовать либо для размещения отдельных слотов в интерполяциях, как показано в этом примере, либо для размещения нескольких слотов в одном узле.
Живая демонстрация
< 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, некоторым значением или массивом узлов, так же, как работает работает.
Живая демонстрация
< 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 Panel + Console)
< 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,1 тыс. , Но, поскольку он разделяет почти каждую библиотеку, которая использует UCE , объединение его вместе выглядела как лучший путь, что приводит к лишним лишним для модуля, который подходит примерно в бюджете от 7 до 10 тысяч .
С другой стороны, поскольку полифилл не является навязчивым и основанным на обнаружениях функций выполнения, это означает, что никто не должен заботиться о том, чтобы принести какой -либо другой полифилл, но также хром , Firefox и Edge , не был нетронут, чтобы каждый пользовательский элемент был нагрузен, либо встроенный, либо регулярный.
В случае Safari , или на основе Webkit , предоставляются только пользовательские элементы, в то время как в IE11 и Old MS Edge , оба Extens Extends и регулярные элементы исправлены.
Вот и все: не беспокойтесь о каком -либо многофилле, потому что все здесь уже включено!
Если вы нацелены на браузеры, которые, как вы знаете, уже предоставляют нативные пользовательские элементы v1, вы можете использовать эту версию ESM, которая исключает все полифиллы и включать только логику.
Нынешний пакет es.js действительно составляет ~ 7K GZEPIPD и ~ 6,5K Brotli, так что в вашем проекте можно сохранить еще дополнительную пропускную способность.
Что ж, в таком случае, если это единственный целевой браузер, модуль @webreflection/custom-letents должен быть включен до того, как модуль UCE-Template приземлится на странице.
< script defer src =" //unpkg.com/@webreflection/custom-elements-builtin " > </ script >
< script defer src =" //unpkg.com/uce-template " > </ script >Это гарантирует, что как обычные, так и настройки будут работать, как и ожидалось.
К сожалению, Shadowdom -одна из тех спецификаций, не которой нельзя полифиль, но хорошая новость заключается в том, что вам редко нуждается в Shadowdom в Uce-Template , но если ваш браузер совместим, вы можете использовать Shadowdom столько, сколько захотите.
Тем не менее , есть как минимум два возможных частичных полифиллов: AttachShadow, которая является минималистичной и легкой, и тенистостью, которая ближе к стандартам, но окончательно тяжелее, хотя оба полифилса могут и должны быть введены только в том случае, если нынешний браузер нуждается в нем, так что придерживаясь этого кода на вершину вашей HTML -страницы, чтобы привести тени к IE11 или другим.
<!-- 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> не опечатка, необходимо не иметь сломанной макета с приверженным сценарием
{{...}} вместо ${...} ? Как бы мне не очень хотелось иметь границы интерполяции ${...} , IE11 сломался бы, если бы элемент в DOM содержит ${...} как атрибут.
Поскольку {{...}} является хорошо установленной альтернативой, я решил избежать возможных вопросов IE11 и просто придерживаться стандартной альтернативы De-Facto.
Также стоит подумать, что Vue также использует {{...}} , как и многие другие двигатели на основе шаблонов.
Function необходима? Как объяснено в части « CSP & Integrity/Nonce », как это нужно/примеры необходимо использовать Function как минимум по двум причинам:
"use strict"; Директива и пройти оператор with(object) , необходимый для понимания интерполяций без создания целого двигателя JS с царапиныrequire функциональность в <script type="module"> content Но даже если бы в уравнении не было никакой Function , анализ и выполнение тега <script> для определения пользовательских элементов было бы таким же эквивалентом использования Function , поскольку CSP в любом случае нуждается в специальных правилах, поскольку операция в основном является вызовом Eval в глобальном контексте.
В качестве резюме, вместо того, чтобы обмануть браузер с такими же безопасными или небезопасными, как и Function вызов, я просто использовал Function , сохраняя размер кода разумным.
Этот проект является актуальным, как могут быть нативные пользовательские элементы, за исключением стоимости определения, которая представляет собой единовременную операцию на каждом уникальном классе пользовательских элементов, следовательно, не имеет значения в долгосрочной перспективе, и в рамках первоначальной логики разбора шаблонов существует незначительная накладная плата, но его повторяющееся выполнение настолько быстро, как и UHTML , и если вы проверите самый последний статус.
Вы можете проверить классическую демонстрацию DBMonster здесь и увидеть, что она работает хорошо.
Ничто в этой библиотеке не блокируется, и модули разрешаются только один раз , даже относительный импорт пути.
Логика довольно проста: если имя модуля не было разрешено, и это относительный импорт, асинхронный запрос будет сделан и оценен позже, в то время как, если модуль не будет разрешен, и это квалифицированное имя, оно будет разрешено только после того, как он его предоставит.
Все это, плюс импорт , требующий разрешения, обрабатывается помощником UCE-Require, намеренно не сочетающимся с самим модулем, поскольку он, как мы надеемся, может вдохновить и использоваться другими проектами.
Если вы хотите узнать больше о uce-template и как это работает, пожалуйста, проверьте эту страницу.