Fase 3: Javascript Avanzado

Contenidos

  • Transpilación (Vite)
  • Lógica de Javascript
  • Código limpio
  • Patrones

Una vez comienzas a comprender los detalles, ¡desbloqueas los límites!

¿Qué es la transpilación?

📁 project-name
 └── 📁 src
      ├── 🟧 index.html
      ├── 📁 css
      │    └── 🟪 global.css
      ├── 📁 modules
      │    ├── 🟨 getData.js      # Obtener datos
      │    └── 🟨 renderCard.js   # Pintar en web
      └── 🟨 index.js




📁 project-name
 ├── 📁 src
 │    ├── 🟧 index.html
 │    ├── 📁 css
 │    │    └── 🟪 global.css
 │    ├── 📁 modules
 │    │    ├── 🟨 getData.js      # Obtener datos
 │    │    └── 🟨 renderCard.js   # Pintar en web
 │    └── 🟨 index.js
 └── 📁 dist
      ├── 🟧 index.html
      ├── 🟪 index.css
      └── 🟨 index.js
📁 project-name
 ├── 📁 src
 │    ├── 🟧 index.html
 │    ├── 📁 css
 │    │    └── 🟪 global.css
 │    ├── 📁 modules
 │    │    ├── 🟨 getData.js      # Obtener datos
 │    │    └── 🟨 renderCard.js   # Pintar en web
 │    └── 🟨 index.js
 └── 📁 dist
      ├── 🟧 index.html
      ├── 🟪 index-34f7822e.css   # hash en CSS
      └── 🟨 index-1d901f84.js    # hash en JS
  • Hasta ahora, trabajamos directamente con nuestro código.
  • Sin embargo, en el desarrollo web es muy habitual trabajar con transpiladores.
  • Los transpiladores son herramientas que leen un lenguaje o formato, y lo convierten a otro lenguaje.
  • Ejemplos de transpiladores (o herramientas que transpilan):
  • ➡ Normalmente el origen está en src/ y el destino en dist/.
  • ➡ A veces añade un hash (por motivos de evitar caché).

Instalación de Vite

  • Documentación de Vite
  • Creamos estructura de carpetas y un fichero vite.config.js
mkdir project-name
cd project-name
pnpm init --init-type module
pnpm add -D vite
touch vite.config.js
code .

pnpm add -D gh-pages
import { defineConfig } from 'vite';

export default defineConfig({
  root: "/",                   /* index.html fuera de src/ */
  publicDir: "public",
  plugins: [],
  server: { port: 1234 },
  build: {
    outDir: "dist",
  }
});

Seguridad

  • Evitar problemas de seguridad de tipo Supply chain (ataque a la cadena de suministro)
  • Tu web es vulnerable al hacer un npm install porque una de tus dependencias fue comprometida
  • Ejemplo de problema de seguridad: paquete axios comprometido v1.14.1 (31-03-2026)
minimumReleaseAge: 1440
minimumReleaseAgeExclude:
- vite
- lightningcss

🔒 Seguridad

  • Crea un fichero pnpm-workspace.yaml (necesita pnpm 10.16.0+)
  • Añade el campo minimumReleaseAge con el tiempo 1440 (24h)
  • Pnpm no instalará ninguna dependencia que haya sido publicada en menos de 24h
  • Con minimumReleaseAgeExclude no influye a ciertas dependencias

Scripts de generación

  • Creamos 3 scripts: desarrollo, generación de build y despliegue
# Entorno de desarrollo
pnpm run dev

# Creamos producción en dist/
pnpm run build

# Revisamos la web de producción
pnpm run preview

# Desplegamos producción
pnpm run deploy
{
  /* ... */
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "deploy": "gh-pages -d dist"
  },
  /* ... */
}

Preparación para despliegue en GitHub Pages (varia en otros providers)

import { defineConfig } from 'vite';

export default defineConfig({
  root: "src",
  base: "/repo-name/",
  publicDir: "../public",
  plugins: [],
  server: { port: 1234 },
  build: {
    outDir: "../dist",
  }
});
import path from "node:path";
import { defineConfig } from 'vite';

const prod = process.env.NODE_ENV === "production";

