
💛



El frontenD
FUTURO
del
starring
MIGUEL ANGEL DURAN
AS
ENABLER FRONTEND

@midudev
youtube/c/midudev

AT

@midudev
youtube/c/midudev

"Nunca pienso en el futuro. Llega demasiado pronto"
@midudev

@midudev


2025!!!!

@midudev
@midudev





🧸

@midudev



@midudev


@midudev
PERO ALGO HABRÁ...



MENOS BUNDELIZADORES 📦

@midudev
<!-- 📄 index.html -->
<body>
<h1>My awesome website</h1>
<script src='./utils.js'></script>
<script src='./components.js'></script>
<script src='./index.js'></script>
</body>
@midudev
import module from './archivo.js'
🧩
// 👋 EcmaScript Modules!
@midudev
✴️ ¿Cómo empiezo a usarlo?
<!-- 📄 index.html -->
<body>
<h1>My awesome website</h1>
<script src='./utils.js'></script>
<script src='./components.js'></script>
<script src='./index.js'></script>
</body>
<script type="module" src="index.js"></script>
<!-- soporta a los navegadores antiguos 👴 -->
<script nomodule src="fallback.js"></script>
@midudev
// 📁 index.js
import {repetir} from './lib.js'
import gritar from './voz.js'
repetir('hello')
// → 'hello hello'
gritar('Los módulos nativos molan!')
// → 'LOS MÓDULOS NATIVOS MOLAN!'
// 🧩 lib.js
export const repetir = string => `${string} ${string}`
// 🧩 voz.js
export default string => `${string.toUpperCase()}!`


@midudev

📥 peticiones
// 📁 index.js
import {repetir} from './lib.js'
import gritar from './voz.js'
<link
rel="preload"
href="./no-module-script.js"
>
¡Haz preload!

<link
rel="modulepreload"
href="./app.mjs"
>
📥 peticiones con modulepreload
<!-- 📝 index.html -->
<link rel="modulepreload" href="./lib.js">
<link rel="modulepreload" href="./voz.js">
<script type="module" src="./index.js"></script>

<!-- 📄 index.html -->
<body>
<h1>My awesome website with React ⚛️</h1>
<script src="https://unpkg.com/react@16/umd/react.js">
</script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.js">
</script>
<script src='./index.js'></script>
</body>
🌍
@midudev
import { html, Component, render } from 'https://unpkg.com/htm/preact/standalone.module.js'
class App extends Component {
componentDidMount() {
fetch('https://the-news.midudev.now.sh/statics/papas.json')
.then(res => res.json())
.then(papas => {
this.setState({ papas })
this.randomPapa()
})
}
randomPapa = () => {
const { papas, papas: { length } } = this.state
this.setState({
papa: papas[Math.floor(Math.random() * length)]
})
}
render = (_, { papa }) =>
html`
<${Papa} onClick=${() => this.randomPapa()} ...${papa} />
`
}
const Papa = ({ onClick, original, title }) =>
html`<div onClick=${onClick}>
<img width="250" src=${original} />
<p>${title}</p>
</div>`
render(html`<${App} />`, document.body)
import { html, Component, render } from 'https://unpkg.com/htm/preact/standalone.mjs'
🌍
@midudev

pika.dev








@pika/web

📥 peticiones de imports externos
// 📁 index.js
import { html, Component, render }
from 'https://unpkg.com/htm/preact/standalone.mjs'
import daet
from 'https://cdn.pika.dev/-/daet/1.2.0/daet.min.mjs'

import('./heavy-stuff-with-modal.js')
📡
.then(module => console.log('module is loaded!'))
// 👋 Dynamic Import!
@midudev
// 📁 main.js
button.addEventListener('click', e => {
e.preventDefault()
import('./heavy-stuff-with-modal.js')
.then(module => module.init())
})
// 📍 router.js
// even with dynamic names!
const page = 'home'
router.onnavigate = () => {
import(`/pages/${page}.js`)
.then(module => module.init())
}
@midudev

the router example
@midudev

¡Algunas consideraciones!
@midudev

