Sidecar para una división de código


División de código. La división de código está en todas partes. Sin embargo, ¿por qué? Solo porque hay demasiado javascript en la actualidad, y no todos están en uso en el mismo momento.


JS es algo muy pesado . No para su iPhone Xs o su nueva computadora portátil i9, sino para millones (probablemente miles de millones) de propietarios de dispositivos más lentos . O, al menos, para tus relojes.


Entonces, JS es malo, pero lo que sucedería si lo deshabilitáramos , el problema desaparecería ... para algunos sitios y desaparecería "con sitios" para los basados ​​en React. Pero de todos modos, hay sitios que podrían funcionar sin JS ... y hay algo que deberíamos aprender de ellos ...


División de código


Hoy tenemos dos caminos por recorrer, dos maneras de mejorarlo o no empeorarlo:


1. Escribe menos código


Eso es lo mejor que puedes hacer. Si bien React Hooks le permite enviar un poco menos de código, y las soluciones como Svelte permiten generar menos código de lo habitual , eso no es tan fácil de hacer.


No se trata solo del código, sino también de la funcionalidad : para mantener el código "compacto", debe mantenerlo "compacto". No hay forma de mantener pequeño el paquete de aplicaciones si está haciendo tantas cosas (y se envió en 20 idiomas).


Hay formas de escribir código corto y sólido , y hay formas de escribir la implementación opuesta: la empresa sangrienta . Y, ya sabes, ambos son legítimos.



Pero el problema principal: el código en sí. Una aplicación de reacción simple podría pasar fácilmente por alto "recomendado" 250kb. Y puede pasar un mes optimizándolo y hacerlo más pequeño. Las optimizaciones "pequeñas" están bien documentadas y son bastante útiles: simplemente obtenga bundle-analyzer con size-limit y vuelva a ponerse en forma.
Hay muchas bibliotecas, que luchan por cada byte, tratando de mantenerlo dentro de sus límites: preact y storeon , por nombrar algunas.


Pero nuestra aplicación está un poco más allá de 200kb. Está más cerca de 100Mb . Eliminar kilobytes no tiene sentido. Incluso eliminar megabytes no tiene sentido.


Después de un momento, es imposible mantener su aplicación pequeña. Se hará más grande en el tiempo.

2. Enviar menos código


Alternativamente, code split . En otras palabras, rendirse . Tome su paquete de 100mb y haga veinte paquetes de 5mb. Honestamente, esa es la única forma posible de manejar su aplicación si es grande: cree un paquete de aplicaciones más pequeñas a partir de ella.


Pero hay una cosa que debe saber ahora: cualquier opción que elija, es un detalle de implementación, mientras buscamos algo más confiable.


La verdad sobre la división de código


La verdad sobre la división de código es que su naturaleza es SEPARACIÓN DE TIEMPO . No solo está dividiendo su código, lo está dividiendo de una manera en la que usará la menor cantidad posible en un solo punto de tiempo.


Simplemente no envíe el código que no necesita en este momento. Deshazte de eso.



Fácil de decir, difícil de hacer. Tengo algunas aplicaciones pesadas, pero no divididas adecuadamente, donde cualquier página se carga como el 50% de todo. A veces code splitting convierte en code separation , quiero decir: puede mover el código a los diferentes fragmentos, pero aún así, usarlo todo. Recuerde que "No envíe el código que no necesita en este momento" , necesitaba el 50% del código, y ese era el verdadero problema.


A veces, simplemente agregar import aquí y allá no es suficiente. Hasta que no sea la separación del tiempo , sino solo la separación del espacio , no importa en absoluto.

Hay 3 formas comunes de división de código:


  1. Solo import dinámica. Apenas usado solo en estos días. Se trata más de problemas con el seguimiento de un estado .
  2. Componente Lazy , cuando puede posponer la representación y carga de un componente React. Probablemente el 90% de la "división de código de reacción" en estos días.
  3. Lazy Library , que en realidad es .1 , pero se le dará un código de biblioteca mediante React render props. Implementado en componentes reactivos importados y componentes cargables . Muy útil, pero no muy conocido.

División de código de nivel de componente


Este es el más popular. Como división de código por ruta o división de código por componente. No es tan fácil hacerlo y, como resultado, mantener buenos resultados de percepción . Es la muerte de Flash of Loading Content .


Las buenas técnicas son:


  • cargar js chunk y data para una ruta en paralelo.
  • use un skeleton para mostrar algo similar a la página antes de cargar la página (como Facebook).
  • prefetch fragmentos, incluso puede usar guess-js para una mejor predicción.
  • use algunos retrasos, indicadores de carga, animations y Suspense (en el futuro) para suavizar las transiciones.

