En un momento tuve que conocer urgentemente los componentes web y encontrar una manera de desarrollarlos convenientemente. Planeo escribir una serie de artículos que
Organice de alguna manera el conocimiento de los componentes web, el elemento iluminado y brinde una breve introducción a esta tecnología para otros. No soy un experto en esta tecnología y con mucho gusto acepto cualquier comentario.
lit-element es un contenedor (plantilla básica) para componentes web nativos. Implementa muchos métodos convenientes que no están en la especificación. Debido a su proximidad a la implementación nativa, lit-element muestra muy buenos resultados en varios puntos de referencia en relación con otros enfoques (a partir del 02/06/2019).
Bonificaciones que veo al usar lit-element como una clase base de componentes web:
- Esta tecnología ya implementa la segunda versión y "se enfermó de enfermedades infantiles", que son peculiares de los instrumentos que acaban de aparecer.
- El ensamblaje se puede llevar a cabo tanto en polímero como en paquete web, mecanografiado, resumen, etc., esto le permite incrustar el elemento iluminado en cualquier proyecto moderno sin ningún problema.
- El elemento iluminado tiene un sistema muy conveniente de trabajar con propiedades en términos de escribir, iniciar y convertir valores.
- lit-element implementa casi la misma lógica que la reacción, es decir proporciona lo mínimo: una plantilla única para compilar componentes y su representación, y no limita al desarrollador a elegir un ecosistema y bibliotecas adicionales.
Cree un componente web simple en elemento iluminado. Pasemos a la documentación. Necesitamos lo siguiente:
- Agregue el paquete npm con elemento iluminado a nuestro ensamblaje
npm install --save lit-element
- Crea nuestro componente.
Por ejemplo, necesitamos crear un componente web inicializado en la etiqueta my-component
. Para hacer esto, cree el archivo js my-component.js
y defina su plantilla básica:
Primero, importamos nuestra plantilla básica:
import { LitElement, html } from 'lit-element';
En segundo lugar, cree el componente web usando LitElement
Y lo último es registrar el componente web en el navegador
customElements.define('my-component', MyComponent);
Como resultado, obtenemos lo siguiente:
import { LitElement, html } from 'lit-element'; class MyComponent extends LitElement { render() { return html`<p>Hello World!</p>` } } customElements.define('my-component', MyComponent);
Si excluye la necesidad de conectar my-component.js
a html, entonces eso es todo. El componente más simple está listo.
Propongo no reinventar la rueda y tomar el ensamblaje terminado de roll-element-build-rollup. Sigue las instrucciones:
git clone https://github.com/PolymerLabs/lit-element-build-rollup.git cd lit-element-build-rollup npm install npm run build npm run start
Después de completar todos los comandos, vamos a la página en el navegador http: // localhost: 5000 / .
Si echamos un vistazo en html, veremos que webcomponents-loader.js está delante de la etiqueta de cierre. Este es un conjunto de polyfills para componentes web, y para el funcionamiento del navegador web del componente web es deseable que este polyfill esté presente. Echemos un vistazo a la tabla de navegadores que implementan todos los estándares para trabajar con componentes web, dice que EDGE todavía no implementa completamente los estándares (no menciono IE11, que aún debe ser compatible).