export default defineConfig({
  root: "src",
  base: prod ? `/${path.basename(process.cwd())}/` : "/",
  mode: prod ? "production" : "development",
  publicDir: "../public",
  plugins: [],
  server: { port: 1234 },
  build: {
    outDir: "../dist",
  }
});
  • GitHub Pages requiere despliegue en ruta específica.
  • La URL es https://user.github.io/repo-name/
  • Por lo tanto, la base es /repo-name/

➡ Automatización:

  • Esta configuración automatiza para que use / en local, y por otro lado /repo-name/ en producción
  • La variable prod detecta si estás ejecutando vite en modo build (producción)
  • En ese caso, hace el build usando el nombre de la carpeta como nombre de repo (mode)

¡Ahora puedes usar ciertas tecnologías!

  • Esto NO es posible en navegadores. Pero SI al transpilarlo Vite a dist/
📁 project-name
 ├── 📁 src
 │    ├── 🟧 index.html
 │    ├── 🟪 index.scss ⬅
 │    └── 🟦 index.ts ⬅
 └── 📁 dist
      ├── 🟧 index.html
      ├── 🟪 index-34f7822e.css
      └── 🟨 index-1d901f84.js
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <link rel="stylesheet" href="./index.scss">
  <script src="./index.ts"></script>
</head>
<body>
  <!-- ... -->
</body>
</html>

Reemplazar PostCSS por LightningCSS

import { defineConfig } from 'vite';

export default defineConfig({
  /* ... */
  css: {
    transformer: "lightningcss", /* "postcss" */
    // lightningcss: {
    //   drafts: {
    //     customMedia: true
    //   }
    // }
  }
});
@custom-media --modern (width <= 900px);

body {
  background: #111;
  color: #eee;

  @media (--modern) {
    background: indigo;
  }
}

Importaciones en Vite

  • NO SON estándar: Sin Vite no te funcionarán (🟥 dependencia de Vite ⚡)
import image from "../assets/image.png";        // URL producción: "/assets/image.2d8efhg.png"
import image from "../assets/image.png?inline"; // Obtiene como Base64 (inline)
import image from "../assets/image.svg?raw";    // Obtiene como texto (contenido)
import data from "./assets/file.json";          // Obtiene JSON y parsea directamente a objeto
import styles from "./assets/file.css";         // Obtiene CSS y parsea directamente a texto
import data from "./assets/file.json" with { type: "json" }; // ❌ Actualmente, no compatible con Vite
import styles from "./assets/file.css" with { type: "css" }; // ❌ Actualmente, no compatible con Vite

Plugins de Vite

import { standardCssModules } from 'vite-plugin-standard-css-modules';
import { defineConfig } from 'vite';

export default defineConfig({
  /* ... */
  plugins: [
    standardCssModules()
  ]
});

Lógica Javascript

Contenidos

  • Array functions (continuación)
  • Introducción a la complejidad
  • Iteradores
  • Iterator functions

Repaso: Array functions: .forEach()

'Gato' 'Pato' 'Oso' 'Topito'

// No devuelve nada nunca (undefined)
animals.forEach((animal, index) => {
  console.log(`Elemento #${index}: ${el}`);
});

El resultado sería el siguiente:

  • Elemento #0: Gato
  • Elemento #1: Pato
  • Elemento #2: Oso
  • Elemento #3: Topito

Array functions: .forEach() vs .map()

'Gato' 'Pato' 'Oso' 'Topito'

// No devuelve nada nunca (undefined)
animals.forEach((el, index) => {
  console.log(`Elemento #${index}: ${el}`);
});
4 4 3 6

// Transforma el array original en uno derivado
const sizes = animals.map((el, index, array) => {
  return el.length;
});

// Versión abreviada
const sizes = animals.map((el) => el.length);

Array functions: .every() vs .some()

'Gato' 'Pato' 'Oso' 'Topito'

const all = animals.every((el) => {
  if (el.includes("a")) return true;
  else return false;
});

// Lo anterior es redundante:
animals.every(el => el.includes("a")); // F
animals.every(el => el.endsWith("o")); // T
'Gato' 'Pato' 'Oso' 'Topito'

const all = animals.some((el) => {
  if (el.includes("a")) return true;
  else return false;
});

// Lo anterior es redundante:
animals.some(el => el.includes("a")); // T
animals.some(el => el.endsWith("o")); // T