Y, ya sabes, todo se trata de rendimiento perceptivo .



Imagen de UX mejorada con elementos fantasma

Eso no suena bien


Sabes, podría llamarme un experto en división de código, pero tengo mis propios fallos.


A veces podría fallar en reducir el tamaño del paquete. A veces podría no mejorar el rendimiento resultante, siempre y cuando the _more_ code splitting you are introducing - the more you spatially split your page - the more time you need to _reassemble_ your page back *. Se llama ondas de carga .


  • sin SSR o pre-renderizado. SSR adecuado es un cambio de juego en este momento.


La semana pasada tengo dos fallas:


  • Perdí en una comparación de biblioteca , siempre que mi biblioteca fuera mejor, pero MUCHO más grande que otra. No he podido "1. Escribir menos código" .
  • optimizar un sitio pequeño, hecho en React por mi esposa. Estaba usando la división de componentes basada en la ruta, pero el header y el footer se mantuvieron en el paquete principal para hacer las transiciones más "aceptables". Solo unas pocas cosas, estrechamente unidas entre sí, se dispararon hacia el lado del paquete hasta 320kb (antes de gzip). No había nada importante, y nada que realmente pudiera eliminar. Una muerte por mil cortes . No he podido enviar menos código .

React-Dom fue del 20%, core-js fue del 10%, react-router, jsLingui, react-powerplug ... 20% del código propio ... Ya hemos terminado.


La solucion


Empecé a pensar en cómo resolver mi problema y por qué las soluciones comunes no funcionan correctamente para mi caso de uso.


Que hice He enumerado todas las ubicaciones cruciales, sin las cuales la aplicación no funcionaría en absoluto, e intenté entender por qué tengo el resto.

Fue una sorpresa Pero mi problema estaba en CSS. En transición CSS vainilla.


Aqui esta el codigo


  • una variable de control - componentControl , eventualmente se establecería en algo que DisplayData debería mostrar.
  • una vez que se establece el valor: DisplayData vuelve visible, cambiando className , lo que desencadena una transición elegante. Simultáneamente, FocusLock se activa haciendo que DisplayData modal .
     <FocusLock enabled={componentControl.value} // ^ it's "disabled". When it's disabled - it's dead. > {componentControl.value && <PageTitle title={componentControl.value.title}/>} // ^ it's does not exists. Also dead <DisplayData data={componentControl.value} visible={componentControl.value !== null} // ^ would change a className basing on visible state /> // ^ that is just not visible, but NOT dead </FocusLock> 

Me gustaría dividir en código esta pieza en su conjunto, pero esto es algo que no pude hacer debido a dos razones:


  1. la información debe ser visible de inmediato, una vez requerida, sin demora. Un requisito comercial.
  2. la información "cromo" debería existir antes, para la propiedad manejar la transición.

Este problema podría resolverse parcialmente mediante CSSTransitionGroup o reacondicionamiento . Pero, ya sabes, arreglar un código agregando otro código suena extraño, incluso si en realidad es suficiente . Quiero decir que agregar más código podría ayudar a eliminar aún más código. Pero ... pero ...


¡Debería haber una mejor manera!

TL; DR: hay dos puntos clave aquí:


  • DisplayData tiene que ser montado , y existe en el DOM antes.
  • FocusLock también debería existir antes, para no causar el montaje de DisplayData , pero sus cerebros no son necesarios al principio.



Así que cambiemos nuestro modelo mental


Batman y Robin


Supongamos que nuestro código es Batman y Robin. Batman puede con la mayoría de los malos, pero cuando no puede, su compañero Robin viene al rescate.


Una vez más, Batman se enfrentará a la batalla, Robin llegará más tarde.

Este es Batman:


 +<FocusLock - enabled={componentControl.value} +> - {componentControl.value && <PageTitle title={componentControl.value.title}/>} + <DisplayData + data={componentControl.value} + visible={componentControl.value !== null} + /> +</FocusLock> 

Este es su compañero, Robin ::


 -<FocusLock + enabled={componentControl.value} -> + {componentControl.value && <PageTitle title={componentControl.value.title}/>} - <DisplayData - data={componentControl.value} - visible={componentControl.value !== null} - /> -</FocusLock> 

Batman y Robin podrían formar un EQUIPO , pero en realidad son dos personas diferentes.


Y no lo olvide, todavía estamos hablando de división de código . Y, en términos de división de código, ¿dónde está el compinche? ¿Dónde está Robin?