📦



⚠️

84%
77%

ECMASCript Modules
Dynamic Import



ECMASCript Modules
Dynamic Import
🚀
👾
🚀


MÁS WEB COMPONENTS
PERO NO EN NUESTRO CÓDIGO
@midudev

¡El aguagua!
@midudev


aprende
y frontend

Bootcamps del futuro
@midudev








629 euros
<kitten-image></kitten-image>
@midudev

// ☕️ kitten.js
class KittenImage extends HTMLElement { ... }
window.customElements.define('kitten-image', KittenImage)
@midudev
<!-- 📄 index.html -->
<script src='kitten.js'></script>
<kitten-image></kitten-image>
@midudev

// ☕️ kitten.js
class KittenImage extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" })
this.shadowRoot.innerHTML = `
<style>
img {
border: 2px solid red;
border-radius: 3px;
overflow: hidden:
padding: 6px;
}
</style>
<img src='https://placekitten.com/200/300' />`
}
}
window.customElements.define("kitten-image", KittenImage)
@midudev


El resultado en el navegador 😺
EXPECTATIVAS
REALIDAD

@midudev



🏗
Las capas de abstracción
📰 los atributos sólo son strings
🤷♂️ mucho boilerplate

a pelo
✍️ los eventos y su limpieza a manija
🔨 frágil control de colisión de nombres
💩 el cachondeo de las versiones
sin estado, reactividad limitada, escritura en el DOM...

