El evento
La conferencia más importante del mundo tecnológico en Europa
El congreso Codemotion Madrid es uno de los eventos más importantes con una asistencia de más de 1.500 asistentes, más de 100 ponentes y 80 sesiones. Se lleva a cabo en Kinépolis y conecta a los desarrolladores para encontrar inspiración y sentirse parte de algo único.
Las diapositivas
Potencia el desarrollo
de tu interfaz con
Web
Components

Proyecto que se va de las manos
Jorge del Casar
Head of Tech at ActioGlobal
Google Developer Expert
+20 years working on web


¿Qué son los Web Components?
Un conjunto de APIs para crear nuevas etiquetas HTML
- personalizadas
- reutilizables
- encapsuladas
¿De qué se componen?
3 APIs nativas del navegador
- Custom Elements
- Shadow DOM
- HTML Templates
Tu primer Web Component
Código
Hola, alguien!
Importar
<script type="module" src="./simple-greeting.js"></script>
Usar
<simple-greeting name="World"></simple-greeting>
Mostrar
El ciclo de vida
connectedCallback
Invocado cuando se conecta por primera vez al DOM del documento.
disconnectedCallback
Invocado cuando se desconecta del DOM del documento.
adoptedCallback
Invocado cuando se mueve a un nuevo documento.
attributeChangedCallback
Invocado cuando uno de los atributos es añadido, removido o modificado.
Lit es una librería sencilla para crear componentes web rápidos y ligeros.
Código
import { html, css, LitElement } from 'lit';
class SimpleGreetingLit extends LitElement {
static styles = css`
p {
color: var(--accent-regular);
}
`;
static properties = {
name: { type: String },
};
constructor() {
super();
this.name = 'alguien';
}
render() {
return html`Hola, ${this.name}!
`;
}
}
customElements.define('simple-greeting-lit', SimpleGreetingLit);
Importar
<script type=" module" src="./simple-greeting.lit.js" />
Usar
<simple-greeting-lit name="Lit"></simple-greeting-lit>
Mostrar
Composición
Composición de components
El proceso de construir un componente grande y complejo a partir de componentes más pequeños y simples.
Código
<list-item>
<img
slot="picture"
src="./piet_mondrian.jpg"
/>
<h3>Piet Mondrian</h3>
</list-item>
Resultado

