💛

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

👈 Biff

Nuestro Technology Almanac 👩‍💻

SIEMPRE.

APUESTA POR LA WEB.

SIEMPRE.

🌍

¡sígueme para más frontend!

@midudev

JSDayCanarias - "El frontend del futuro"

By Miguel Angel Durán García

JSDayCanarias - "El frontend del futuro"

  • 39

More from Miguel Angel Durán García