=
🧱
para crear mejores abstracciones
o compiladores
conseguir apps más livianas
y performantes ⚡️
// ⚛️ Nuestro propio React usando Web Components!
export class Component extends HTMLElement {
constructor () {
super()
this.attachShadow({ mode: 'open' })
const styles = typeof this.styles === 'function'
? `<style>${this.styles()}</style>`
: ''
this.shadowRoot.innerHTML = `${styles}<slot id="render"></slot>`
this.state = typeof this.getInitialState === 'function'
? this.getInitialState()
: {}
this._render({
attrs: this.getAllAttributes(),
state: this.state
})
}



@midudev
¿LA MUERTE DE LAS LIBRERÍAS?



☠️
@midudev


@midudev

¡show time!
@midudev
MÁS EXTENSIBLE
EL FRONTEND DEL FUTURO ES
@midudev
import storage from 'std:kv-storage'
⚒️
import lodash from 'lodash'
import utilidad from 'std:<librería-interna>'
@midudev
'std:kv-storage'
es como localStorage 🗄 pero...
🔀 es totalmente asíncrono
🎯 guarda todo tipo de datos
👋 adiós JSON.parse
🧰 métodos: keys(), values() y entries()
@midudev
// app.js
import storage from 'std:kv-storage'
const main = async () => {
const oldPreferences = await storage.get('preferences')
document.querySelector('form')
.addEventListener('submit', async () => {
const newPreferences = {...oldPreferences, {
// Tus nuevas opciones
})
await storage.set('preferences', newPreferences);
})
}
main()
@midudev

ver herramientas
de desarrollo

// y si no existe...?
import storage from 'std:kv-storage'
@midudev
🗺️
<script type="importmap">
~ alias de Webpack
<!-- Script para crear los imports mapeados -->
<script type="importmap">
{
"imports": {
"std:kv-storage": [
// primero intenta este
"std:kv-storage",
// si no está disponible, usa este polyfill externo
"https://unpkg.com/kv-storage-polyfill?module"
]
}
}
</script>
<script type="module">
import storage from 'std:kv-storage'
/* Ya podemos usar `storage`... */
</script>
🗺️
import { Component } from '../main/Component.js'
import { Component } from 'miduReact'
⚛️ 🗺️


Y para que lo pruebes tú..
Para usar
<script type="importmap">
y
import storage from 'std:kv-storage'
Tienes que activar los flags 🚩 de Chrome

Extendiendo CSS con Houdini 🎩
// ⚙️ paint-modules.js
registerPaint('slanted-bg', class {
paint (ctx, geom, properties, args) {
// do magic with the canvas in ctx
}
})
/* 🎨 styles.css */
span {
background: paint(slanted-bg)
}

<!-- 📄 index.html -->
<script>
CSS.paintWorklet.addModule('/paint-modules.js')
</script>


MÁS NATIVO
EL FRONTEND DEL FUTURO ES
Lazy Load nativo⚡
<!-- 📄 index.html -->
<!-- solo la carga cuando esté cerca -->
<img src="/statics/articles/1.webp" loading="lazy">
<!-- cargará esta imagen sin importar dónde está -->
<img src="/statics/articles/2.webp" loading="eager">
<!-- deja al navegador decidir si la carga o no -->
<img src="/statics/articles/3.webp" loading="auto">

¡demo time!
<!-- desde HTML -->
<portal src='https://jsdaycanarias.com/'></portal>
<script>
// desde Javascript
const portal = document.createElement('portal')
portal.src = 'https://jsdaycanarias.com/'
document.body.appendChild(portal)
</script>
Portales

<iframe> con vitaminas 💊

Picture In Picture 📺
button.addEventListener("click", async () => {
let isVideoInPip = false
// first, check if video is the pictureInPicture
if (document.pictureInPictureElement === video) {
isVideoInPip = true
}
// if the video is in Pip, then exit the PiP,
// if not activate it
await (isVideoInPip
? document.exitPictureInPicture()
: video.requestPictureInPicture()
).catch(console.error)
})
Picture In Picture 📺

¡demo time!
Web Platform 🏗
¡Más, más, más!
🕵️♂️ FaceDetector
🧾 BarcodeDetector
📄 TextDetector
📻 navigator.mozFMRadio
💳 PaymentRequest
🎤 SpeechRecognition
📆 Intl.RelativeTimeFormat
🛣 IntersectionObserver
Algunos ejemplos 🏗
🗣 SpeechSynthesis
🦠 MutationObserver
⚡ PerformanceObserver
💡 AmbientLightSensor
📐 ResizeObserver
📆 Intl.RelativeTimeFormat
🛣 Intersection Observer
Algunos ejemplos 🏗

demo!
CSS Scroll Snap 👌
.slider {
/* ... resto de propiedades */
scroll-snap-type: x mandatory;
}
.slider img {
/* ... resto de propiedades */
scroll-snap-align: center;
}

.slider {
scroll-snap-type: x mandatory;
}
.slider img {
scroll-snap-align: center;
}

!function(e,n){if("function"==typeof define&&define.amd)define(["exports"],n);else if("undefined"!=typeof exports)n(exports);else{var t={exports:{}};n(t.exports),e.simpleslider=t.exports}}(this,function(e){"use strict";function n(e,n){return null==e?n:e}function t(e){return e.length}function i(e,n,i,o,r,u){var d=void 0,c=[];n||(n=e.children);for(var f=t(n);--f>=0;)c[f]=n[f],d=c[f].style,d.position="absolute",d.top=d.left=d.zIndex=0,d[u]=o+i;return d[u]=r+i,d.zIndex=1,c}function o(e,n,t,i){return(e/=i/2)<1?t/2*e*e*e+n:t/2*((e-=2)*e*e+2)+n}function r(e){function r(){b=F(),g=setTimeout(function(){b=F(),T=C,s(v()),r()},T)}function u(){d()&&(g&&clearTimeout(g),r())}function d(){return!j&&t(z)>1}function c(){d()&&(T=C-(F()-b),clearTimeout(g),g=0)}function f(){var e=M;M=_,_=e,I=Math.abs(I-(t(z)-1)),z=z.reverse()}function s(e){for(var n=t(z);--n>=0;)z[n].style.zIndex=1;z[e].style.zIndex=3,z[I].style.zIndex=2,y(z[I].style,S,_,z[e].style,M,S,q,0,0,k),I=e,A&&A(p(),I)}function l(){s(v()),u()}function a(){s(p()),u()}function v(){var e=I+1;return e>=t(z)?0:e}function p(){var e=I-1;return e<0?t(z)-1:e}function x(){clearTimeout(g),document.removeEventListener("visibilitychange",h),z=w=g=E=q=C=M=_=j=I=T=A=D=null}function m(){return I}function y(e,n,t,i,o,r,u,d,c,f){function s(e,n,t){e[E]=f(c-d,n,t-n,u)+L}if(d>0){if(!(c-d<u))return e[E]=t+L,i[E]=r+L,void(D&&D(I,v()));s(e,n,t),s(i,o,r)}requestAnimationFrame(function(c){0===d&&(d=c),y(e,n,t,i,o,r,u,d,c,f)})}function h(){document.hidden?c():u()}e=e||{};var I=void 0,g=void 0,b=void 0,z=void 0,T=void 0,w=n(e.container,document.querySelector("*[data-simple-slider]")),E=n(e.prop,"left"),q=1e3*n(e.duration,.5),C=1e3*n(e.delay,3),L=n(e.unit,"%"),M=n(e.init,-100),S=n(e.show,0),_=n(e.end,100),j=e.paused,k=n(e.ease,o),A=n(e.onChange,0),D=n(e.onChangeEnd,0),F=Date.now;return document.addEventListener("visibilitychange",h),function(){if(t(w.children)>0){var n=w.style;n.position="relative",n.overflow="hidden",n.display="block",z=i(w,e.children,L,M,S,E),I=0,T=C}}(),z&&t(z)>1&&u(),{currentIndex:m,pause:c,resume:u,nextIndex:v,prevIndex:p,next:l,prev:a,change:s,reverse:f,dispose:x}}Object.defineProperty(e,"__esModule",{value:!0}),e.getSlider=r});

CSS Scroll Snap 👌

¡demo time!
EL FUTURO DEL CSS 🤖🎨
.article {
& .content {
color: #333;
font-size: 18px;
}
}
@media (width < 480px) {}
@media (480px <= width < 768px) {}
@media (width >= 768px) {}
a:has(> .title) {
background: red;
}
@custom-selector :--headings h1, h2, h3, h4
article :--heading { color: tomato };

body {
margin: 0;
overscroll-behavior: none;
}




EL FUTURO DEL CSS 🤖🎨




¿ES LA MUERTE DE SASS?
EL JAVASCRIPT QUE NECESITAS ☕️
Record
objetos inmutables
const record = #{ a: 1, b: 2, c: 3 }
const fromObject = Record.from({ a: 1, b: 2, c: 3 })
record === sameRecord // true!
const withSpread = #{ ...record, b: 5 }
const string = JSON.stringify(record)
// { "a": 1, "b": 2, "c": 3 }
Tuples
arrays inmutables
const tuple = #[1, 2, 3]

EL JAVASCRIPT QUE NECESITAS ☕️
const doubleSay = str => `${str}, ${str}`
const capitalize = str => str[0].toUpperCase() + str.substring(1)
const shout = str => str + '!'
const message = doubleSay(capitalize(shout('hola canarias')))
console.log(message)
// Hola canarias!, Hola canarias!
Pipeline Operator ▷
const message = 'hola canarias'
|> doubleSay
|> capitalize
|> shout

NUEVAS TENDENCIAS
EL FRONTEND DEL FUTURO VIENE CON
MOBILE ONLY 📱

CLIENT SIDE RENDERING






GOOGLE BOT FUTURE EDITION 🤖
🧩 JS Modules
📐 Intersection Observer
🧱 Custom Elements
🌚 Shadow DOM
🏫 Classes
🏷 Tagged Template Literals


INTERNET OF THINGS

INTERNET WEB OF THINGS


npm install webthing
LA WEB ES LA PLATAFORMA
El frontenD
FUTURO
del
👈 Biff

Nuestro Technology Almanac 👩💻
SIEMPRE.
APUESTA POR LA WEB.
SIEMPRE.
🌍


¡sígueme para más frontend!

@midudev

GRACIAS!
!

JSDayCanarias - "El frontend del futuro"
By Miguel Angel Durán García
JSDayCanarias - "El frontend del futuro"
- 455