Piet Mondrian
Mixin
Son un patrón para compartir código entre clases utilizando JavaScript estándar.
Mixin de Lista con Imagen
import { html } from 'lit';
import { listWithPictureStyles } from './listWithPictureStyles.css.js';
import './list-item.js';
export const ListWithPictureMixin = (superClass) => class extends superClass {
static styles = listWithPictureStyles;
error() {
return html`There was an error
`;
}
pending() {
return html`Loading...
`
}
complete = (items) => {
if (items.length) {
return items.map(this.item);
}
return this.empty();
}
empty() {
return html`There are no content
`;
}
item = (item) => {
return html`
${item.name}
`;
}
};
Lista con Imagen usando un Mixin
import { LitElement } from 'lit';
import { ListWithPictureMixin } from './ListWithPictureMixin.js';
export class ListWithPicture extends ListWithPictureMixin(LitElement) {
static properties = {
items: { type: Array },
};
constructor() {
super();
this.items = [];
}
render() {
if (this.items) {
return this.complete(this.items)
}
return this.error();
}
}
Resultado
Controlador
Es un objeto que puede conectarse al ciclo de actualización reactiva de un componente.
Controlador Fetch
export class FetchController {
constructor(host, endpointFn) {
(this.host = host).addController(this);
this.endpointFn = endpointFn;
}
hostUpdated() {
const endpoint = this.endpointFn();
if (endpoint !== this._previousEndpoint) {
this.run(endpoint);
}
}
async run(endpoint) {
if (endpoint) {
try {
this.value = await fetch(endpoint)
.then(response => response.json());
} catch (error) {
console.error(error);
this.value = null;
}
} else {
this.value = [];
}
this._previousEndpoint = endpoint;
this.host.requestUpdate();
}
}
Lista con imagen y fetch
import { html, nothing } from 'lit';
import { FetchController } from './FetchController.js';
import './list-with-picture.js';
import { ListWithPicture } from './ListWithPicture.js';
class ListWithPictureFetch extends ListWithPicture {
data = new FetchController(this, () => this.endpoint);
static properties = {
value: { type: String }
};
get endpoint() {
if (!this.value) {
return '';
}
return `/deck/ui-web-components/data/${this.value}.json`;
}
update(changedProperties) {
super.update(changedProperties);
this.items = this.data.value;
}
render() {
return html`
${this.renderSelect()}
${super.render()}
`;
}
renderSelect() {
return html``;
}
handleChange = (event) => {
this.value = event.target.value;
}
}
customElements.define('list-with-picture-fetch', ListWithPictureFetch);
Resultado
Manejando datos
Tarea Asíncrona
Es una operación asíncrona que realiza una acción para obtener datos y devolverlos en una Promesa.
Code
import { Task } from '@lit/task';
import { LitElement } from 'lit';
import { ListWithPictureMixin } from '../composition/ListWithPictureMixin.js';
export class ListWithPictureTask extends ListWithPictureMixin(LitElement) {
static properties = {
endpoint: { type: String },
}
task = new Task(this, {
args: () => [this.endpoint],
task: async ([endpoint], { signal }) => {
const response = await fetch(endpoint, { signal });
if (!response.ok) { throw new Error(response.status); }
return response.json();
}
});
render() {
if (this.task) {
return this.task.render({
pending: this.pending,
complete: this.complete,
error: this.error
});
}
return this.empty();
}
}
Código
<list-with-picture-task endpoint="/api/artists"></list-with-picture-task>
Resultado
Código
<list-with-picture-task endpoint="/api/ccaa"></list-with-picture-task>
Resultado
Contexto
Es una forma de hacer que los datos estén disponibles para sin tener que vincular manualmente propiedades a cada componente
Crear contexto
import { createContext } from '@lit/context';
export const contextList = createContext('list');
Consumir contexto
import { ContextConsumer } from '@lit/context';
import { LitElement } from 'lit';
import { contextList } from './contextList.js';
import { ListWithPictureMixin } from '../composition/ListWithPictureMixin.js';
export class ListWithPictureContext extends ListWithPictureMixin(LitElement) {
context = new ContextConsumer(this, {
context: contextList,
subscribe: true,
callback: (taskFactory) => {
this.task = taskFactory(this);
}
});
render() {
if (this.task) {
return this.task.render(this);
}
return this.empty();
}
}
Proveer contexto
import { ContextProvider } from '@lit/context';
import { Task } from '@lit/task';
import { LitElement, html } from 'lit';
import { contextList } from './contextList.js';
export class ListContextProvider extends LitElement {
#context = new ContextProvider(this, {
context: contextList
});
context = 'No implementado';
static properties = {
context: { type: String },
};
task = async () => {
throw new Error('Task not implemented');
}
taskFactory = (host) => new Task(host, {
task: this.task,
args: () => []
});
constructor() {
super();
this.#context.setValue(this.taskFactory);
}
render() {
return html`
Drop a component to provide the context
${this.context}
`;
}
}
Proveer contexto artistas
import { ListContextProvider } from "./ListContextProvider.js";
const endpoint = '/deck/ui-web-components/data/artists.json';
export class ListContextProviderArtists extends ListContextProvider {
context = 'Artistas';
task = async ({ signal }) => {
const response = await fetch(endpoint, { signal });
if (!response.ok) { throw new Error(response.status); }
return response.json();
}
}
Uso
Mismo componentes en diferentes contextos
Signals
Son estructuras de datos para gestionar el estado observable.
Crear signal
import { signal } from "@lit-labs/signals";
export const count = signal(0);
Mostrar signal
import { SignalWatcher, watch } from '@lit-labs/signals';
import { html, LitElement } from 'lit';
import { count } from './signalCount.js';
export class SelectedItemsSignals extends SignalWatcher(LitElement) {
render() {
return html`Seleccionados: ${watch(count)}
`;
}
}
Asignar signal
import { SignalWatcher } from '@lit-labs/signals';
import { html, css } from 'lit';
import { ListWithPictureContext } from './ListWithPictureContext.js';
import { count } from './signalCount.js';
export class ListWithPictureSignals extends SignalWatcher(ListWithPictureContext) {
static styles = [
super.styles,
css`.added { background: var(--surface-2); }`
]
item = (item) => {
return html`
${item.name}
`;
}
#onClick(event) {
const { classList } = event.currentTarget;
if (classList.contains('added')) {
count.set(count.get() - 1);
classList.remove('added');
} else {
count.set(count.get() + 1);
classList.add('added');
}
}
}
Selecciona tus favoritos
Internacionalización
Localization
Es el proceso de soportar múltiples idiomas y regiones en tu app y componentes.
Instalación
Instala la biblioteca de cliente y la CLI
npm i @lit/localize
npm i -D @lit/localize-tools
Envuelve el texto en la función msg
import { SignalWatcher, watch } from '@lit-labs/signals';
import { html, LitElement } from 'lit';
import { msg, updateWhenLocaleChanges } from '@lit/localize';
import { count } from '../manage-data/signalCount.js';
import './locale-picker.js';
export class SelectedItemsSignalsI18n extends SignalWatcher(LitElement) {
constructor() {
super();
updateWhenLocaleChanges(this);
}
render() {
const num = watch(count);
return html`
${msg(html`Seleccionados: ${num}`)}
`;
}
}
Crea un fichero de configuración lit-localize.json
{
"$schema": "https://raw.githubusercontent.com/lit/lit/main/packages/localize-tools/config.schema.json",
"sourceLocale": "es",
"targetLocales": ["en"],
"inputFiles": ["public/**/*.js"],
"output": {
"mode": "runtime",
"outputDir": "./public/generated/locales",
"localeCodesModule": "./public/generated/locale-codes.js"
},
"interchange": {
"format": "xliff",
"xliffDir": "./xliff/"
}
}
Ejecuta lit-localize extract
para generar los ficheros
XLIFF.
Edita el fichero XLIFF generado para añadir la traducción
Ejecuta lit-localize build
para generar una versión
localizada de tus textos.
Modos de salida
- Tiempo de ejecución
- Transformación
Tiempo de ejecución
import { configureLocalization } from '@lit/localize';
// Generated via output.localeCodesModule
import { sourceLocale, targetLocales } from '../../../generated/locale-codes.js';
export const { getLocale, setLocale } = configureLocalization({
sourceLocale,
targetLocales,
loadLocale: (locale) => import(`/generated/locales/${locale}.js`),
});
export const setLocaleFromUrl = async () => {
const url = new URL(window.location.href);
const locale = url.searchParams.get('locale') || sourceLocale;
await setLocale(locale);
};
Selecciona tus favoritos
Conclusión
- Principio de Rubik: divide y vencerás
- Principios SOLID: bajo acoplamiento y alta cohesión