Cuando queremos construir una aplicación móvil "multiplataforma", la respuesta siempre es React Native, pero ¿qué pasa si quieres construir una aplicación "multiplataforma" que se ejecute en dispositivos móviles y navegadores? Aquí es donde React Native se queda corto. react-native-web intenta cerrar esta brecha hasta cierto punto, pero el requisito principal es escribir tu código en React Native, que se convierte en código web, pero esto tiene varias desventajas y la más grande es obligar a los desarrolladores de aplicaciones móviles a entender cómo funcionan los navegadores. En esta charla, compartiré cómo estamos construyendo una arquitectura verdaderamente multiplataforma sin usar react-native-web para nuestro sistema de diseño en Razorpay.
La magia de construir una arquitectura de sistema de diseño multiplataforma
Video Summary and Transcription
Esta charla discute el desarrollo de una arquitectura de sistema de diseño multiplataforma. Explora diferentes enfoques y propone una API unificada que funciona en plataformas web y nativas. La charla cubre técnicas para resolver archivos y declaraciones, configurar bundlers y realizar pruebas tanto en plataformas web como nativas. También destaca el empaquetado de tipos TypeScript y el manejo de la accesibilidad para diferentes plataformas.
1. Introducción a los sistemas de diseño multiplataforma
Hola a todos. Soy Kamlesh. Trabajo como ingeniero de producto principal en el equipo de sistemas de diseño y herramientas de infraestructura, que forma parte del equipo de plataformas en Razorpay. Hoy voy a hablar sobre la magia de construir una arquitectura de sistemas de diseño multiplataforma. Queríamos un sistema de lenguaje de diseño que funcionara en varias plataformas. El primer enfoque fue que cada equipo construyera para cada plataforma.
Hola a todos. Soy Kamlesh. Trabajo como ingeniero de producto principal en el equipo de sistemas de diseño y herramientas de infraestructura, que forma parte del equipo de plataformas en Razorpay. Así que, hoy voy a hablar sobre la magia de construir una arquitectura de sistemas de diseño multiplataforma. Entonces, antes de comenzar, quiero aclarar que esto se basa en nuestra experiencia y en cómo abordamos este espacio de problemas según los factores. Y eso no significa necesariamente que sea la única forma. Así que, comencemos primero con el enunciado del problema. Queríamos un sistema de lenguaje de diseño que funcionara en varias plataformas. Ahora, comenzamos con qué otros enfoques tenemos disponibles. El primer enfoque fue que cada equipo construyera para cada plataforma. Eso es natural. Tienes diferentes equipos, diferentes plataformas y tienes diferentes equipos trabajando.
2. Enfoque de los Sistemas de Diseño Multiplataforma
Exploramos diferentes enfoques, incluyendo utilizar la experiencia de equipos individuales para cada plataforma y aprovechar las capacidades nativas ofrecidas por cada plataforma. Sin embargo, ninguno de ellos cumplía con nuestras necesidades de una API unificada que funcione en la web y en las plataformas nativas. Por lo tanto, desarrollamos nuestro propio enfoque, buscando un estado de Nirvana donde los desarrolladores pudieran implementar código una vez y que funcione sin problemas en ambas plataformas. Identificamos la necesidad de una API unificada, un centro de pruebas, empaquetado separado para cada plataforma y enviar tipos de TS con los paquetes individuales. Para demostrarlo, implementaremos un componente tipográfico que funcione en ambas plataformas, comenzando con la plataforma web.
Los contras de este enfoque son múltiples equipos construyendo lo mismo, ¿verdad? Como tienes varias personas que están resolviendo el mismo problema una y otra vez. Y luego teníamos código redundante para cosas similares. El tercero era una menor unificación de las API, ¿verdad? Porque ahora tus API serían redundantes o serían creadas por diferentes equipos.
Luego había otro enfoque que era usar React Native Web, por supuesto, que es una opción muy famosa en estos días. Así que tenía pros, que era que podías escribir una vez y usarlo en la web y en las plataformas nativas, que es lo que estábamos buscando. En segundo lugar, eran API similares en todas las plataformas. Los contras eran que React Native Web se usa para escribir también para la web, ¿verdad? Ahora los desafíos para React Native Web para debug cosas de la web. Es como nativo primero y luego penetrar con la web.
Ahora, ninguno de los anteriores cumplía con nuestras necesidades. Estábamos buscando algo que tuviera una misma API y funcionara en la web y en las plataformas nativas, aprovechar las capacidades nativas ofrecidas por cada plataforma y luego queríamos que los desarrolladores de aplicaciones hicieran lo que mejor saben hacer y los desarrolladores web hicieran lo que mejor saben hacer. Así que, básicamente, nuestro deseo en el estado de Nirvana era qué pasaría si un desarrollador tuviera que implementar lo siguiente en plataformas, para ellos debería ser como para nosotros si tomas este subcódigo y lo copias y pegas tanto en la web como en las plataformas nativas, debería funcionar sin problemas. Eso era como podrías decir nuestro estado de Nirvana. Entonces, ¿cómo abordamos esto? Así que, enumeramos todas las cosas que necesitamos abordar. La primera fue una API única que funcionara en todas las plataformas, luego queríamos un testing center porque aunque escribieras una vez y ejecutaras en ambas plataformas, aún queríamos probar nuestros componentes en ambas plataformas individualmente para no perder ningún error en ninguna de las plataformas. Y luego queríamos empaquetar cada plataforma por separado para que tu paquete web no se mezcle con react native y viceversa porque de lo contrario se romperá. Y también queríamos enviar tipos de TS junto con cada uno de estos paquetes individuales.
Sí, veamos las cosas en acción implementando un componente tipográfico. Entonces, este es el componente que implementaremos y debería funcionar tal como está en ambas plataformas. Esto es básicamente y el estado que haremos durante esta sesión. Ahora, comencemos con el primero, mismas APIs que funcionan en todas las plataformas. Entonces, si lo piensas, la API para esto se vería algo así, digamos que quieres design o crear un componente tipográfico, ¿cómo se vería la API? Tendría ID de color, familia de fuentes, tamaño de fuente, peso de fuente, estas son las propiedades básicas para un componente tipográfico. Entonces, por lo general, lo que harías es comenzar implementando plataforma por plataforma. Así que, lo obvio para comenzar es simplemente implementarlo en la web.
3. Extracción de Estilos Comunes y Exposición de Componentes
Estamos utilizando componentes de estilo y renderizando texto de estilo. Sin embargo, hay un problema con el código que se está resaltando. La única diferencia son las importaciones, que son diferentes para la web y para nativo. Para mejorarlo, extraemos los estilos comunes y creamos una función para devolverlos. Luego refactorizamos el código para reemplazar la parte común con la nueva función. Queremos ofrecer una forma para que los consumidores importen el sistema de diseño y elijan entre un componente web o nativo. Esto es responsabilidad del equipo de diseño del sistema. Para lograr esto, utilizamos empaquetadores y extensiones de archivo. Ahora, hablemos sobre cómo Metro resuelve los archivos en React Native.
Correcto. Estamos utilizando componentes de estilo. Simplemente definimos los estilos y luego renderizamos el texto de estilo y lo devolvemos, y luego lo exportamos. Luego, en nativo, volvemos a definir los estilos y cómo se renderizarán y avanzamos. Pero hay algo que está sucediendo aquí. Vamos a averiguar qué está pasando. Si observas esta parte del código que se está resaltando y esta otra parte de código, ambas son iguales. La única diferencia entre ambas es la parte superior, que básicamente son tus importaciones donde estás importando tu estilo.dev en la web porque estás renderizando un estilo.txt en nativo. Esta es la única diferencia. Esto no es lo que queríamos ver. Vamos a ver qué podemos hacer para mejorarlo.
Entonces, primero vamos a extraer los estilos. Vamos a ver que los estilos son comunes. Vamos a crear una función que acepte ciertos argumentos y luego los devuelva. Estos son básicamente nuestros estilos. Ahora, hagamos una refactorización. Esta parte que es común tanto en la web como en nativo, la vamos a eliminar y reemplazar con nuestra nueva función getTextStyles. Ahora, a continuación, queremos exponer algo para nuestros consumidores. Este es el paso tres. Si el consumidor, el consumidor solo debería decir importar texto desde el sistema de diseño. Ya sea que deba ser un componente web que queremos devolver o queremos devolver un componente nativo, esa debería ser la abstracción y eso debería ser manejado por la herramienta o la plataforma que estamos creando. Entonces, esa es básicamente la responsabilidad del equipo de diseño del sistema.
Entonces, ¿cómo convertimos este concepto en realidad? ¿Cómo logramos algo que sea como, ya sabes, dices importar y la plataforma debería identificarlo directamente? Utilizamos el poder de los empaquetadores, las extensiones de archivo y los empaquetadores. Ahora, antes de adentrarnos en eso, quiero hablar sobre cómo Metro, que es básicamente un empaquetador para React Native,
4. Resolviendo Archivos y Declaraciones
Metro permite implementar código específico de la plataforma resolviendo archivos según las extensiones de archivo. Podemos utilizar esta idea para que funcione en la web. Al configurar las extensiones de resolución de Webpack, podemos definir el orden de prioridad para la resolución de archivos. Renombramos los archivos para indicar la plataforma específica y asegurarnos de que nuestros empaquetadores puedan seleccionar el archivo correcto. Sin embargo, TypeScript aún puede encontrar errores. Para resolver esto, podemos definir declaraciones para exportar el archivo correcto.
5. Configurando Empaquetadores y Pruebas
Resolvimos el error de TypeScript configurando los empaquetadores y definiendo nuestras declaraciones. Ahora, pasemos a las pruebas. Estamos utilizando React Testing Library y Jest para probar tanto en plataformas web como nativas. Configuramos Jest para incluir las extensiones necesarias y omitir los archivos de prueba irrelevantes. Creamos pruebas para un componente de texto y nos aseguramos de que funcionen tanto en plataformas web como en React Native. Las pruebas están completas.
Ahora pasemos al segundo elemento de nuestra lista de verificación, como pruebas, pruebas para ambas plataformas. Ahora estamos utilizando la biblioteca de pruebas de React. Así que configuremos Jest para pruebas para la web, porque si recuerdas, estaba diciendo que necesitamos Jest para pruebas para ambas plataformas, queremos probar para la web y también queremos probar para nativo. Entonces, comenzando con la configuración web para Jest, lo que estamos tratando de decirle a Jest es que, ya sabes, solo incluye la cosa de extensiones web y omite todos los archivos de prueba nativos. De manera similar, para nativo, estamos haciendo lo contrario. Estamos diciendo que ignore todos los archivos de prueba web e incluya solo las extensiones nativas que terminan en archivos .native. Ahora escribamos algunas pruebas. Así que creamos un componente de texto ahora mismo. Simplemente escribimos una prueba para ello. Esto es solo el componente básico extraído para pruebas. Ahora veamos la magia multiplataforma. Crearemos un archivo llamado test.web.test.tsx que básicamente utilizará el DOM de Jest y lo renderizará usando el renderizador que proporciona la biblioteca de pruebas de React. Y de manera similar lo haremos para para nativos. Ahora veamos si la prueba realmente funciona. Así que prueba con react. Está en ejecución. Finalmente, nuestras pruebas funcionan para la web. Ahora veamos si esto también funciona para React Native. Muy bien, las pruebas están pasando, lo que significa que las cosas funcionan bien. El nativo está haciendo lo que se supone que debe probar la plataforma nativa y la web está probando para las plataformas web. Sigamos adelante.
6. Bundling and Configuring Rollup
Ahora vamos a empaquetar cada plataforma por separado junto con nuestros tipos de TypeScript. Utilizaremos Rollup para empaquetar nuestro sistema de diseño tanto para la web como para React Native. Rollup puede empaquetar bibliotecas de React Native. El primer paso es configurar Babel definiendo complementos y ajustes para React Native. Utilizaremos una variable de entorno llamada framework para exportar la configuración de Babel adecuada. El segundo paso es configurar Rollup con configuraciones separadas para React Native y React. Definiremos los archivos de entrada y salida para cada plataforma. También agregaremos scripts a nuestro package.json para construir para React y React Native. La variable de entorno framework determinará qué configuración utilizar. Cuando se ejecuta el script de construcción de React, establece la variable de entorno en React, que luego es utilizada por Rollup para crear la configuración web y empaquetar el archivo index.web.ts.
Entonces, el primer paso sería configurar Babel. Esta es nuestra configuración de Babel. Primero definiremos los complementos y los ajustes para React Native. Esta configuración es simplemente un objeto que estamos creando ahora mismo. Tiene una clave llamada React Native. De manera similar, tiene una clave llamada React. Ahora lo que haremos es cuando exportemos, la forma en que exportamos es leyendo una variable de entorno llamada framework, y según el framework que se pase, exportaremos esa configuración de Babel en particular. Solo mantén esa variable de entorno. Volveremos a eso.
El segundo paso es configurar Rollup. De la misma manera que hicimos para Babel, lo haremos también para Rollup. Definiremos dos configuraciones. Primero comenzaremos con React Native. Definiremos la entrada como index.ts. Y luego la salida será index.native.js. Presta atención a esto en particular. De manera similar, para React, simplemente diremos que el archivo de salida es index.web.js en la configuración. Luego agregaremos scripts a nuestro package.json, que simplemente diremos que tendremos dos scripts, uno para construir React y otro para construir React Native. Si ves aquí, está la variable de entorno que estamos pasando. Decimos que el framework es React para el script de React, y el framework es React Native para el script de React Native. Pero veamos cómo funciona todo esto. Permíteme mostrarte o ayudarte a visualizar cómo funcionarían estas cosas. Entonces, primero cuando se ejecute el script para construir React, lo que hará es configurar la variable de entorno en React, lo cual luego actuará como una entrada para la variable config, que simplemente entenderá, okay, necesito exportar mi configuración de React. Y luego el siguiente paso sería Rollup. Así que simplemente leerá la variable de entorno y devolverá la configuración de React, básicamente la configuración web para Rollup. Y después de eso, simplemente creará un paquete,
7. Empaquetando Tipos y Accesibilidad
Y de manera similar, lo hará para React Native. Nuestros tipos están ubicados junto con nuestros componentes, pero no estamos empaquetando los tipos. Necesitamos empaquetar nuestros tipos en la misma estructura que nuestro paquete de biblioteca. Mejoramos nuestra configuración y usamos un complemento llamado 'declarations' para generar un paquete de archivos de declaración. Tenemos solo un paquete de tipos porque tenemos la misma API en todas las plataformas. Hemos logrado la misma API, configuración de pruebas y empaquetado para cada plataforma junto con los tipos de TypeScript. Ahora, hablemos sobre accesibilidad. En React Native, se llama 'accessibility role description', mientras que en la web se llama 'area role description'.
8. Manejo de Accesibilidad para Diferentes Plataformas
Creamos dos mapas con alias y valores respectivos para cada plataforma. Los colocamos en archivos separados y usamos una función para determinar el nombre de la propiedad según la plataforma de construcción. Al importar la función 'make accessible' y pasar la propiedad de accesibilidad, podemos renderizar la propiedad adecuada para cada plataforma. Esto asegura que la accesibilidad se maneje correctamente.