Array functions: .find() vs .findIndex() vs .filter()

const users = [
  { name: "ManzDev", life: 39 },
  { name: "felixicaza", life: 75 },
  { name: "krepssi", life: 99 },
  { name: "DHardySD", life: -5 },
];

users.find(el => el.life < 50);
// { name: "ManzDev", life: 39 }

users.findIndex(el => el.life < 50);
// 0
const filteredUsers = users.filter(el => el.life > 50);
// [
//  { name: "felixicaza", life: 75 }
//  { name: "krepssi", life: 99 }
// ]

users.filter(el => el.life > 500);    // []
users.find(el => el.life > 500);      // undefined
users.findIndex(el => el.life > 500); // -1

users.findLast();      // Idem pero buscando desde el final
users.findLastIndex(); // Idem pero buscando desde el final

Array functions: .flat() vs .flatMap()

const animals = [
  ["Gato", "Tigre"],
  ["Pato", [ "Ganso", "Gansa" ] ],
  "Oso"
];

animals.flat(1);
// ['Gato', 'Tigre', 'Pato', Array(2), 'Oso']

animals.flat(2);
animals.flat(Infinity);
// ['Gato', 'Tigre', 'Pato', 'Ganso', 'Gansa', 'Oso']
animals.flatMap(el => /* ... */);

// Equivalente a:
animals.map(el => /* ... */).flat(1);

// ...pero más eficiente:

// → flatMap (1 bucle)
// → map + flat (2 bucles)

Array functions: .reduce() y .reduceRight()

const values = [25, 25, 25, 25];

values.reduce((acc, first) => acc + first);
// 25 + 25 + 25 + 25 = 100

values.reduce((acc, first) => acc - first);
// 25 - 25 - 25 - 25 = -50

// Último parámetro: Valor inicial
values.reduce((acc, first) => acc - first, 0);
// 0 - 25 - 25 - 25 - 25 = -100
const values = [5, 10, 15, 20];

values.reduceRight((acc, first) => acc - first);
// 20 - 15 - 10 - 5 = -10

// Último parámetro: Valor inicial
values.reduceRight((acc, first) => acc - first, 0);
// 0 - 20 - 15 - 10 - 5 = -50

Notación Big O(N)

  • Complejidad: La notación Big O(N)
  • Es una forma de medir la complejidad del código (tiempo de CPU o memoria) según aumenta el tamaño de la entrada.
Complejidad Denominación Descripción
O(1) Constante Acceso una carta concreta. Da igual cuantas sean. Inmediato.
O(log n) Logarítmica Cartas ordenadas. Búsqueda binaria. Eliminas la mitad y sigues buscando.
O(n) Lineal Buscas una carta concreta. Carta por carta hasta encontrarla.
O(n log n) Lineal-logarítmica Mazo desordenado. Divides en montones, ordenadas y combinas.
O(n²) Cuadrática ⚠ Ordenas mazo comparando cada carta con el resto.
O(2ⁿ) Exponencial ⚠⚠ Cada carta que añades, dobla el total.
O(n!) Factorial ⚠⚠⚠ Explora todas las combinaciones posibles.

Complejidad

{
  "$schema": "./node_modules/oxlint/configuration_schema.json",
  "env": { "es2024": true },
  "ignorePatterns": ["**/node_modules/**"],
  "plugins": ["eslint", "unicorn", "oxc"],
  "jsPlugins": ["oxlint-plugin-complexity"],
  "rules": {
    "complexity/complexity": ["warn", {
      "cyclomatic": 4,
      "cognitive": 4
    }]
  }
}
  • Complejidad ciclomática: Número de caminos independientes que puede seguir el código (matemático).
  • Complejidad cognitiva: Mide lo difícil que es para un humano leer y entender el código.

Recordemos:

Iteradores

  • Iterar es avanzar por una estructura de forma eficiente (bajo demanda)
const data = ["One", "Two", "Three", "Four"];

// Sin iteradores
for (let i = 0; i < data.length; i++) {
  const number = data[i];
  console.log(number);
}
const data = ["One", "Two", "Three", "Four"];

// Iterador implícito
data.forEach(number => console.log(number));

// En los arrays, tenemos un iterador "escondido"
data[Symbol.iterator]

// Veamos como funciona... ➡
const data = ["One", "Two", "Three", "Four"];