Se implementaron 2 opciones para este polyfill:
- webcomponents-bundle.js : esta versión contiene todas las opciones posibles para polyfill, todas se inician, pero cada polyfill solo funcionará en función de los signos detectados.
- webcomponents-loader.js es un gestor de arranque mínimo que, según los síntomas detectados, carga los polyfills necesarios
También le pido que preste atención a otro polyfill: custom-elements-es5-adapter.js . Según la especificación, solo se pueden agregar clases ES6 a customElements.define nativo. Para obtener el mejor rendimiento, el código ES6 debe pasarse solo a los navegadores que lo admitan, y ES5 a todos los demás. Esto no siempre es posible, por lo tanto, para una mejor compatibilidad entre navegadores, se recomienda que todo el código ES6 se convierta a ES5. Pero en este caso, los componentes web en ES5 no podrán funcionar en los navegadores. Para resolver este problema, hay custom-elements-es5-adapter.js.
Ahora abramos el archivo ./src/my-element.js
import {html, LitElement, property} from 'lit-element'; class MyElement extends LitElement {
El motor de plantillas lit-html puede procesar una cadena de manera diferente. Te daré varias opciones:
Consejos para optimizar la función render ():
- no debe cambiar el estado de un elemento,
- no debería tener efectos secundarios,
- debería depender solo de las propiedades del elemento,
- debería devolver el mismo resultado al transmitir los mismos valores.
No actualice el DOM fuera de la función render ().
Lit-html es responsable de representar el elemento lit: esta es una forma declarativa de describir cómo se debe mostrar el componente web. lit-html garantiza actualizaciones rápidas al cambiar solo aquellas partes del DOM que necesitan ser cambiadas.
Casi todo este código estaba en un ejemplo simple, pero se @property
decorador @property
para la propiedad myProp
. Este decorador indica que estamos esperando un atributo llamado myprop
en nuestro my-element
. Si no se establece dicho atributo, el valor de la cadena se establece en stuff
de forma predeterminada.
<my-element></my-element> <my-element myprop="else"></my-element>
lit-element proporciona 2 formas de trabajar con property
:
- A través del decorador.
- A través de un getter estático
properties
.
La primera opción permite especificar cada propiedad por separado:
@property({type: String}) prop1 = ''; @property({type: Number}) prop2 = 0; @property({type: Boolean}) prop3 = false; @property({type: Array}) prop4 = []; @property({type: Object}) prop5 = {};
El segundo es especificar todo en un lugar, pero en este caso, si la propiedad tiene un valor predeterminado, debe escribirse en el método del constructor de la clase:
static get properties() { return { prop1: {type: String}, prop2: {type: Number}, prop3: {type: Boolean}, prop4: {type: Array}, prop5: {type: Object} }; } constructor() { this.prop1 = ''; this.prop2 = 0; this.prop3 = false; this.prop4 = []; this.prop5 = {}; }
La API para trabajar con propiedades en elemento iluminado es bastante extensa:
- atributo : si una propiedad puede convertirse en un atributo observable. Si es
false
, el atributo se excluirá de la observación; no se creará ningún captador para él. Si true
o attribute
ausente, la propiedad especificada en el captador en el formato lowerCamelCase se corresponderá con el atributo en el formato de cadena. Si se especifica una cadena, por ejemplo my-prop
, corresponderá con el mismo nombre en los atributos. - convertidor : contiene una descripción de cómo convertir un valor de / a un atributo / propiedad. El valor puede ser una función que funciona para serializar y deserializar el valor, o puede ser un objeto con las teclas
fromAttribute
y toAttribute
, estas teclas contienen funciones separadas para convertir los valores. De manera predeterminada, la propiedad contiene una conversión a los tipos base Boolean
, String
, Number
, Object
y Array
. Las reglas de conversión se enumeran aquí . - tipo : indica uno de los tipos base que contendrá esta propiedad. Se utiliza como una "pista" para el convertidor sobre qué tipo debe contener la propiedad.
- reflejar : indica si el atributo debe asociarse con la propiedad (
true
) y cambiarse de acuerdo con las reglas de type
y converter
. - hasChanged : cada propiedad lo tiene, contiene una función que determina si hay un cambio entre el valor antiguo y el nuevo, respectivamente, devuelve un valor
Boolean
. Si es true
, comienza a actualizar el elemento. - noAccessor : esta propiedad acepta un valor
Boolean
y su valor predeterminado es false
. Prohíbe la generación de captadores y establecedores para cada propiedad para acceder a ellos desde la clase. Esto no cancela la conversión.
Hagamos un ejemplo hipotético: escribiremos un componente web que contenga un parámetro que contenga una cadena, esta palabra debe dibujarse en la pantalla, en la que cada letra es más grande que la anterior.
<ladder-of-letters letters=""></ladder-of-letters>
al final obtenemos:

Cuando se hizo clic en el botón, se cambió la propiedad, lo que provocó el cheque primero y luego se envió para volver a dibujar.

y usando reflect
también podemos ver cambios html

Si cambia este atributo con código fuera de este componente web, también provocaremos un redibujo del componente web.
Ahora considere el estilo del componente. Tenemos 2 formas de diseñar el elemento iluminado:
- Estilo al agregar una etiqueta de estilo al método de renderizado
render() { return html` <style> p { color: green; } </style> <p>Hello World</p> `; }

- A través de
styles
getter estáticos
import {html, LitElement, css} from 'lit-element'; class MyElement extends LitElement { static get styles() { return [ css` p { color: red; } ` ]; } render() { return html` <p>Hello World</p> `; } } customElements.define('my-element', MyElement);
Como resultado, obtenemos que no se crea una etiqueta con estilos, sino que se escribe ( >= Chrome 73
) en el Shadow DOM
elemento de acuerdo con la especificación . Esto mejora el rendimiento con una gran cantidad de elementos, porque Al registrar un nuevo componente, él ya sabe qué propiedades determinan sus estilos; no es necesario que se registren y recuenten cada vez.

Además, si esta especificación no es compatible, se crea una etiqueta de style
regular en el componente.

Además, no olvide que de esta manera también podemos separar qué estilos se agregarán y calcularán en la página. Por ejemplo, para usar consultas de medios no en css, sino en JS e implementar solo el estilo deseado, por ejemplo (esto es comodín, pero tiene que serlo):
static get styles() { const mobileStyle = css`p { color: red; }`; const desktopStyle = css`p { color: green; }`; return [ window.matchMedia("(min-width: 400px)").matches ? desktopStyle : mobileStyle ]; }
En consecuencia, veremos esto si el usuario inició sesión en un dispositivo con un ancho de pantalla de más de 400 px.

Y esto es si el usuario visitó el sitio desde un dispositivo con un ancho de menos de 400 px.

Mi opinión: prácticamente no hay un caso adecuado cuando un usuario, trabajando en un dispositivo móvil, de repente se enfrenta a un monitor completo con un ancho de pantalla de 1920px. Agregue a esto la carga diferida de componentes. Como resultado, obtenemos un frente muy optimizado con una rápida representación de componentes. El único problema es la dificultad de apoyo.
Ahora propongo familiarizarse con los métodos del ciclo de vida del elemento iluminado:
- render () : implementa una descripción del elemento DOM usando
lit-html
. Idealmente, la función de render
es una función pura que usa solo las propiedades actuales del elemento. La función update()
llama al método render()
. - shouldUpdate (changedProperties) : implementado si es necesario controlar la actualización y la representación, cuando se cambiaron las propiedades o
requestUpdate()
llamó a requestUpdate()
. El argumento de la función changedProperties
es un Map
contiene claves para las propiedades cambiadas. Por defecto, este método siempre devuelve true
, pero la lógica del método se puede cambiar para controlar la actualización del componente. - performUpdate () : implementado para controlar el tiempo de actualización, por ejemplo, para integrarse con el planificador.
- update (changedProperties) : este método llama a
render()
. También actualiza los atributos de un elemento de acuerdo con el valor de la propiedad. Establecer propiedades dentro de este método no causará otra actualización. - firstUpdated (changedProperties) : llamado después de la primera actualización del elemento DOM inmediatamente antes de la llamada
updated()
. Este método puede ser útil para capturar enlaces a nodos estáticos visualizados con los que necesita trabajar directamente, por ejemplo, en updated()
. - updated (changedProperties) : se llama cada vez que se actualiza y muestra el DOM de un elemento. Una implementación para realizar tareas después de actualizar a través de la API DOM, por ejemplo, enfocándose en un elemento.
- requestUpdate (name, oldValue) : invoca una solicitud de actualización asincrónica para un elemento. Se debe llamar cuando el elemento necesita actualizarse en función de algún estado no causado por la configuración de la propiedad.
- createRenderRoot () : por defecto crea una Shadow Root para el elemento. Si el uso del Shadow DOM no es necesario, entonces el método debería devolverlo.
Cómo se actualiza el elemento:
- La propiedad recibe un nuevo valor.
- Si la
hasChanged(value, oldValue)
devuelve false
, el elemento no se actualiza. De lo contrario, se planifica una actualización llamando a requestUpdate()
. - requestUpdate () : actualiza el elemento después de microtask (al final del bucle de eventos y antes de la próxima actualización).
- performUpdate () : la actualización está en progreso y continúa con el resto de la API de actualización.
- shouldUpdate (changedProperties) : la actualización continúa si
true
devuelve true
. - firstUpdated (changedProperties) : se llama cuando el elemento se actualiza por primera vez, inmediatamente antes de llamar a
updated()
. - update (changedProperties) : actualiza el elemento. Cambiar las propiedades en este método no causa otra actualización.
- render () : devuelve una plantilla
lit-html
para representar un elemento en el DOM. Cambiar las propiedades en este método no causa otra actualización.
- updated (changedProperties) : se llama cada vez que se actualiza un elemento.
Para comprender todos los matices del ciclo de vida del componente, le aconsejo que consulte la documentación .
En el trabajo, tengo un proyecto sobre adobe experience manager (AEM), en su autoría el usuario puede arrastrar y soltar componentes en la página, y de acuerdo con la ideología de AEM, este componente contiene una etiqueta de script
que contiene todo lo que se necesita para implementar la lógica de este componente. Pero, de hecho, este enfoque dio lugar a muchos recursos de bloqueo y dificultades con la implementación del frente en este sistema. Para implementar el frente, los componentes web se eligieron como una forma de no cambiar la representación del lado del servidor (lo que hizo muy bien), así como para enriquecer la implementación anterior con un nuevo enfoque suavemente, a nivel de bits. En mi opinión, hay varias opciones para implementar la carga de componentes web para este sistema: recopilar un paquete (puede llegar a ser muy grande) o dividirlo en trozos (se necesitan muchos archivos pequeños, carga dinámica), o usar el enfoque actual con incrustar un script en cada uno un componente que se representa en el lado del servidor (realmente no quiero volver a esto). En mi opinión, la primera y tercera opción no es una opción. Para el segundo, necesita un gestor de arranque dinámico, como en la galería de símbolos. Pero para el elemento iluminado en el "cuadro" esto no se proporciona. Los desarrolladores de elementos iluminados intentaron crear un cargador dinámico , pero es un experimento y no se recomienda su uso en producción. También de los desarrolladores de elementos iluminados hay un problema en el repositorio de especificaciones de componentes web con una propuesta para agregar a la especificación la capacidad de cargar dinámicamente los js necesarios para el componente web según el marcado html en la página. Y, en mi opinión, esta herramienta nativa es una muy buena idea que le permitirá crear un punto de inicialización de componentes web y simplemente agregarlo a todas las páginas del sitio.
Para cargar dinámicamente componentes web de elementos iluminados dinámicamente con los chicos de PolymerLabs, se desarrolló el elemento dividido . Esta es una solución experimental. Funciona de la siguiente manera:
- Para crear un SplitElement, escribe dos definiciones de elementos en dos módulos.
- Uno de ellos es un trozo, que define las partes cargadas de un elemento: generalmente este es el nombre y las propiedades. Las propiedades deben definirse con un apéndice para que el elemento iluminado pueda generar atributos observables de manera oportuna para llamar a
customElements.define()
. - El apéndice también debe tener un método de carga asíncrono estático que devuelva una clase de implementación.
- Otra clase es la "implementación", que contiene todo lo demás.
- El constructor
SplitElement
carga la clase de implementación y ejecuta upgrade()
.
Ejemplo de trozo:
import {SplitElement, property} from '../split-element.js'; export class MyElement extends SplitElement {
Ejemplo de implementación:
import {MyElement} from './my-element.js'; import {html} from '../split-element.js';
Ejemplo de SplitElement en ES6:
import {LitElement, html} from 'lit-element'; export * from 'lit-element';
Si todavía está utilizando el ensamblaje sugerido anteriormente en Rollup, asegúrese de configurar babel para poder manejar las importaciones dinámicas
npm install @babel/plugin-syntax-dynamic-import
Y en la configuración de .babelrc agregue
{ "plugins": ["@babel/plugin-syntax-dynamic-import"] }
Aquí hice un pequeño ejemplo de la implementación de componentes web con carga retrasada: https://github.com/malay76a/elbrus-split-litelement-web-components
Traté de aplicar el enfoque de carga dinámica de componentes web, llegué a la siguiente conclusión: la herramienta está funcionando bastante bien, debe recopilar todas las definiciones de componentes web en un archivo y conectar la descripción del componente a través de fragmentos por separado. Sin http2, este enfoque no funciona, porque Se forma un grupo muy grande de archivos pequeños que describen los componentes. Basado en el principio del diseño atómico , la importación de átomos debe determinarse en el cuerpo, pero el cuerpo ya debe estar conectado como un componente separado. Uno de los cuellos de botella es que el usuario recibirá muchas definiciones de elementos de usuario en el navegador que se inicializarán de una forma u otra en el navegador y se determinará el estado inicial. Tal solución es redundante. Una de las opciones para una solución simple para el cargador de componentes es el siguiente algoritmo:
- cargar los servicios públicos requeridos,
- cargar polyfills,
- ensamblar elementos personalizados de DOM ligero:
- se seleccionan todos los elementos DOM que contienen un guión en el nombre de la etiqueta
- la lista se filtra y se forma una lista de los primeros elementos.
- :
- Intersection Observer,
- +- 100px import.
- 3 shadowDOM,
- , shadowDOM , , import JS.
- lit-element open-wc.org . webpack rollup, - storybook, IDE.
:
- Let's Build Web Components! Part 5: LitElement
- Web Component Essentials
- A night experimenting with Lit-HTML…
- LitElement To Do App
- LitElement app tutorial part 1: Getting started
- LitElement tutorial part 2: Templating, properties, and events
- LitElement tutorial part 3: State management with Redux
- LitElement tutorial part 4: Navigation and code splitting
- LitElement tutorial part 5: PWA and offline
- Lit-html workshop
- Awesome lit-html