en un sidecar Robin está esperando en un trozo de sidecar .

Sidecar


  • Batman aquí es todo lo visual que su cliente debe ver lo antes posible. Idealmente al instante.
  • Robin aquí es todo lógico y características interactivas sofisticadas, que pueden estar disponibles un segundo después, pero no desde el principio.

Sería mejor llamar a esto una división de código vertical donde existen ramificaciones de código en paralelo, en oposición a una división de código horizontal común donde se cortan ramas de código.


En algunos países , este trío era conocido como replace reducer u otras formas de lógica de reducción de carga lenta y efectos secundarios.


En algunas otras tierras , se conoce como "3 Phased" code splitting .


Es solo otra separación de preocupaciones, aplicable solo a casos, donde puede diferir la carga de una parte de un componente, pero no de otra parte.

fase 3


imagen de Building the New facebook.com con React, GraphQL y Relay , donde importForInteractions o importAfter son el sidecar .

Y hay una observación interesante : mientras Batman es más valioso para un cliente, siempre que sea algo que el cliente pueda ver , siempre está en forma ... Mientras que Robin , ya sabes, podría tener un poco de sobrepeso y requerir muchos más bytes para viviendo


Como resultado, Batman solo es algo mucho más llevadero para un cliente, proporciona más valor a un costo menor. Eres mi héroe Bat!


Lo que se podría mover a un sidecar:


  • mayoría de useEffect , componentDidMount y amigos.
  • como todos los efectos modales . Es decir, focus y scroll cerraduras. Primero puede mostrar un modal, y solo luego hacer modal modal , es decir, "bloquear" la atención del cliente.
  • Formas Mueva toda la lógica y las validaciones a un sidecar y bloquee el envío de formularios hasta que se cargue esa lógica. El cliente podría comenzar a llenar el formulario, sin saber que es solo Batman .
  • Algunas animaciones Toda una react-spring en mi caso.
  • Algunas cosas visuales. Al igual que las barras de desplazamiento personalizadas , que pueden mostrar barras de desplazamiento elegantes un segundo después.

Además, no olvide: cada pieza de código, descargada en un sidecar, también descarga cosas como core-js poly- y ponyfills, utilizadas por el código eliminado.


La división de código puede ser más inteligente de lo que es hoy en nuestras aplicaciones. Debemos darnos cuenta de que hay 2 tipos de código para dividir: 1) aspectos visuales 2) aspectos interactivos. Este último puede llegar unos momentos más tarde. Sidecar hace que sea más fácil dividir las dos tareas, dando la percepción de que todo se cargó más rápido . Y lo hará


La forma más antigua de dividir código


Si bien aún puede no estar muy claro cuándo y qué es un sidecar , daré una explicación simple:


Sidecar es TODOS TUS SCRIPTS . Sidecar es la forma en que dividimos los códigos antes de todas esas cosas frontend que obtuvimos hoy.

Estoy hablando de la Representación del lado del servidor ( SSR ), o simplemente HTML , todos estábamos acostumbrados ayer. Sidecar hace las cosas tan fáciles como solía ser cuando las páginas contenían HTML y la lógica vivían por separado en scripts externos incrustables (separación de preocupaciones).


Teníamos HTML, más CSS, más algunos scripts en línea, más el resto de los scripts extraídos en archivos .js .


HTML + CSS + inlined-js eran Batman , mientras que los scripts externos eran Robin , y el sitio podía funcionar sin Robin y, honestamente, parcialmente sin Batman (continuará la pelea con ambas piernas (scripts en línea) rotos). Eso fue ayer, y muchos sitios "no modernos y geniales" son los mismos hoy.




Si su aplicación admite SSR, intente deshabilitar js y haga que funcione sin ella. Entonces quedaría claro qué se podía mover a un sidecar.
Si su aplicación es un SPA solo del lado del cliente, intente imaginar cómo funcionaría, si existiera SSR.


Por ejemplo, theurge.com , escrito en React, es completamente funcional sin ningún js habilitado .

Hay muchas cosas que puede descargar a un sidecar. Por ejemplo:


  • comentarios Puede enviar código para display comentarios, pero no answer , siempre que requiera más código (incluido el editor WYSIWYG), que no se requiere inicialmente. Es mejor retrasar un cuadro de comentarios , o incluso simplemente ocultar la carga de código detrás de la animación, que retrasar toda una página.
  • reproductor de video Enviar "video" sin "controles". Cárguelos un segundo después, el cliente podría intentar interactuar con él.
  • galería de imágenes, como slick . No es un gran problema dibujarlo , pero es mucho más difícil de animar y administrar. Está claro qué se podría mover a un sidecar.