data[Symbol.iterator] === data.values   // true
const iterator = data.values()  // Array Iterator {}

iterator.next() // { value: "One", done: false }
iterator.next() // { value: "Two", done: false }
// ...
iterator.next() // { value: undefined, done: true }

[...iterator]   // [] (Ya se han "consumido")
// Al usar .values(), creas una copia

Métodos de iteradores

  • .take(n) → Toma los elementos desde el actual hasta el indicado
  • .drop(n) → Descarta los n primeros elementos
  • .toArray() → Convierte a Array (o usar spread [...])
const data = ["One", "Two", "Three", "Four", "Five", "Six", "Seven"];

const iterator = data.values();

data.values()     // Hace copia
  .drop(3)        // Descarta los 3 primeros
  .take(2)        // Se queda sólo con los dos siguientes
  .toArray();     // Convierte a Array

// ["Four", "Five"]

Array functions para Iteradores

  • Podemos aplicar las clásicas array functions a los iteradores
const messages = ["Hola a todos", "Estoy muy contento", "Adiós"];

const iterator = Iterator.from(messages);

iterator.values().forEach(...)  // Por cada elemento...
iterator.values().every(...)    // Si todas cumplen...
iterator.values().some(...)     // Si al menos una cumple...
iterator.values().filter(...)   // Filtrar elementos
iterator.values().find(...)     // Busca un elemento...
iterator.values().map(...)      // Transforma cada elemento...
iterator.values().flatMap(...)  // Transforma y aplana cada elemento...
iterator.values().reduce(...)   // Aplica función, acumulándola...

Crear estructuras iterables

const data = {
  from: 1,
  to: 5,
}

[...data]         // ❌ Uncaught TypeError: data is not iterable
function *generator() {
  yield 1;
  yield "a";
  yield 42;
}

const it = generator()
it.next()     // {value: 1, done: false }
it.next()     // {value: "a", done: false }
it.next()     // {value: 42, done: false }
it.next()     // {value: undefined, done: true }
const data = {
  from: 1,
  to: 5,
  *[Symbol.iterator]() {
    for (let i = this.from; i <= this.to; i++) {
      yield i;
    }
  }
}

[...data]        // ✅ [1, 2, 3, 4, 5]

Código limpio

Nombrado inteligente

const d = 42;                         // ❌
const days = 42;                      // 🟩
const daysSinceCreation = 42;         // ✅

const genymdhms = /* ... */           // ❌
const generateTimestamp = /* ... */   // ✅

const calculateTotal = () => { ... }
const sendEmail = () => { ... }
class User { ... }
class Invoice { ... }
const isValid = true;
  • Buen nombre → código limpio, breve y sin explicaciones
  • Verbos → funciones
  • Sustantivos → clases
  • Prefijos is/has → booleanos
  • Evita tipos en nombres (ya los da el compilador/editor)
  • Nombres de una letra, sólo en bucles

DRY (No te repitas)

// ❌ No te repitas
$product.textContent = `$${(product.price).toFixed(2)}`;
$cart.textContent = `$${(cart.total).toFixed(2)}`;
$summary.textContent = `$${(summary.amount).toFixed(2)}`;

// ✅ Generaliza
const formatPrice = (n) => `$${n.toFixed(2)}`;

$product.textContent = formatPrice(product.price);
$cart.textContent = formatPrice(cart.total);
$summary.textContent = formatPrice(summary.amount);
  • Si detectas que repites la misma lógica... significa que podrías reutilizar.
  • Extrae la lógica, generaliza si es necesario, y usa una función reutilizable.
  • Más legible, más fácil de mantener si tienes que hacer cambios.

YAGNI (No lo necesitas)

⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣠⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⣀⣀⣀⣠⠔⠊⠑⠒⣷⠆⢸⢳⠀⠀⠀⠀⠀⠀⠀
⠀⠀⢀⠔⠋⠁⠀⠀⠉⠁⠒⠤⣄⠋⠀⠈⢧⡇⠀⠀⣰⣶⢆⠀
⠀⠀⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢉⣐⠒⠼⠿⠔⣊⣿⣿⢸⠀
⠀⠀⡇⠀⠀⠀⠀⢀⣤⡒⠋⣩⢉⠙⠛⢿⣿⡶⣾⣿⣿⣿⡌⠀
⠀⠀⠹⡄⠀⠀⢀⣾⣾⠁⢘⣭⣷⣶⠃⣿⣾⣷⣿⣿⣟⠝⠀⠀
⠀⠀⠀⠈⠢⣀⣸⢳⠛⡄⠀⠀⠀⠁⠀⣧⡀⢸⡽⠗⠁⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠈⠳⡄⠁⢠⠀⠀⠋⢱⢿⣷⡿⠁⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⡿⠀⢀⠐⠀⢭⣭⣽⣩⡇⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⢀⣴⡇⠑⢬⣀⠀⠀⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⢀⠎⠹⡕⠠⢀⣈⠻⢿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀
⠀⣀⠤⠒⠒⡇⠀⠀⠈⠢⣔⣭⣙⣛⣿⣽⣇⡏⣣⣀⠀⠀⠀⠀
⠉⠀⠀⠀⠀⠘⡄⠀⠀⣠⠴⡟⠍⡻⠟⣿⠘⣷⣇⢧⡉⠉⠉⠁
⠀⠀⠀⠀⠀⠀⣇⣠⠾⠃⠀⠓⠤⢔⣄⢣⠃⡇⣿⠚⠉
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠉⢧⠁⡇
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠃⠀⠛



Busca el punto medio entre dos extremos:

  • YAGNI → You Aren't Gonna Need It (evita el «por si acaso»)
  • COWBOY → Estilo «llanero solitario» (no pensar NADA en el futuro)
  • Evita construir cosas genéricas para problemas que aún no tienes.
  • Abstracción prematura, añade complejidad sin beneficios.
  • Pero tampoco vayas estilo libre, sin pensar más allá del futuro inmediato

Funciones puras

// Función pura: Misma entrada, siempre misma salida
const add = (a, b) => a + b;

// Función impura: Depende de algo externo (y lo modifica)
let total = 0;
const addToTotal = (n) => {
  total += n;
  return total;
}
  • Una función que siempre produce el mismo resultado para los mismos argumentos.
  • Una función que no modifica nada fuera de su ámbito.
  • ✅ Son predecibles y fáciles de testear
  • Una función aleatoria sería impura (no es predecible).
  • Una función que consulta una API es impura (no es predecible).
  • Efectos secundarios: Cambios fuera del alcance de la función.
  • Ej: modificar variable global, escribir en disco, llamar API...
  • No se trata de evitarlas, sino de aislarlas.

Tamaño de las funciones

// oxlint.json

{
  "rules": {
    "max-lines": ["warn", {
      "max": 300,
      "skipBlankLines": false,
      "skipComments": false
    }],
    "max-lines-per-function": ["warn", 50],
  }
}
  • Si una función no cabe en tu pantalla... hace demasiadas cosas.
  • Divídela y que tengan solo una responsabilidad cada una.
  • No se trata de hacer literalmente una cosa, sino una tarea.
  • Puedes configurar oxlint para que te ayude a detectarlo.
  • Formato rápido (número), formato largo (objeto con opciones)
  • Puedes limitar también el número de líneas por fichero.

Evita números y strings mágicos

// ❌ Números mágicos
if (user.role === 3) showAdminPanel();
if (password.length < 8) throw new Error('Muy corta');
setTimeout(logout, 900000); // 15 min

// ✅ Constantes descriptivas
const ROLE_ADMIN = 3;
const MIN_PASSWORD_LENGTH = 8;
const SESSION_TIMEOUT = 15 * 60 * 1000;

if (user.role === ROLE_ADMIN) showAdminPanel();
if (password.length < MIN_PASSWORD_LENGTH)
  throw new Error('Muy corta');
setTimeout(logout, SESSION_TIMEOUT);
  • Un 42 o un "manzdev" suelto no dice nada.
  • Crea una constante MAX_VALUE o ADMIN_USER y ayudas a que se entienda mejor.
  • Los números con separador: 900_000 mejor que 900000.
  • Pero aún mejor, 15 * 60 * 1000 (min x seg x ms)

Composición vs Herencia

  • El problema no es usar herencia, es abusar de la herencia
class Bird {
  fly() { /* ... */ }
}

class Duck extends Bird {
  quack() { /* ... */ }
}

