#FrontendFuturoAdevinta
@midudev
youtube/c/midudev
@midudev
youtube/c/midudev
"Nunca pienso en el futuro. Llega demasiado pronto"
2025!!!!
PERO ALGO HABRÁ...
MENOS BUNDELIZADORES 📦
<!-- 📄 index.html -->
<body>
<h1>My awesome website</h1>
<script src='./utils.js'></script>
<script src='./components.js'></script>
<script src='./index.js'></script>
</body>
import module from './archivo.js'
🧩
// 👋 EcmaScript Modules!
// 📁 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 function(string) {
return `${string.toUpperCase()}!`
}
📥 peticiones
// 📁 index.js
import {repetir} from './lib.js'
import gritar from './voz.js'
<!-- 📄 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>
🌍
import { html, Component, render } from 'https://unpkg.com/htm/preact/standalone.mjs'
class App extends Component {
addTodo() {
const { todos = [] } = this.state
this.setState({ todos: todos.concat(`Item ${todos.length}`) })
}
render({ page }, { todos = [] }) {
return html`
<div class="app">
<${Header} name="ToDo's (${page})" />
<ul>
${todos.map(todo => html`
<li>${todo}</li>
`)}
</ul>
<button onClick=${() => this.addTodo()}>Add Todo</button>
<${Footer}>footer content here<//>
</div>
`;
}
}
import { html, Component, render } from 'https://unpkg.com/htm/preact/standalone.mjs'
🌍
📥 peticiones
// 📁 index.js
import { html, Component, render }
from 'https://unpkg.com/htm/preact/standalone.mjs'
import('./heavy-stuff-with-modal.js')
📡
.then(module => console.log('module is loaded!'))
// 👋 Dynamic Import!
// 📁 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())
}
✴️ ¿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>
¡Consideraciones!
📦
84%
77%
ECMASCript Modules
Dynamic Import
ECMASCript Modules
Dynamic Import
🚀
👾
🚀
MÁS WEB COMPONENTS
PERO TAMPOCO TANTOS...
aprende
y frontend
629 euros
<kitten-image></kitten-image>
// ☕️ kitten.js
class KittenImage extends HTMLElement { ... }
window.customElements.define('kitten-image', KittenImage)
<!-- 📄 index.html -->
<script src='kitten.js'></script>
<kitten-image></kitten-image>
// ☕️ 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)
El resultado en el navegador 😺
EXPECTATIVAS
REALIDAD
// ⚛️ Mi 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
})
}
¿LA MUERTE DE LAS LIBRERÍAS?
☠️
¡show time!
MÁS EXTENSIBLE
EL FRONTEND DEL FUTURO ES
import {storage} from 'std:kv-storage'
⚒️
import lodash from 'lodash'
import utilidad from 'std:<librería-interna>'
// 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()
// y si no existe...?
import {storage} from 'std:kv-storage'
🗺️
<script type="importmap">
<!-- 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'
⚛️ 🗺️
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!
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 🏗
🕵️♂️ FaceDetector
🧾 BarcodeDetector
📄 TextDetector
📻 navigator.mozFMRadio
💳 PaymentRequest
🗣 SpeechRecognition
📆 Intl.RelativeTimeFormat
🛣 Intersection Observer
Algunas 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;
}
¿LA MUERTE DE LOS PREPROCESADORES?
NUEVAS TENDENCIAS
EL FRONTEND DEL FUTURO SIGUE LAS
MOBILE ONLY 📱
CLIENT SIDE RENDERING
GOOGLE BOT FUTURE EDITION 🤖
🧩 JS Modules
📐 Intersection Observer
🧱 Custom Elements
🌚 Shadow DOM
🏫 Classes
🏷 Tagged Template Literals
PROGRESSIVE WEB APP
INTERNET OF THINGS
INTERNET WEB OF THINGS
WEB OF THINGS
LA WEB ES LA PLATAFORMA
👈 Biff
Nuestro Technology Almanac 👩💻
SIEMPRE.
APUESTA POR LA WEB.
SIEMPRE.
🌍
¡sígueme para más frontend!
@midudev
slides
app
código fuente