Solo piense en lo que es esencial para su aplicación, y lo que no es del todo ...

Detalles de implementación


(DI) División de código de componente


La forma más simple de sidecar es fácil de implementar: simplemente mueva todo a un subcomponente, puede dividir el código de una manera "antigua". Es casi una separación entre los componentes Smart y Dumb, pero esta vez Smart no contacta a uno Dumb, es lo contrario.


 const SmartComponent = React.lazy( () => import('./SmartComponent')); class DumbComponent extends React.Component { render() { return ( <React.Fragment> <SmartComponent ref={this} /> // <-- move smart one inside <TheActualMarkup /> // <-- the "real" stuff is here </React.Fragment> } } 

Eso también requiere mover el código de inicialización a uno tonto, pero aún puede dividir el código en la parte más pesada de un código.


¿Puedes ver un patrón de división de código parallel o vertical ahora?

useSidecar


La construcción del nuevo facebook.com con React, GraphQL y Relay , que ya he mencionado aquí, tenía un concepto de loadAfter o importForInteractivity , que es un concepto de sidecar bastante similar.


Al mismo tiempo, no recomendaría crear algo como useSidecar siempre y cuando intentes intencionalmente usar hooks dentro, pero la división de código en esta forma rompería la regla de los ganchos .


Prefiere una forma de componente más declarativa. Y puede usar hooks dentro del componente SideCar .


 const Controller = React.lazy( () => import('./Controller')); const DumbComponent = () => { const ref = useRef(); const state = useState(); return ( <> <Controller componentRef={ref} state={state} /> <TheRealStuff ref={ref} state={state[0]} /> </> ) } 

Captación previa


No lo olvide: puede usar sugerencias de prioridad de carga para precargar o pretratar el sidecar y hacer que el envío sea más transparente e invisible.


Cosas importantes: las secuencias de comandos de captación previa lo cargarían a través de la red , pero no se ejecutarían (y gastarían la CPU) a menos que realmente sea necesario.


SSR


A diferencia de la división de código normal , no se requiere ninguna acción especial para SSR. Sidecar podría no ser parte del proceso de SSR y no ser requerido antes del paso de hydration . Podría posponerse "por diseño".


Por lo tanto, siéntase libre de usar React.lazy (idealmente algo sin Suspense , no necesita ningún indicador de recuperación (carga) aquí), o cualquier otra biblioteca, con, pero mejor sin soporte de SSR para omitir fragmentos de sidecar durante el proceso de SSR.


Las partes malas


Pero hay algunas partes malas de esta idea.


Batman no es un nombre de producción


Si bien Batman / Robin podría ser un buen concepto mental, y el sidecar es una combinación perfecta para la tecnología en sí misma, no hay un "buen" nombre para el maincar . No existe un maincar , y obviamente Batman , Lonely Wolf , Solitude , Driver y Solo no se usarán para nombrar una parte que no sea un sidecar.


Facebook ha utilizado la display y la interactivity , y esa podría ser la mejor opción para todos nosotros.


Si tienes un buen nombre para mí, déjalo en los comentarios

Sacudida del árbol


Se trata más de la separación de las preocupaciones desde el punto de vista del paquete . Imaginemos que tienes a Batman y Robin . Y stuff.js


  • stuff.js
     export * from `./batman.js` export * from `./robin.js` 

Entonces puede intentar dividir el código del componente componente para implementar un sidecar


  • main.js


     import {batman} from './stuff.js' const Robin = React.lazy( () => import('./sidecar.js')); export const Component = () => ( <> <Robin /> // sidecar <Batman /> // main content </> ) 

  • sidecar.js


     // and sidecar.js... that's another chunk as long as we `import` it import {robin} from './stuff.js' ..... 


En resumen: el código anterior funcionaría, pero no hará "el trabajo".


  • si está utilizando solo batman de stuff.js , la sacudida del árbol solo lo mantendría.
  • si está usando solo robin de stuff.js , la sacudida del árbol solo lo mantendría.
  • pero si está utilizando ambos, incluso en diferentes fragmentos, ambos se stuff.js en una primera aparición de stuff.js , es decir, el paquete principal .

La sacudida de árboles no es amigable para la división de código. Tienes que separar las preocupaciones por archivos.

Des-importar


Otra cosa, olvidada por todos, es el costo de javascript. Era bastante común en la era jQuery, la era de la carga útil de jsonp cargar el script (con json payload), obtener la carga útil y eliminar el script.


Hoy en día todos import script, y se importará para siempre, incluso si ya no es necesario.

Como dije antes, hay demasiado JS, y tarde o temprano, con la navegación continua , cargará todo. Deberíamos encontrar una manera de eliminar la importación que ya no necesite trozos, borrando todos los cachés internos y liberando memoria para hacer que la web sea más confiable, y no para aplastar la aplicación con excepciones de falta de memoria.


Probablemente, la capacidad de un-import (webpack podría hacerlo ) es una de las razones por las que debemos seguir con la API basada en componentes , siempre y cuando nos brinde la capacidad de manejar el unmount .


Hasta ahora, los estándares de los módulos ESM no tienen nada que ver con cosas como esta, ni con el control de caché ni con la inversión de la acción de importación.


Crear una biblioteca habilitada para sidecar


Hoy en día solo hay una forma de crear una biblioteca habilitada para sidecar :


  • divide tu componente en partes
  • exponer una parte main y una parte connected (para no romper la API) a través del index
  • exponer un sidecar través de un punto de entrada separado.
  • en el código de destino - importe la parte main y el sidecar - la sacudida del árbol debería cortar una parte connected .

Esta vez, la sacudida del árbol debería funcionar correctamente, y el único problema es cómo nombrar la parte main .


  • main.js

 export const Main = ({sidecar, ...props}) => ( <div> {sidecar} .... </div> ); 

  • connected.js

 import Main from './Component'; import Sidecar from './Sidecar'; export const Connected = props => ( <Main sidecar={<Sidecar />} {...props} /> ); 

  • index.js

 export * from './Main'; export * from './Connected'; 

  • sidecar.js

 import * from './Sidecar'; 

En resumen, el cambio podría representarse mediante una pequeña comparación


 //your app BEFORE import {Connected} from 'library'; // // ------------------------- //your app AFTER, compare this core to `connected.js` import {Main} from 'library'; const Sidecar = React.lazy(import( () => import('library/sidecar'))); // ^ all the difference ^ export SideConnected = props => ( <Main sidecar={<Sidecar />} {...props} /> ); // ^ you will load only Main, Sidecar will arrive later. 

La dynamic import teóricamente dynamic import podría usarse dentro de node_modules, haciendo que el proceso de ensamblaje sea más transparente.


De todos modos, no es más que un patrón de children / slot , tan común en React.

El futuro


Facebook demostró que la idea es correcta. Si no has visto ese video, hazlo ahora mismo. Acabo de explicar la misma idea desde un ángulo un poco diferente (y comencé a escribir este artículo una semana antes de la conferencia F8).


En este momento requiere que se apliquen algunos cambios de código a su base de código. Se requiere una separación más explícita de las preocupaciones para separarlas realmente, y dejar que el código se divida no horizontalmente, sino verticalmente, enviando código menor para una mayor experiencia del usuario.


Sidecar , probablemente, es la única forma, excepto la SSR de la vieja escuela, de manejar bases de códigos GRANDES. Última oportunidad de enviar una cantidad mínima de código, cuando tienes mucho.


Podría hacer una aplicación GRANDE más pequeña, y una aplicación PEQUEÑA aún más pequeña.

Hace 10 años, el sitio web mediano estaba "listo" en 300 ms, y estaba realmente listo unos pocos milisegundos después. Hoy segundos e incluso más de 10 segundos son los números comunes. Que pena


Hagamos una pausa y pensemos: cómo podríamos resolver el problema y hacer que UX vuelva a ser genial ...



En general


  • La división de código de componente es una herramienta muy poderosa, que le brinda la capacidad de dividir algo por completo , pero tiene un costo: es posible que no muestre nada excepto una página en blanco o un esqueleto por un tiempo. Esa es una separación horizontal.
  • La división del código de la biblioteca podría ayudar cuando la división de componentes no lo haría. Esa es una separación horizontal.
  • El código, descargado en un sidecar, completaría la imagen y puede permitirle proporcionar una experiencia de usuario mucho mejor. Pero también requeriría un esfuerzo de ingeniería. Esa es una separación vertical.

Tengamos una conversación sobre esto .


Para! Entonces, ¿qué pasa con los problemas que trataste de resolver?


Bueno, eso fue solo la primera parte. Estamos en el final del juego ahora , tomaría algunas semanas más escribir la segunda parte de esta propuesta. Mientras tanto ... ¡sube al sidecar!

Source: https://habr.com/ru/post/450942/


All Articles