// 🚨 No vuela, pero hereda fly()
class RubberDuck extends Duck {
  fly() { throw new Error('Patito de goma y no hay nada mal'); }
}
// Composición
const canQuack = { quack() {} };
const canFly   = { fly() {} };

const duck       = { ...canQuack, ...canFly };
const rubberDuck = { ...canQuack };

Cláusulas de guarda / Early returns

  • Retorna lo antes posible cuando una condición no se cumple.
  • Evita anidamiento excesivo y hace que el flujo del código sea más fácil de leer.
// ❌ Anidación excesiva, difícil de leer
function processOrder(user, cart) {
  if (user) {
    if (user.isActive) {
      if (cart.items.length > 0) {
        if (cart.total > 0) {
          return placeOrder(user, cart);
        }
      }
    }
  }
}
// ✅ Uso de cláusulas de guarda / early return
function processOrder(user, cart) {
  if (!user)                  return null;
  if (!user.isActive)         return null;
  if (!cart.items.length)     return null;
  if (cart.total <= 0)        return null;

  return placeOrder(user, cart);
}

Patrones

Patrones

Design patterns

  • Patrones: Estrategias y formas de resolver problemas (que sabemos que funcionan muy bien)
  • Patrones de diseño (Programación): Formas de resolver con código un problema concreto.
  • Patrones de UI/UX (Diseño): Formas de ofrecer al usuario una interfaz para una tarea concreta.

Patrón Observador (Programación)

class EventEmitter {
  constructor() {
    this.listeners = {};  // Store de listeners
  }

  on(event, fn) {
    // Si no existe, inicializamos
    this.listeners[event] ??= [];
    this.listeners[event].push(fn);
  }
  emit(event, data) {
    this.listeners[event]?.forEach(fn => fn(data));
  }
}
const emitter = new EventEmitter();

// Escuchar
emitter.on('login', user =>
  console.log(`Bienvenido ${user}`)
);
emitter.on('login', user =>
  console.log(`Notificando acceso de ${user}`)
);

// Emitir
emitter.emit('login', 'Manz');

Patrón de Reactividad

  • La reactividad es un patrón derivado del patrón «Observador».
  • Es un patrón en el que los datos se «actualizan» de forma automática (muy usado en ciertos frameworks).
  • Existe una propuesta para añadirlo nativamente a Javascript
import { Signal } from "https://cdn.jsdelivr.net/npm/signal-polyfill@0.2.2/dist/index.min.js";

const price = new Signal.State(100);
const quantity = new Signal.State(3);
const total = new Signal.Computed( () => price.get() * quantity.get() );

console.log(total.get()); // 300 (100 * 3)
price.set(150);
console.log(total.get()); // 450 (150 * 3)

Patrón Breadcrumbs (Diseño)

<div class="breadcrumbs">
  <div class="part">Home</div>
  <div class="part">Articles</div>
  <div class="part">Temática</div>
</div>

:root {
  --offset: 0.75rem;
  --shape-part: polygon(0 0,
    calc(100% - var(--offset)) 0, 100% 50%,
    calc(100% - var(--offset)) 100%, 0 100%,
    var(--offset) 50%
  );
}
.breadcrumbs {
  display: flex;
  color: #ccc;
  font-family: "Victor Mono", sans-serif;
  font-weight: 600;
  filter: drop-shadow(0 0 2px #2229);
  cursor: pointer;

  .part {
    background: #222;
    padding: 0.5rem 2rem;
    clip-path: var(--shape-part);

    &:hover { background: indigo }
  }
}

Introducción a componentes

  • Cambiar el enfoque global por un enfoque local
  • Pequeños fragmentos de HTML/CSS/JS reutilizables, enfocados en una parte concreta
<body>
  <div class="user">
    <h1>Paco</h1>
    <img src="images/paco.webp" alt="Paco">
  </div>
  <div class="user">
    <h1>Sara</h1>
    <img src="images/sara.webp" alt="Sara">
  </div>
  <!-- ... -->
</body>
<body>
  <user-component name="Paco"></user-component>
  <user-component name="Sara"></user-component>

  <script type="module">
    const user = document.querySelector("user-component");
    user.action();  // Su propio JS
    user.shadowRoot.adoptedStyleSheet // Su propio CSS
  </script>
</body>

Preguntas