#FrontendFuturoAdevinta

starring

MIGUEL ANGEL DURAN

AS

ENABLER FRONTEND

@midudev

youtube/c/midudev

AT

@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!

slides

app

código fuente

Made with Slides.com