Optimización de una aplicación Vue

 

 

 

  • Design System Planning and Process, with Nathan Curtis
  • Creating and Maintaining Successful Design Systems, with Brad Fost

  • Índice
    1. Elegir un marco
    2. Sacudida, compresión y minificación de árboles con herramientas de construcción
    3. API de composición de Vue
    4. Importando dependencias
    5. División de código con importaciones dinámicas
    6. Rutas de carga diferida con Vue Router
    7. Componentes asíncronos
    8. División de solicitudes de API
      1. CSS
      2. Refinando el estado de carga
    9. Stale While Revalidate
      1. Indicador de actualización
    10. Conclusión
      1. Lecturas adicionales sobre la revista Smashing

    Dar prioridad al rendimiento al crear nuestras aplicaciones web mejora la experiencia del usuario y ayuda a garantizar que puedan ser utilizadas por la mayor cantidad de personas posible. En este artículo, Michelle Barker le explicará algunos de los consejos de optimización del front-end para mantener nuestras aplicaciones Vue lo más eficientes posible.

     

    Las aplicaciones de página única (SPA) pueden proporcionar una experiencia de usuario rica e interactiva cuando se trata de datos dinámicos en tiempo real. Pero también pueden ser pesados, hinchados y funcionar mal. En este artículo, analizaremos algunos de los consejos de optimización de front-end para mantener nuestras aplicaciones Vue relativamente eficientes y solo enviar el JS que necesitamos cuando sea necesario.

    Nota : Se supone cierta familiaridad con Vue y la API de composición, pero es de esperar que haya algunas conclusiones útiles independientemente del marco que elija.

     

    Como desarrollador front-end en Ada Mode , mi trabajo consiste en crear Windscope, una aplicación web para que los operadores de parques eólicos administren y mantengan su flota de turbinas. Debido a la necesidad de recibir datos en tiempo real y el alto nivel de interactividad requerido, se optó por una arquitectura SPA para el proyecto. Nuestra aplicación web depende de algunas bibliotecas JS pesadas, pero queremos brindar la mejor experiencia al usuario final al obtener datos y renderizarlos de la manera más rápida y eficiente posible.

    Elegir un marco

    Nuestro marco JS preferido es Vue, elegido en parte porque es el marco con el que estoy más familiarizado. Anteriormente, Vue tenía un tamaño de paquete general más pequeño en comparación con React. Sin embargo, desde las recientes actualizaciones de React, la balanza parece haberse inclinado a favor de React. Eso no necesariamente importa, ya que veremos cómo importar solo lo que necesitamos en el transcurso de este artículo. Ambos marcos tienen una documentación excelente y un gran ecosistema de desarrolladores, lo cual fue otra consideración. Svelte es otra posible opción, pero habría requerido una curva de aprendizaje más pronunciada debido a la falta de familiaridad y, al ser más nuevo, tiene un ecosistema menos desarrollado.

    Como ejemplo para demostrar las diversas optimizaciones, creé una aplicación Vue simple que obtiene datos de una API y representa algunos gráficos usando D3.js.

    ( Vista previa grande )

    Nota : consulte el repositorio de GitHub de ejemplo para obtener el código completo.

    Estamos usando Parcel , una herramienta de compilación de configuración mínima, para agrupar nuestra aplicación, pero todas las optimizaciones que cubriremos aquí son aplicables al paquete que elija.

    Sacudida, compresión y minificación de árboles con herramientas de construcción

    Es una buena práctica enviar solo el código que necesita y, nada más sacarlo de la caja, Parcel elimina el código Javascript no utilizado durante el proceso de compilación (sacudida del árbol). También minimiza el resultado y se puede configurar para comprimir la salida con Gzip o Brotli.

    Además de la minificación, Parcel también emplea elevación de alcance como parte de su proceso de producción, lo que puede ayudar a que la minificación sea aún más eficiente. Una guía detallada sobre el levantamiento del alcance está fuera del alcance (¿ves lo que hice allí?) de este artículo. Aún así, si ejecutamos el proceso de compilación de Parcel en nuestra aplicación de ejemplo con las banderas --no-optimizey --no-scope-hoist, podemos ver que el paquete resultante es de 510 kB, aproximadamente 5 veces mayor que la versión optimizada y minimizada. Entonces, cualquiera que sea el paquete que esté utilizando, es justo decir que probablemente querrá asegurarse de que esté realizando tantas optimizaciones como sea posible.

    Pero el trabajo no termina aquí. Incluso si enviamos un paquete más pequeño en general, el navegador aún necesita tiempo para analizar y compilar nuestro JS, lo que puede contribuir a una experiencia de usuario más lenta. Este artículo sobre la optimización del tamaño de los paquetes de Calibre explica cómo los paquetes JS grandes afectan las métricas de rendimiento.

     

    Veamos qué más podemos hacer para reducir la cantidad de trabajo que tiene que hacer el navegador.

    API de composición de Vue

    Vue 3 introdujo la API de composición , un nuevo conjunto de API para crear componentes como alternativa a la API de opciones. Al utilizar exclusivamente la API de composición, podemos importar solo las funciones de Vue que necesitamos en lugar del paquete completo. También nos permite escribir más código reutilizable usando elementos componibles . El código escrito con la API de composición se presta mejor a la minificación y toda la aplicación es más susceptible a las sacudidas de los árboles.

    Nota : aún puedes usar la API de composición si estás usando una versión anterior de Vue: se actualizó a Vue 2.7 y hay un complemento oficial para versiones anteriores.

    Importando dependencias

    Un objetivo clave era reducir el tamaño del paquete JS inicial descargado por el cliente. Windscope hace un uso extensivo de D3 para la visualización de datos, una gran biblioteca y un amplio alcance. Sin embargo, Windscope sólo necesita una parte (hay módulos completos en la biblioteca D3 que no necesitamos en absoluto). Si examinamos todo el paquete D3 en Bundlephobia , podemos ver que nuestra aplicación utiliza menos de la mitad de los módulos disponibles y quizás ni siquiera todas las funciones dentro de esos módulos.

    Una de las formas más sencillas de mantener el tamaño de nuestro paquete lo más pequeño posible es importar únicamente los módulos que necesitamos.

    Tomemos selectAllla función de D3. En lugar de utilizar una importación predeterminada, podemos simplemente importar la función que necesitamos desde el d3-selectionmódulo:

    // Previous:import * as d3 from 'd3'// Instead:import { selectAll } from 'd3-selection'

    División de código con importaciones dinámicas

    Hay ciertos paquetes que se utilizan en varios lugares de Windscope, como la biblioteca de autenticación de AWS Amplify, específicamente el Authmétodo. Esta es una gran dependencia que contribuye en gran medida al tamaño de nuestro paquete JS. En lugar de importar el módulo estáticamente en la parte superior del archivo, las importaciones dinámicas nos permiten importar el módulo exactamente donde lo necesitamos en nuestro código.

    En lugar de:

    import { Auth } from '@aws-amplify/auth'const user = Auth.currentAuthenticatedUser()

    Podemos importar el módulo cuando queramos usarlo:

    import('@aws-amplify/auth').then(({ Auth }) = { const user = Auth.currentAuthenticatedUser()})

    Esto significa que el módulo se dividirá en un paquete JS separado (o "fragmento"), que el navegador solo descargará cuando sea necesario. Además, el navegador puede almacenar en caché estas dependencias, que pueden cambiar con menos frecuencia que el código del resto de nuestra aplicación.

     

    Rutas de carga diferida con Vue Router

    Nuestra aplicación utiliza Vue Router para la navegación. De manera similar a las importaciones dinámicas, podemos cargar de forma diferida nuestros componentes de ruta, por lo que solo se importarán (junto con sus dependencias asociadas) cuando un usuario navegue a esa ruta.

    En nuestro index/router.jsarchivo:

    // Previously:import Home from "../routes/Home.vue";import About = "../routes/About.vue";// Lazyload the route components instead:const Home = () = import("../routes/Home.vue");const About = () = import("../routes/About.vue");const routes = [ { name: "home", path: "/", component: Home, }, { name: "about", path: "/about", component: About, },];

    El código para la ruta "Acerca de" solo se cargará cuando el usuario haga clic en el enlace "Acerca de" y navegue hasta la ruta.

    Componentes asíncronos

    Además de realizar una carga diferida de cada ruta, también podemos realizar una carga diferida de componentes individuales utilizando el defineAsyncComponentmétodo de Vue.

    const KPIComponent = defineAsyncComponent(() = import('../components/KPI.vue))

    Esto significa que el código para el componente KPI se importará dinámicamente, como vimos en el ejemplo del enrutador. También podemos proporcionar algunos componentes para mostrar mientras está en estado de carga o de error (útil si estamos cargando un archivo particularmente grande). Blog sobre gatos

    const KPIComponent = defineAsyncComponent({ loader: () = import('../components/KPI.vue), loadingComponent: Loader, errorComponent: Error, delay: 200, timeout: 5000,});

    División de solicitudes de API

    Nuestra aplicación se ocupa principalmente de la visualización de datos y depende en gran medida de la obtención de grandes cantidades de datos del servidor. Algunas de estas solicitudes pueden ser bastante lentas, ya que el servidor tiene que realizar una serie de cálculos con los datos. En nuestro prototipo inicial, realizamos una única solicitud a la API REST por ruta. Desafortunadamente, descubrimos que esto hacía que los usuarios tuvieran que esperar mucho tiempo, a veces hasta 10 segundos, mirando un control giratorio de carga antes de que la aplicación recibiera correctamente los datos y pudiera comenzar a representar las visualizaciones.

    Tomamos la decisión de dividir la API en varios puntos finales y realizar una solicitud para cada widget. Si bien esto podría aumentar el tiempo de respuesta general , significa que la aplicación debería poder usarse mucho más rápido, ya que los usuarios verán partes de la página representadas mientras todavía esperan otras. Además, cualquier error que pueda ocurrir se localizará mientras el resto de la página sigue siendo utilizable.

    Puedes ver la diferencia ilustrada aquí:

    llamado WidgetLoader, que puede ver en el repositorio.

    Este patrón se puede extender a cualquier lugar de la aplicación donde se representa un componente tras la interacción del usuario. Por ejemplo, en Windscope, cargamos un componente de mapa (y sus dependencias) solo cuando el usuario hace clic en la pestaña "Mapa". Esto se conoce como Importación en interacción .

     

    CSS

    Si ejecuta el código de ejemplo, verá que al hacer clic en el enlace de navegación 'Ubicaciones' se carga el componente del mapa. Además de importar dinámicamente el módulo JS, importar la dependencia dentro del stylebloque del componente también cargará el CSS de forma diferida:

    // In MapView.vuestyle@import "../../node_modules/leaflet/dist/leaflet.css";.map-wrapper { aspect-ratio: 16 / 9;}/style

    Refinando el estado de carga

    En este punto, nuestras solicitudes de API se ejecutan en paralelo y los componentes se procesan en diferentes momentos. Una cosa que podríamos notar es que la página parece desordenada, ya que el diseño cambiará bastante.

    interface, which enables us to abort API requests as desired.

    In our setup function, we create a new controller and pass its signal into our fetch request parameters:

    setup(props) { const controller = new AbortController(); const loadComponent = () = { return fetch(url, { signal: controller.signal }) .then((response) = response.json()) .then((response) = (data.value = response)) .then(importFunction) .catch((e) = console.error(e)) .finally(() = (loading.value = false)); };}

    Then we abort the request before the component is unmounted, using Vue’s onBeforeUnmount function:

    onBeforeUnmount(() = controller.abort());

    If you run the project and navigate to another page before the requests have been completed, you should see errors logged in the console stating that the requests have been aborted.

    Stale While Revalidate

    So far, we’ve done a pretty good of optimizing our app. But when a user navigates to the second view and then back to the previous one, all the components remount and are returned to their loading state, and we have to wait for the request responses all over again.

    Stale-while-revalidate is an HTTP cache invalidation strategy where the browser determines whether to serve a response from the cache if that content is still fresh or “revalidate” and serve from the network if the response is stale.

    In addition to applying cache-control headers to our HTTP response (out of the scope of this article, but read this article from Web.dev for more detail), we can apply a similar strategy to our Vue component state, using the SWRV library.

    First, we must import the composable from the SWRV library:

    import useSWRV from "swrv";

    Then we can use it in our setup function. We’ll rename our loadComponent function to fetchData, as it will only deal with data fetching. We’ll no longer import our component in this function, as we’ll take care of that separately.

    We’ll pass this into the useSWRV function call as the second argument. We only need to do this if we need a custom function for fetching data (maybe we need to update some other pieces of state). As we’re using an Abort Controller, we’ll do this; otherwise, the second argument can be omitted, and SWRV will use the Fetch API:

     

    // In setup()const { url, importFunction } = props;const controller = new AbortController();const fetchData = () = { return fetch(url, { signal: controller.signal }) .then((response) = response.json()) .then((response) = (data.value = response)) .catch((e) = (error.value = e));};const { data, isValidating, error } = useSWRV(url, fetchData);

    Then we’ll remove the loadingComponent and errorComponent options from our async component definition, as we’ll use SWRV to handle the error and loading states.

    // In setup()const AsyncComponent = defineAsyncComponent({ loader: importFunction, delay: 200, timeout: 5000,});

    Esto significa que necesitaremos incluir los componentes Loadery Erroren nuestra plantilla y mostrarlos y ocultarlos según el estado. El isValidatingvalor de retorno nos indica si se está produciendo una solicitud o una revalidación.

    template div Loader v-if="isValidating !data"/Loader Error v-else-if="error" :errorMessage="error.message"/Error component :is="AsyncComponent" :data="data" v-else/component /div/templatescriptimport { defineComponent, defineAsyncComponent,} from "vue";import useSWRV from "swrv";export default defineComponent({ components: { Error, Loader, }, props: { url: String, importFunction: Function, }, setup(props) { const { url, importFunction } = props; const controller = new AbortController(); const fetchData = () = { return fetch(url, { signal: controller.signal }) .then((response) = response.json()) .then((response) = (data.value = response)) .catch((e) = (error.value = e)); }; const { data, isValidating, error } = useSWRV(url, fetchData); const AsyncComponent = defineAsyncComponent({ loader: importFunction, delay: 200, timeout: 5000, }); onBeforeUnmount(() = controller.abort()); return { AsyncComponent, isValidating, data, error, }; },});/script

    Podríamos refactorizar esto en su propia composición, haciendo que nuestro código sea un poco más limpio y permitiéndonos usarlo en cualquier lugar.

    // composables/lazyFetch.jsimport { onBeforeUnmount } from "vue";import useSWRV from "swrv";export function useLazyFetch(url) { const controller = new AbortController(); const fetchData = () = { return fetch(url, { signal: controller.signal }) .then((response) = response.json()) .then((response) = (data.value = response)) .catch((e) = (error.value = e)); }; const { data, isValidating, error } = useSWRV(url, fetchData); onBeforeUnmount(() = controller.abort()); return { isValidating, data, error, };}
    // WidgetLoader.vuescriptimport { defineComponent, defineAsyncComponent, computed } from "vue";import Loader from "./Loader";import Error from "./Error";import { useLazyFetch } from "../composables/lazyFetch";export default defineComponent({ components: { Error, Loader, }, props: { aspectRatio: { type: String, default: "5 / 3", }, url: String, importFunction: Function, }, setup(props) { const { aspectRatio, url, importFunction } = props; const { data, isValidating, error } = useLazyFetch(url); const AsyncComponent = defineAsyncComponent({ loader: importFunction, delay: 200, timeout: 5000, }); return { aspectRatio, AsyncComponent, isValidating, data, error, }; },});/script

    Indicador de actualización

    Podría ser útil si pudiéramos mostrar un indicador al usuario mientras nuestra solicitud se revalida para que sepa que la aplicación está buscando nuevos datos. En el ejemplo, agregué un pequeño indicador de carga en la esquina del componente, que solo se mostrará si ya hay datos, pero el componente está buscando actualizaciones. También agregué una transición gradual simple en el componente (usando el Transitioncomponente integrado de Vue), por lo que no hay un salto tan abrupto cuando se renderiza el componente.

    template div :style="{ 'aspect-ratio': isValidating !data ? aspectRatio : '' }" Loader v-if="isValidating !data"/Loader Error v-else-if="error" :errorMessage="error.message"/Error Transition component :is="AsyncComponent" :data="data" v-else/component /Transition !--Indicator if data is updating-- Loader v-if="isValidating data" text="" /Loader /div/template

    Conclusión

    Dar prioridad al rendimiento al crear nuestras aplicaciones web mejora la experiencia del usuario y ayuda a garantizar que puedan ser utilizadas por la mayor cantidad de personas posible. Hemos utilizado con éxito las técnicas anteriores en Ada Mode para hacer que nuestras aplicaciones sean más rápidas. Espero que este artículo haya proporcionado algunos consejos sobre cómo hacer que su aplicación sea lo más eficiente posible, ya sea que elija implementarla total o parcialmente.

    Los SPA pueden funcionar bien, pero también pueden suponer un cuello de botella en el rendimiento. Entonces, intentemos construirlos mejor.

    Lecturas adicionales sobre la revista Smashing

    • Refactorización de CSS (Parte 1-3)
    • Cinco patrones de carga de datos para mejorar el rendimiento web
    • Cambiador de juego de rendimiento: caché de avance/retroceso del navegador
    • Reducir la huella de carbono de la Web: optimizar las incrustaciones en las redes sociales

    (vf, yk, il)Explora más en

    • vista
    • Aplicaciones
    • javascript
    • Herramientas





    Tal vez te puede interesar:

    1. ¿Deberían abrirse los enlaces en ventanas nuevas?
    2. 24 excelentes tutoriales de AJAX
    3. 70 técnicas nuevas y útiles de AJAX y JavaScript
    4. Más de 45 excelentes recursos y repositorios de fragmentos de código

    Optimización de una aplicación Vue

    Optimización de una aplicación Vue

    Design System Planning and Process, with Nathan Curtis Creating and Maintaining Successful Design Systems, with Brad Fost Índice

    programar

    es

    https://aprendeprogramando.es/static/images/programar-optimizacion-de-una-aplicacion-vue-1162-0.jpg

    2024-04-04

     

    Optimización de una aplicación Vue
    Optimización de una aplicación Vue

    Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente

     

     

    Top 20